logwork-cli 0.0.0-stage → 1.1.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.
Files changed (96) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +330 -2
  3. package/RELEASE.md +89 -0
  4. package/bin/logwork.mjs +104 -0
  5. package/commands/auth.mjs +227 -0
  6. package/commands/commits.mjs +82 -0
  7. package/commands/delete.mjs +69 -0
  8. package/commands/doctor.mjs +24 -0
  9. package/commands/edit.mjs +88 -0
  10. package/commands/history.mjs +62 -0
  11. package/commands/query.mjs +52 -0
  12. package/commands/quick.mjs +163 -0
  13. package/commands/reconcile.mjs +55 -0
  14. package/commands/release.mjs +77 -0
  15. package/commands/reminder.mjs +172 -0
  16. package/commands/setup.mjs +508 -0
  17. package/commands/skill.mjs +91 -0
  18. package/commands/tui.mjs +50 -0
  19. package/commands/update.mjs +161 -0
  20. package/docs/advanced.md +334 -0
  21. package/docs/mcp-setup.md +368 -0
  22. package/docs/operations-guide.md +587 -0
  23. package/docs/recipes.md +259 -0
  24. package/docs/security.md +151 -0
  25. package/examples/mcp/antigravity.mcp_config.json +8 -0
  26. package/examples/mcp/codex.config.toml +5 -0
  27. package/examples/mcp/copilot.mcp.json +9 -0
  28. package/lib/agent-skill.mjs +16 -0
  29. package/lib/api-auth.mjs +1259 -0
  30. package/lib/api.mjs +1422 -0
  31. package/lib/apply-ledger.mjs +227 -0
  32. package/lib/atomic-file.mjs +24 -0
  33. package/lib/auth-diagnostics.mjs +113 -0
  34. package/lib/auth-errors.mjs +22 -0
  35. package/lib/auth-redaction.mjs +78 -0
  36. package/lib/auth-terminal.mjs +97 -0
  37. package/lib/auth.mjs +323 -0
  38. package/lib/batch-parser.mjs +145 -0
  39. package/lib/batch-workflow.mjs +842 -0
  40. package/lib/commit-workflow.mjs +402 -0
  41. package/lib/config.mjs +191 -0
  42. package/lib/credential-store.mjs +130 -0
  43. package/lib/diagnostics.mjs +307 -0
  44. package/lib/entrypoint.mjs +14 -0
  45. package/lib/file-lock.mjs +44 -0
  46. package/lib/http.mjs +125 -0
  47. package/lib/jira-api.mjs +258 -0
  48. package/lib/jira-auth.mjs +203 -0
  49. package/lib/jira-workflow.mjs +523 -0
  50. package/lib/keychain.mjs +105 -0
  51. package/lib/legacy-migration.mjs +66 -0
  52. package/lib/list-navigation.mjs +19 -0
  53. package/lib/logwork-config.mjs +199 -0
  54. package/lib/logwork-f03-enrich.mjs +41 -0
  55. package/lib/macos.mjs +33 -0
  56. package/lib/manual-apply-state.mjs +35 -0
  57. package/lib/manual-commands.mjs +294 -0
  58. package/lib/manual-drafts.mjs +154 -0
  59. package/lib/manual-ink-app.mjs +1654 -0
  60. package/lib/manual-ink-auth.mjs +349 -0
  61. package/lib/manual-ink-ui.mjs +675 -0
  62. package/lib/manual-input.mjs +21 -0
  63. package/lib/manual-logwork-wizard.mjs +310 -0
  64. package/lib/manual-mcp.mjs +187 -0
  65. package/lib/manual-repl.mjs +1037 -0
  66. package/lib/manual-targets.mjs +78 -0
  67. package/lib/mcp-server.mjs +833 -0
  68. package/lib/os-notification.mjs +105 -0
  69. package/lib/package-info.mjs +17 -0
  70. package/lib/paths.mjs +68 -0
  71. package/lib/project-identity.mjs +66 -0
  72. package/lib/project-mapping-workflow.mjs +309 -0
  73. package/lib/project-resolver.mjs +373 -0
  74. package/lib/query-workflow.mjs +553 -0
  75. package/lib/reconciliation-workflow.mjs +446 -0
  76. package/lib/release-doctor.mjs +448 -0
  77. package/lib/reminder-config.mjs +71 -0
  78. package/lib/reminder-scheduler.mjs +301 -0
  79. package/lib/reminder-service.mjs +118 -0
  80. package/lib/reminder-state.mjs +38 -0
  81. package/lib/reminder-workflow.mjs +299 -0
  82. package/lib/ro-delete-args.mjs +38 -0
  83. package/lib/ro-delete-workflow.mjs +115 -0
  84. package/lib/ro-edit-args.mjs +142 -0
  85. package/lib/ro-edit-workflow.mjs +632 -0
  86. package/lib/ro-entry-reader.mjs +56 -0
  87. package/lib/ro-resubmit-workflow.mjs +134 -0
  88. package/lib/terminal-auth-prompts.mjs +120 -0
  89. package/lib/ui.mjs +133 -0
  90. package/lib/update-service.mjs +350 -0
  91. package/lib/util.mjs +66 -0
  92. package/lib/windows-credential-manager.mjs +260 -0
  93. package/lib/worklog-task-resolver.mjs +113 -0
  94. package/package.json +73 -4
  95. package/setup.sh +22 -0
  96. package/skills/logwork/SKILL.md +43 -0
