@skillrecordings/cli 0.10.2 → 0.11.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/bin/skill.mjs +0 -5
- package/bin/skill.ts +0 -1
- package/dist/{chunk-TFLLCHUX.js → chunk-54VQQSWT.js} +13 -2
- package/dist/chunk-5ACOI6Z3.js +198 -0
- package/dist/chunk-5ACOI6Z3.js.map +1 -0
- package/dist/{chunk-HCFBOGCL.js → chunk-EGBS3VJI.js} +4 -5
- package/dist/{config-523F4OBH.js → config-HLXR42M4.js} +3 -3
- package/dist/duckdb-source-T7EJUOC7.js +353 -0
- package/dist/duckdb-source-T7EJUOC7.js.map +1 -0
- package/dist/index.js +9334 -7543
- package/dist/index.js.map +1 -1
- package/dist/{pipeline-DRKFIH73.js → pipeline-H52QEIFE.js} +2 -2
- package/package.json +8 -6
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/skills/front-inbox/SKILL.md +360 -0
- package/plugin/skills/front-inbox/examples/bulk-archive.md +16 -0
- package/plugin/skills/front-inbox/examples/triage-tt.md +16 -0
- package/.env.encrypted +0 -0
- package/dist/preload.js +0 -112
- package/dist/preload.js.map +0 -1
- /package/dist/{chunk-TFLLCHUX.js.map → chunk-54VQQSWT.js.map} +0 -0
- /package/dist/{chunk-HCFBOGCL.js.map → chunk-EGBS3VJI.js.map} +0 -0
- /package/dist/{config-523F4OBH.js.map → config-HLXR42M4.js.map} +0 -0
- /package/dist/{pipeline-DRKFIH73.js.map → pipeline-H52QEIFE.js.map} +0 -0
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
storeDraftSuccess,
|
|
54
54
|
validate,
|
|
55
55
|
validateSync
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-54VQQSWT.js";
|
|
57
57
|
import "./chunk-KEV3QKXP.js";
|
|
58
58
|
import "./chunk-HK3PEWFD.js";
|
|
59
59
|
import "./chunk-WYKL32C3.js";
|
|
@@ -117,4 +117,4 @@ export {
|
|
|
117
117
|
validate,
|
|
118
118
|
validateSync
|
|
119
119
|
};
|
|
120
|
-
//# sourceMappingURL=pipeline-
|
|
120
|
+
//# sourceMappingURL=pipeline-H52QEIFE.js.map
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillrecordings/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skill": "./bin/skill.mjs"
|
|
7
7
|
},
|
|
8
|
-
"files": ["dist", "bin", "
|
|
8
|
+
"files": ["dist", "bin", "plugin"],
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "bun src/index.ts",
|
|
11
11
|
"build": "tsup",
|
|
12
|
+
"build:compile": "bun build.ts",
|
|
12
13
|
"check-types": "tsc --noEmit",
|
|
13
|
-
"test": "vitest --run"
|
|
14
|
+
"test": "vitest --run",
|
|
15
|
+
"plugin:sync": "bun src/index.ts plugin sync"
|
|
14
16
|
},
|
|
15
17
|
"devDependencies": {
|
|
16
18
|
"@repo/typescript-config": "*",
|
|
@@ -18,18 +20,18 @@
|
|
|
18
20
|
"@skillrecordings/database": "workspace:*",
|
|
19
21
|
"@skillrecordings/front-sdk": "workspace:*",
|
|
20
22
|
"@skillrecordings/memory": "workspace:*",
|
|
21
|
-
"@skillrecordings/sdk": "workspace:*",
|
|
22
23
|
"@types/node": "^22.15.3",
|
|
23
24
|
"tsup": "^8.5.0",
|
|
24
|
-
"typescript": "5.9.2"
|
|
25
|
+
"typescript": "5.9.2",
|
|
26
|
+
"@vitest/coverage-v8": "^3.0.0"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
29
|
+
"@1password/sdk": "^0.2.1",
|
|
27
30
|
"@axiomhq/js": "^1.3.1",
|
|
28
31
|
"@inquirer/prompts": "^8.2.0",
|
|
29
32
|
"age-encryption": "^0.3.0",
|
|
30
33
|
"ai": "^6.0.49",
|
|
31
34
|
"commander": "^12.1.0",
|
|
32
|
-
"dotenv-flow": "^4.1.0",
|
|
33
35
|
"glob": "^13.0.0",
|
|
34
36
|
"gray-matter": "^4.0.3",
|
|
35
37
|
"mysql2": "^3.16.1",
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# Front Inbox Management
|
|
2
|
+
|
|
3
|
+
A Claude Code skill for managing Skill Recordings Front inboxes via the `skill` CLI.
|
|
4
|
+
This skill is agent-first and optimized for rapid, safe inbox operations.
|
|
5
|
+
It includes product inbox aliases, HATEOAS chaining guidance, and daily briefing workflows.
|
|
6
|
+
|
|
7
|
+
## Activation
|
|
8
|
+
|
|
9
|
+
Use this skill automatically when a user mentions any of the following:
|
|
10
|
+
- inbox
|
|
11
|
+
- front
|
|
12
|
+
- triage
|
|
13
|
+
- archive
|
|
14
|
+
- tags
|
|
15
|
+
- any product name listed in the alias table
|
|
16
|
+
|
|
17
|
+
## Scope
|
|
18
|
+
|
|
19
|
+
This skill is intentionally narrow.
|
|
20
|
+
It focuses on Front inbox management and daily cross-inbox briefings.
|
|
21
|
+
Do not use it for billing, refunds, or content support.
|
|
22
|
+
|
|
23
|
+
## Required Environment
|
|
24
|
+
|
|
25
|
+
- `FRONT_API_TOKEN`
|
|
26
|
+
|
|
27
|
+
If the token is missing, report the issue and suggest checking `FRONT_API_TOKEN`.
|
|
28
|
+
|
|
29
|
+
## Inbox Aliases
|
|
30
|
+
|
|
31
|
+
These aliases resolve to Front inbox IDs.
|
|
32
|
+
All aliases are case-insensitive.
|
|
33
|
+
|
|
34
|
+
| Product | Inbox ID | Aliases |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Total TypeScript | `inb_3srbb` | `TT`, `typescript`, `total` |
|
|
37
|
+
| Pro Tailwind | `inb_3pqh3` | `tailwind`, `ptw` |
|
|
38
|
+
| AI Hero | `inb_4bj7r` | `ai-hero`, `aihero` |
|
|
39
|
+
| Epic React | `inb_1bwzr` | `react`, `kcd` |
|
|
40
|
+
| Epic Web | `inb_jqs2t` | `epicweb` |
|
|
41
|
+
| Epic AI | `inb_jqs11` | `epicai` |
|
|
42
|
+
| egghead | `inb_1c77r` | `egg` |
|
|
43
|
+
| Just JavaScript | `inb_2odqf` | `justjs`, `jj` |
|
|
44
|
+
| Testing Accessibility | `inb_3bkef` | `a11y` |
|
|
45
|
+
| Pro Next.js | `inb_43olj` | `nextjs`, `pro-next` |
|
|
46
|
+
|
|
47
|
+
When a user provides an alias, expand it to the corresponding inbox ID.
|
|
48
|
+
If the alias is ambiguous or unknown, ask a clarifying question.
|
|
49
|
+
|
|
50
|
+
## Command Reference
|
|
51
|
+
|
|
52
|
+
Always prefer JSON output for agent parsing.
|
|
53
|
+
Use `--json` when running commands.
|
|
54
|
+
|
|
55
|
+
### `skill front inbox`
|
|
56
|
+
|
|
57
|
+
Purpose: list all inboxes with pending counts.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
```bash
|
|
61
|
+
skill front inbox --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `skill front triage`
|
|
65
|
+
|
|
66
|
+
Purpose: get pending conversations in JSON for a specific inbox.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
```bash
|
|
70
|
+
skill front triage -i inb_3srbb --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `skill front conversation`
|
|
74
|
+
|
|
75
|
+
Purpose: fetch full conversation with messages.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
```bash
|
|
79
|
+
skill front conversation cnv_123 -m --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `skill front bulk-archive`
|
|
83
|
+
|
|
84
|
+
Purpose: preview bulk actions with `--dry-run`.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
```bash
|
|
88
|
+
skill front bulk-archive -i inb_3srbb --filter "tag:handled" --dry-run --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `skill front tags`
|
|
92
|
+
|
|
93
|
+
Purpose: list all tags used in an inbox.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
```bash
|
|
97
|
+
skill front tags inb_3srbb --json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## HATEOAS Chaining Rules
|
|
101
|
+
|
|
102
|
+
The CLI returns HATEOAS metadata for Front resources.
|
|
103
|
+
Follow these rules when chaining calls.
|
|
104
|
+
|
|
105
|
+
Rules:
|
|
106
|
+
- Always use `--json` for agent parsing.
|
|
107
|
+
- Follow `_actions` from responses to chain operations.
|
|
108
|
+
- Non-destructive actions such as `mark-read` or `tag` can be auto-executed.
|
|
109
|
+
- Destructive actions such as `archive` or `delete` require explicit user approval.
|
|
110
|
+
- Always run with `--dry-run` before `bulk-archive`.
|
|
111
|
+
- When `_actions` has multiple items, ask the user which to prioritize.
|
|
112
|
+
|
|
113
|
+
HATEOAS response fields:
|
|
114
|
+
- `_type` identifies the entity type.
|
|
115
|
+
- `_links` includes related resources.
|
|
116
|
+
- `_actions` includes next-step operations.
|
|
117
|
+
|
|
118
|
+
## Daily Briefing Flow
|
|
119
|
+
|
|
120
|
+
Goal: produce a cross-inbox summary and pick a focus area.
|
|
121
|
+
|
|
122
|
+
Steps:
|
|
123
|
+
1. Query all inboxes in parallel with `skill front triage`.
|
|
124
|
+
2. Extract pending counts and urgent tags.
|
|
125
|
+
3. Synthesize a concise summary.
|
|
126
|
+
4. Ask the user: “What should I focus on?”
|
|
127
|
+
5. Execute actions based on the user’s response.
|
|
128
|
+
|
|
129
|
+
Example summary format:
|
|
130
|
+
- `TT: 5 pending (2 urgent), AI Hero: 3 pending (0 urgent)`
|
|
131
|
+
|
|
132
|
+
## Error Handling
|
|
133
|
+
|
|
134
|
+
Connection errors:
|
|
135
|
+
- Suggest checking `FRONT_API_TOKEN`.
|
|
136
|
+
|
|
137
|
+
Rate limits:
|
|
138
|
+
- Use exponential backoff.
|
|
139
|
+
- Retry with increasing delays before giving up.
|
|
140
|
+
|
|
141
|
+
Malformed responses:
|
|
142
|
+
- Fall back to text output and ask the user how to proceed.
|
|
143
|
+
|
|
144
|
+
## Working Notes
|
|
145
|
+
|
|
146
|
+
Prefer to work inbox-by-inbox unless the user explicitly asks for a global sweep.
|
|
147
|
+
Confirm the inbox ID before destructive operations.
|
|
148
|
+
Respect human-in-the-loop approval requirements.
|
|
149
|
+
|
|
150
|
+
## Examples
|
|
151
|
+
|
|
152
|
+
See the examples in `examples/` for real command flows.
|
|
153
|
+
Use these as templates and adapt to the user’s needs.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Extended Guidance
|
|
158
|
+
|
|
159
|
+
The following sections provide more detailed operational guidance.
|
|
160
|
+
They are intentionally verbose to support consistent behavior.
|
|
161
|
+
|
|
162
|
+
### Triage Principles
|
|
163
|
+
|
|
164
|
+
Triage is for sorting actionable vs non-actionable conversations.
|
|
165
|
+
Use tags to mark status, urgency, and next steps.
|
|
166
|
+
Always log actions in a short summary.
|
|
167
|
+
|
|
168
|
+
Common triage categories:
|
|
169
|
+
- Actionable
|
|
170
|
+
- Needs follow-up
|
|
171
|
+
- Duplicate
|
|
172
|
+
- Noise
|
|
173
|
+
- Spam
|
|
174
|
+
|
|
175
|
+
### Tagging Standards
|
|
176
|
+
|
|
177
|
+
Use tags consistently across inboxes.
|
|
178
|
+
If a tag does not exist, ask before creating a new one.
|
|
179
|
+
Avoid creating near-duplicate tags.
|
|
180
|
+
|
|
181
|
+
Examples of standard tags:
|
|
182
|
+
- `urgent`
|
|
183
|
+
- `billing`
|
|
184
|
+
- `account`
|
|
185
|
+
- `bug`
|
|
186
|
+
- `feature-request`
|
|
187
|
+
|
|
188
|
+
### Conversation Review
|
|
189
|
+
|
|
190
|
+
When reading a conversation:
|
|
191
|
+
- Check the subject line.
|
|
192
|
+
- Check sender and recipient.
|
|
193
|
+
- Read the latest message first.
|
|
194
|
+
- Verify tags and assignee.
|
|
195
|
+
|
|
196
|
+
If a response is needed:
|
|
197
|
+
- Draft a short response summary.
|
|
198
|
+
- Ask for approval before sending.
|
|
199
|
+
|
|
200
|
+
### Bulk Archive Guidance
|
|
201
|
+
|
|
202
|
+
Bulk archive should be used carefully.
|
|
203
|
+
Always run with `--dry-run` first.
|
|
204
|
+
Ensure the filter is specific and includes a tag or status.
|
|
205
|
+
Confirm with the user before executing without `--dry-run`.
|
|
206
|
+
|
|
207
|
+
### HATEOAS Examples
|
|
208
|
+
|
|
209
|
+
Example: chain from inbox list to triage.
|
|
210
|
+
|
|
211
|
+
1. Run inbox list.
|
|
212
|
+
```bash
|
|
213
|
+
skill front inbox --json
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
2. Choose the inbox with highest pending count.
|
|
217
|
+
3. Follow `_actions.triage` for that inbox.
|
|
218
|
+
4. Run triage with the resolved inbox ID.
|
|
219
|
+
|
|
220
|
+
Example: mark read.
|
|
221
|
+
|
|
222
|
+
1. Get conversation details.
|
|
223
|
+
```bash
|
|
224
|
+
skill front conversation cnv_123 -m --json
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
2. Follow `_actions.mark-read` and execute.
|
|
228
|
+
|
|
229
|
+
Example: archive.
|
|
230
|
+
|
|
231
|
+
1. Get conversation details.
|
|
232
|
+
2. If `_actions.archive` exists, ask the user for approval.
|
|
233
|
+
3. Execute the archive action after approval.
|
|
234
|
+
|
|
235
|
+
### Daily Briefing Deep Dive
|
|
236
|
+
|
|
237
|
+
The daily briefing is a repeatable flow.
|
|
238
|
+
It should be fast, consistent, and non-destructive.
|
|
239
|
+
|
|
240
|
+
Recommended flow order:
|
|
241
|
+
- Total TypeScript
|
|
242
|
+
- Pro Tailwind
|
|
243
|
+
- AI Hero
|
|
244
|
+
- Epic React
|
|
245
|
+
- Epic Web
|
|
246
|
+
- Epic AI
|
|
247
|
+
- egghead
|
|
248
|
+
- Just JavaScript
|
|
249
|
+
- Testing Accessibility
|
|
250
|
+
- Pro Next.js
|
|
251
|
+
|
|
252
|
+
For each inbox:
|
|
253
|
+
- Capture pending count.
|
|
254
|
+
- Count urgent items.
|
|
255
|
+
- Note any blocked or escalated threads.
|
|
256
|
+
|
|
257
|
+
Synthesize into a single sentence summary.
|
|
258
|
+
Ask the user what to focus on next.
|
|
259
|
+
|
|
260
|
+
### Structured Summary Template
|
|
261
|
+
|
|
262
|
+
Use this format when reporting to the user:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
Summary: TT: X pending (Y urgent), AI Hero: X pending (Y urgent), Epic React: X pending (Y urgent)
|
|
266
|
+
Recommendation: Focus on TT and AI Hero first.
|
|
267
|
+
Question: What should I focus on?
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Suggested Next Actions
|
|
271
|
+
|
|
272
|
+
If the user asks for a plan, propose one of these:
|
|
273
|
+
- Start with highest urgent count.
|
|
274
|
+
- Clear handled-tagged conversations via bulk archive.
|
|
275
|
+
- Review recent unassigned threads.
|
|
276
|
+
|
|
277
|
+
### Safety Checks
|
|
278
|
+
|
|
279
|
+
Before destructive actions:
|
|
280
|
+
- Confirm the inbox and filter.
|
|
281
|
+
- Re-run `--dry-run` if the filter changed.
|
|
282
|
+
- Ask for explicit approval.
|
|
283
|
+
|
|
284
|
+
### Troubleshooting
|
|
285
|
+
|
|
286
|
+
If commands fail:
|
|
287
|
+
- Confirm CLI is installed and `skill` resolves.
|
|
288
|
+
- Confirm `FRONT_API_TOKEN` is set.
|
|
289
|
+
- Retry with `--json` for full response detail.
|
|
290
|
+
|
|
291
|
+
### Escalation Path
|
|
292
|
+
|
|
293
|
+
If the user reports unexpected failures:
|
|
294
|
+
- Suggest running `skill front inbox --json`.
|
|
295
|
+
- Ask for the error output.
|
|
296
|
+
- Provide targeted guidance based on the error.
|
|
297
|
+
|
|
298
|
+
### Support Boundaries
|
|
299
|
+
|
|
300
|
+
Do not send messages directly.
|
|
301
|
+
Always ask for approval before drafting or sending replies.
|
|
302
|
+
Avoid auto-archiving when context is unclear.
|
|
303
|
+
|
|
304
|
+
### Recap Checklist
|
|
305
|
+
|
|
306
|
+
Use this checklist before closing a triage session:
|
|
307
|
+
- Inbox processed
|
|
308
|
+
- Urgent items identified
|
|
309
|
+
- Bulk actions reviewed with `--dry-run`
|
|
310
|
+
- Summary shared with the user
|
|
311
|
+
|
|
312
|
+
### Additional References
|
|
313
|
+
|
|
314
|
+
- `skill front inbox`
|
|
315
|
+
- `skill front triage`
|
|
316
|
+
- `skill front conversation`
|
|
317
|
+
- `skill front bulk-archive`
|
|
318
|
+
- `skill front tags`
|
|
319
|
+
|
|
320
|
+
### Output Format Expectations
|
|
321
|
+
|
|
322
|
+
JSON output should be parseable and consistent.
|
|
323
|
+
If JSON output fails, fall back to text and ask for guidance.
|
|
324
|
+
|
|
325
|
+
### Example: Alias Expansion
|
|
326
|
+
|
|
327
|
+
User: “Check TT inbox.”
|
|
328
|
+
Action: Expand `TT` to `inb_3srbb` and proceed with triage.
|
|
329
|
+
|
|
330
|
+
User: “Check Pro Next.”
|
|
331
|
+
Action: Expand `pro-next` to `inb_43olj` and proceed.
|
|
332
|
+
|
|
333
|
+
### Example: Urgent Tag Sweep
|
|
334
|
+
|
|
335
|
+
Run triage for each inbox.
|
|
336
|
+
Collect tags with `urgent` or `escalated`.
|
|
337
|
+
Report counts and ask for focus.
|
|
338
|
+
|
|
339
|
+
### Example: Safe Bulk Archive
|
|
340
|
+
|
|
341
|
+
1. Run `--dry-run` with a specific tag filter.
|
|
342
|
+
2. Share the preview with the user.
|
|
343
|
+
3. If approved, re-run without `--dry-run`.
|
|
344
|
+
|
|
345
|
+
### Example: Conversation Detail Follow-up
|
|
346
|
+
|
|
347
|
+
When the user asks for details:
|
|
348
|
+
- Fetch the conversation.
|
|
349
|
+
- Summarize the latest message.
|
|
350
|
+
- Offer next actions using `_actions`.
|
|
351
|
+
|
|
352
|
+
### Example: Cross-Inbox Summary
|
|
353
|
+
|
|
354
|
+
Provide a compact summary in one line.
|
|
355
|
+
Follow it with a clear question asking for focus.
|
|
356
|
+
|
|
357
|
+
### End State
|
|
358
|
+
|
|
359
|
+
This skill should make inbox management predictable and safe.
|
|
360
|
+
Always prefer clarity over speed when making decisions.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Example: Bulk Archive with Dry Run
|
|
2
|
+
|
|
3
|
+
Goal: preview handled-tag conversations before archiving.
|
|
4
|
+
|
|
5
|
+
Dry run:
|
|
6
|
+
```bash
|
|
7
|
+
skill front bulk-archive -i inb_3srbb --filter "tag:handled" --dry-run --json
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Review the preview output with the user.
|
|
11
|
+
Only proceed after explicit approval.
|
|
12
|
+
|
|
13
|
+
Execute after approval:
|
|
14
|
+
```bash
|
|
15
|
+
skill front bulk-archive -i inb_3srbb --filter "tag:handled" --json
|
|
16
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Example: Triage Total TypeScript
|
|
2
|
+
|
|
3
|
+
Goal: fetch pending conversations for Total TypeScript and summarize urgency.
|
|
4
|
+
|
|
5
|
+
Command:
|
|
6
|
+
```bash
|
|
7
|
+
skill front triage -i inb_3srbb --json
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Expected flow:
|
|
11
|
+
- Parse JSON response.
|
|
12
|
+
- Note pending count and urgent tags.
|
|
13
|
+
- Ask the user what to focus on next.
|
|
14
|
+
|
|
15
|
+
Follow-up question:
|
|
16
|
+
What should I focus on in the TT inbox?
|
package/.env.encrypted
DELETED
|
Binary file
|
package/dist/preload.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
init_esm_shims
|
|
3
|
-
} from "./chunk-WFANXVQG.js";
|
|
4
|
-
|
|
5
|
-
// preload.ts
|
|
6
|
-
init_esm_shims();
|
|
7
|
-
import path2 from "path";
|
|
8
|
-
|
|
9
|
-
// src/lib/env-loader.ts
|
|
10
|
-
init_esm_shims();
|
|
11
|
-
import fs from "fs/promises";
|
|
12
|
-
import path from "path";
|
|
13
|
-
import { config } from "dotenv-flow";
|
|
14
|
-
|
|
15
|
-
// src/lib/crypto.ts
|
|
16
|
-
init_esm_shims();
|
|
17
|
-
import { Decrypter } from "age-encryption";
|
|
18
|
-
async function decrypt(encrypted, privateKey) {
|
|
19
|
-
const decrypter = new Decrypter();
|
|
20
|
-
decrypter.addIdentity(privateKey);
|
|
21
|
-
const input = encrypted instanceof Buffer ? new Uint8Array(encrypted) : encrypted;
|
|
22
|
-
return decrypter.decrypt(input, "text");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// src/lib/env-loader.ts
|
|
26
|
-
async function loadSecrets(cliDirOverride) {
|
|
27
|
-
const cliDir2 = cliDirOverride ?? path.resolve(import.meta.dirname, "../..");
|
|
28
|
-
const localEnvExists = await checkLocalEnv(cliDir2);
|
|
29
|
-
if (localEnvExists) {
|
|
30
|
-
config({ path: cliDir2, silent: true });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const encryptedPath = path.join(cliDir2, ".env.encrypted");
|
|
34
|
-
if (await fileExists(encryptedPath)) {
|
|
35
|
-
const privateKey = process.env.AGE_SECRET_KEY;
|
|
36
|
-
if (!privateKey) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
'Found .env.encrypted but AGE_SECRET_KEY is not set.\n\nAdd to your shell profile (~/.zshrc):\n export AGE_SECRET_KEY="AGE-SECRET-KEY-1..."\n\nOr create .env.local in the CLI package directory to bypass encryption.'
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const encryptedData = await fs.readFile(encryptedPath);
|
|
43
|
-
const decrypted = await decrypt(encryptedData, privateKey);
|
|
44
|
-
parseAndInjectEnv(decrypted);
|
|
45
|
-
return;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`Failed to decrypt .env.encrypted: ${error instanceof Error ? error.message : String(error)}
|
|
49
|
-
Check that AGE_SECRET_KEY matches the encryption key.`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
throw new Error(
|
|
54
|
-
"No environment configuration found.\n\nOptions:\n1. Set AGE_SECRET_KEY in your shell profile (for .env.encrypted)\n2. Create .env.local in the CLI package directory\n\nSee docs/CLI-AUTH.md for setup instructions."
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
async function checkLocalEnv(dir) {
|
|
58
|
-
const candidates = [".env.local", ".env"];
|
|
59
|
-
for (const file of candidates) {
|
|
60
|
-
if (await fileExists(path.join(dir, file))) {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
async function fileExists(filePath) {
|
|
67
|
-
try {
|
|
68
|
-
await fs.access(filePath);
|
|
69
|
-
return true;
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function parseAndInjectEnv(content) {
|
|
75
|
-
const lines = content.split("\n");
|
|
76
|
-
for (const line of lines) {
|
|
77
|
-
const trimmed = line.trim();
|
|
78
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
const eqIndex = trimmed.indexOf("=");
|
|
82
|
-
if (eqIndex === -1) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
86
|
-
let value = trimmed.slice(eqIndex + 1).trim();
|
|
87
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
88
|
-
value = value.slice(1, -1);
|
|
89
|
-
}
|
|
90
|
-
if (key && process.env[key] === void 0) {
|
|
91
|
-
process.env[key] = value;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// preload.ts
|
|
97
|
-
var dirname = import.meta.dirname;
|
|
98
|
-
var cliDir = dirname.endsWith("/dist") || dirname.endsWith("\\dist") ? path2.resolve(dirname, "..") : dirname;
|
|
99
|
-
var noSecretsNeeded = process.argv.length <= 2 || process.argv.some(
|
|
100
|
-
(a) => ["--help", "-h", "--version", "-V", "auth"].includes(a)
|
|
101
|
-
);
|
|
102
|
-
try {
|
|
103
|
-
await loadSecrets(cliDir);
|
|
104
|
-
} catch (err) {
|
|
105
|
-
if (noSecretsNeeded) {
|
|
106
|
-
process.env.SKIP_ENV_VALIDATION = "1";
|
|
107
|
-
} else {
|
|
108
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=preload.js.map
|
package/dist/preload.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../preload.ts","../src/lib/env-loader.ts","../src/lib/crypto.ts"],"sourcesContent":["import path from 'node:path'\nimport { loadSecrets } from './src/lib/env-loader.js'\n\n// Calculate CLI package root - works for both dev (preload.ts) and bundled (dist/preload.js)\n// In dev: dirname is packages/cli, so '..' goes to packages, '../..' to root - wrong, we want '.'\n// In bundle: dirname is packages/cli/dist, so '..' goes to packages/cli - correct\n// Solution: check if we're in dist/ and adjust accordingly\nconst dirname = import.meta.dirname\nconst cliDir =\n dirname.endsWith('/dist') || dirname.endsWith('\\\\dist')\n ? path.resolve(dirname, '..')\n : dirname\n\n// Don't crash for commands that don't need secrets (--help, auth, etc.)\nconst noSecretsNeeded =\n process.argv.length <= 2 ||\n process.argv.some((a) =>\n ['--help', '-h', '--version', '-V', 'auth'].includes(a)\n )\n\ntry {\n await loadSecrets(cliDir)\n} catch (err) {\n if (noSecretsNeeded) {\n // Skip env validation so index.js can load without DATABASE_URL\n process.env.SKIP_ENV_VALIDATION = '1'\n } else {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { config } from 'dotenv-flow'\nimport { decrypt } from './crypto.js'\n\n/**\n * Load secrets from layered fallback:\n * 1. Local .env/.env.local files (dev override)\n * 2. Encrypted .env.encrypted + AGE_SECRET_KEY (npm installs)\n * 3. Fail with clear instructions\n *\n * Injects secrets into process.env\n *\n * @param cliDirOverride - Optional path to CLI package root (use when calling from bundled code)\n */\nexport async function loadSecrets(cliDirOverride?: string): Promise<void> {\n const cliDir = cliDirOverride ?? path.resolve(import.meta.dirname, '../..')\n\n // Layer 1: Check for local .env files (takes priority — dev workflow)\n const localEnvExists = await checkLocalEnv(cliDir)\n if (localEnvExists) {\n config({ path: cliDir, silent: true })\n return\n }\n\n // Layer 2: Decrypt .env.encrypted with AGE_SECRET_KEY\n const encryptedPath = path.join(cliDir, '.env.encrypted')\n if (await fileExists(encryptedPath)) {\n const privateKey = process.env.AGE_SECRET_KEY\n if (!privateKey) {\n throw new Error(\n 'Found .env.encrypted but AGE_SECRET_KEY is not set.\\n\\n' +\n 'Add to your shell profile (~/.zshrc):\\n' +\n ' export AGE_SECRET_KEY=\"AGE-SECRET-KEY-1...\"\\n\\n' +\n 'Or create .env.local in the CLI package directory to bypass encryption.'\n )\n }\n\n try {\n const encryptedData = await fs.readFile(encryptedPath)\n const decrypted = await decrypt(encryptedData, privateKey)\n parseAndInjectEnv(decrypted)\n return\n } catch (error) {\n throw new Error(\n `Failed to decrypt .env.encrypted: ${error instanceof Error ? error.message : String(error)}\\n` +\n 'Check that AGE_SECRET_KEY matches the encryption key.'\n )\n }\n }\n\n // Layer 3: Nothing found\n throw new Error(\n 'No environment configuration found.\\n\\n' +\n 'Options:\\n' +\n '1. Set AGE_SECRET_KEY in your shell profile (for .env.encrypted)\\n' +\n '2. Create .env.local in the CLI package directory\\n\\n' +\n 'See docs/CLI-AUTH.md for setup instructions.'\n )\n}\n\n/**\n * Check if any local .env files exist\n */\nasync function checkLocalEnv(dir: string): Promise<boolean> {\n const candidates = ['.env.local', '.env']\n for (const file of candidates) {\n if (await fileExists(path.join(dir, file))) {\n return true\n }\n }\n return false\n}\n\n/**\n * Check if a file exists\n */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Parse env file format (KEY=VALUE) and inject into process.env\n * Handles quoted values, comments, and empty lines\n */\nfunction parseAndInjectEnv(content: string): void {\n const lines = content.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Skip comments and empty lines\n if (!trimmed || trimmed.startsWith('#')) {\n continue\n }\n\n const eqIndex = trimmed.indexOf('=')\n if (eqIndex === -1) {\n continue\n }\n\n const key = trimmed.slice(0, eqIndex).trim()\n let value = trimmed.slice(eqIndex + 1).trim()\n\n // Remove quotes if present\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n\n // Only set if not already defined (respect existing env vars)\n if (key && process.env[key] === undefined) {\n process.env[key] = value\n }\n }\n}\n","import { Decrypter } from 'age-encryption'\n\n/**\n * Decrypt data with an age private key\n * @param encrypted - Encrypted data as Uint8Array or Buffer\n * @param privateKey - age private key (AGE-SECRET-KEY-1...)\n * @returns Decrypted data as string\n */\nexport async function decrypt(\n encrypted: Uint8Array | Buffer,\n privateKey: string\n): Promise<string> {\n const decrypter = new Decrypter()\n decrypter.addIdentity(privateKey)\n\n const input =\n encrypted instanceof Buffer ? new Uint8Array(encrypted) : encrypted\n return decrypter.decrypt(input, 'text')\n}\n"],"mappings":"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","names":["path","cliDir","path"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|