shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents

- 16 cross-shell utilities converted to sh/bash chezmoi executables:
  ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone
  ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj
  (ghg-clone not named ghg: unrelated cargo binary owns that name)
- cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho
  cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files
- fish keeps only shell-state functions; converted ones now hit PATH
- cp-appid ignore gating moved to .local/bin path
- dot_agents/skills: 82 skills materialized from dotstate storage so
  dot_claude skill symlinks resolve on fresh bootstrap; dotstate
  profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
Prad Nukala committed 2026-08-10 15:15:06 -04:00
1 parent 6776366ce6
commit 6406208b35
637 files changed
+100297 -497

No files matched your search

@@ -0,0 +1,125 @@
# Email Workers Gotchas
## Critical Issues
### ReadableStream Single-Use
```typescript
// ❌ WRONG: Stream consumed twice
const email = await PostalMime.parse(await new Response(message.raw).arrayBuffer());
const rawText = await new Response(message.raw).text(); // EMPTY!
// ✅ CORRECT: Buffer first
const buffer = await new Response(message.raw).arrayBuffer();
const email = await PostalMime.parse(buffer);
const rawText = new TextDecoder().decode(buffer);
```
### ctx.waitUntil() Errors Silent
```typescript
// ❌ Errors dropped silently
ctx.waitUntil(fetch(webhookUrl, { method: 'POST', body: data }));
// ✅ Catch and log
ctx.waitUntil(
fetch(webhookUrl, { method: 'POST', body: data })
.catch(err => env.ERROR_LOG.put(`error:${Date.now()}`, err.message))
);
```
## Security
### Envelope vs Header From (Spoofing)
```typescript
const envelopeFrom = message.from; // SMTP MAIL FROM (trusted)
const headerFrom = (await PostalMime.parse(buffer)).from?.address; // (untrusted)
// Use envelope for security decisions
```
### Input Validation
```typescript
if (message.rawSize > 5_000_000) { message.setReject('Too large'); return; }
if ((message.headers.get('Subject') || '').length > 1000) {
message.setReject('Invalid subject'); return;
}
```
### DMARC for Replies
Replies fail silently without DMARC. Verify: `dig TXT _dmarc.example.com`
## Parsing
### Address Parsing
```typescript
const email = await PostalMime.parse(buffer);
const fromAddress = email.from?.address || 'unknown';
const toAddresses = Array.isArray(email.to) ? email.to.map(t => t.address) : [email.to?.address];
```
### Character Encoding
Let postal-mime handle decoding - `email.subject`, `email.text`, `email.html` are UTF-8.
## API Behavior
### setReject() vs throw
```typescript
// setReject() for SMTP rejection
if (blockList.includes(message.from)) { message.setReject('Blocked'); return; }
// throw for worker errors
if (!env.KV) throw new Error('KV not configured');
```
### forward() Only X-* Headers
```typescript
headers.set('X-Processed-By', 'worker'); // ✅ Works
headers.set('Subject', 'Modified'); // ❌ Dropped
```
### Reply Requires Verified Domain
```typescript
// Use same domain as receiving address
const receivingDomain = message.to.split('@')[1];
await message.reply(new EmailMessage(`noreply@${receivingDomain}`, message.from, rawMime));
```
## Performance
### CPU Limit
```typescript
// Skip parsing large emails
if (message.rawSize > 5_000_000) {
await message.forward('inbox@example.com');
return;
}
```
Monitor: `npx wrangler tail`
## Limits
| Limit | Value |
|-------|-------|
| Max message size | 25 MiB |
| Max rules/zone | 200 |
| CPU time (free/paid) | 10ms / 50ms |
| Reply References | 100 |
## Common Errors
| Error | Fix |
|-------|-----|
| "Address not verified" | Add in Email Routing dashboard |
| "Exceeded CPU time" | Use `ctx.waitUntil()` or upgrade |
| "Stream is locked" | Buffer `message.raw` first |
| Silent reply failure | Check DMARC records |