@@ -0,0 +1,259 @@
1
+ # Quick Recipes
2
+
3
+ These recipes cover the common end-user paths. Credentials are entered only in Terminal, never in MCP prompts or AI chat.
4
+
5
+ ## Log Resource Optimiser Only
6
+
7
+ Terminal:
8
+
9
+ ```bash
10
+ logwork
11
+ ```
12
+
13
+ Then run `/logwork ro`, choose a date and project, and enter tasks as `+<hours> <worklog task> | <description>`. The part before `|` must be a Resource Optimiser worklog task (for example `Feature Development`); the part after `|` is the description saved on the entry:
14
+
15
+ ```text
16
+ +2 Feature Development | Maintenance mode management (SCB-213)
17
+ +1 Feature Development | Update system page
18
+ ```
19
+
20
+ Review the preview and approve the Resource Optimiser apply.
21
+
22
+ MCP prompt:
23
+
24
+ ```text
25
+ Preview this Resource Optimiser logwork. Show the project and totals, then ask before applying:
26
+ Monday, 01 Jun 2026
27
+ +2 Feature Development | Maintenance mode management (SCB-213)
28
+ ```
29
+
30
+ The assistant calls `log_work` with the text to preview. Only after you approve does it call `log_work` again with the returned `batchId` and `confirm: true`.
31
+
32
+ For a single entry today from the terminal, you can also run `logwork quick -m "Maintenance mode management (SCB-213)"`.
33
+
34
+ If you do not know the worklog task names, ask:
35
+
36
+ ```text
37
+ List the Resource Optimiser worklog tasks for my booked project today, then log 2h for SCB-213 "maintenance mode management" with a proper description. Preview first.
38
+ ```
39
+
40
+ The assistant uses `list_projects` and `list_tasks`, writes `+2 <task> | <description>`, and previews. If you write lines without `|`, the assistant can pass `defaultWorklogTask` (for example `Feature Development`) so each line becomes the description.
41
+
42
+ ## Edit Existing Resource Optimiser Logwork
43
+
44
+ Find the RO logwork ID with `logwork query this-week`, `/query this-week`, or by asking the assistant to call `get_logwork`. Then use Terminal:
45
+
46
+ ```bash
47
+ logwork edit 304032 --hours 0.5
48
+ logwork edit 304032 --desc "Fix date picker timezone (SAP-42)"
49
+ logwork edit 304032 --task "Bug Fix" --type correct
50
+ ```
51
+
52
+ Or ask MCP:
53
+
54
+ ```text
55
+ Preview changing RO logwork 304032 to 0.5 hours with description "Fix date picker timezone (SAP-42)". Keep project, date, and worklog task unchanged, then ask before applying.
56
+ ```
57
+
58
+ The assistant calls `edit_entry` to preview and presents the diff. Only after you approve does it call `edit_entry` again with the `previewId` and `confirm: true`.
59
+
60
+ Apply replaces the entry: it creates the corrected entry, then deletes the original, so the entry gets a new id. If the delete fails, the new entry is rolled back.
61
+
62
+ The edit is blocked when the original entry:
63
+ - changed after the preview,
64
+ - is rejected (use `resubmit_entry` instead),
65
+ - is on a locked day, or
66
+ - was created by Jira.
67
+
68
+ ## Log Jira Only
69
+
70
+ Authenticate once:
71
+
72
+ ```bash
73
+ logwork login jira
74
+ ```
75
+
76
+ Run `logwork`, then `/logwork jira`. Every task needs exactly one Jira issue key:
77
+
78
+ ```text
79
+ +2 Feature Development | Maintenance mode management (SCB-213)
80
+ +1 Bug Fix | Fix deployment regression (SCB-214)
81
+ ```
82
+
83
+ The preview shows issue summary/status, duplicate state, configured started time, duration, and final comment. The assistant equivalent is `log_jira_work`: a preview call, then a call with `batchId` and `confirm: true` after approval.
84
+
85
+ ## Log Both
86
+
87
+ Run `logwork`, then `/logwork both`. Use the same date and tasks for both targets:
88
+
89
+ ```text
90
+ +2 Feature Development | Maintenance mode management (SCB-213)
91
+ +1 Bug Fix | Fix deployment regression (SCB-214)
92
+ ```
93
+
94
+ Resource Optimiser and Jira previews remain separate. Approve Resource Optimiser first and Jira second. The flow is not atomic, so a failure on one target does not roll back the other target.
95
+
96
+ If RO cannot resolve a project, the terminal flow can rank RO memberships using the Jira project key/name and show a command such as:
97
+
98
+ ```text
99
+ /map SCB 5234
100
+ ```
101
+
102
+ Review the proposed project before confirming. Suggestions never write mappings automatically.
103
+
104
+ ## Change Jira Started Time
105
+
106
+ macOS/Linux shell:
107
+
108
+ ```bash
109
+ export LOGWORK_JIRA_STARTED_TIME=08:30
110
+ export LOGWORK_TIMEZONE=Asia/Singapore
111
+ logwork
112
+ ```
113
+
114
+ Windows PowerShell:
115
+
116
+ ```powershell
117
+ $env:LOGWORK_JIRA_STARTED_TIME = "08:30"
118
+ $env:LOGWORK_TIMEZONE = "Asia/Singapore"
119
+ logwork
120
+ ```
121
+
122
+ Time must use 24-hour `HH:mm`; timezone must be an IANA name. The exact `started` value appears in Jira preview before approval.
123
+
124
+ ## Recover From Missing Jira Auth
125
+
126
+ If preview says Jira auth is required:
127
+
128
+ ```bash
129
+ logwork login jira
130
+ logwork status jira
131
+ ```
132
+
133
+ Paste the PAT only into the Terminal prompt, then rerun preview. Do not paste the PAT into MCP input or chat.
134
+
135
+ ## Handle Duplicate Jira Worklog
136
+
137
+ Duplicate Jira worklogs block the entire Jira apply. Preview identifies duplicates by the `#lh:<hash>` marker or matching issue/date/duration/normalized task comment.
138
+
139
+ Check Jira and the preview before continuing. If the work already exists, do not apply it again. If the task is genuinely different, change the task/date/duration as appropriate and rerun preview. There is no duplicate override.
140
+
141
+ ## Recover From npm E401
142
+
143
+ An `E401` from `npm whoami` means the local npm login is missing, expired, or invalid:
144
+
145
+ ```bash
146
+ npm logout --registry=https://registry.npmjs.org/
147
+ npm login --registry=https://registry.npmjs.org/
148
+ npm whoami
149
+ ```
150
+
151
+ Before publishing, run:
152
+
153
+ ```bash
154
+ logwork release doctor --full
155
+ ```
156
+
157
+ Do not paste npm passwords, tokens, or OTP values into chat.
158
+
159
+ ## Inspect Apply History
160
+
161
+ All recent targets:
162
+
163
+ ```bash
164
+ logwork history
165
+ ```
166
+
167
+ Jira only, last 10:
168
+
169
+ ```bash
170
+ logwork history --target jira --limit 10
171
+ ```
172
+
173
+ Combined Both sessions:
174
+
175
+ ```bash
176
+ logwork history --target both
177
+ ```
178
+
179
+ Inside the terminal UI, use `/history`, `/history ro`, `/history jira`, or `/history both`. With MCP, ask the assistant to use `get_history`.
180
+
181
+ ## Check And Install An Update
182
+
183
+ Terminal check:
184
+
185
+ ```bash
186
+ logwork update check
187
+ ```
188
+
189
+ Terminal install with confirmation:
190
+
191
+ ```bash
192
+ logwork update install
193
+ ```
194
+
195
+ MCP prompt:
196
+
197
+ ```text
198
+ Check whether logwork-cli has an update. Show me the current and latest versions, and ask before installing anything.
199
+ ```
200
+
201
+ The assistant calls `update` with `action: "check"`. After approval, it calls `update` with `action: "install"`, the exact version, and `confirm: true`. Restart or reload the IDE MCP connection after the update completes.
202
+
203
+ ## Enable A Native Weekday Reminder
204
+
205
+ Default Both reminder at 17:00:
206
+
207
+ ```bash
208
+ logwork reminder enable
209
+ ```
210
+
211
+ RO-only reminder at 18:00:
212
+
213
+ ```bash
214
+ logwork reminder enable --time 18:00 --target ro
215
+ ```
216
+
217
+ Send a sample notification and inspect scheduler status:
218
+
219
+ ```bash
220
+ logwork reminder test
221
+ logwork reminder status
222
+ ```
223
+
224
+ MCP prompt:
225
+
226
+ ```text
227
+ Enable a Both logwork reminder at 17:00 on weekdays. Explain the OS schedule and ask for approval before changing anything.
228
+ ```
229
+
230
+ The assistant calls `reminder` with `action: "enable"` and `confirm: true` after approval. The notification remains silent when enabled checks are complete. Disable and remove the OS schedule with `logwork reminder disable`.
231
+
232
+ ## Reconcile RO And Jira
233
+
234
+ Terminal summary:
235
+
236
+ ```bash
237
+ logwork reconcile this-week
238
+ ```
239
+
240
+ Machine-readable output:
241
+
242
+ ```bash
243
+ logwork reconcile last-month --json
244
+ ```
245
+
246
+ Inside `logwork`:
247
+
248
+ ```text
249
+ /reconcile
250
+ /reconcile this-week
251
+ ```
252
+
253
+ MCP prompt:
254
+
255
+ ```text
256
+ Compare my RO and Jira worklogs for this week. Explain each mismatch and do not apply corrections.
257
+ ```
258
+
259
+ The assistant calls `reconcile`. When a correction is needed, it previews with `log_work`, `edit_entry` or `log_jira_work` and asks for target-specific approval before applying.
@@ -0,0 +1,151 @@
1
+ # Security And Auth
2
+
3
+ logwork-cli does not accept or store Bearer tokens in config files. Resource Optimiser login and Jira PAT login both run in Terminal.
4
+
5
+ ## Auth Setup
6
+
7
+ Start login:
8
+
9
+ ```bash
10
+ logwork login
11
+ ```
12
+
13
+ What happens:
14
+
15
+ 1. The helper creates a fresh Keycloak authorization request.
16
+ 2. You enter Resource Optimiser email and password in Terminal.
17
+ 3. You choose the 2FA device in Terminal.
18
+ 4. You enter the 2FA code in Terminal.
19
+ 5. The helper exchanges the Keycloak authorization code.
20
+ 6. The helper calls Resource Optimiser `signinKeyCloak`.
21
+ 7. The helper stores the Resource Optimiser access/refresh token session in the OS credential store.
22
+
23
+ Check auth status without printing the token:
24
+
25
+ ```bash
26
+ logwork status
27
+ ```
28
+
29
+ Delete the stored token:
30
+
31
+ ```bash
32
+ logwork logout
33
+ ```
34
+
35
+ ## Auth Notes
36
+
37
+ - Password and 2FA are never accepted through MCP tool inputs or AI chat.
38
+ - Password and 2FA are never stored.
39
+ - Keycloak URLs contain dynamic `state`, `nonce`, `execution`, and `tab_id` values. Do not hardcode or paste them into config.
40
+ - Keycloak access tokens are intermediate only and are not stored.
41
+ - Only the final Resource Optimiser access/refresh token session is stored in the OS credential store: macOS Keychain on macOS, or Windows Credential Manager on Windows.
42
+ - Auth does not open or read any browser profile.
43
+
44
+ ## Jira PAT Setup
45
+
46
+ Jira self-hosted worklogs use a Jira Personal Access Token. The default Jira base URL is:
47
+
48
+ ```text
49
+ https://jira-vnv.vinova.sg
50
+ ```
51
+
52
+ Start Jira auth:
53
+
54
+ ```bash
55
+ logwork login jira
56
+ ```
57
+
58
+ Use a different Jira URL only when needed:
59
+
60
+ ```bash
61
+ logwork login jira --base-url https://jira.example.com
62
+ ```
63
+
64
+ What happens:
65
+
66
+ 1. You paste the Jira Personal Access Token in Terminal.
67
+ 2. The helper validates the token with `GET /rest/api/2/myself`.
68
+ 3. The helper stores `{ baseUrl, token, user }` in the OS credential store under service `logwork-cli`, account `jira`.
69
+
70
+ Check or delete Jira auth without printing the token:
71
+
72
+ ```bash
73
+ logwork status jira
74
+ logwork logout jira
75
+ ```
76
+
77
+ Sessions saved by `logwork-helper` (service `logwork-helper`) are read once and copied to service `logwork-cli`, so an upgrade does not require a new login. `logwork logout` removes both entries.
78
+
79
+ Jira username/password login is not supported in MCP. Do not paste Jira passwords, PATs, cookies, or raw Jira auth logs into AI chat.
80
+
81
+ ## Auth Security Flow
82
+
83
+ ```mermaid
84
+ flowchart TD
85
+ A["User runs logwork login"] --> B["Helper creates fresh Keycloak authorize request"]
86
+ B --> C["User enters email/password/device/2FA in terminal"]
87
+ C --> D["Helper exchanges Keycloak authorization code"]
88
+ D --> E["Helper calls Resource Optimiser signinKeyCloak"]
89
+ E --> F["Helper validates JWT expiry and user id"]
90
+ F --> G["Final RO access/refresh session saved to OS credential store"]
91
+ G --> H["MCP tools call Resource Optimiser APIs with the local credential session"]
92
+ L["User runs logwork login jira"] --> M["User pastes Jira PAT in terminal"]
93
+ M --> N["Helper validates GET /rest/api/2/myself"]
94
+ N --> O["Jira PAT session saved to OS credential store account jira"]
95
+ O --> P["MCP Jira tools call Jira REST APIs with the local PAT"]
96
+
97
+ I["MCP config"] -. "no token" .-> H
98
+ I -. "no Jira PAT" .-> P
99
+ J[".logwork.json"] -. "project mapping only" .-> H
100
+ K["AI chat / MCP args"] -. "no password or 2FA" .-> C
101
+ K -. "no Jira username/password/PAT" .-> M
102
+ ```
103
+
104
+ ## Stored Files
105
+
106
+ The runtime and local state live in `~/.logwork-cli` (`%USERPROFILE%\.logwork-cli` on Windows). `LOGWORK_HOME` overrides this location, and the older `LOGWORK_HELPER_HOME` is still honored. When upgrading from `logwork-helper`, the state files below are copied once from `~/.logwork-helper`, which is left in place.
107
+
108
+ | File | Content |
109
+ | --- | --- |
110
+ | `.logwork.json` | Project mappings: ticket prefixes and keywords. A repo-level `.logwork.json` (or a legacy `.logwork-helper.json`) holds project-scope mappings. |
111
+ | `manual-drafts.json` | Terminal UI drafts. |
112
+ | `apply-ledger.json` | Sanitized apply history. |
113
+ | `update-state.json` | Update check cache: package name, latest SemVer, and check timestamp. Expires after 24 hours and contains no npm credentials. |
114
+ | `reminder.json`, `reminder-state.json` | Reminder config: enabled state, local time, target and weekdays. Last-run state: sanitized timestamps and status summaries. |
115
+ | `diagnostics/` | Sanitized diagnostics reports from `logwork doctor`. |
116
+
117
+ The apply ledger stores the target, flow target, batch ID, timestamp, workspace path, entry metadata, and submitted/failed/blocked status. It does not store credentials, cookies, or raw API responses, and token-like error text is redacted before writing.
118
+
119
+ The macOS launch agent and Windows scheduled task contain only fixed Node and script paths (`bin/logwork.mjs reminder run`). They never contain RO tokens, Jira PATs, passwords, or API responses.
120
+
121
+ ## Safety Model
122
+
123
+ **Where secrets are never stored**
124
+ - Tokens are not stored in MCP config or in `.logwork.json`.
125
+ - The email address may be remembered in the OS credential store to prefill the next login.
126
+
127
+ **How MCP writes are gated**
128
+ - Every MCP write is two calls to the same tool. The first call, without `confirm`, returns a cached preview with a `batchId` or `previewId`. The second call must pass that id with `confirm: true`, which the assistant sends only after explicit approval.
129
+ - The apply call writes the cached preview as-is. It rejects content fields such as `text` or `hours`, so nothing can be changed after approval without a new preview.
130
+ - `edit_entry` preserves the project and date. It rejects entries that are Jira-created, rejected, or on a locked day, and stale-checks the original entry.
131
+ - `edit_entry` apply replaces the entry: it creates the corrected entry first, then deletes the original, and rolls back the new entry if the delete fails. It then verifies the new entry.
132
+ - `log_work` (Resource Optimiser) and `log_jira_work` (Jira) are separate flows. Applying Resource Optimiser logwork does not write Jira worklogs.
133
+ - Jira duplicate detection is blocking and has no override option.
134
+
135
+ **Read-only tools**
136
+ - `get_logwork`, `list_projects`, `list_tasks`, `get_jira_issue`, `reconcile`, and `get_history` are read-only.
137
+ - Reconciliation accepts preset periods only, filters Jira worklogs to the stored Jira user, and never applies its correction suggestions directly.
138
+
139
+ **Updates**
140
+ - `update` with `action: "check"` is read-only.
141
+ - `update` with `action: "install"` requires `confirm: true`, re-checks npm latest, accepts only an exact SemVer, and cannot receive an arbitrary registry or shell command.
142
+ - Installation preserves local files and OS credential-store entries. The current MCP process must be reconnected after a successful update.
143
+
144
+ **Reminder**
145
+ - `reminder` with `action: "status"` is read-only.
146
+ - `enable`, `disable` and `test` require `confirm: true`. They never accept arbitrary scheduler commands, paths, JQL, or credentials.
147
+ - The background reminder reads RO and Jira sessions from the OS credential store only when it runs. Notification and state text are sanitized and never include raw API responses.
148
+
149
+ **Login and diagnostics**
150
+ - `login` never accepts credentials. It only opens or explains a terminal login.
151
+ - Diagnostics reports redact tokens, cookies, passwords, OTPs, auth codes, and raw HTML.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "logwork": {
4
+ "command": "node",
5
+ "args": ["<home>/.logwork-cli/bin/logwork.mjs", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ [mcp_servers.logwork]
2
+ command = "node"
3
+ args = ["<home>/.logwork-cli/bin/logwork.mjs", "mcp"]
4
+ startup_timeout_sec = 20
5
+ tool_timeout_sec = 120
@@ -0,0 +1,9 @@
1
+ {
2
+ "servers": {
3
+ "logwork": {
4
+ "type": "stdio",
5
+ "command": "node",
6
+ "args": ["<home>/.logwork-cli/bin/logwork.mjs", "mcp"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,16 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // skills/logwork/SKILL.md is the single source for the agent procedure: it is
6
+ // shipped as an installable skill (`logwork skill install`) and served as the
7
+ // MCP prompt log_from_commits.
8
+ export const SKILL_NAME = 'logwork';
9
+ export const SKILL_PATH = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'skills', SKILL_NAME, 'SKILL.md');
10
+
11
+ export function readLogworkSkill({ withFrontmatter = false, path = SKILL_PATH } = {}) {
12
+ // Normalise CRLF (Windows git checkouts) so the frontmatter strip and the
13
+ // installed copies are identical on every platform.
14
+ const text = readFileSync(path, 'utf8').replace(/\r\n/g, '\n');
15
+ return withFrontmatter ? text : text.replace(/^---\n[\s\S]*?\n---\n+/, '').trim();
16
+ }