engly 1.2.0 โ 1.3.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/CHANGELOG.md +9 -0
- package/README.md +6 -6
- package/dist/cli.js +567 -176
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-09-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `engly status` shows whether Engly is on, its settings, where the block and skills are installed, and warns about anything that needs attention: broken markers, an agent file that is too long, missing files, or project files older than the CLI.
|
|
13
|
+
- `engly disable` turns the coach off permanently by emptying the Engly block, and `engly enable` turns it back on. The block stays where it was.
|
|
14
|
+
- `engly update` regenerates `.engly/engly.md`, the skills and the block with the installed CLI version and your settings. It never adds a block on its own: run `engly init` for that.
|
|
15
|
+
- `engly remove` removes the Engly block, the `engly-*` skills and `.engly/`, and leaves the rest of your files as they were. It asks first; `--yes` skips the question.
|
|
16
|
+
|
|
8
17
|
## [1.2.0] - 2026-09-26
|
|
9
18
|
|
|
10
19
|
### Changed
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ Sure, here's the updated function...
|
|
|
60
60
|
## ๐ Quick start
|
|
61
61
|
|
|
62
62
|
> [!NOTE]
|
|
63
|
-
> Engly is in early development. Today
|
|
63
|
+
> Engly is in early development. Today it works for **Claude Code**. More agents and `engly config` are coming soon.
|
|
64
64
|
|
|
65
65
|
Install it with one command:
|
|
66
66
|
|
|
@@ -114,11 +114,11 @@ $ /engly-review # Deep review of a prompt: every mistake, why, and better alte
|
|
|
114
114
|
| Command | What it does |
|
|
115
115
|
|---|---|
|
|
116
116
|
| `engly init` | Sets up Engly in the current project. Asks two questions: which agents you use and how detailed the feedback should be. |
|
|
117
|
-
| `engly update` |
|
|
118
|
-
| `engly enable` / `engly disable` | Turns the coach on or off permanently.
|
|
117
|
+
| `engly update` | Refreshes the skills and instructions in this project with the version of the CLI you have installed. To get a newer CLI, run `npm install -g engly@latest` first. |
|
|
118
|
+
| `engly enable` / `engly disable` | Turns the coach on or off permanently in this project, without uninstalling anything. It takes effect in your next agent session. |
|
|
119
119
|
| `engly config <option> <value>` | Changes a setting, e.g. `engly config level B2`. *(coming soon)* |
|
|
120
|
-
| `engly status` | Shows where Engly is installed and whether it is active
|
|
121
|
-
| `engly remove` | Removes everything Engly added.
|
|
120
|
+
| `engly status` | Shows where Engly is installed and whether it is active, and warns about anything that needs attention. |
|
|
121
|
+
| `engly remove` | Removes everything Engly added and leaves the rest of your files as they were. Asks first; use `--yes` to skip the question. |
|
|
122
122
|
|
|
123
123
|
`engly init` options:
|
|
124
124
|
|
|
@@ -183,7 +183,7 @@ Yes. Engly stays silent when your prompt is right, and at higher levels it sugge
|
|
|
183
183
|
<details>
|
|
184
184
|
<summary><b>How do I uninstall it?</b></summary>
|
|
185
185
|
|
|
186
|
-
`engly remove`
|
|
186
|
+
Run `engly remove` in your project. It deletes the Engly block from your agent file, the `.engly/` folder and the `engly-*` skills. Everything else stays exactly as it was. To only pause the coach, use `engly disable` instead.
|
|
187
187
|
</details>
|
|
188
188
|
|
|
189
189
|
## ๐ค Contributing
|
package/dist/cli.js
CHANGED
|
@@ -13,131 +13,10 @@ async function configCommand(_option, _value) {
|
|
|
13
13
|
notImplemented("config");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// src/commands/
|
|
17
|
-
|
|
18
|
-
notImplemented("disable");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// src/commands/enable.ts
|
|
22
|
-
async function enableCommand() {
|
|
23
|
-
notImplemented("enable");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// src/commands/init.ts
|
|
27
|
-
import { join as join5 } from "path";
|
|
16
|
+
// src/commands/set-enabled.ts
|
|
17
|
+
import { join as join7 } from "path";
|
|
28
18
|
import * as p from "@clack/prompts";
|
|
29
19
|
|
|
30
|
-
// src/adapters/detect.ts
|
|
31
|
-
import { access } from "fs/promises";
|
|
32
|
-
import { dirname, join } from "path";
|
|
33
|
-
async function anyExists(cwd, paths) {
|
|
34
|
-
for (const path of paths) {
|
|
35
|
-
try {
|
|
36
|
-
await access(join(cwd, path));
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
async function anyExistsUpward(cwd, paths) {
|
|
44
|
-
let dir = cwd;
|
|
45
|
-
while (true) {
|
|
46
|
-
if (await anyExists(dir, paths)) return true;
|
|
47
|
-
const parent = dirname(dir);
|
|
48
|
-
if (parent === dir) return false;
|
|
49
|
-
dir = parent;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/adapters/claude.ts
|
|
54
|
-
var claude = {
|
|
55
|
-
id: "claude",
|
|
56
|
-
name: "Claude Code",
|
|
57
|
-
delivery: "import",
|
|
58
|
-
detect: (cwd) => anyExists(cwd, ["CLAUDE.md", ".claude"]),
|
|
59
|
-
// Claude Code reads AGENTS.md only when there is no CLAUDE.md, .claude/CLAUDE.md or
|
|
60
|
-
// CLAUDE.local.md in the working directory or any directory above it.
|
|
61
|
-
instructionFile: async (cwd) => {
|
|
62
|
-
if (await anyExistsUpward(cwd, ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"])) return "CLAUDE.md";
|
|
63
|
-
return await anyExists(cwd, ["AGENTS.md"]) ? "AGENTS.md" : "CLAUDE.md";
|
|
64
|
-
},
|
|
65
|
-
skillDirs: [".claude/skills"],
|
|
66
|
-
sizeLimit: {
|
|
67
|
-
maxLines: 200,
|
|
68
|
-
warning: "Claude Code follows long instruction files less closely."
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// src/adapters/codex.ts
|
|
73
|
-
var codex = {
|
|
74
|
-
id: "codex",
|
|
75
|
-
name: "Codex",
|
|
76
|
-
delivery: "embedded",
|
|
77
|
-
detect: (cwd) => anyExists(cwd, [".codex", "AGENTS.md"]),
|
|
78
|
-
instructionFile: () => "AGENTS.md",
|
|
79
|
-
skillDirs: [".agents/skills"],
|
|
80
|
-
sizeLimit: {
|
|
81
|
-
// Real limit is 32 KiB for all AGENTS.md files combined (spec ยง9.6).
|
|
82
|
-
maxBytes: 24 * 1024,
|
|
83
|
-
warning: "Codex may stop reading long instruction files.\nThe Engly block could be cut off."
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// src/adapters/copilot.ts
|
|
88
|
-
var copilot = {
|
|
89
|
-
id: "copilot",
|
|
90
|
-
name: "GitHub Copilot",
|
|
91
|
-
delivery: "embedded",
|
|
92
|
-
detect: (cwd) => anyExists(cwd, [".github/copilot-instructions.md", ".github/instructions"]),
|
|
93
|
-
instructionFile: () => ".github/copilot-instructions.md",
|
|
94
|
-
skillDirs: [".agents/skills", ".claude/skills", ".github/skills"]
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// src/adapters/cursor.ts
|
|
98
|
-
var cursor = {
|
|
99
|
-
id: "cursor",
|
|
100
|
-
name: "Cursor",
|
|
101
|
-
delivery: "own-file",
|
|
102
|
-
detect: (cwd) => anyExists(cwd, [".cursor", ".cursorrules"]),
|
|
103
|
-
instructionFile: () => ".cursor/rules/engly.mdc",
|
|
104
|
-
skillDirs: [".agents/skills", ".cursor/skills", ".claude/skills"]
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// src/adapters/opencode.ts
|
|
108
|
-
var opencode = {
|
|
109
|
-
id: "opencode",
|
|
110
|
-
name: "OpenCode",
|
|
111
|
-
delivery: "embedded",
|
|
112
|
-
detect: (cwd) => anyExists(cwd, ["opencode.json", "opencode.jsonc", ".opencode"]),
|
|
113
|
-
instructionFile: () => "AGENTS.md",
|
|
114
|
-
skillDirs: [".agents/skills", ".claude/skills", ".opencode/skills"]
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// src/adapters/types.ts
|
|
118
|
-
var TOOL_IDS = ["claude", "codex", "opencode", "cursor", "copilot"];
|
|
119
|
-
|
|
120
|
-
// src/adapters/index.ts
|
|
121
|
-
var adapters = { claude, codex, opencode, cursor, copilot };
|
|
122
|
-
var IMPLEMENTED_TOOLS = ["claude"];
|
|
123
|
-
function skillTargets(tools) {
|
|
124
|
-
const chosen = [];
|
|
125
|
-
const byChoices = [...tools].sort((a, b) => adapters[a].skillDirs.length - adapters[b].skillDirs.length);
|
|
126
|
-
for (const tool of byChoices) {
|
|
127
|
-
const dirs = adapters[tool].skillDirs;
|
|
128
|
-
const [preferred] = dirs;
|
|
129
|
-
if (preferred && !dirs.some((dir) => chosen.includes(dir))) chosen.push(preferred);
|
|
130
|
-
}
|
|
131
|
-
return chosen;
|
|
132
|
-
}
|
|
133
|
-
async function detectTools(cwd) {
|
|
134
|
-
const found = [];
|
|
135
|
-
for (const adapter of Object.values(adapters)) {
|
|
136
|
-
if (await adapter.detect(cwd)) found.push(adapter.id);
|
|
137
|
-
}
|
|
138
|
-
return found;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
20
|
// src/core/block.ts
|
|
142
21
|
var START_MARKER_PREFIX = "<!-- engly:start";
|
|
143
22
|
var END_MARKER = "<!-- engly:end -->";
|
|
@@ -176,6 +55,13 @@ function findBlock(text) {
|
|
|
176
55
|
function hasBlock(text) {
|
|
177
56
|
return findBlock(text) !== null;
|
|
178
57
|
}
|
|
58
|
+
function readBlock(text) {
|
|
59
|
+
const range = findBlock(text);
|
|
60
|
+
if (!range) return null;
|
|
61
|
+
const inner = text.slice(range.start, range.end);
|
|
62
|
+
const afterStart = inner.indexOf("-->") + 3;
|
|
63
|
+
return stripOneEol(inner.slice(afterStart, inner.length - END_MARKER.length), "both");
|
|
64
|
+
}
|
|
179
65
|
function buildBlock(content, version, eol = "\n") {
|
|
180
66
|
const body = content.replace(/\r\n/g, "\n").replace(/\n+$/, "");
|
|
181
67
|
const lines = [startMarker(version), ...body === "" ? [] : body.split("\n"), END_MARKER];
|
|
@@ -197,9 +83,33 @@ function upsertBlock(text, content, options) {
|
|
|
197
83
|
const separator = endsWithEol(text) ? eol : eol + eol;
|
|
198
84
|
return { text: text + separator + block + eol, action: "appended" };
|
|
199
85
|
}
|
|
86
|
+
function removeBlock(text) {
|
|
87
|
+
const range = findBlock(text);
|
|
88
|
+
if (!range) return { text, removed: false };
|
|
89
|
+
let before = text.slice(0, range.start);
|
|
90
|
+
let after = stripOneEol(text.slice(range.end), "start");
|
|
91
|
+
if (before === "") {
|
|
92
|
+
after = stripOneEol(after, "start");
|
|
93
|
+
} else if (/(\r?\n)\r?\n$/.test(before)) {
|
|
94
|
+
before = stripOneEol(before, "end");
|
|
95
|
+
}
|
|
96
|
+
return { text: before + after, removed: true };
|
|
97
|
+
}
|
|
200
98
|
function endsWithEol(text) {
|
|
201
99
|
return text.endsWith("\n");
|
|
202
100
|
}
|
|
101
|
+
function stripOneEol(text, side) {
|
|
102
|
+
let result = text;
|
|
103
|
+
if (side === "start" || side === "both") {
|
|
104
|
+
if (result.startsWith("\r\n")) result = result.slice(2);
|
|
105
|
+
else if (result.startsWith("\n")) result = result.slice(1);
|
|
106
|
+
}
|
|
107
|
+
if (side === "end" || side === "both") {
|
|
108
|
+
if (result.endsWith("\r\n")) result = result.slice(0, -2);
|
|
109
|
+
else if (result.endsWith("\n")) result = result.slice(0, -1);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
203
113
|
function indexesOf(text, search) {
|
|
204
114
|
const result = [];
|
|
205
115
|
let index = text.indexOf(search);
|
|
@@ -212,18 +122,18 @@ function indexesOf(text, search) {
|
|
|
212
122
|
|
|
213
123
|
// src/core/config.ts
|
|
214
124
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
215
|
-
import { dirname as
|
|
125
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
216
126
|
|
|
217
127
|
// src/core/paths.ts
|
|
218
128
|
import { existsSync, readFileSync } from "fs";
|
|
219
|
-
import { dirname
|
|
129
|
+
import { dirname, join } from "path";
|
|
220
130
|
import { fileURLToPath } from "url";
|
|
221
131
|
var cachedRoot;
|
|
222
132
|
function packageRoot() {
|
|
223
133
|
if (cachedRoot) return cachedRoot;
|
|
224
|
-
let dir =
|
|
134
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
225
135
|
while (true) {
|
|
226
|
-
const pkgPath =
|
|
136
|
+
const pkgPath = join(dir, "package.json");
|
|
227
137
|
if (existsSync(pkgPath)) {
|
|
228
138
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
229
139
|
if (pkg.name === "engly") {
|
|
@@ -231,16 +141,16 @@ function packageRoot() {
|
|
|
231
141
|
return dir;
|
|
232
142
|
}
|
|
233
143
|
}
|
|
234
|
-
const parent =
|
|
144
|
+
const parent = dirname(dir);
|
|
235
145
|
if (parent === dir) throw new Error("Could not locate the engly package root.");
|
|
236
146
|
dir = parent;
|
|
237
147
|
}
|
|
238
148
|
}
|
|
239
149
|
function templatesDir() {
|
|
240
|
-
return
|
|
150
|
+
return join(packageRoot(), "templates");
|
|
241
151
|
}
|
|
242
152
|
function packageVersion() {
|
|
243
|
-
const pkg = JSON.parse(readFileSync(
|
|
153
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot(), "package.json"), "utf8"));
|
|
244
154
|
return pkg.version;
|
|
245
155
|
}
|
|
246
156
|
var ENGLY_DIR = ".engly";
|
|
@@ -268,7 +178,7 @@ var ConfigError = class extends Error {
|
|
|
268
178
|
}
|
|
269
179
|
};
|
|
270
180
|
function configPath(cwd) {
|
|
271
|
-
return
|
|
181
|
+
return join2(cwd, CONFIG_FILE);
|
|
272
182
|
}
|
|
273
183
|
async function readConfig(cwd, version) {
|
|
274
184
|
let raw;
|
|
@@ -291,7 +201,7 @@ async function readConfig(cwd, version) {
|
|
|
291
201
|
}
|
|
292
202
|
async function writeConfig(cwd, config) {
|
|
293
203
|
const path = configPath(cwd);
|
|
294
|
-
await mkdir(
|
|
204
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
295
205
|
await writeFile(path, `${JSON.stringify(validateConfig(config), null, 2)}
|
|
296
206
|
`, "utf8");
|
|
297
207
|
}
|
|
@@ -315,8 +225,8 @@ function validateConfig(config) {
|
|
|
315
225
|
}
|
|
316
226
|
|
|
317
227
|
// src/core/fs.ts
|
|
318
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
319
|
-
import { dirname as
|
|
228
|
+
import { access, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
229
|
+
import { dirname as dirname3 } from "path";
|
|
320
230
|
async function readTextIfExists(path) {
|
|
321
231
|
try {
|
|
322
232
|
return await readFile2(path, "utf8");
|
|
@@ -326,22 +236,132 @@ async function readTextIfExists(path) {
|
|
|
326
236
|
}
|
|
327
237
|
}
|
|
328
238
|
async function writeText(path, content) {
|
|
329
|
-
await mkdir2(
|
|
239
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
330
240
|
await writeFile2(path, content, "utf8");
|
|
331
241
|
}
|
|
242
|
+
async function pathExists(path) {
|
|
243
|
+
try {
|
|
244
|
+
await access(path);
|
|
245
|
+
return true;
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
332
250
|
|
|
333
|
-
// src/core/
|
|
334
|
-
import {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
251
|
+
// src/core/install.ts
|
|
252
|
+
import { rm, rmdir } from "fs/promises";
|
|
253
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
254
|
+
|
|
255
|
+
// src/adapters/detect.ts
|
|
256
|
+
import { access as access2 } from "fs/promises";
|
|
257
|
+
import { dirname as dirname4, join as join3 } from "path";
|
|
258
|
+
async function anyExists(cwd, paths) {
|
|
259
|
+
for (const path of paths) {
|
|
260
|
+
try {
|
|
261
|
+
await access2(join3(cwd, path));
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
339
267
|
}
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
268
|
+
async function anyExistsUpward(cwd, paths) {
|
|
269
|
+
let dir = cwd;
|
|
270
|
+
while (true) {
|
|
271
|
+
if (await anyExists(dir, paths)) return true;
|
|
272
|
+
const parent = dirname4(dir);
|
|
273
|
+
if (parent === dir) return false;
|
|
274
|
+
dir = parent;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/adapters/claude.ts
|
|
279
|
+
var claude = {
|
|
280
|
+
id: "claude",
|
|
281
|
+
name: "Claude Code",
|
|
282
|
+
delivery: "import",
|
|
283
|
+
detect: (cwd) => anyExists(cwd, ["CLAUDE.md", ".claude"]),
|
|
284
|
+
// Claude Code reads AGENTS.md only when there is no CLAUDE.md, .claude/CLAUDE.md or
|
|
285
|
+
// CLAUDE.local.md in the working directory or any directory above it.
|
|
286
|
+
instructionFile: async (cwd) => {
|
|
287
|
+
if (await anyExistsUpward(cwd, ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"])) return "CLAUDE.md";
|
|
288
|
+
return await anyExists(cwd, ["AGENTS.md"]) ? "AGENTS.md" : "CLAUDE.md";
|
|
289
|
+
},
|
|
290
|
+
instructionFiles: ["CLAUDE.md", "AGENTS.md"],
|
|
291
|
+
skillDirs: [".claude/skills"],
|
|
292
|
+
sizeLimit: {
|
|
293
|
+
maxLines: 200,
|
|
294
|
+
warning: "Claude Code follows long instruction files less closely."
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/adapters/codex.ts
|
|
299
|
+
var codex = {
|
|
300
|
+
id: "codex",
|
|
301
|
+
name: "Codex",
|
|
302
|
+
delivery: "embedded",
|
|
303
|
+
detect: (cwd) => anyExists(cwd, [".codex", "AGENTS.md"]),
|
|
304
|
+
instructionFile: () => "AGENTS.md",
|
|
305
|
+
skillDirs: [".agents/skills"],
|
|
306
|
+
sizeLimit: {
|
|
307
|
+
// Real limit is 32 KiB for all AGENTS.md files combined (spec ยง9.6).
|
|
308
|
+
maxBytes: 24 * 1024,
|
|
309
|
+
warning: "Codex may stop reading long instruction files.\nThe Engly block could be cut off."
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/adapters/copilot.ts
|
|
314
|
+
var copilot = {
|
|
315
|
+
id: "copilot",
|
|
316
|
+
name: "GitHub Copilot",
|
|
317
|
+
delivery: "embedded",
|
|
318
|
+
detect: (cwd) => anyExists(cwd, [".github/copilot-instructions.md", ".github/instructions"]),
|
|
319
|
+
instructionFile: () => ".github/copilot-instructions.md",
|
|
320
|
+
skillDirs: [".agents/skills", ".claude/skills", ".github/skills"]
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/adapters/cursor.ts
|
|
324
|
+
var cursor = {
|
|
325
|
+
id: "cursor",
|
|
326
|
+
name: "Cursor",
|
|
327
|
+
delivery: "own-file",
|
|
328
|
+
detect: (cwd) => anyExists(cwd, [".cursor", ".cursorrules"]),
|
|
329
|
+
instructionFile: () => ".cursor/rules/engly.mdc",
|
|
330
|
+
skillDirs: [".agents/skills", ".cursor/skills", ".claude/skills"]
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/adapters/opencode.ts
|
|
334
|
+
var opencode = {
|
|
335
|
+
id: "opencode",
|
|
336
|
+
name: "OpenCode",
|
|
337
|
+
delivery: "embedded",
|
|
338
|
+
detect: (cwd) => anyExists(cwd, ["opencode.json", "opencode.jsonc", ".opencode"]),
|
|
339
|
+
instructionFile: () => "AGENTS.md",
|
|
340
|
+
skillDirs: [".agents/skills", ".claude/skills", ".opencode/skills"]
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/adapters/types.ts
|
|
344
|
+
var TOOL_IDS = ["claude", "codex", "opencode", "cursor", "copilot"];
|
|
345
|
+
|
|
346
|
+
// src/adapters/index.ts
|
|
347
|
+
var adapters = { claude, codex, opencode, cursor, copilot };
|
|
348
|
+
var IMPLEMENTED_TOOLS = ["claude"];
|
|
349
|
+
function skillTargets(tools) {
|
|
350
|
+
const chosen = [];
|
|
351
|
+
const byChoices = [...tools].sort((a, b) => adapters[a].skillDirs.length - adapters[b].skillDirs.length);
|
|
352
|
+
for (const tool of byChoices) {
|
|
353
|
+
const dirs = adapters[tool].skillDirs;
|
|
354
|
+
const [preferred] = dirs;
|
|
355
|
+
if (preferred && !dirs.some((dir) => chosen.includes(dir))) chosen.push(preferred);
|
|
356
|
+
}
|
|
357
|
+
return chosen;
|
|
358
|
+
}
|
|
359
|
+
async function detectTools(cwd) {
|
|
360
|
+
const found = [];
|
|
361
|
+
for (const adapter of Object.values(adapters)) {
|
|
362
|
+
if (await adapter.detect(cwd)) found.push(adapter.id);
|
|
363
|
+
}
|
|
364
|
+
return found;
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
// src/core/instructions.ts
|
|
@@ -363,6 +383,179 @@ async function readSkill(name) {
|
|
|
363
383
|
return readFile3(join4(templatesDir(), "skills", name, "SKILL.md"), "utf8");
|
|
364
384
|
}
|
|
365
385
|
|
|
386
|
+
// src/core/install.ts
|
|
387
|
+
async function writeInstructions(cwd, config) {
|
|
388
|
+
await writeText(join5(cwd, INSTRUCTIONS_FILE), await renderInstructions(config));
|
|
389
|
+
}
|
|
390
|
+
async function installSkills(cwd, tools) {
|
|
391
|
+
for (const dir of skillTargets(tools)) {
|
|
392
|
+
for (const skill of SKILL_NAMES) {
|
|
393
|
+
await writeText(join5(cwd, dir, skill, "SKILL.md"), await readSkill(skill));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function skillStatus(cwd, tools) {
|
|
398
|
+
const folders = [];
|
|
399
|
+
for (const dir of skillTargets(tools)) {
|
|
400
|
+
const missing = [];
|
|
401
|
+
for (const skill of SKILL_NAMES) {
|
|
402
|
+
if (!await pathExists(join5(cwd, dir, skill, "SKILL.md"))) missing.push(skill);
|
|
403
|
+
}
|
|
404
|
+
folders.push({ dir, missing });
|
|
405
|
+
}
|
|
406
|
+
return folders;
|
|
407
|
+
}
|
|
408
|
+
async function removeSkills(cwd) {
|
|
409
|
+
const dirs = new Set(Object.values(adapters).flatMap((adapter) => adapter.skillDirs));
|
|
410
|
+
const removed = [];
|
|
411
|
+
for (const dir of dirs) {
|
|
412
|
+
let removedHere = false;
|
|
413
|
+
for (const skill of SKILL_NAMES) {
|
|
414
|
+
const path = join5(cwd, dir, skill);
|
|
415
|
+
if (!await pathExists(path)) continue;
|
|
416
|
+
await rm(path, { recursive: true, force: true });
|
|
417
|
+
removed.push(join5(dir, skill));
|
|
418
|
+
removedHere = true;
|
|
419
|
+
}
|
|
420
|
+
if (removedHere) await removeEmptyDirs(cwd, dir);
|
|
421
|
+
}
|
|
422
|
+
return removed;
|
|
423
|
+
}
|
|
424
|
+
async function hasInstalledSkills(cwd) {
|
|
425
|
+
const dirs = new Set(Object.values(adapters).flatMap((adapter) => adapter.skillDirs));
|
|
426
|
+
for (const dir of dirs) {
|
|
427
|
+
for (const skill of SKILL_NAMES) {
|
|
428
|
+
if (await pathExists(join5(cwd, dir, skill))) return true;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
async function removeEmptyDirs(cwd, relativeDir) {
|
|
434
|
+
let dir = relativeDir;
|
|
435
|
+
for (let level = 0; level < 2 && dir !== "."; level++) {
|
|
436
|
+
try {
|
|
437
|
+
await rmdir(join5(cwd, dir));
|
|
438
|
+
} catch {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
dir = dirname5(dir);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/core/project.ts
|
|
446
|
+
import { join as join6 } from "path";
|
|
447
|
+
function blockContent(enabled) {
|
|
448
|
+
return enabled ? `@${INSTRUCTIONS_FILE}` : "";
|
|
449
|
+
}
|
|
450
|
+
async function inspectAgentFile(cwd, file) {
|
|
451
|
+
const path = join6(cwd, file);
|
|
452
|
+
const text = await readTextIfExists(path);
|
|
453
|
+
if (text === null) return { file, path, text: "", state: "missing" };
|
|
454
|
+
try {
|
|
455
|
+
const content = readBlock(text);
|
|
456
|
+
if (content === null) return { file, path, text, state: "no-block" };
|
|
457
|
+
return { file, path, text, state: content.trim() === "" ? "inactive" : "active" };
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error instanceof BlockError) return { file, path, text, state: "broken", error: error.message };
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function candidateFiles(cwd, tools) {
|
|
464
|
+
const files = /* @__PURE__ */ new Set();
|
|
465
|
+
for (const tool of tools) {
|
|
466
|
+
if (!IMPLEMENTED_TOOLS.includes(tool)) continue;
|
|
467
|
+
const adapter = adapters[tool];
|
|
468
|
+
for (const file of adapter.instructionFiles ?? [await adapter.instructionFile(cwd)]) files.add(file);
|
|
469
|
+
}
|
|
470
|
+
return [...files];
|
|
471
|
+
}
|
|
472
|
+
async function inspectAgentFiles(cwd, tools) {
|
|
473
|
+
const files = [];
|
|
474
|
+
for (const file of await candidateFiles(cwd, tools)) {
|
|
475
|
+
const inspected = await inspectAgentFile(cwd, file);
|
|
476
|
+
if (inspected.state !== "missing") files.push(inspected);
|
|
477
|
+
}
|
|
478
|
+
return files;
|
|
479
|
+
}
|
|
480
|
+
async function findBlocks(cwd, tools) {
|
|
481
|
+
const files = await inspectAgentFiles(cwd, tools);
|
|
482
|
+
for (const file of files) {
|
|
483
|
+
if (file.state === "broken") throw new BlockError(`${file.file}: ${file.error}`);
|
|
484
|
+
}
|
|
485
|
+
return files.filter((file) => file.state === "active" || file.state === "inactive");
|
|
486
|
+
}
|
|
487
|
+
async function requireConfig(cwd, version) {
|
|
488
|
+
const config = await readConfig(cwd, version);
|
|
489
|
+
if (!config) throw new Error('Engly is not installed here. Run "engly init" first.');
|
|
490
|
+
return config;
|
|
491
|
+
}
|
|
492
|
+
function configuredTools(config) {
|
|
493
|
+
const tools = config?.tools.filter((tool) => IMPLEMENTED_TOOLS.includes(tool)) ?? [];
|
|
494
|
+
return tools.length > 0 ? tools : [...IMPLEMENTED_TOOLS];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/commands/set-enabled.ts
|
|
498
|
+
async function setEnabled(cwd, enabled) {
|
|
499
|
+
const version = packageVersion();
|
|
500
|
+
const word = enabled ? "enabled" : "disabled";
|
|
501
|
+
p.intro(`Engly v${version}`);
|
|
502
|
+
const config = await requireConfig(cwd, version);
|
|
503
|
+
const tools = configuredTools(config);
|
|
504
|
+
const blocks = await findBlocks(cwd, tools);
|
|
505
|
+
if (blocks.length === 0) {
|
|
506
|
+
const files = (await candidateFiles(cwd, tools)).join(" or ");
|
|
507
|
+
p.log.warn(`No Engly block found in ${files}. Run "engly init" to add it.`);
|
|
508
|
+
p.outro("Nothing changed.");
|
|
509
|
+
process.exitCode = 1;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const pending = blocks.filter((block) => block.state === "active" !== enabled);
|
|
513
|
+
if (pending.length === 0 && config.enabled === enabled) {
|
|
514
|
+
p.outro(`Engly is already ${word}.`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
for (const block of pending) {
|
|
518
|
+
const { text } = upsertBlock(block.text, blockContent(enabled), { version: config.version });
|
|
519
|
+
await writeText(block.path, text);
|
|
520
|
+
}
|
|
521
|
+
if (enabled && !await pathExists(join7(cwd, INSTRUCTIONS_FILE))) {
|
|
522
|
+
await writeInstructions(cwd, config);
|
|
523
|
+
}
|
|
524
|
+
config.enabled = enabled;
|
|
525
|
+
await writeConfig(cwd, config);
|
|
526
|
+
const where = pending.length > 0 ? ` in ${pending.map((block) => block.file).join(" and ")}` : "";
|
|
527
|
+
p.log.success(`\u2713 Engly ${word}${where}`);
|
|
528
|
+
p.outro("Takes effect in your next agent session.");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/commands/disable.ts
|
|
532
|
+
async function disableCommand() {
|
|
533
|
+
await setEnabled(process.cwd(), false);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/commands/enable.ts
|
|
537
|
+
async function enableCommand() {
|
|
538
|
+
await setEnabled(process.cwd(), true);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/commands/init.ts
|
|
542
|
+
import { join as join8 } from "path";
|
|
543
|
+
import * as p2 from "@clack/prompts";
|
|
544
|
+
|
|
545
|
+
// src/core/git.ts
|
|
546
|
+
import { spawnSync } from "child_process";
|
|
547
|
+
function git(cwd, args) {
|
|
548
|
+
const result = spawnSync("git", args, { cwd, stdio: "ignore" });
|
|
549
|
+
if (result.error) return null;
|
|
550
|
+
return result.status;
|
|
551
|
+
}
|
|
552
|
+
function gitStatus(cwd, relativePath) {
|
|
553
|
+
if (git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== 0) return "no-git";
|
|
554
|
+
if (git(cwd, ["ls-files", "--error-unmatch", "--", relativePath]) === 0) return "tracked";
|
|
555
|
+
if (git(cwd, ["check-ignore", "-q", "--", relativePath]) === 0) return "ignored";
|
|
556
|
+
return "untracked";
|
|
557
|
+
}
|
|
558
|
+
|
|
366
559
|
// src/core/size.ts
|
|
367
560
|
function checkSize(text, limit) {
|
|
368
561
|
if (!limit) return null;
|
|
@@ -381,7 +574,7 @@ function checkSize(text, limit) {
|
|
|
381
574
|
var CancelledError = class extends Error {
|
|
382
575
|
};
|
|
383
576
|
function guard(value) {
|
|
384
|
-
if (
|
|
577
|
+
if (p2.isCancel(value)) throw new CancelledError();
|
|
385
578
|
return value;
|
|
386
579
|
}
|
|
387
580
|
async function initCommand(options) {
|
|
@@ -389,21 +582,20 @@ async function initCommand(options) {
|
|
|
389
582
|
await runInit(options);
|
|
390
583
|
} catch (error) {
|
|
391
584
|
if (error instanceof CancelledError) {
|
|
392
|
-
|
|
585
|
+
p2.cancel("Cancelled. Nothing was changed.");
|
|
393
586
|
process.exitCode = 1;
|
|
394
587
|
return;
|
|
395
588
|
}
|
|
396
589
|
throw error;
|
|
397
590
|
}
|
|
398
591
|
}
|
|
399
|
-
async function runInit(options) {
|
|
400
|
-
const cwd = process.cwd();
|
|
592
|
+
async function runInit(options, cwd = process.cwd()) {
|
|
401
593
|
const version = packageVersion();
|
|
402
594
|
const interactive = !options.yes;
|
|
403
595
|
if (interactive && !process.stdin.isTTY) {
|
|
404
596
|
throw new Error('No interactive terminal detected. Run "engly init --yes" to use the defaults.');
|
|
405
597
|
}
|
|
406
|
-
|
|
598
|
+
p2.intro(`Engly v${version} \u2013 your English coach for AI prompts`);
|
|
407
599
|
const config = await readConfig(cwd, version) ?? defaultConfig(version);
|
|
408
600
|
config.version = version;
|
|
409
601
|
const tools = await chooseTools(cwd, options);
|
|
@@ -413,9 +605,9 @@ async function runInit(options) {
|
|
|
413
605
|
config.style = await chooseStyle(options, config.style);
|
|
414
606
|
const adapter = adapters.claude;
|
|
415
607
|
const agentFile = await adapter.instructionFile(cwd);
|
|
416
|
-
const agentPath =
|
|
608
|
+
const agentPath = join8(cwd, agentFile);
|
|
417
609
|
if (agentFile === "AGENTS.md") {
|
|
418
|
-
|
|
610
|
+
p2.log.info("No CLAUDE.md found. Using AGENTS.md, which Claude Code reads when there is no CLAUDE.md.");
|
|
419
611
|
}
|
|
420
612
|
const current = await readTextIfExists(agentPath);
|
|
421
613
|
const blockExists = current !== null && hasBlock(current);
|
|
@@ -426,10 +618,10 @@ async function runInit(options) {
|
|
|
426
618
|
if (placement === "skip") apply = false;
|
|
427
619
|
else position = placement;
|
|
428
620
|
}
|
|
429
|
-
const planned = upsertBlock(current ?? "",
|
|
621
|
+
const planned = upsertBlock(current ?? "", blockContent(true), { version, position });
|
|
430
622
|
if (apply && interactive) {
|
|
431
623
|
const choice = guard(
|
|
432
|
-
await
|
|
624
|
+
await p2.select({
|
|
433
625
|
message: `Add Engly block to:
|
|
434
626
|
${agentFile} (${adapter.name}) \u2192 ${describePlan(current, planned.action)}`,
|
|
435
627
|
options: [
|
|
@@ -444,22 +636,18 @@ ${agentFile} (${adapter.name}) \u2192 ${describePlan(current, planned.action)}
|
|
|
444
636
|
await warnIfInGit(cwd, agentFile, interactive);
|
|
445
637
|
}
|
|
446
638
|
const done = [];
|
|
447
|
-
await
|
|
639
|
+
await writeInstructions(cwd, config);
|
|
448
640
|
if (apply) {
|
|
449
641
|
await writeText(agentPath, planned.text);
|
|
450
642
|
done.push(`\u2713 ${agentFile} ${current === null ? "created" : "updated"}`);
|
|
451
643
|
}
|
|
452
|
-
|
|
453
|
-
for (const skill of SKILL_NAMES) {
|
|
454
|
-
await writeText(join5(cwd, dir, skill, "SKILL.md"), await readSkill(skill));
|
|
455
|
-
}
|
|
456
|
-
}
|
|
644
|
+
await installSkills(cwd, tools);
|
|
457
645
|
done.push(`\u2713 Skills installed: ${SKILL_NAMES.join(", ")}`);
|
|
458
646
|
config.tools = tools;
|
|
459
647
|
config.enabled = true;
|
|
460
648
|
await writeConfig(cwd, config);
|
|
461
|
-
|
|
462
|
-
|
|
649
|
+
p2.log.success(done.join("\n"));
|
|
650
|
+
p2.outro(
|
|
463
651
|
apply ? "Done! Your next prompt will be coached." : `Skills installed. Run "engly init" again to add the Engly block to ${agentFile}.`
|
|
464
652
|
);
|
|
465
653
|
}
|
|
@@ -477,7 +665,7 @@ async function chooseTools(cwd, options) {
|
|
|
477
665
|
const defaults = initial.length > 0 ? initial : [...IMPLEMENTED_TOOLS];
|
|
478
666
|
if (options.yes) return defaults;
|
|
479
667
|
return guard(
|
|
480
|
-
await
|
|
668
|
+
await p2.multiselect({
|
|
481
669
|
message: "Which AI tools do you use?",
|
|
482
670
|
options: TOOL_IDS.map((id) => ({
|
|
483
671
|
value: id,
|
|
@@ -493,7 +681,7 @@ async function chooseTools(cwd, options) {
|
|
|
493
681
|
function keepImplemented(tools) {
|
|
494
682
|
const skipped = tools.filter((tool) => !IMPLEMENTED_TOOLS.includes(tool));
|
|
495
683
|
if (skipped.length > 0) {
|
|
496
|
-
|
|
684
|
+
p2.log.warn(`Not supported yet, skipped: ${skipped.map((tool) => adapters[tool].name).join(", ")}`);
|
|
497
685
|
}
|
|
498
686
|
return tools.filter((tool) => IMPLEMENTED_TOOLS.includes(tool));
|
|
499
687
|
}
|
|
@@ -506,7 +694,7 @@ async function chooseStyle(options, current) {
|
|
|
506
694
|
}
|
|
507
695
|
if (options.yes) return current;
|
|
508
696
|
return guard(
|
|
509
|
-
await
|
|
697
|
+
await p2.select({
|
|
510
698
|
message: "How detailed should the feedback be?",
|
|
511
699
|
initialValue: current,
|
|
512
700
|
options: [
|
|
@@ -526,13 +714,13 @@ async function choosePlacement(file, size, interactive) {
|
|
|
526
714
|
const message = `${file} is ${size.label}
|
|
527
715
|
\u26A0 ${size.warning}`;
|
|
528
716
|
if (!interactive) {
|
|
529
|
-
|
|
717
|
+
p2.log.warn(`${message}
|
|
530
718
|
The Engly block goes near the top.`);
|
|
531
719
|
return "top";
|
|
532
720
|
}
|
|
533
|
-
|
|
721
|
+
p2.log.warn(message);
|
|
534
722
|
return guard(
|
|
535
|
-
await
|
|
723
|
+
await p2.select({
|
|
536
724
|
message: "Where should the block go?",
|
|
537
725
|
options: [
|
|
538
726
|
{ value: "top", label: "Near the top \u2013 recommended" },
|
|
@@ -548,12 +736,12 @@ async function warnIfInGit(cwd, file, interactive) {
|
|
|
548
736
|
const title = status === "tracked" ? `${file} is tracked by git.` : `${file} is not ignored by git.`;
|
|
549
737
|
const warning = "\u26A0 Your teammates will also be coached if you commit it.";
|
|
550
738
|
if (!interactive) {
|
|
551
|
-
|
|
739
|
+
p2.log.warn(`${title}
|
|
552
740
|
${warning}`);
|
|
553
741
|
return;
|
|
554
742
|
}
|
|
555
743
|
const choice = guard(
|
|
556
|
-
await
|
|
744
|
+
await p2.select({
|
|
557
745
|
message: `${title}
|
|
558
746
|
${warning}`,
|
|
559
747
|
options: [
|
|
@@ -566,18 +754,221 @@ ${warning}`,
|
|
|
566
754
|
}
|
|
567
755
|
|
|
568
756
|
// src/commands/remove.ts
|
|
569
|
-
|
|
570
|
-
|
|
757
|
+
import { rm as rm2 } from "fs/promises";
|
|
758
|
+
import { join as join9 } from "path";
|
|
759
|
+
import * as p3 from "@clack/prompts";
|
|
760
|
+
async function removeCommand(options) {
|
|
761
|
+
await runRemove(process.cwd(), options);
|
|
762
|
+
}
|
|
763
|
+
async function runRemove(cwd, options) {
|
|
764
|
+
const version = packageVersion();
|
|
765
|
+
p3.intro(`Engly v${version}`);
|
|
766
|
+
let config = null;
|
|
767
|
+
try {
|
|
768
|
+
config = await readConfig(cwd, version);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
if (!(error instanceof ConfigError)) throw error;
|
|
771
|
+
}
|
|
772
|
+
const blocks = await findBlocks(cwd, config ? configuredTools(config) : [...IMPLEMENTED_TOOLS]);
|
|
773
|
+
const hasEnglyDir = await pathExists(join9(cwd, ENGLY_DIR));
|
|
774
|
+
const hasSkills = await hasInstalledSkills(cwd);
|
|
775
|
+
if (blocks.length === 0 && !hasEnglyDir && !hasSkills) {
|
|
776
|
+
p3.outro("Nothing to remove.");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const plan = [
|
|
780
|
+
...blocks.map((block) => `The Engly block in ${block.file}`),
|
|
781
|
+
...hasEnglyDir ? [`The ${ENGLY_DIR}/ folder (settings and instructions)`] : [],
|
|
782
|
+
...hasSkills ? [`The skills: ${SKILL_NAMES.join(", ")}`] : []
|
|
783
|
+
];
|
|
784
|
+
if (!options.yes) {
|
|
785
|
+
if (!process.stdin.isTTY) {
|
|
786
|
+
throw new Error('No interactive terminal detected. Run "engly remove --yes" to remove without asking.');
|
|
787
|
+
}
|
|
788
|
+
const confirmed = await p3.confirm({
|
|
789
|
+
message: `Remove Engly from this project?
|
|
790
|
+
${plan.map((line) => ` \u2022 ${line}`).join("\n")}`
|
|
791
|
+
});
|
|
792
|
+
if (p3.isCancel(confirmed) || !confirmed) {
|
|
793
|
+
p3.cancel("Cancelled. Nothing was changed.");
|
|
794
|
+
process.exitCode = 1;
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const done = [];
|
|
799
|
+
for (const block of blocks) {
|
|
800
|
+
await writeText(block.path, removeBlock(block.text).text);
|
|
801
|
+
done.push(`\u2713 Engly block removed from ${block.file}`);
|
|
802
|
+
}
|
|
803
|
+
if (hasSkills) {
|
|
804
|
+
await removeSkills(cwd);
|
|
805
|
+
done.push(`\u2713 Skills removed: ${SKILL_NAMES.join(", ")}`);
|
|
806
|
+
}
|
|
807
|
+
if (hasEnglyDir) {
|
|
808
|
+
await rm2(join9(cwd, ENGLY_DIR), { recursive: true, force: true });
|
|
809
|
+
done.push(`\u2713 ${ENGLY_DIR}/ removed`);
|
|
810
|
+
}
|
|
811
|
+
p3.log.success(done.join("\n"));
|
|
812
|
+
p3.outro("Engly removed. Everything outside the Engly block was left untouched.");
|
|
571
813
|
}
|
|
572
814
|
|
|
573
815
|
// src/commands/status.ts
|
|
816
|
+
import { join as join10 } from "path";
|
|
817
|
+
import * as p4 from "@clack/prompts";
|
|
574
818
|
async function statusCommand() {
|
|
575
|
-
|
|
819
|
+
await runStatus(process.cwd());
|
|
820
|
+
}
|
|
821
|
+
async function runStatus(cwd) {
|
|
822
|
+
const version = packageVersion();
|
|
823
|
+
p4.intro(`Engly v${version}`);
|
|
824
|
+
let config = null;
|
|
825
|
+
let configError = null;
|
|
826
|
+
try {
|
|
827
|
+
config = await readConfig(cwd, version);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (!(error instanceof ConfigError)) throw error;
|
|
830
|
+
configError = error.message;
|
|
831
|
+
}
|
|
832
|
+
const tools = configuredTools(config);
|
|
833
|
+
const files = await inspectAgentFiles(cwd, tools);
|
|
834
|
+
const withBlock = files.filter((file) => file.state === "active" || file.state === "inactive");
|
|
835
|
+
const broken = files.filter((file) => file.state === "broken");
|
|
836
|
+
const hasInstructions = await pathExists(join10(cwd, INSTRUCTIONS_FILE));
|
|
837
|
+
const skills = config ? await skillStatus(cwd, tools) : [];
|
|
838
|
+
const installed = config !== null || configError !== null || withBlock.length > 0 || broken.length > 0 || hasInstructions || await hasInstalledSkills(cwd);
|
|
839
|
+
if (!installed) {
|
|
840
|
+
p4.outro('Engly is not installed in this project. Run "engly init".');
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const active = withBlock.some((file) => file.state === "active");
|
|
844
|
+
const status = withBlock.length === 0 ? "off (no Engly block in the agent files)" : active ? "enabled" : "disabled";
|
|
845
|
+
const rows = [["Status", status]];
|
|
846
|
+
if (config) {
|
|
847
|
+
rows.push(
|
|
848
|
+
["Version", config.version],
|
|
849
|
+
["Style", config.style],
|
|
850
|
+
["Level", config.level],
|
|
851
|
+
["Language", config.language],
|
|
852
|
+
["Tools", tools.map((tool) => adapters[tool].name).join(", ")]
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
rows.push(["Files", fileLines(files, hasInstructions)]);
|
|
856
|
+
if (skills.length > 0) {
|
|
857
|
+
rows.push([
|
|
858
|
+
"Skills",
|
|
859
|
+
skills.map((folder) => `${folder.dir} ${folder.missing.length === 0 ? "\u2713" : "\u2717"}`).join("\n")
|
|
860
|
+
]);
|
|
861
|
+
}
|
|
862
|
+
p4.note(formatRows(rows), "Engly status");
|
|
863
|
+
const warnings = await collectWarnings({ cwd, version, config, configError, tools, withBlock, broken, skills });
|
|
864
|
+
for (const warning of warnings) p4.log.warn(warning);
|
|
865
|
+
p4.outro(warnings.length === 0 ? "All good." : `${warnings.length} warning${warnings.length === 1 ? "" : "s"}.`);
|
|
866
|
+
}
|
|
867
|
+
async function collectWarnings(input) {
|
|
868
|
+
const { cwd, version, config, configError, tools, withBlock, broken, skills } = input;
|
|
869
|
+
const warnings = [];
|
|
870
|
+
if (configError) {
|
|
871
|
+
warnings.push(`${configError} Fix it by hand, or run "engly init" to write a new one.`);
|
|
872
|
+
} else if (!config) {
|
|
873
|
+
warnings.push(`${CONFIG_FILE} is missing. Run "engly init" to write it again.`);
|
|
874
|
+
} else if (config.version !== version) {
|
|
875
|
+
warnings.push(`This project uses Engly v${config.version} and the CLI is v${version}. Run "engly update".`);
|
|
876
|
+
}
|
|
877
|
+
for (const file of broken) warnings.push(`${file.file}: ${file.error}`);
|
|
878
|
+
if (withBlock.length === 0 && broken.length === 0) {
|
|
879
|
+
const names = (await candidateFiles(cwd, tools)).join(" or ");
|
|
880
|
+
warnings.push(`No Engly block found in ${names}, so nothing is coached. Run "engly init" to add it.`);
|
|
881
|
+
}
|
|
882
|
+
const active = withBlock.some((file) => file.state === "active");
|
|
883
|
+
if (config && withBlock.length > 0 && config.enabled !== active) {
|
|
884
|
+
warnings.push(
|
|
885
|
+
`${CONFIG_FILE} says Engly is ${config.enabled ? "enabled" : "disabled"}, but the block is ${active ? "active" : "empty"}. Run "engly ${config.enabled ? "enable" : "disable"}" to sync them.`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (active && !await pathExists(join10(cwd, INSTRUCTIONS_FILE))) {
|
|
889
|
+
warnings.push(`${INSTRUCTIONS_FILE} is missing, but the block imports it. Run "engly update".`);
|
|
890
|
+
}
|
|
891
|
+
for (const folder of skills) {
|
|
892
|
+
if (folder.missing.length > 0) {
|
|
893
|
+
warnings.push(`Missing skills in ${folder.dir}: ${folder.missing.join(", ")}. Run "engly update".`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
for (const file of withBlock) {
|
|
897
|
+
const size = sizeWarning(file, tools);
|
|
898
|
+
if (size) warnings.push(size);
|
|
899
|
+
}
|
|
900
|
+
return warnings;
|
|
901
|
+
}
|
|
902
|
+
function sizeWarning(file, tools) {
|
|
903
|
+
for (const tool of tools) {
|
|
904
|
+
const adapter = adapters[tool];
|
|
905
|
+
if (!adapter.instructionFiles?.includes(file.file)) continue;
|
|
906
|
+
const report = checkSize(file.text, adapter.sizeLimit);
|
|
907
|
+
if (report) return `${file.file} is ${report.label}. ${report.warning.replace(/\n/g, " ")}`;
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
function fileLines(files, hasInstructions) {
|
|
912
|
+
const lines = files.map((file) => {
|
|
913
|
+
switch (file.state) {
|
|
914
|
+
case "active":
|
|
915
|
+
return `${file.file} \u2713 (block active)`;
|
|
916
|
+
case "inactive":
|
|
917
|
+
return `${file.file} \u2713 (block empty, coach off)`;
|
|
918
|
+
case "broken":
|
|
919
|
+
return `${file.file} \u2717 (broken markers)`;
|
|
920
|
+
default:
|
|
921
|
+
return `${file.file} \u2013 (no Engly block)`;
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
lines.push(`${INSTRUCTIONS_FILE} ${hasInstructions ? "\u2713" : "\u2717"}`);
|
|
925
|
+
return lines.join("\n");
|
|
926
|
+
}
|
|
927
|
+
function formatRows(rows) {
|
|
928
|
+
const width = Math.max(...rows.map(([label]) => label.length)) + 2;
|
|
929
|
+
return rows.map(
|
|
930
|
+
([label, value]) => value.split("\n").map((line, index) => `${index === 0 ? `${label}:`.padEnd(width) : " ".repeat(width)}${line}`).join("\n")
|
|
931
|
+
).join("\n");
|
|
576
932
|
}
|
|
577
933
|
|
|
578
934
|
// src/commands/update.ts
|
|
935
|
+
import * as p5 from "@clack/prompts";
|
|
579
936
|
async function updateCommand() {
|
|
580
|
-
|
|
937
|
+
await runUpdate(process.cwd());
|
|
938
|
+
}
|
|
939
|
+
async function runUpdate(cwd) {
|
|
940
|
+
const version = packageVersion();
|
|
941
|
+
p5.intro(`Engly v${version}`);
|
|
942
|
+
const config = await requireConfig(cwd, version);
|
|
943
|
+
const previous = config.version;
|
|
944
|
+
const tools = configuredTools(config);
|
|
945
|
+
const blocks = await findBlocks(cwd, tools);
|
|
946
|
+
config.version = version;
|
|
947
|
+
await writeInstructions(cwd, config);
|
|
948
|
+
await installSkills(cwd, tools);
|
|
949
|
+
const done = ["\u2713 .engly/engly.md and skills refreshed"];
|
|
950
|
+
for (const block of blocks) {
|
|
951
|
+
const { text } = upsertBlock(block.text, blockContent(config.enabled), { version });
|
|
952
|
+
if (text === block.text) {
|
|
953
|
+
done.push(`\u2713 ${block.file} block is up to date`);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
await writeText(block.path, text);
|
|
957
|
+
done.push(`\u2713 ${block.file} block updated`);
|
|
958
|
+
}
|
|
959
|
+
await writeConfig(cwd, config);
|
|
960
|
+
p5.log.success(done.join("\n"));
|
|
961
|
+
if (blocks.length === 0) {
|
|
962
|
+
const files = (await candidateFiles(cwd, tools)).join(" or ");
|
|
963
|
+
p5.log.warn(`No Engly block found in ${files}. Run "engly init" to add it.`);
|
|
964
|
+
}
|
|
965
|
+
if (!config.enabled) {
|
|
966
|
+
p5.log.warn('Engly is disabled. Run "engly enable" to turn it on.');
|
|
967
|
+
}
|
|
968
|
+
p5.log.info(
|
|
969
|
+
"This updates the files in this project. To update the engly CLI itself, run: npm install -g engly@latest"
|
|
970
|
+
);
|
|
971
|
+
p5.outro(previous === version ? `Already on v${version}.` : `Updated from v${previous} to v${version}.`);
|
|
581
972
|
}
|
|
582
973
|
|
|
583
974
|
// src/cli.ts
|
|
@@ -589,7 +980,7 @@ program.command("enable").description("Turn the coach on permanently").action(en
|
|
|
589
980
|
program.command("disable").description("Turn the coach off permanently, without uninstalling").action(disableCommand);
|
|
590
981
|
program.command("config").description('Change an option, e.g. "engly config level B2"').argument("<option>", "language, level or style").argument("<value>", "new value").action(configCommand);
|
|
591
982
|
program.command("status").description("Show where Engly is installed and whether it is active").action(statusCommand);
|
|
592
|
-
program.command("remove").description("Remove the Engly block, skills and config").action(removeCommand);
|
|
983
|
+
program.command("remove").description("Remove the Engly block, skills and config").option("-y, --yes", "remove without asking").action(removeCommand);
|
|
593
984
|
try {
|
|
594
985
|
await program.parseAsync();
|
|
595
986
|
} catch (error) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/not-implemented.ts","../src/commands/config.ts","../src/commands/disable.ts","../src/commands/enable.ts","../src/commands/init.ts","../src/adapters/detect.ts","../src/adapters/claude.ts","../src/adapters/codex.ts","../src/adapters/copilot.ts","../src/adapters/cursor.ts","../src/adapters/opencode.ts","../src/adapters/types.ts","../src/adapters/index.ts","../src/core/block.ts","../src/core/config.ts","../src/core/paths.ts","../src/core/fs.ts","../src/core/git.ts","../src/core/instructions.ts","../src/core/size.ts","../src/commands/remove.ts","../src/commands/status.ts","../src/commands/update.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { configCommand } from './commands/config.js';\nimport { disableCommand } from './commands/disable.js';\nimport { enableCommand } from './commands/enable.js';\nimport { initCommand } from './commands/init.js';\nimport { removeCommand } from './commands/remove.js';\nimport { statusCommand } from './commands/status.js';\nimport { updateCommand } from './commands/update.js';\nimport { packageVersion } from './core/paths.js';\n\nconst program = new Command();\n\nprogram\n .name('engly')\n .description('Your English coach for AI prompts. One command. Every agent. Always on. Zero cost.')\n .version(packageVersion());\n\nprogram\n .command('init')\n .description('Install Engly in the current project')\n .option('--tools <tools>', 'comma-separated agents: claude,codex,opencode,cursor,copilot')\n .option('--style <style>', 'feedback style: light or detailed')\n .option('-y, --yes', 'accept all defaults without asking')\n .option('--no-apply', \"only install the skills, don't touch the agent files\")\n .action(initCommand);\n\nprogram.command('update').description('Update the skills and regenerate the Engly block').action(updateCommand);\nprogram.command('enable').description('Turn the coach on permanently').action(enableCommand);\nprogram.command('disable').description('Turn the coach off permanently, without uninstalling').action(disableCommand);\nprogram\n .command('config')\n .description('Change an option, e.g. \"engly config level B2\"')\n .argument('<option>', 'language, level or style')\n .argument('<value>', 'new value')\n .action(configCommand);\nprogram.command('status').description('Show where Engly is installed and whether it is active').action(statusCommand);\nprogram.command('remove').description('Remove the Engly block, skills and config').action(removeCommand);\n\ntry {\n await program.parseAsync();\n} catch (error) {\n console.error(`engly: ${error instanceof Error ? error.message : String(error)}`);\n process.exitCode = 1;\n}\n","export function notImplemented(command: string): void {\n console.log(`engly ${command}: not implemented yet`);\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function configCommand(_option: string, _value: string): Promise<void> {\n notImplemented('config');\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function disableCommand(): Promise<void> {\n notImplemented('disable');\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function enableCommand(): Promise<void> {\n notImplemented('enable');\n}\n","import { join } from 'node:path';\nimport * as p from '@clack/prompts';\nimport { adapters, detectTools, IMPLEMENTED_TOOLS, skillTargets, TOOL_IDS, type ToolId } from '../adapters/index.js';\nimport { type BlockPosition, hasBlock, type UpsertAction, upsertBlock } from '../core/block.js';\nimport { defaultConfig, readConfig, STYLES, type Style, writeConfig } from '../core/config.js';\nimport { readTextIfExists, writeText } from '../core/fs.js';\nimport { gitStatus } from '../core/git.js';\nimport { readSkill, renderInstructions, SKILL_NAMES } from '../core/instructions.js';\nimport { INSTRUCTIONS_FILE, packageVersion } from '../core/paths.js';\nimport { checkSize, type SizeReport } from '../core/size.js';\n\nexport interface InitOptions {\n tools?: string;\n style?: string;\n yes?: boolean;\n apply: boolean;\n}\n\nclass CancelledError extends Error {}\n\nfunction guard<T>(value: T): Exclude<T, symbol> {\n if (p.isCancel(value)) throw new CancelledError();\n return value as Exclude<T, symbol>;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n try {\n await runInit(options);\n } catch (error) {\n if (error instanceof CancelledError) {\n p.cancel('Cancelled. Nothing was changed.');\n process.exitCode = 1;\n return;\n }\n throw error;\n }\n}\n\nasync function runInit(options: InitOptions): Promise<void> {\n const cwd = process.cwd();\n const version = packageVersion();\n const interactive = !options.yes;\n\n if (interactive && !process.stdin.isTTY) {\n throw new Error('No interactive terminal detected. Run \"engly init --yes\" to use the defaults.');\n }\n\n p.intro(`Engly v${version} โ your English coach for AI prompts`);\n\n const config = (await readConfig(cwd, version)) ?? defaultConfig(version);\n config.version = version;\n\n const tools = await chooseTools(cwd, options);\n if (tools.length === 0) {\n throw new Error(`No supported tool selected. Supported today: ${IMPLEMENTED_TOOLS.join(', ')}.`);\n }\n\n config.style = await chooseStyle(options, config.style);\n\n const adapter = adapters.claude;\n const agentFile = await adapter.instructionFile(cwd);\n const agentPath = join(cwd, agentFile);\n if (agentFile === 'AGENTS.md') {\n p.log.info('No CLAUDE.md found. Using AGENTS.md, which Claude Code reads when there is no CLAUDE.md.');\n }\n\n const current = await readTextIfExists(agentPath);\n // Checked before any prompt or write, so broken markers fail early.\n const blockExists = current !== null && hasBlock(current);\n\n let apply = options.apply;\n let position: BlockPosition = 'end';\n if (apply && current !== null && !blockExists) {\n const placement = await choosePlacement(agentFile, checkSize(current, adapter.sizeLimit), interactive);\n if (placement === 'skip') apply = false;\n else position = placement;\n }\n const planned = upsertBlock(current ?? '', `@${INSTRUCTIONS_FILE}`, { version, position });\n\n if (apply && interactive) {\n const choice = guard(\n await p.select({\n message: `Add Engly block to:\\n${agentFile} (${adapter.name}) โ ${describePlan(current, planned.action)}`,\n options: [\n { value: 'yes', label: 'Yes' },\n { value: 'skills', label: 'No, only install skills' },\n ],\n }),\n );\n apply = choice === 'yes';\n }\n\n if (apply) {\n await warnIfInGit(cwd, agentFile, interactive);\n }\n\n const done: string[] = [];\n\n await writeText(join(cwd, INSTRUCTIONS_FILE), await renderInstructions(config));\n\n if (apply) {\n await writeText(agentPath, planned.text);\n done.push(`โ ${agentFile} ${current === null ? 'created' : 'updated'}`);\n }\n\n for (const dir of skillTargets(tools)) {\n for (const skill of SKILL_NAMES) {\n await writeText(join(cwd, dir, skill, 'SKILL.md'), await readSkill(skill));\n }\n }\n done.push(`โ Skills installed: ${SKILL_NAMES.join(', ')}`);\n\n config.tools = tools;\n config.enabled = true;\n await writeConfig(cwd, config);\n\n p.log.success(done.join('\\n'));\n p.outro(\n apply\n ? 'Done! Your next prompt will be coached.'\n : `Skills installed. Run \"engly init\" again to add the Engly block to ${agentFile}.`,\n );\n}\n\nasync function chooseTools(cwd: string, options: InitOptions): Promise<ToolId[]> {\n if (options.tools) {\n const requested = options.tools\n .split(',')\n .map((tool) => tool.trim().toLowerCase())\n .filter(Boolean);\n const unknown = requested.filter((tool) => !(TOOL_IDS as readonly string[]).includes(tool));\n if (unknown.length > 0) {\n throw new Error(`Unknown tool(s): ${unknown.join(', ')}. Use: ${TOOL_IDS.join(', ')}.`);\n }\n return keepImplemented(requested as ToolId[]);\n }\n\n const detected = await detectTools(cwd);\n const initial = detected.filter((tool) => IMPLEMENTED_TOOLS.includes(tool));\n const defaults = initial.length > 0 ? initial : [...IMPLEMENTED_TOOLS];\n\n if (options.yes) return defaults;\n\n return guard(\n await p.multiselect<ToolId>({\n message: 'Which AI tools do you use?',\n options: TOOL_IDS.map((id) => ({\n value: id,\n label: adapters[id].name,\n hint: IMPLEMENTED_TOOLS.includes(id) ? undefined : 'coming soon',\n disabled: !IMPLEMENTED_TOOLS.includes(id),\n })),\n initialValues: defaults,\n required: true,\n }),\n );\n}\n\nfunction keepImplemented(tools: ToolId[]): ToolId[] {\n const skipped = tools.filter((tool) => !IMPLEMENTED_TOOLS.includes(tool));\n if (skipped.length > 0) {\n p.log.warn(`Not supported yet, skipped: ${skipped.map((tool) => adapters[tool].name).join(', ')}`);\n }\n return tools.filter((tool) => IMPLEMENTED_TOOLS.includes(tool));\n}\n\nasync function chooseStyle(options: InitOptions, current: Style): Promise<Style> {\n if (options.style) {\n if (!(STYLES as readonly string[]).includes(options.style)) {\n throw new Error(`Invalid style \"${options.style}\". Use: ${STYLES.join(', ')}.`);\n }\n return options.style as Style;\n }\n if (options.yes) return current;\n\n return guard(\n await p.select<Style>({\n message: 'How detailed should the feedback be?',\n initialValue: current,\n options: [\n { value: 'light', label: 'Light โ max 3 corrections' },\n { value: 'detailed', label: 'Detailed โ explain every mistake' },\n ],\n }),\n );\n}\n\nfunction describePlan(current: string | null, action: UpsertAction): string {\n if (current === null) return 'new file';\n if (action === 'updated') return 'exists, block updated';\n return action === 'prepended' ? 'exists, block added near the top' : 'exists, block appended';\n}\n\nasync function choosePlacement(\n file: string,\n size: SizeReport | null,\n interactive: boolean,\n): Promise<BlockPosition | 'skip'> {\n if (!size) return 'end';\n\n const message = `${file} is ${size.label}\\nโ ${size.warning}`;\n if (!interactive) {\n p.log.warn(`${message}\\nThe Engly block goes near the top.`);\n return 'top';\n }\n\n p.log.warn(message);\n return guard(\n await p.select<BlockPosition | 'skip'>({\n message: 'Where should the block go?',\n options: [\n { value: 'top', label: 'Near the top โ recommended' },\n { value: 'end', label: 'At the end' },\n { value: 'skip', label: `Skip ${file}` },\n ],\n }),\n );\n}\n\nasync function warnIfInGit(cwd: string, file: string, interactive: boolean): Promise<void> {\n const status = gitStatus(cwd, file);\n if (status !== 'tracked' && status !== 'untracked') return;\n\n const title = status === 'tracked' ? `${file} is tracked by git.` : `${file} is not ignored by git.`;\n const warning = 'โ Your teammates will also be coached if you commit it.';\n\n if (!interactive) {\n p.log.warn(`${title}\\n${warning}`);\n return;\n }\n\n const choice = guard(\n await p.select({\n message: `${title}\\n${warning}`,\n options: [\n { value: 'continue', label: 'Continue anyway' },\n { value: 'cancel', label: 'Cancel' },\n ],\n }),\n );\n if (choice === 'cancel') throw new CancelledError();\n}\n","import { access } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nexport async function anyExists(cwd: string, paths: string[]): Promise<boolean> {\n for (const path of paths) {\n try {\n await access(join(cwd, path));\n return true;\n } catch {}\n }\n return false;\n}\n\n// Same as anyExists, but also looks in every directory above `cwd`.\nexport async function anyExistsUpward(cwd: string, paths: string[]): Promise<boolean> {\n let dir = cwd;\n while (true) {\n if (await anyExists(dir, paths)) return true;\n const parent = dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n","import { anyExists, anyExistsUpward } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const claude: Adapter = {\n id: 'claude',\n name: 'Claude Code',\n delivery: 'import',\n detect: (cwd) => anyExists(cwd, ['CLAUDE.md', '.claude']),\n // Claude Code reads AGENTS.md only when there is no CLAUDE.md, .claude/CLAUDE.md or\n // CLAUDE.local.md in the working directory or any directory above it.\n instructionFile: async (cwd) => {\n if (await anyExistsUpward(cwd, ['CLAUDE.md', '.claude/CLAUDE.md', 'CLAUDE.local.md'])) return 'CLAUDE.md';\n return (await anyExists(cwd, ['AGENTS.md'])) ? 'AGENTS.md' : 'CLAUDE.md';\n },\n skillDirs: ['.claude/skills'],\n sizeLimit: {\n maxLines: 200,\n warning: 'Claude Code follows long instruction files less closely.',\n },\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const codex: Adapter = {\n id: 'codex',\n name: 'Codex',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['.codex', 'AGENTS.md']),\n instructionFile: () => 'AGENTS.md',\n skillDirs: ['.agents/skills'],\n sizeLimit: {\n // Real limit is 32 KiB for all AGENTS.md files combined (spec ยง9.6).\n maxBytes: 24 * 1024,\n warning: 'Codex may stop reading long instruction files.\\nThe Engly block could be cut off.',\n },\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const copilot: Adapter = {\n id: 'copilot',\n name: 'GitHub Copilot',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['.github/copilot-instructions.md', '.github/instructions']),\n instructionFile: () => '.github/copilot-instructions.md',\n skillDirs: ['.agents/skills', '.claude/skills', '.github/skills'],\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const CURSOR_RULE_FRONTMATTER = '---\\ndescription: Engly English coach\\nalwaysApply: true\\n---\\n';\n\n// TODO(spec ยง15): Cursor reads .claude/skills/ only as a \"legacy\" folder and\n// was not tested. If it stops doing so, Cursor + Claude Code would get no skills.\nexport const cursor: Adapter = {\n id: 'cursor',\n name: 'Cursor',\n delivery: 'own-file',\n detect: (cwd) => anyExists(cwd, ['.cursor', '.cursorrules']),\n instructionFile: () => '.cursor/rules/engly.mdc',\n skillDirs: ['.agents/skills', '.cursor/skills', '.claude/skills'],\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const opencode: Adapter = {\n id: 'opencode',\n name: 'OpenCode',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['opencode.json', 'opencode.jsonc', '.opencode']),\n instructionFile: () => 'AGENTS.md',\n skillDirs: ['.agents/skills', '.claude/skills', '.opencode/skills'],\n};\n","export const TOOL_IDS = ['claude', 'codex', 'opencode', 'cursor', 'copilot'] as const;\nexport type ToolId = (typeof TOOL_IDS)[number];\n\nexport type Delivery = 'import' | 'embedded' | 'own-file';\n\nexport interface SizeLimit {\n maxBytes?: number;\n maxLines?: number;\n warning: string;\n}\n\nexport interface Adapter {\n id: ToolId;\n name: string;\n delivery: Delivery;\n detect(cwd: string): Promise<boolean>;\n instructionFile(cwd: string): string | Promise<string>;\n // Order matters: the first folder is the one Engly installs to (see skillTargets).\n skillDirs: readonly string[];\n sizeLimit?: SizeLimit;\n}\n","import { claude } from './claude.js';\nimport { codex } from './codex.js';\nimport { copilot } from './copilot.js';\nimport { cursor } from './cursor.js';\nimport { opencode } from './opencode.js';\nimport type { Adapter, ToolId } from './types.js';\n\nexport type { Adapter, Delivery, ToolId } from './types.js';\nexport { TOOL_IDS } from './types.js';\n\n// Adding a new agent = adding its adapter here (spec ยง5).\nexport const adapters: Record<ToolId, Adapter> = { claude, codex, opencode, cursor, copilot };\n\nexport const IMPLEMENTED_TOOLS: readonly ToolId[] = ['claude'];\n\nexport function skillTargets(tools: readonly ToolId[]): string[] {\n const chosen: string[] = [];\n const byChoices = [...tools].sort((a, b) => adapters[a].skillDirs.length - adapters[b].skillDirs.length);\n for (const tool of byChoices) {\n const dirs = adapters[tool].skillDirs;\n const [preferred] = dirs;\n if (preferred && !dirs.some((dir) => chosen.includes(dir))) chosen.push(preferred);\n }\n return chosen;\n}\n\nexport async function detectTools(cwd: string): Promise<ToolId[]> {\n const found: ToolId[] = [];\n for (const adapter of Object.values(adapters)) {\n if (await adapter.detect(cwd)) found.push(adapter.id);\n }\n return found;\n}\n","export const START_MARKER_PREFIX = '<!-- engly:start';\nexport const END_MARKER = '<!-- engly:end -->';\n\nconst START_MARKER_RE = /<!-- engly:start(?: v?([^\\s>]+))? -->/g;\n\nexport type Eol = '\\n' | '\\r\\n';\nexport type BlockPosition = 'top' | 'end';\nexport type UpsertAction = 'created' | 'appended' | 'prepended' | 'updated';\n\nexport class BlockError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BlockError';\n }\n}\n\nexport interface BlockRange {\n start: number;\n end: number;\n version: string | null;\n}\n\nexport function detectEol(text: string): Eol {\n const index = text.indexOf('\\n');\n if (index > 0 && text[index - 1] === '\\r') return '\\r\\n';\n return '\\n';\n}\n\nexport function startMarker(version: string): string {\n return `${START_MARKER_PREFIX} v${version.replace(/^v/, '')} -->`;\n}\n\n// Broken markers throw instead of being repaired: editing around them could destroy user content.\nexport function findBlock(text: string): BlockRange | null {\n const starts = [...text.matchAll(START_MARKER_RE)];\n const ends = indexesOf(text, END_MARKER);\n\n if (starts.length === 0 && ends.length === 0) return null;\n\n const [startMatch] = starts;\n const [endIndex] = ends;\n if (!startMatch || endIndex === undefined || starts.length > 1 || ends.length > 1) {\n throw new BlockError(\n `Expected exactly one Engly block, found ${starts.length} start and ${ends.length} end marker(s). ` +\n 'Fix the markers by hand before running Engly again.',\n );\n }\n\n const start = startMatch.index;\n if (endIndex < start) {\n throw new BlockError('The Engly end marker appears before the start marker.');\n }\n\n return { start, end: endIndex + END_MARKER.length, version: startMatch[1] ?? null };\n}\n\nexport function hasBlock(text: string): boolean {\n return findBlock(text) !== null;\n}\n\nexport function readBlock(text: string): string | null {\n const range = findBlock(text);\n if (!range) return null;\n const inner = text.slice(range.start, range.end);\n const afterStart = inner.indexOf('-->') + 3;\n return stripOneEol(inner.slice(afterStart, inner.length - END_MARKER.length), 'both');\n}\n\nexport function buildBlock(content: string, version: string, eol: Eol = '\\n'): string {\n const body = content.replace(/\\r\\n/g, '\\n').replace(/\\n+$/, '');\n const lines = [startMarker(version), ...(body === '' ? [] : body.split('\\n')), END_MARKER];\n return lines.join(eol);\n}\n\nexport interface UpsertOptions {\n version: string;\n position?: BlockPosition;\n}\n\nexport interface UpsertResult {\n text: string;\n action: UpsertAction;\n}\n\nexport function upsertBlock(text: string, content: string, options: UpsertOptions): UpsertResult {\n const eol = detectEol(text);\n const block = buildBlock(content, options.version, eol);\n const range = findBlock(text);\n\n if (range) {\n return { text: text.slice(0, range.start) + block + text.slice(range.end), action: 'updated' };\n }\n\n if (text === '') {\n return { text: block + eol, action: 'created' };\n }\n\n if (options.position === 'top') {\n return { text: block + eol + eol + text, action: 'prepended' };\n }\n\n const separator = endsWithEol(text) ? eol : eol + eol;\n return { text: text + separator + block + eol, action: 'appended' };\n}\n\nexport interface RemoveResult {\n text: string;\n removed: boolean;\n}\n\nexport function removeBlock(text: string): RemoveResult {\n const range = findBlock(text);\n if (!range) return { text, removed: false };\n\n let before = text.slice(0, range.start);\n let after = stripOneEol(text.slice(range.end), 'start');\n\n if (before === '') {\n after = stripOneEol(after, 'start');\n } else if (/(\\r?\\n)\\r?\\n$/.test(before)) {\n before = stripOneEol(before, 'end');\n }\n\n return { text: before + after, removed: true };\n}\n\nfunction endsWithEol(text: string): boolean {\n return text.endsWith('\\n');\n}\n\nfunction stripOneEol(text: string, side: 'start' | 'end' | 'both'): string {\n let result = text;\n if (side === 'start' || side === 'both') {\n if (result.startsWith('\\r\\n')) result = result.slice(2);\n else if (result.startsWith('\\n')) result = result.slice(1);\n }\n if (side === 'end' || side === 'both') {\n if (result.endsWith('\\r\\n')) result = result.slice(0, -2);\n else if (result.endsWith('\\n')) result = result.slice(0, -1);\n }\n return result;\n}\n\nfunction indexesOf(text: string, search: string): number[] {\n const result: number[] = [];\n let index = text.indexOf(search);\n while (index !== -1) {\n result.push(index);\n index = text.indexOf(search, index + search.length);\n }\n return result;\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type { ToolId } from '../adapters/types.js';\nimport { CONFIG_FILE } from './paths.js';\n\nexport const LEVELS = ['auto', 'A2', 'B1', 'B2', 'C1'] as const;\nexport const STYLES = ['light', 'detailed'] as const;\n\nexport type Level = (typeof LEVELS)[number];\nexport type Style = (typeof STYLES)[number];\n\nexport interface EnglyConfig {\n version: string;\n language: string;\n level: Level;\n style: Style;\n reviewEnglish: boolean;\n enabled: boolean;\n tools: ToolId[];\n}\n\nexport function defaultConfig(version: string): EnglyConfig {\n return {\n version,\n language: 'auto',\n level: 'auto',\n style: 'light',\n reviewEnglish: true,\n enabled: true,\n tools: [],\n };\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nexport function configPath(cwd: string): string {\n return join(cwd, CONFIG_FILE);\n}\n\nexport async function readConfig(cwd: string, version: string): Promise<EnglyConfig | null> {\n let raw: string;\n try {\n raw = await readFile(configPath(cwd), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON.`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new ConfigError(`${CONFIG_FILE} must contain a JSON object.`);\n }\n\n return validateConfig({ ...defaultConfig(version), ...(parsed as Partial<EnglyConfig>) });\n}\n\nexport async function writeConfig(cwd: string, config: EnglyConfig): Promise<void> {\n const path = configPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(validateConfig(config), null, 2)}\\n`, 'utf8');\n}\n\nexport function validateConfig(config: EnglyConfig): EnglyConfig {\n if (!(LEVELS as readonly string[]).includes(config.level)) {\n throw new ConfigError(`Invalid level \"${config.level}\". Use one of: ${LEVELS.join(', ')}.`);\n }\n if (!(STYLES as readonly string[]).includes(config.style)) {\n throw new ConfigError(`Invalid style \"${config.style}\". Use one of: ${STYLES.join(', ')}.`);\n }\n if (config.language !== 'auto' && !/^[a-z]{2,3}$/.test(config.language)) {\n throw new ConfigError(`Invalid language \"${config.language}\". Use \"auto\" or a language code like \"es\".`);\n }\n if (typeof config.enabled !== 'boolean' || typeof config.reviewEnglish !== 'boolean') {\n throw new ConfigError('\"enabled\" and \"reviewEnglish\" must be true or false.');\n }\n if (!Array.isArray(config.tools)) {\n throw new ConfigError('\"tools\" must be a list.');\n }\n return config;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nlet cachedRoot: string | undefined;\n\nexport function packageRoot(): string {\n if (cachedRoot) return cachedRoot;\n let dir = dirname(fileURLToPath(import.meta.url));\n while (true) {\n const pkgPath = join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };\n if (pkg.name === 'engly') {\n cachedRoot = dir;\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('Could not locate the engly package root.');\n dir = parent;\n }\n}\n\nexport function templatesDir(): string {\n return join(packageRoot(), 'templates');\n}\n\nexport function packageVersion(): string {\n const pkg = JSON.parse(readFileSync(join(packageRoot(), 'package.json'), 'utf8')) as { version: string };\n return pkg.version;\n}\n\nexport const ENGLY_DIR = '.engly';\nexport const CONFIG_FILE = `${ENGLY_DIR}/config.json`;\nexport const INSTRUCTIONS_FILE = `${ENGLY_DIR}/engly.md`;\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\nexport async function readTextIfExists(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n}\n\nexport async function writeText(path: string, content: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, content, 'utf8');\n}\n","import { spawnSync } from 'node:child_process';\n\nexport type GitStatus = 'tracked' | 'untracked' | 'ignored' | 'no-git';\n\nfunction git(cwd: string, args: string[]): number | null {\n const result = spawnSync('git', args, { cwd, stdio: 'ignore' });\n if (result.error) return null;\n return result.status;\n}\n\nexport function gitStatus(cwd: string, relativePath: string): GitStatus {\n if (git(cwd, ['rev-parse', '--is-inside-work-tree']) !== 0) return 'no-git';\n if (git(cwd, ['ls-files', '--error-unmatch', '--', relativePath]) === 0) return 'tracked';\n if (git(cwd, ['check-ignore', '-q', '--', relativePath]) === 0) return 'ignored';\n return 'untracked';\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { EnglyConfig } from './config.js';\nimport { templatesDir } from './paths.js';\n\nexport const SKILL_NAMES = ['engly-on', 'engly-off', 'engly-review'] as const;\nexport type SkillName = (typeof SKILL_NAMES)[number];\n\nexport async function renderInstructions(config: EnglyConfig): Promise<string> {\n const template = await readFile(join(templatesDir(), 'engly.md'), 'utf8');\n\n const values: Record<string, string> = {\n language: config.language === 'auto' ? 'auto' : config.language,\n level: config.level,\n styleRule:\n config.style === 'light'\n ? 'show up to 3 corrections (\"wrong\" โ \"right\" with a short reason) and the improved prompt.'\n : 'explain every mistake (\"wrong\" โ \"right\" with a short reason) and show the improved prompt.',\n englishRule: config.reviewEnglish\n ? 'Prompt in correct English: show nothing.'\n : 'Prompt in English: show nothing, even if it has mistakes. Only coach prompts in other languages.',\n lengthRule: config.style === 'light' ? 'Keep the note to 6 lines at most.' : 'Keep the note as short as possible.',\n };\n\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => values[key] ?? match);\n}\n\nexport async function readSkill(name: SkillName): Promise<string> {\n return readFile(join(templatesDir(), 'skills', name, 'SKILL.md'), 'utf8');\n}\n","import type { SizeLimit } from '../adapters/types.js';\n\nexport interface SizeReport {\n label: string;\n warning: string;\n}\n\nexport function checkSize(text: string, limit: SizeLimit | undefined): SizeReport | null {\n if (!limit) return null;\n\n const bytes = Buffer.byteLength(text, 'utf8');\n if (limit.maxBytes !== undefined && bytes > limit.maxBytes) {\n return { label: `${(bytes / 1024).toFixed(1)} KB`, warning: limit.warning };\n }\n\n const lines = text === '' ? 0 : text.split('\\n').length - (text.endsWith('\\n') ? 1 : 0);\n if (limit.maxLines !== undefined && lines > limit.maxLines) {\n return { label: `${lines} lines`, warning: limit.warning };\n }\n\n return null;\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function removeCommand(): Promise<void> {\n notImplemented('remove');\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function statusCommand(): Promise<void> {\n notImplemented('status');\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function updateCommand(): Promise<void> {\n notImplemented('update');\n}\n"],"mappings":"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","names":["join","dirname","join","dirname","join","join","dirname","mkdir","readFile","writeFile","dirname","readFile","join","readFile","join","join"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/not-implemented.ts","../src/commands/config.ts","../src/commands/set-enabled.ts","../src/core/block.ts","../src/core/config.ts","../src/core/paths.ts","../src/core/fs.ts","../src/core/install.ts","../src/adapters/detect.ts","../src/adapters/claude.ts","../src/adapters/codex.ts","../src/adapters/copilot.ts","../src/adapters/cursor.ts","../src/adapters/opencode.ts","../src/adapters/types.ts","../src/adapters/index.ts","../src/core/instructions.ts","../src/core/project.ts","../src/commands/disable.ts","../src/commands/enable.ts","../src/commands/init.ts","../src/core/git.ts","../src/core/size.ts","../src/commands/remove.ts","../src/commands/status.ts","../src/commands/update.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { configCommand } from './commands/config.js';\nimport { disableCommand } from './commands/disable.js';\nimport { enableCommand } from './commands/enable.js';\nimport { initCommand } from './commands/init.js';\nimport { removeCommand } from './commands/remove.js';\nimport { statusCommand } from './commands/status.js';\nimport { updateCommand } from './commands/update.js';\nimport { packageVersion } from './core/paths.js';\n\nconst program = new Command();\n\nprogram\n .name('engly')\n .description('Your English coach for AI prompts. One command. Every agent. Always on. Zero cost.')\n .version(packageVersion());\n\nprogram\n .command('init')\n .description('Install Engly in the current project')\n .option('--tools <tools>', 'comma-separated agents: claude,codex,opencode,cursor,copilot')\n .option('--style <style>', 'feedback style: light or detailed')\n .option('-y, --yes', 'accept all defaults without asking')\n .option('--no-apply', \"only install the skills, don't touch the agent files\")\n .action(initCommand);\n\nprogram.command('update').description('Update the skills and regenerate the Engly block').action(updateCommand);\nprogram.command('enable').description('Turn the coach on permanently').action(enableCommand);\nprogram.command('disable').description('Turn the coach off permanently, without uninstalling').action(disableCommand);\nprogram\n .command('config')\n .description('Change an option, e.g. \"engly config level B2\"')\n .argument('<option>', 'language, level or style')\n .argument('<value>', 'new value')\n .action(configCommand);\nprogram.command('status').description('Show where Engly is installed and whether it is active').action(statusCommand);\nprogram\n .command('remove')\n .description('Remove the Engly block, skills and config')\n .option('-y, --yes', 'remove without asking')\n .action(removeCommand);\n\ntry {\n await program.parseAsync();\n} catch (error) {\n console.error(`engly: ${error instanceof Error ? error.message : String(error)}`);\n process.exitCode = 1;\n}\n","export function notImplemented(command: string): void {\n console.log(`engly ${command}: not implemented yet`);\n}\n","import { notImplemented } from './not-implemented.js';\n\nexport async function configCommand(_option: string, _value: string): Promise<void> {\n notImplemented('config');\n}\n","import { join } from 'node:path';\nimport * as p from '@clack/prompts';\nimport { upsertBlock } from '../core/block.js';\nimport { writeConfig } from '../core/config.js';\nimport { pathExists, writeText } from '../core/fs.js';\nimport { writeInstructions } from '../core/install.js';\nimport { INSTRUCTIONS_FILE, packageVersion } from '../core/paths.js';\nimport { blockContent, candidateFiles, configuredTools, findBlocks, requireConfig } from '../core/project.js';\n\n// Turning the coach on or off only touches the content of the block, so its position stays as it was.\nexport async function setEnabled(cwd: string, enabled: boolean): Promise<void> {\n const version = packageVersion();\n const word = enabled ? 'enabled' : 'disabled';\n p.intro(`Engly v${version}`);\n\n const config = await requireConfig(cwd, version);\n const tools = configuredTools(config);\n const blocks = await findBlocks(cwd, tools);\n\n if (blocks.length === 0) {\n const files = (await candidateFiles(cwd, tools)).join(' or ');\n p.log.warn(`No Engly block found in ${files}. Run \"engly init\" to add it.`);\n p.outro('Nothing changed.');\n process.exitCode = 1;\n return;\n }\n\n const pending = blocks.filter((block) => (block.state === 'active') !== enabled);\n if (pending.length === 0 && config.enabled === enabled) {\n p.outro(`Engly is already ${word}.`);\n return;\n }\n\n // Not a version bump: only \"engly update\" changes the version the files were written with.\n for (const block of pending) {\n const { text } = upsertBlock(block.text, blockContent(enabled), { version: config.version });\n await writeText(block.path, text);\n }\n if (enabled && !(await pathExists(join(cwd, INSTRUCTIONS_FILE)))) {\n await writeInstructions(cwd, config);\n }\n config.enabled = enabled;\n await writeConfig(cwd, config);\n\n const where = pending.length > 0 ? ` in ${pending.map((block) => block.file).join(' and ')}` : '';\n p.log.success(`โ Engly ${word}${where}`);\n p.outro('Takes effect in your next agent session.');\n}\n","export const START_MARKER_PREFIX = '<!-- engly:start';\nexport const END_MARKER = '<!-- engly:end -->';\n\nconst START_MARKER_RE = /<!-- engly:start(?: v?([^\\s>]+))? -->/g;\n\nexport type Eol = '\\n' | '\\r\\n';\nexport type BlockPosition = 'top' | 'end';\nexport type UpsertAction = 'created' | 'appended' | 'prepended' | 'updated';\n\nexport class BlockError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BlockError';\n }\n}\n\nexport interface BlockRange {\n start: number;\n end: number;\n version: string | null;\n}\n\nexport function detectEol(text: string): Eol {\n const index = text.indexOf('\\n');\n if (index > 0 && text[index - 1] === '\\r') return '\\r\\n';\n return '\\n';\n}\n\nexport function startMarker(version: string): string {\n return `${START_MARKER_PREFIX} v${version.replace(/^v/, '')} -->`;\n}\n\n// Broken markers throw instead of being repaired: editing around them could destroy user content.\nexport function findBlock(text: string): BlockRange | null {\n const starts = [...text.matchAll(START_MARKER_RE)];\n const ends = indexesOf(text, END_MARKER);\n\n if (starts.length === 0 && ends.length === 0) return null;\n\n const [startMatch] = starts;\n const [endIndex] = ends;\n if (!startMatch || endIndex === undefined || starts.length > 1 || ends.length > 1) {\n throw new BlockError(\n `Expected exactly one Engly block, found ${starts.length} start and ${ends.length} end marker(s). ` +\n 'Fix the markers by hand before running Engly again.',\n );\n }\n\n const start = startMatch.index;\n if (endIndex < start) {\n throw new BlockError('The Engly end marker appears before the start marker.');\n }\n\n return { start, end: endIndex + END_MARKER.length, version: startMatch[1] ?? null };\n}\n\nexport function hasBlock(text: string): boolean {\n return findBlock(text) !== null;\n}\n\nexport function readBlock(text: string): string | null {\n const range = findBlock(text);\n if (!range) return null;\n const inner = text.slice(range.start, range.end);\n const afterStart = inner.indexOf('-->') + 3;\n return stripOneEol(inner.slice(afterStart, inner.length - END_MARKER.length), 'both');\n}\n\nexport function buildBlock(content: string, version: string, eol: Eol = '\\n'): string {\n const body = content.replace(/\\r\\n/g, '\\n').replace(/\\n+$/, '');\n const lines = [startMarker(version), ...(body === '' ? [] : body.split('\\n')), END_MARKER];\n return lines.join(eol);\n}\n\nexport interface UpsertOptions {\n version: string;\n position?: BlockPosition;\n}\n\nexport interface UpsertResult {\n text: string;\n action: UpsertAction;\n}\n\nexport function upsertBlock(text: string, content: string, options: UpsertOptions): UpsertResult {\n const eol = detectEol(text);\n const block = buildBlock(content, options.version, eol);\n const range = findBlock(text);\n\n if (range) {\n return { text: text.slice(0, range.start) + block + text.slice(range.end), action: 'updated' };\n }\n\n if (text === '') {\n return { text: block + eol, action: 'created' };\n }\n\n if (options.position === 'top') {\n return { text: block + eol + eol + text, action: 'prepended' };\n }\n\n const separator = endsWithEol(text) ? eol : eol + eol;\n return { text: text + separator + block + eol, action: 'appended' };\n}\n\nexport interface RemoveResult {\n text: string;\n removed: boolean;\n}\n\nexport function removeBlock(text: string): RemoveResult {\n const range = findBlock(text);\n if (!range) return { text, removed: false };\n\n let before = text.slice(0, range.start);\n let after = stripOneEol(text.slice(range.end), 'start');\n\n if (before === '') {\n after = stripOneEol(after, 'start');\n } else if (/(\\r?\\n)\\r?\\n$/.test(before)) {\n before = stripOneEol(before, 'end');\n }\n\n return { text: before + after, removed: true };\n}\n\nfunction endsWithEol(text: string): boolean {\n return text.endsWith('\\n');\n}\n\nfunction stripOneEol(text: string, side: 'start' | 'end' | 'both'): string {\n let result = text;\n if (side === 'start' || side === 'both') {\n if (result.startsWith('\\r\\n')) result = result.slice(2);\n else if (result.startsWith('\\n')) result = result.slice(1);\n }\n if (side === 'end' || side === 'both') {\n if (result.endsWith('\\r\\n')) result = result.slice(0, -2);\n else if (result.endsWith('\\n')) result = result.slice(0, -1);\n }\n return result;\n}\n\nfunction indexesOf(text: string, search: string): number[] {\n const result: number[] = [];\n let index = text.indexOf(search);\n while (index !== -1) {\n result.push(index);\n index = text.indexOf(search, index + search.length);\n }\n return result;\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type { ToolId } from '../adapters/types.js';\nimport { CONFIG_FILE } from './paths.js';\n\nexport const LEVELS = ['auto', 'A2', 'B1', 'B2', 'C1'] as const;\nexport const STYLES = ['light', 'detailed'] as const;\n\nexport type Level = (typeof LEVELS)[number];\nexport type Style = (typeof STYLES)[number];\n\nexport interface EnglyConfig {\n version: string;\n language: string;\n level: Level;\n style: Style;\n reviewEnglish: boolean;\n enabled: boolean;\n tools: ToolId[];\n}\n\nexport function defaultConfig(version: string): EnglyConfig {\n return {\n version,\n language: 'auto',\n level: 'auto',\n style: 'light',\n reviewEnglish: true,\n enabled: true,\n tools: [],\n };\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nexport function configPath(cwd: string): string {\n return join(cwd, CONFIG_FILE);\n}\n\nexport async function readConfig(cwd: string, version: string): Promise<EnglyConfig | null> {\n let raw: string;\n try {\n raw = await readFile(configPath(cwd), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON.`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new ConfigError(`${CONFIG_FILE} must contain a JSON object.`);\n }\n\n return validateConfig({ ...defaultConfig(version), ...(parsed as Partial<EnglyConfig>) });\n}\n\nexport async function writeConfig(cwd: string, config: EnglyConfig): Promise<void> {\n const path = configPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(validateConfig(config), null, 2)}\\n`, 'utf8');\n}\n\nexport function validateConfig(config: EnglyConfig): EnglyConfig {\n if (!(LEVELS as readonly string[]).includes(config.level)) {\n throw new ConfigError(`Invalid level \"${config.level}\". Use one of: ${LEVELS.join(', ')}.`);\n }\n if (!(STYLES as readonly string[]).includes(config.style)) {\n throw new ConfigError(`Invalid style \"${config.style}\". Use one of: ${STYLES.join(', ')}.`);\n }\n if (config.language !== 'auto' && !/^[a-z]{2,3}$/.test(config.language)) {\n throw new ConfigError(`Invalid language \"${config.language}\". Use \"auto\" or a language code like \"es\".`);\n }\n if (typeof config.enabled !== 'boolean' || typeof config.reviewEnglish !== 'boolean') {\n throw new ConfigError('\"enabled\" and \"reviewEnglish\" must be true or false.');\n }\n if (!Array.isArray(config.tools)) {\n throw new ConfigError('\"tools\" must be a list.');\n }\n return config;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nlet cachedRoot: string | undefined;\n\nexport function packageRoot(): string {\n if (cachedRoot) return cachedRoot;\n let dir = dirname(fileURLToPath(import.meta.url));\n while (true) {\n const pkgPath = join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };\n if (pkg.name === 'engly') {\n cachedRoot = dir;\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('Could not locate the engly package root.');\n dir = parent;\n }\n}\n\nexport function templatesDir(): string {\n return join(packageRoot(), 'templates');\n}\n\nexport function packageVersion(): string {\n const pkg = JSON.parse(readFileSync(join(packageRoot(), 'package.json'), 'utf8')) as { version: string };\n return pkg.version;\n}\n\nexport const ENGLY_DIR = '.engly';\nexport const CONFIG_FILE = `${ENGLY_DIR}/config.json`;\nexport const INSTRUCTIONS_FILE = `${ENGLY_DIR}/engly.md`;\n","import { access, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\nexport async function readTextIfExists(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n}\n\nexport async function writeText(path: string, content: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, content, 'utf8');\n}\n\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { rm, rmdir } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { adapters, skillTargets, type ToolId } from '../adapters/index.js';\nimport type { EnglyConfig } from './config.js';\nimport { pathExists, writeText } from './fs.js';\nimport { readSkill, renderInstructions, SKILL_NAMES } from './instructions.js';\nimport { INSTRUCTIONS_FILE } from './paths.js';\n\nexport async function writeInstructions(cwd: string, config: EnglyConfig): Promise<void> {\n await writeText(join(cwd, INSTRUCTIONS_FILE), await renderInstructions(config));\n}\n\nexport async function installSkills(cwd: string, tools: readonly ToolId[]): Promise<void> {\n for (const dir of skillTargets(tools)) {\n for (const skill of SKILL_NAMES) {\n await writeText(join(cwd, dir, skill, 'SKILL.md'), await readSkill(skill));\n }\n }\n}\n\nexport interface SkillFolderStatus {\n dir: string;\n missing: string[];\n}\n\nexport async function skillStatus(cwd: string, tools: readonly ToolId[]): Promise<SkillFolderStatus[]> {\n const folders: SkillFolderStatus[] = [];\n for (const dir of skillTargets(tools)) {\n const missing: string[] = [];\n for (const skill of SKILL_NAMES) {\n if (!(await pathExists(join(cwd, dir, skill, 'SKILL.md')))) missing.push(skill);\n }\n folders.push({ dir, missing });\n }\n return folders;\n}\n\n// Looks in every folder any agent uses, so it works even when the config is gone.\n// Only the engly-* folders are deleted; the parent folders go only if that leaves them empty.\nexport async function removeSkills(cwd: string): Promise<string[]> {\n const dirs = new Set(Object.values(adapters).flatMap((adapter) => adapter.skillDirs));\n const removed: string[] = [];\n\n for (const dir of dirs) {\n let removedHere = false;\n for (const skill of SKILL_NAMES) {\n const path = join(cwd, dir, skill);\n if (!(await pathExists(path))) continue;\n await rm(path, { recursive: true, force: true });\n removed.push(join(dir, skill));\n removedHere = true;\n }\n if (removedHere) await removeEmptyDirs(cwd, dir);\n }\n\n return removed;\n}\n\nexport async function hasInstalledSkills(cwd: string): Promise<boolean> {\n const dirs = new Set(Object.values(adapters).flatMap((adapter) => adapter.skillDirs));\n for (const dir of dirs) {\n for (const skill of SKILL_NAMES) {\n if (await pathExists(join(cwd, dir, skill))) return true;\n }\n }\n return false;\n}\n\nasync function removeEmptyDirs(cwd: string, relativeDir: string): Promise<void> {\n let dir = relativeDir;\n // Up to two levels, e.g. .claude/skills then .claude.\n for (let level = 0; level < 2 && dir !== '.'; level++) {\n try {\n await rmdir(join(cwd, dir));\n } catch {\n return;\n }\n dir = dirname(dir);\n }\n}\n","import { access } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nexport async function anyExists(cwd: string, paths: string[]): Promise<boolean> {\n for (const path of paths) {\n try {\n await access(join(cwd, path));\n return true;\n } catch {}\n }\n return false;\n}\n\n// Same as anyExists, but also looks in every directory above `cwd`.\nexport async function anyExistsUpward(cwd: string, paths: string[]): Promise<boolean> {\n let dir = cwd;\n while (true) {\n if (await anyExists(dir, paths)) return true;\n const parent = dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n","import { anyExists, anyExistsUpward } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const claude: Adapter = {\n id: 'claude',\n name: 'Claude Code',\n delivery: 'import',\n detect: (cwd) => anyExists(cwd, ['CLAUDE.md', '.claude']),\n // Claude Code reads AGENTS.md only when there is no CLAUDE.md, .claude/CLAUDE.md or\n // CLAUDE.local.md in the working directory or any directory above it.\n instructionFile: async (cwd) => {\n if (await anyExistsUpward(cwd, ['CLAUDE.md', '.claude/CLAUDE.md', 'CLAUDE.local.md'])) return 'CLAUDE.md';\n return (await anyExists(cwd, ['AGENTS.md'])) ? 'AGENTS.md' : 'CLAUDE.md';\n },\n instructionFiles: ['CLAUDE.md', 'AGENTS.md'],\n skillDirs: ['.claude/skills'],\n sizeLimit: {\n maxLines: 200,\n warning: 'Claude Code follows long instruction files less closely.',\n },\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const codex: Adapter = {\n id: 'codex',\n name: 'Codex',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['.codex', 'AGENTS.md']),\n instructionFile: () => 'AGENTS.md',\n skillDirs: ['.agents/skills'],\n sizeLimit: {\n // Real limit is 32 KiB for all AGENTS.md files combined (spec ยง9.6).\n maxBytes: 24 * 1024,\n warning: 'Codex may stop reading long instruction files.\\nThe Engly block could be cut off.',\n },\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const copilot: Adapter = {\n id: 'copilot',\n name: 'GitHub Copilot',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['.github/copilot-instructions.md', '.github/instructions']),\n instructionFile: () => '.github/copilot-instructions.md',\n skillDirs: ['.agents/skills', '.claude/skills', '.github/skills'],\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const CURSOR_RULE_FRONTMATTER = '---\\ndescription: Engly English coach\\nalwaysApply: true\\n---\\n';\n\n// TODO(spec ยง15): Cursor reads .claude/skills/ only as a \"legacy\" folder and\n// was not tested. If it stops doing so, Cursor + Claude Code would get no skills.\nexport const cursor: Adapter = {\n id: 'cursor',\n name: 'Cursor',\n delivery: 'own-file',\n detect: (cwd) => anyExists(cwd, ['.cursor', '.cursorrules']),\n instructionFile: () => '.cursor/rules/engly.mdc',\n skillDirs: ['.agents/skills', '.cursor/skills', '.claude/skills'],\n};\n","import { anyExists } from './detect.js';\nimport type { Adapter } from './types.js';\n\nexport const opencode: Adapter = {\n id: 'opencode',\n name: 'OpenCode',\n delivery: 'embedded',\n detect: (cwd) => anyExists(cwd, ['opencode.json', 'opencode.jsonc', '.opencode']),\n instructionFile: () => 'AGENTS.md',\n skillDirs: ['.agents/skills', '.claude/skills', '.opencode/skills'],\n};\n","export const TOOL_IDS = ['claude', 'codex', 'opencode', 'cursor', 'copilot'] as const;\nexport type ToolId = (typeof TOOL_IDS)[number];\n\nexport type Delivery = 'import' | 'embedded' | 'own-file';\n\nexport interface SizeLimit {\n maxBytes?: number;\n maxLines?: number;\n warning: string;\n}\n\nexport interface Adapter {\n id: ToolId;\n name: string;\n delivery: Delivery;\n detect(cwd: string): Promise<boolean>;\n instructionFile(cwd: string): string | Promise<string>;\n // Every file `instructionFile` may have picked over time. Defaults to just that file.\n instructionFiles?: readonly string[];\n // Order matters: the first folder is the one Engly installs to (see skillTargets).\n skillDirs: readonly string[];\n sizeLimit?: SizeLimit;\n}\n","import { claude } from './claude.js';\nimport { codex } from './codex.js';\nimport { copilot } from './copilot.js';\nimport { cursor } from './cursor.js';\nimport { opencode } from './opencode.js';\nimport type { Adapter, ToolId } from './types.js';\n\nexport type { Adapter, Delivery, ToolId } from './types.js';\nexport { TOOL_IDS } from './types.js';\n\n// Adding a new agent = adding its adapter here (spec ยง5).\nexport const adapters: Record<ToolId, Adapter> = { claude, codex, opencode, cursor, copilot };\n\nexport const IMPLEMENTED_TOOLS: readonly ToolId[] = ['claude'];\n\nexport function skillTargets(tools: readonly ToolId[]): string[] {\n const chosen: string[] = [];\n const byChoices = [...tools].sort((a, b) => adapters[a].skillDirs.length - adapters[b].skillDirs.length);\n for (const tool of byChoices) {\n const dirs = adapters[tool].skillDirs;\n const [preferred] = dirs;\n if (preferred && !dirs.some((dir) => chosen.includes(dir))) chosen.push(preferred);\n }\n return chosen;\n}\n\nexport async function detectTools(cwd: string): Promise<ToolId[]> {\n const found: ToolId[] = [];\n for (const adapter of Object.values(adapters)) {\n if (await adapter.detect(cwd)) found.push(adapter.id);\n }\n return found;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { EnglyConfig } from './config.js';\nimport { templatesDir } from './paths.js';\n\nexport const SKILL_NAMES = ['engly-on', 'engly-off', 'engly-review'] as const;\nexport type SkillName = (typeof SKILL_NAMES)[number];\n\nexport async function renderInstructions(config: EnglyConfig): Promise<string> {\n const template = await readFile(join(templatesDir(), 'engly.md'), 'utf8');\n\n const values: Record<string, string> = {\n language: config.language === 'auto' ? 'auto' : config.language,\n level: config.level,\n styleRule:\n config.style === 'light'\n ? 'show up to 3 corrections (\"wrong\" โ \"right\" with a short reason) and the improved prompt.'\n : 'explain every mistake (\"wrong\" โ \"right\" with a short reason) and show the improved prompt.',\n englishRule: config.reviewEnglish\n ? 'Prompt in correct English: show nothing.'\n : 'Prompt in English: show nothing, even if it has mistakes. Only coach prompts in other languages.',\n lengthRule: config.style === 'light' ? 'Keep the note to 6 lines at most.' : 'Keep the note as short as possible.',\n };\n\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => values[key] ?? match);\n}\n\nexport async function readSkill(name: SkillName): Promise<string> {\n return readFile(join(templatesDir(), 'skills', name, 'SKILL.md'), 'utf8');\n}\n","import { join } from 'node:path';\nimport { adapters, IMPLEMENTED_TOOLS, type ToolId } from '../adapters/index.js';\nimport { BlockError, readBlock } from './block.js';\nimport { type EnglyConfig, readConfig } from './config.js';\nimport { readTextIfExists } from './fs.js';\nimport { INSTRUCTIONS_FILE } from './paths.js';\n\nexport type AgentFileState = 'missing' | 'no-block' | 'active' | 'inactive' | 'broken';\n\nexport interface AgentFile {\n file: string;\n path: string;\n text: string;\n state: AgentFileState;\n // Set when the state is \"broken\".\n error?: string;\n}\n\n// A disabled block keeps its markers and loses its content, so it stays where the user had it.\nexport function blockContent(enabled: boolean): string {\n return enabled ? `@${INSTRUCTIONS_FILE}` : '';\n}\n\nexport async function inspectAgentFile(cwd: string, file: string): Promise<AgentFile> {\n const path = join(cwd, file);\n const text = await readTextIfExists(path);\n if (text === null) return { file, path, text: '', state: 'missing' };\n\n try {\n const content = readBlock(text);\n if (content === null) return { file, path, text, state: 'no-block' };\n return { file, path, text, state: content.trim() === '' ? 'inactive' : 'active' };\n } catch (error) {\n if (error instanceof BlockError) return { file, path, text, state: 'broken', error: error.message };\n throw error;\n }\n}\n\n// The block may live in any file `engly init` could have picked: Claude Code's choice between\n// CLAUDE.md and AGENTS.md depends on which files exist today, not on when Engly was installed.\nexport async function candidateFiles(cwd: string, tools: readonly ToolId[]): Promise<string[]> {\n const files = new Set<string>();\n for (const tool of tools) {\n if (!IMPLEMENTED_TOOLS.includes(tool)) continue;\n const adapter = adapters[tool];\n for (const file of adapter.instructionFiles ?? [await adapter.instructionFile(cwd)]) files.add(file);\n }\n return [...files];\n}\n\nexport async function inspectAgentFiles(cwd: string, tools: readonly ToolId[]): Promise<AgentFile[]> {\n const files: AgentFile[] = [];\n for (const file of await candidateFiles(cwd, tools)) {\n const inspected = await inspectAgentFile(cwd, file);\n if (inspected.state !== 'missing') files.push(inspected);\n }\n return files;\n}\n\n// Files that contain an Engly block. Broken markers throw: editing around them could destroy user content.\nexport async function findBlocks(cwd: string, tools: readonly ToolId[]): Promise<AgentFile[]> {\n const files = await inspectAgentFiles(cwd, tools);\n for (const file of files) {\n if (file.state === 'broken') throw new BlockError(`${file.file}: ${file.error}`);\n }\n return files.filter((file) => file.state === 'active' || file.state === 'inactive');\n}\n\nexport async function requireConfig(cwd: string, version: string): Promise<EnglyConfig> {\n const config = await readConfig(cwd, version);\n if (!config) throw new Error('Engly is not installed here. Run \"engly init\" first.');\n return config;\n}\n\n// Only tools Engly supports today; a config written by a newer version may list more.\nexport function configuredTools(config: EnglyConfig | null): ToolId[] {\n const tools = config?.tools.filter((tool) => IMPLEMENTED_TOOLS.includes(tool)) ?? [];\n return tools.length > 0 ? tools : [...IMPLEMENTED_TOOLS];\n}\n","import { setEnabled } from './set-enabled.js';\n\nexport async function disableCommand(): Promise<void> {\n await setEnabled(process.cwd(), false);\n}\n","import { setEnabled } from './set-enabled.js';\n\nexport async function enableCommand(): Promise<void> {\n await setEnabled(process.cwd(), true);\n}\n","import { join } from 'node:path';\nimport * as p from '@clack/prompts';\nimport { adapters, detectTools, IMPLEMENTED_TOOLS, TOOL_IDS, type ToolId } from '../adapters/index.js';\nimport { type BlockPosition, hasBlock, type UpsertAction, upsertBlock } from '../core/block.js';\nimport { defaultConfig, readConfig, STYLES, type Style, writeConfig } from '../core/config.js';\nimport { readTextIfExists, writeText } from '../core/fs.js';\nimport { gitStatus } from '../core/git.js';\nimport { installSkills, writeInstructions } from '../core/install.js';\nimport { SKILL_NAMES } from '../core/instructions.js';\nimport { packageVersion } from '../core/paths.js';\nimport { blockContent } from '../core/project.js';\nimport { checkSize, type SizeReport } from '../core/size.js';\n\nexport interface InitOptions {\n tools?: string;\n style?: string;\n yes?: boolean;\n apply: boolean;\n}\n\nclass CancelledError extends Error {}\n\nfunction guard<T>(value: T): Exclude<T, symbol> {\n if (p.isCancel(value)) throw new CancelledError();\n return value as Exclude<T, symbol>;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n try {\n await runInit(options);\n } catch (error) {\n if (error instanceof CancelledError) {\n p.cancel('Cancelled. Nothing was changed.');\n process.exitCode = 1;\n return;\n }\n throw error;\n }\n}\n\nexport async function runInit(options: InitOptions, cwd: string = process.cwd()): Promise<void> {\n const version = packageVersion();\n const interactive = !options.yes;\n\n if (interactive && !process.stdin.isTTY) {\n throw new Error('No interactive terminal detected. Run \"engly init --yes\" to use the defaults.');\n }\n\n p.intro(`Engly v${version} โ your English coach for AI prompts`);\n\n const config = (await readConfig(cwd, version)) ?? defaultConfig(version);\n config.version = version;\n\n const tools = await chooseTools(cwd, options);\n if (tools.length === 0) {\n throw new Error(`No supported tool selected. Supported today: ${IMPLEMENTED_TOOLS.join(', ')}.`);\n }\n\n config.style = await chooseStyle(options, config.style);\n\n const adapter = adapters.claude;\n const agentFile = await adapter.instructionFile(cwd);\n const agentPath = join(cwd, agentFile);\n if (agentFile === 'AGENTS.md') {\n p.log.info('No CLAUDE.md found. Using AGENTS.md, which Claude Code reads when there is no CLAUDE.md.');\n }\n\n const current = await readTextIfExists(agentPath);\n // Checked before any prompt or write, so broken markers fail early.\n const blockExists = current !== null && hasBlock(current);\n\n let apply = options.apply;\n let position: BlockPosition = 'end';\n if (apply && current !== null && !blockExists) {\n const placement = await choosePlacement(agentFile, checkSize(current, adapter.sizeLimit), interactive);\n if (placement === 'skip') apply = false;\n else position = placement;\n }\n const planned = upsertBlock(current ?? '', blockContent(true), { version, position });\n\n if (apply && interactive) {\n const choice = guard(\n await p.select({\n message: `Add Engly block to:\\n${agentFile} (${adapter.name}) โ ${describePlan(current, planned.action)}`,\n options: [\n { value: 'yes', label: 'Yes' },\n { value: 'skills', label: 'No, only install skills' },\n ],\n }),\n );\n apply = choice === 'yes';\n }\n\n if (apply) {\n await warnIfInGit(cwd, agentFile, interactive);\n }\n\n const done: string[] = [];\n\n await writeInstructions(cwd, config);\n\n if (apply) {\n await writeText(agentPath, planned.text);\n done.push(`โ ${agentFile} ${current === null ? 'created' : 'updated'}`);\n }\n\n await installSkills(cwd, tools);\n done.push(`โ Skills installed: ${SKILL_NAMES.join(', ')}`);\n\n config.tools = tools;\n config.enabled = true;\n await writeConfig(cwd, config);\n\n p.log.success(done.join('\\n'));\n p.outro(\n apply\n ? 'Done! Your next prompt will be coached.'\n : `Skills installed. Run \"engly init\" again to add the Engly block to ${agentFile}.`,\n );\n}\n\nasync function chooseTools(cwd: string, options: InitOptions): Promise<ToolId[]> {\n if (options.tools) {\n const requested = options.tools\n .split(',')\n .map((tool) => tool.trim().toLowerCase())\n .filter(Boolean);\n const unknown = requested.filter((tool) => !(TOOL_IDS as readonly string[]).includes(tool));\n if (unknown.length > 0) {\n throw new Error(`Unknown tool(s): ${unknown.join(', ')}. Use: ${TOOL_IDS.join(', ')}.`);\n }\n return keepImplemented(requested as ToolId[]);\n }\n\n const detected = await detectTools(cwd);\n const initial = detected.filter((tool) => IMPLEMENTED_TOOLS.includes(tool));\n const defaults = initial.length > 0 ? initial : [...IMPLEMENTED_TOOLS];\n\n if (options.yes) return defaults;\n\n return guard(\n await p.multiselect<ToolId>({\n message: 'Which AI tools do you use?',\n options: TOOL_IDS.map((id) => ({\n value: id,\n label: adapters[id].name,\n hint: IMPLEMENTED_TOOLS.includes(id) ? undefined : 'coming soon',\n disabled: !IMPLEMENTED_TOOLS.includes(id),\n })),\n initialValues: defaults,\n required: true,\n }),\n );\n}\n\nfunction keepImplemented(tools: ToolId[]): ToolId[] {\n const skipped = tools.filter((tool) => !IMPLEMENTED_TOOLS.includes(tool));\n if (skipped.length > 0) {\n p.log.warn(`Not supported yet, skipped: ${skipped.map((tool) => adapters[tool].name).join(', ')}`);\n }\n return tools.filter((tool) => IMPLEMENTED_TOOLS.includes(tool));\n}\n\nasync function chooseStyle(options: InitOptions, current: Style): Promise<Style> {\n if (options.style) {\n if (!(STYLES as readonly string[]).includes(options.style)) {\n throw new Error(`Invalid style \"${options.style}\". Use: ${STYLES.join(', ')}.`);\n }\n return options.style as Style;\n }\n if (options.yes) return current;\n\n return guard(\n await p.select<Style>({\n message: 'How detailed should the feedback be?',\n initialValue: current,\n options: [\n { value: 'light', label: 'Light โ max 3 corrections' },\n { value: 'detailed', label: 'Detailed โ explain every mistake' },\n ],\n }),\n );\n}\n\nfunction describePlan(current: string | null, action: UpsertAction): string {\n if (current === null) return 'new file';\n if (action === 'updated') return 'exists, block updated';\n return action === 'prepended' ? 'exists, block added near the top' : 'exists, block appended';\n}\n\nasync function choosePlacement(\n file: string,\n size: SizeReport | null,\n interactive: boolean,\n): Promise<BlockPosition | 'skip'> {\n if (!size) return 'end';\n\n const message = `${file} is ${size.label}\\nโ ${size.warning}`;\n if (!interactive) {\n p.log.warn(`${message}\\nThe Engly block goes near the top.`);\n return 'top';\n }\n\n p.log.warn(message);\n return guard(\n await p.select<BlockPosition | 'skip'>({\n message: 'Where should the block go?',\n options: [\n { value: 'top', label: 'Near the top โ recommended' },\n { value: 'end', label: 'At the end' },\n { value: 'skip', label: `Skip ${file}` },\n ],\n }),\n );\n}\n\nasync function warnIfInGit(cwd: string, file: string, interactive: boolean): Promise<void> {\n const status = gitStatus(cwd, file);\n if (status !== 'tracked' && status !== 'untracked') return;\n\n const title = status === 'tracked' ? `${file} is tracked by git.` : `${file} is not ignored by git.`;\n const warning = 'โ Your teammates will also be coached if you commit it.';\n\n if (!interactive) {\n p.log.warn(`${title}\\n${warning}`);\n return;\n }\n\n const choice = guard(\n await p.select({\n message: `${title}\\n${warning}`,\n options: [\n { value: 'continue', label: 'Continue anyway' },\n { value: 'cancel', label: 'Cancel' },\n ],\n }),\n );\n if (choice === 'cancel') throw new CancelledError();\n}\n","import { spawnSync } from 'node:child_process';\n\nexport type GitStatus = 'tracked' | 'untracked' | 'ignored' | 'no-git';\n\nfunction git(cwd: string, args: string[]): number | null {\n const result = spawnSync('git', args, { cwd, stdio: 'ignore' });\n if (result.error) return null;\n return result.status;\n}\n\nexport function gitStatus(cwd: string, relativePath: string): GitStatus {\n if (git(cwd, ['rev-parse', '--is-inside-work-tree']) !== 0) return 'no-git';\n if (git(cwd, ['ls-files', '--error-unmatch', '--', relativePath]) === 0) return 'tracked';\n if (git(cwd, ['check-ignore', '-q', '--', relativePath]) === 0) return 'ignored';\n return 'untracked';\n}\n","import type { SizeLimit } from '../adapters/types.js';\n\nexport interface SizeReport {\n label: string;\n warning: string;\n}\n\nexport function checkSize(text: string, limit: SizeLimit | undefined): SizeReport | null {\n if (!limit) return null;\n\n const bytes = Buffer.byteLength(text, 'utf8');\n if (limit.maxBytes !== undefined && bytes > limit.maxBytes) {\n return { label: `${(bytes / 1024).toFixed(1)} KB`, warning: limit.warning };\n }\n\n const lines = text === '' ? 0 : text.split('\\n').length - (text.endsWith('\\n') ? 1 : 0);\n if (limit.maxLines !== undefined && lines > limit.maxLines) {\n return { label: `${lines} lines`, warning: limit.warning };\n }\n\n return null;\n}\n","import { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as p from '@clack/prompts';\nimport { IMPLEMENTED_TOOLS } from '../adapters/index.js';\nimport { removeBlock } from '../core/block.js';\nimport { ConfigError, type EnglyConfig, readConfig } from '../core/config.js';\nimport { pathExists, writeText } from '../core/fs.js';\nimport { hasInstalledSkills, removeSkills } from '../core/install.js';\nimport { SKILL_NAMES } from '../core/instructions.js';\nimport { ENGLY_DIR, packageVersion } from '../core/paths.js';\nimport { configuredTools, findBlocks } from '../core/project.js';\n\nexport interface RemoveOptions {\n yes?: boolean;\n}\n\nexport async function removeCommand(options: RemoveOptions): Promise<void> {\n await runRemove(process.cwd(), options);\n}\n\nexport async function runRemove(cwd: string, options: RemoveOptions): Promise<void> {\n const version = packageVersion();\n p.intro(`Engly v${version}`);\n\n // A broken config must not stop the removal: the block and skills are found without it.\n let config: EnglyConfig | null = null;\n try {\n config = await readConfig(cwd, version);\n } catch (error) {\n if (!(error instanceof ConfigError)) throw error;\n }\n\n const blocks = await findBlocks(cwd, config ? configuredTools(config) : [...IMPLEMENTED_TOOLS]);\n const hasEnglyDir = await pathExists(join(cwd, ENGLY_DIR));\n const hasSkills = await hasInstalledSkills(cwd);\n\n if (blocks.length === 0 && !hasEnglyDir && !hasSkills) {\n p.outro('Nothing to remove.');\n return;\n }\n\n const plan = [\n ...blocks.map((block) => `The Engly block in ${block.file}`),\n ...(hasEnglyDir ? [`The ${ENGLY_DIR}/ folder (settings and instructions)`] : []),\n ...(hasSkills ? [`The skills: ${SKILL_NAMES.join(', ')}`] : []),\n ];\n\n if (!options.yes) {\n if (!process.stdin.isTTY) {\n throw new Error('No interactive terminal detected. Run \"engly remove --yes\" to remove without asking.');\n }\n const confirmed = await p.confirm({\n message: `Remove Engly from this project?\\n${plan.map((line) => ` โข ${line}`).join('\\n')}`,\n });\n if (p.isCancel(confirmed) || !confirmed) {\n p.cancel('Cancelled. Nothing was changed.');\n process.exitCode = 1;\n return;\n }\n }\n\n // The config goes last, so a failed run can be repeated.\n const done: string[] = [];\n for (const block of blocks) {\n await writeText(block.path, removeBlock(block.text).text);\n done.push(`โ Engly block removed from ${block.file}`);\n }\n if (hasSkills) {\n await removeSkills(cwd);\n done.push(`โ Skills removed: ${SKILL_NAMES.join(', ')}`);\n }\n if (hasEnglyDir) {\n await rm(join(cwd, ENGLY_DIR), { recursive: true, force: true });\n done.push(`โ ${ENGLY_DIR}/ removed`);\n }\n\n p.log.success(done.join('\\n'));\n p.outro('Engly removed. Everything outside the Engly block was left untouched.');\n}\n","import { join } from 'node:path';\nimport * as p from '@clack/prompts';\nimport { adapters, type ToolId } from '../adapters/index.js';\nimport { ConfigError, type EnglyConfig, readConfig } from '../core/config.js';\nimport { pathExists } from '../core/fs.js';\nimport { hasInstalledSkills, skillStatus } from '../core/install.js';\nimport { CONFIG_FILE, INSTRUCTIONS_FILE, packageVersion } from '../core/paths.js';\nimport { type AgentFile, candidateFiles, configuredTools, inspectAgentFiles } from '../core/project.js';\nimport { checkSize } from '../core/size.js';\n\nexport async function statusCommand(): Promise<void> {\n await runStatus(process.cwd());\n}\n\nexport async function runStatus(cwd: string): Promise<void> {\n const version = packageVersion();\n p.intro(`Engly v${version}`);\n\n let config: EnglyConfig | null = null;\n let configError: string | null = null;\n try {\n config = await readConfig(cwd, version);\n } catch (error) {\n if (!(error instanceof ConfigError)) throw error;\n configError = error.message;\n }\n\n const tools = configuredTools(config);\n const files = await inspectAgentFiles(cwd, tools);\n const withBlock = files.filter((file) => file.state === 'active' || file.state === 'inactive');\n const broken = files.filter((file) => file.state === 'broken');\n const hasInstructions = await pathExists(join(cwd, INSTRUCTIONS_FILE));\n const skills = config ? await skillStatus(cwd, tools) : [];\n\n // A plain CLAUDE.md or AGENTS.md is not a sign of Engly; only the block, config, instructions or skills are.\n const installed =\n config !== null ||\n configError !== null ||\n withBlock.length > 0 ||\n broken.length > 0 ||\n hasInstructions ||\n (await hasInstalledSkills(cwd));\n if (!installed) {\n p.outro('Engly is not installed in this project. Run \"engly init\".');\n return;\n }\n\n // The block is what the agent reads, so it decides whether the coach is on.\n const active = withBlock.some((file) => file.state === 'active');\n const status = withBlock.length === 0 ? 'off (no Engly block in the agent files)' : active ? 'enabled' : 'disabled';\n\n const rows: [string, string][] = [['Status', status]];\n if (config) {\n rows.push(\n ['Version', config.version],\n ['Style', config.style],\n ['Level', config.level],\n ['Language', config.language],\n ['Tools', tools.map((tool) => adapters[tool].name).join(', ')],\n );\n }\n rows.push(['Files', fileLines(files, hasInstructions)]);\n if (skills.length > 0) {\n rows.push([\n 'Skills',\n skills.map((folder) => `${folder.dir} ${folder.missing.length === 0 ? 'โ' : 'โ'}`).join('\\n'),\n ]);\n }\n p.note(formatRows(rows), 'Engly status');\n\n const warnings = await collectWarnings({ cwd, version, config, configError, tools, withBlock, broken, skills });\n for (const warning of warnings) p.log.warn(warning);\n\n p.outro(warnings.length === 0 ? 'All good.' : `${warnings.length} warning${warnings.length === 1 ? '' : 's'}.`);\n}\n\ninterface WarningInput {\n cwd: string;\n version: string;\n config: EnglyConfig | null;\n configError: string | null;\n tools: ToolId[];\n withBlock: AgentFile[];\n broken: AgentFile[];\n skills: Awaited<ReturnType<typeof skillStatus>>;\n}\n\nasync function collectWarnings(input: WarningInput): Promise<string[]> {\n const { cwd, version, config, configError, tools, withBlock, broken, skills } = input;\n const warnings: string[] = [];\n\n if (configError) {\n warnings.push(`${configError} Fix it by hand, or run \"engly init\" to write a new one.`);\n } else if (!config) {\n warnings.push(`${CONFIG_FILE} is missing. Run \"engly init\" to write it again.`);\n } else if (config.version !== version) {\n warnings.push(`This project uses Engly v${config.version} and the CLI is v${version}. Run \"engly update\".`);\n }\n\n for (const file of broken) warnings.push(`${file.file}: ${file.error}`);\n\n if (withBlock.length === 0 && broken.length === 0) {\n const names = (await candidateFiles(cwd, tools)).join(' or ');\n warnings.push(`No Engly block found in ${names}, so nothing is coached. Run \"engly init\" to add it.`);\n }\n\n const active = withBlock.some((file) => file.state === 'active');\n if (config && withBlock.length > 0 && config.enabled !== active) {\n warnings.push(\n `${CONFIG_FILE} says Engly is ${config.enabled ? 'enabled' : 'disabled'}, but the block is ${active ? 'active' : 'empty'}. ` +\n `Run \"engly ${config.enabled ? 'enable' : 'disable'}\" to sync them.`,\n );\n }\n\n if (active && !(await pathExists(join(cwd, INSTRUCTIONS_FILE)))) {\n warnings.push(`${INSTRUCTIONS_FILE} is missing, but the block imports it. Run \"engly update\".`);\n }\n\n for (const folder of skills) {\n if (folder.missing.length > 0) {\n warnings.push(`Missing skills in ${folder.dir}: ${folder.missing.join(', ')}. Run \"engly update\".`);\n }\n }\n\n for (const file of withBlock) {\n const size = sizeWarning(file, tools);\n if (size) warnings.push(size);\n }\n\n return warnings;\n}\n\nfunction sizeWarning(file: AgentFile, tools: readonly ToolId[]): string | null {\n for (const tool of tools) {\n const adapter = adapters[tool];\n if (!adapter.instructionFiles?.includes(file.file)) continue;\n const report = checkSize(file.text, adapter.sizeLimit);\n if (report) return `${file.file} is ${report.label}. ${report.warning.replace(/\\n/g, ' ')}`;\n }\n return null;\n}\n\nfunction fileLines(files: AgentFile[], hasInstructions: boolean): string {\n const lines = files.map((file) => {\n switch (file.state) {\n case 'active':\n return `${file.file} โ (block active)`;\n case 'inactive':\n return `${file.file} โ (block empty, coach off)`;\n case 'broken':\n return `${file.file} โ (broken markers)`;\n default:\n return `${file.file} โ (no Engly block)`;\n }\n });\n lines.push(`${INSTRUCTIONS_FILE} ${hasInstructions ? 'โ' : 'โ'}`);\n return lines.join('\\n');\n}\n\nfunction formatRows(rows: [string, string][]): string {\n const width = Math.max(...rows.map(([label]) => label.length)) + 2;\n return rows\n .map(([label, value]) =>\n value\n .split('\\n')\n .map((line, index) => `${index === 0 ? `${label}:`.padEnd(width) : ' '.repeat(width)}${line}`)\n .join('\\n'),\n )\n .join('\\n');\n}\n","import * as p from '@clack/prompts';\nimport { upsertBlock } from '../core/block.js';\nimport { writeConfig } from '../core/config.js';\nimport { writeText } from '../core/fs.js';\nimport { installSkills, writeInstructions } from '../core/install.js';\nimport { packageVersion } from '../core/paths.js';\nimport { blockContent, candidateFiles, configuredTools, findBlocks, requireConfig } from '../core/project.js';\n\nexport async function updateCommand(): Promise<void> {\n await runUpdate(process.cwd());\n}\n\nexport async function runUpdate(cwd: string): Promise<void> {\n const version = packageVersion();\n p.intro(`Engly v${version}`);\n\n const config = await requireConfig(cwd, version);\n const previous = config.version;\n const tools = configuredTools(config);\n // Read before any write, so broken markers stop the update with nothing changed.\n const blocks = await findBlocks(cwd, tools);\n\n config.version = version;\n await writeInstructions(cwd, config);\n await installSkills(cwd, tools);\n\n const done = ['โ .engly/engly.md and skills refreshed'];\n for (const block of blocks) {\n const { text } = upsertBlock(block.text, blockContent(config.enabled), { version });\n if (text === block.text) {\n done.push(`โ ${block.file} block is up to date`);\n continue;\n }\n await writeText(block.path, text);\n done.push(`โ ${block.file} block updated`);\n }\n await writeConfig(cwd, config);\n p.log.success(done.join('\\n'));\n\n if (blocks.length === 0) {\n const files = (await candidateFiles(cwd, tools)).join(' or ');\n p.log.warn(`No Engly block found in ${files}. Run \"engly init\" to add it.`);\n }\n if (!config.enabled) {\n p.log.warn('Engly is disabled. Run \"engly enable\" to turn it on.');\n }\n p.log.info(\n 'This updates the files in this project. To update the engly CLI itself, run: npm install -g engly@latest',\n );\n\n p.outro(previous === version ? `Already on v${version}.` : `Updated from v${previous} to v${version}.`);\n}\n"],"mappings":"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","names":["join","dirname","join","join","dirname","mkdir","readFile","writeFile","dirname","dirname","join","access","dirname","join","readFile","join","readFile","join","join","dirname","join","join","join","join","p","join","rm","join","p","join","rm","join","p","join","p"]}
|