@otto-assistant/bridge 0.4.101 → 0.4.103

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 (70) hide show
  1. package/dist/agent-model.e2e.test.js +1 -0
  2. package/dist/anthropic-auth-plugin.js +22 -1
  3. package/dist/anthropic-auth-state.js +31 -0
  4. package/dist/btw-prefix-detection.js +17 -0
  5. package/dist/btw-prefix-detection.test.js +63 -0
  6. package/dist/cli.js +101 -15
  7. package/dist/commands/agent.js +21 -2
  8. package/dist/commands/ask-question.js +50 -4
  9. package/dist/commands/ask-question.test.js +92 -0
  10. package/dist/commands/btw.js +71 -66
  11. package/dist/commands/new-worktree.js +92 -35
  12. package/dist/commands/queue.js +17 -0
  13. package/dist/commands/worktrees.js +196 -139
  14. package/dist/context-awareness-plugin.js +16 -8
  15. package/dist/context-awareness-plugin.test.js +4 -2
  16. package/dist/discord-bot.js +35 -2
  17. package/dist/discord-command-registration.js +9 -2
  18. package/dist/memory-overview-plugin.js +3 -1
  19. package/dist/opencode.js +24 -1
  20. package/dist/queue-question-select-drain.e2e.test.js +135 -10
  21. package/dist/session-handler/thread-runtime-state.js +27 -0
  22. package/dist/session-handler/thread-session-runtime.js +58 -28
  23. package/dist/session-title-rename.test.js +12 -0
  24. package/dist/skill-filter.js +31 -0
  25. package/dist/skill-filter.test.js +65 -0
  26. package/dist/store.js +2 -0
  27. package/dist/system-message.js +12 -3
  28. package/dist/system-message.test.js +10 -6
  29. package/dist/thread-message-queue.e2e.test.js +109 -0
  30. package/dist/worktree-lifecycle.e2e.test.js +4 -1
  31. package/dist/worktrees.js +106 -12
  32. package/dist/worktrees.test.js +232 -6
  33. package/package.json +2 -2
  34. package/skills/goke/SKILL.md +13 -619
  35. package/skills/new-skill/SKILL.md +34 -10
  36. package/skills/npm-package/SKILL.md +336 -2
  37. package/skills/profano/SKILL.md +24 -0
  38. package/skills/zele/SKILL.md +50 -21
  39. package/src/agent-model.e2e.test.ts +1 -0
  40. package/src/anthropic-auth-plugin.ts +24 -4
  41. package/src/anthropic-auth-state.ts +45 -0
  42. package/src/btw-prefix-detection.test.ts +73 -0
  43. package/src/btw-prefix-detection.ts +23 -0
  44. package/src/cli.ts +138 -46
  45. package/src/commands/agent.ts +24 -2
  46. package/src/commands/ask-question.test.ts +111 -0
  47. package/src/commands/ask-question.ts +69 -4
  48. package/src/commands/btw.ts +105 -85
  49. package/src/commands/new-worktree.ts +107 -40
  50. package/src/commands/queue.ts +22 -0
  51. package/src/commands/worktrees.ts +246 -154
  52. package/src/context-awareness-plugin.test.ts +4 -2
  53. package/src/context-awareness-plugin.ts +16 -8
  54. package/src/discord-bot.ts +40 -2
  55. package/src/discord-command-registration.ts +12 -2
  56. package/src/memory-overview-plugin.ts +3 -1
  57. package/src/opencode.ts +31 -1
  58. package/src/queue-question-select-drain.e2e.test.ts +174 -10
  59. package/src/session-handler/thread-runtime-state.ts +36 -1
  60. package/src/session-handler/thread-session-runtime.ts +72 -32
  61. package/src/session-title-rename.test.ts +18 -0
  62. package/src/skill-filter.test.ts +83 -0
  63. package/src/skill-filter.ts +42 -0
  64. package/src/store.ts +17 -0
  65. package/src/system-message.test.ts +10 -6
  66. package/src/system-message.ts +12 -3
  67. package/src/thread-message-queue.e2e.test.ts +126 -0
  68. package/src/worktree-lifecycle.e2e.test.ts +6 -1
  69. package/src/worktrees.test.ts +274 -9
  70. package/src/worktrees.ts +144 -23
@@ -26,7 +26,7 @@ The folder name should match the skill name in kebab-case. Each skill gets its o
26
26
  For personal skills that follow you across all repos and are not meant for distribution in a GitHub repository, place them in:
27
27
 
28
28
  ```
29
- ~/.opencode/skills/<skill-name>/SKILL.md
29
+ ~/.config/opencode/skills/<skill-name>/SKILL.md
30
30
  ```
31
31
 
32
32
  Personal skills are only available on your machine. Repository skills are shared with everyone who clones the repo.
@@ -133,18 +133,41 @@ A good skill captures **hard-won knowledge** that is not obvious from reading do
133
133
 
134
134
  A bad skill is just a copy of the tool's README or man page. If the agent could figure it out from `--help`, it does not need a skill for it.
135
135
 
136
- ## Skills for CLI tools
136
+ ## Keep the SKILL.md thin — point at canonical docs
137
137
 
138
- For CLI tools, put as much documentation as possible into the CLI itself in command descriptions, option help text, and examples shown by `--help`. The skill file should not duplicate that content. Instead, the skill should instruct the agent to run the help command first:
138
+ The best skills are **thin**. They contain almost no documentation themselves. Their only job is to tell the agent where to find the full, fresh docs and to forbid truncation. This keeps docs in one place and stops the skill from going stale.
139
+
140
+ There are two variants:
141
+
142
+ **1. CLI tools → run `<tool> --help`**
143
+
144
+ Put as much documentation as possible into the CLI itself — command descriptions, option help text, examples. The skill then says:
139
145
 
140
146
  ```markdown
141
- **Always run `mytool --help` before using this tool.** The help output
142
- is the source of truth for all commands, options, and examples.
147
+ Every time you use mytool, you MUST run:
148
+
149
+ \`\`\`bash
150
+ mytool --help # NEVER pipe to head/tail, read the full output
151
+ \`\`\`
152
+ ```
153
+
154
+ Exception: some CLIs have a dedicated `<tool> skill` subcommand when `--help` is not rich enough (e.g. `playwriter skill`). Prefer `--help` by default and only use a custom subcommand when the CLI ships one.
155
+
156
+ **2. Libraries and projects → curl the raw README**
157
+
158
+ For libraries, frameworks, and pattern skills, keep the canonical docs in `README.md` and have the skill curl the raw file from the main branch so the agent always reads the latest version:
159
+
160
+ ```markdown
161
+ Every time you work with myproject, you MUST fetch the latest README:
162
+
163
+ \`\`\`bash
164
+ curl -s https://raw.githubusercontent.com/owner/repo/main/README.md # NEVER pipe to head/tail
165
+ \`\`\`
143
166
  ```
144
167
 
145
- This keeps documentation in one place (the CLI binary) and avoids the skill going stale when the CLI updates.
168
+ If the README lives in a subdirectory (e.g. a package inside a monorepo), include that subpath: `.../main/packagename/README.md`.
146
169
 
147
- When running help commands, the agent must read the **full untruncated output**. Never pipe help output through `head`, `tail`, `sed -n`, or any command that strips or truncates lines. Agents do this frequently and it causes them to miss critical options and context. The help output exists to be read in full.
170
+ **Never truncate docs output.** The agent must read `--help` and curl'd README output **in full**. Never pipe through `head`, `tail`, `sed -n`, `awk`, `| less`, or any command that strips or limits lines. Critical rules are spread throughout the doc, not just at the top. Agents truncate frequently and miss important context forbid it explicitly in the skill body.
148
171
 
149
172
  ## Examples from real skills
150
173
 
@@ -206,6 +229,7 @@ Before saving a new skill:
206
229
 
207
230
  1. Does the **description** clearly state when to load this skill? Would an agent reading just the description know whether to load it?
208
231
  2. Does the **name** match the folder name?
209
- 3. Are there **concrete code examples** for the main workflows?
210
- 4. Did you avoid duplicating content the agent can get from `--help` or standard docs?
211
- 5. Did you capture the **gotchas** the things that took trial and error to figure out?
232
+ 3. Does the skill **point at a single source of truth** (README curl URL or `--help` command) instead of duplicating docs inline?
233
+ 4. Is there an explicit **"never truncate"** rule next to any docs command?
234
+ 5. Are there **concrete code examples** for the main workflows?
235
+ 6. Did you capture the **gotchas** — the things that took trial and error to figure out?
@@ -38,6 +38,9 @@ Use this skill when scaffolding or fixing npm packages.
38
38
  - `dist`
39
39
  - any runtime-required extra files (for example `schema.prisma`)
40
40
  - docs like `README.md` and `CHANGELOG.md`
41
+ - `skills/` directory if the package ships an agent skill (see "Agent
42
+ skill" section below). Skill files live at `skills/<name>/SKILL.md`,
43
+ never at the package root.
41
44
  - if tests are inside src and gets included in dist, it's fine. don't try to exclude them
42
45
  10. `scripts.build` should be `tsc && chmod +x dist/cli.js` (skip the chmod if
43
46
  the package has no bin). No bundling. Do not delete `dist/` in `build` by
@@ -111,8 +114,9 @@ import path from "node:path";
111
114
 
112
115
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
113
116
 
114
- // e.g. from src/cli.ts → read SKILL.md at the package root
115
- const skillPath = path.resolve(__dirname, "../SKILL.md");
117
+ // e.g. from src/cli.ts → read SKILL.md under skills/<name>/SKILL.md
118
+ // (skill files always live in skills/<name>/SKILL.md, never at the package root)
119
+ const skillPath = path.resolve(__dirname, "../skills/mypkg/SKILL.md");
116
120
 
117
121
  // from dist/cli.js (after tsc) → reach back to src/
118
122
  const srcFile = path.resolve(__dirname, "../src/template.md");
@@ -218,11 +222,341 @@ Use Node ESM-compatible compiler settings:
218
222
  ```
219
223
 
220
224
 
225
+ ## Package.json `imports` map (internal `#` aliases)
226
+
227
+ Use `imports` when you need a package to swap between different implementations
228
+ based on runtime (Node vs Bun vs browser vs SQLite vs better-sqlite3, etc.).
229
+ Internal imports are `#`-prefixed, scoped to the package itself, and never
230
+ leak to consumers. Consumers resolve through `exports`, not `imports`.
231
+
232
+ ### Point `types` at `dist`, not `src`
233
+
234
+ The TypeScript docs are explicit about this:
235
+
236
+ > If the package.json is part of the local project, an additional remapping
237
+ > step is performed in order to find the **input** TypeScript implementation
238
+ > file... This remapping uses the `outDir`/`declarationDir` and `rootDir`
239
+ > from the tsconfig.json, so using `"imports"` usually requires an explicit
240
+ > `rootDir` to be set.
241
+ >
242
+ > This variation allows package authors to write `"imports"` and `"exports"`
243
+ > fields that reference only the compilation outputs that will be published
244
+ > to npm, while still allowing local development to use the original
245
+ > TypeScript source files.
246
+
247
+ In other words, TypeScript automatically walks from `./dist/foo.d.ts` back
248
+ to `./src/foo.ts` using `outDir` → `rootDir` during compilation. You do not
249
+ need to point `types` at `src` manually — **let TypeScript remap it**.
250
+
251
+ ```json
252
+ {
253
+ "imports": {
254
+ "#sqlite": {
255
+ "bun": "./src/platform/bun/sqlite.ts",
256
+ "node": {
257
+ "types": "./dist/platform/node/sqlite.d.ts",
258
+ "default": "./dist/platform/node/sqlite.js"
259
+ },
260
+ "default": {
261
+ "types": "./dist/platform/node/sqlite.d.ts",
262
+ "default": "./dist/platform/node/sqlite.js"
263
+ }
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ Resolution flow when `tsc` sees `import db from '#sqlite'`:
270
+
271
+ 1. `imports["#sqlite"].node.types` → `./dist/platform/node/sqlite.d.ts`
272
+ 2. package.json is in the local project → apply the remap.
273
+ 3. Replace `outDir` (`dist`) with `rootDir` (`src`) → `./src/platform/node/sqlite.d.ts`
274
+ 4. Replace `.d.ts` with the source extension `.ts` → `./src/platform/node/sqlite.ts`
275
+ 5. Return `./src/platform/node/sqlite.ts` (it exists on a fresh clone, no build needed).
276
+ 6. Otherwise fall back to `./dist/platform/node/sqlite.d.ts`.
277
+
278
+ ### Why dist-first is correct
279
+
280
+ - **No chicken-and-egg.** The remap is compile-time, so `tsc` works on a fresh
281
+ clone without `dist/` existing yet.
282
+ - **Published map describes shipped files.** Every `imports` entry points at
283
+ something that will actually be in the npm tarball. No stale src paths
284
+ leaking into the published package.json.
285
+ - **Works under plain Node.** If the package is loaded by Node without
286
+ TypeScript involvement, Node reads the same `imports` map at runtime and
287
+ resolves to real `dist/*.js` files that exist.
288
+ - **Bun / browser runtime conditions can still point at `src`**, because
289
+ those runtimes execute `.ts` directly and skip the build step.
290
+
291
+ ### Requirements
292
+
293
+ This only works when:
294
+
295
+ - `moduleResolution` is `node16`, `nodenext`, or `bundler`
296
+ - `rootDir` is set explicitly in `tsconfig.json` (the skill's tsconfig rules
297
+ already require `"rootDir": "src"`)
298
+ - `outDir` is set (already in the template)
299
+ - `resolvePackageJsonImports` is not disabled (it is on by default for the
300
+ supported `moduleResolution` modes)
301
+
302
+ ### Anti-pattern: pointing `types` at `src` manually
303
+
304
+ ```json
305
+ {
306
+ "imports": {
307
+ "#sqlite": {
308
+ "node": {
309
+ "types": "./src/platform/node/sqlite.ts", // ❌ don't do this
310
+ "default": "./dist/platform/node/sqlite.js"
311
+ }
312
+ }
313
+ }
314
+ }
315
+ ```
316
+
317
+ This works but:
318
+
319
+ 1. The published `package.json` advertises `src/*.ts` paths that may or may
320
+ not exist depending on what you include in `files`.
321
+ 2. It bypasses TypeScript's built-in remapping, which is the whole point of
322
+ the local-project `imports` feature.
323
+ 3. It is inconsistent with `default` — mixing source (for types) and dist
324
+ (for runtime) paths in the same entry is easy to get wrong.
325
+
326
+ Source of truth: [TypeScript Modules Reference — package.json "imports" and self-name imports](https://www.typescriptlang.org/docs/handbook/modules/reference.html#packagejson-imports-and-self-name-imports).
327
+
221
328
  ## tests location
222
329
 
223
330
  test files should be close with the associated source files. for example if you have an utils.ts file you will create utils.test.ts file next to it. with tests, importing from utils. preferred testing framework is vitest (or bun if project already using `bun test` or depends on bun APIs, rare)
224
331
 
225
332
 
333
+ ## Agent skill
334
+
335
+ If the package ships an agent skill (SKILL.md for AI coding agents), place it
336
+ at:
337
+
338
+ ```
339
+ skills/<package-name>/SKILL.md
340
+ ```
341
+
342
+ Never put `SKILL.md` at the package root. The `skills/<name>/SKILL.md` layout
343
+ matches the convention used by the [`skills`](https://skills.sh) CLI so users
344
+ can install it with:
345
+
346
+ ```bash
347
+ npx -y skills add owner/repo
348
+ ```
349
+
350
+ Add this installation snippet to the README so users know how to get the skill:
351
+
352
+ ```markdown
353
+ ## Agent Skill
354
+
355
+ This package ships a skill file that teaches AI coding agents how and when to
356
+ use it. Install it with:
357
+
358
+ \`\`\`bash
359
+ npx -y skills add owner/repo
360
+ \`\`\`
361
+ ```
362
+
363
+ Remember to add `skills` to the `files` array in `package.json` so the skill
364
+ directory is included when publishing.
365
+
366
+ ### Keep the SKILL.md thin
367
+
368
+ The SKILL.md body should be a **few lines**, not a full docs dump. Put all
369
+ real documentation in `README.md` (which already lives in `files`) and have
370
+ the skill tell the agent to fetch it. This way agents always read the latest
371
+ docs and the skill never goes stale.
372
+
373
+ The body stays thin, but the **frontmatter `description` must be rich**. It
374
+ is what the agent sees in its main context, and it is the only signal the
375
+ agent uses to decide whether to load the skill. Make it long enough to
376
+ cover: what the package is, the core concepts and APIs, concrete trigger
377
+ phrases the user might say, and explicit "ALWAYS load this skill when..."
378
+ conditions. A one-sentence description is almost always too short. See the
379
+ `new-skill` skill for full guidance on writing descriptions.
380
+
381
+ **CLI package template:**
382
+
383
+ ```md
384
+ ---
385
+ name: mypkg
386
+ description: |
387
+ mypkg is <what it does and the core concepts>. It exposes <main commands
388
+ or APIs> and is used for <typical tasks>. ALWAYS load this skill when
389
+ the user mentions mypkg, runs <binary>, edits files that import mypkg,
390
+ or asks about <trigger keywords>. Load it before writing any code that
391
+ touches mypkg so you know the correct usage patterns and gotchas.
392
+ ---
393
+
394
+ # mypkg
395
+
396
+ Every time you use mypkg, you MUST run:
397
+
398
+ \`\`\`bash
399
+ mypkg --help # NEVER pipe to head/tail, read the full output
400
+ \`\`\`
401
+ ```
402
+
403
+ **Library package template:**
404
+
405
+ ```md
406
+ ---
407
+ name: mypkg
408
+ description: |
409
+ mypkg is <what it does and the core concepts>. It exports <main APIs>
410
+ and is used for <typical tasks>. ALWAYS load this skill when the user
411
+ mentions mypkg, imports from mypkg, edits files that depend on it, or
412
+ asks about <trigger keywords>. Load it before writing any code that
413
+ touches mypkg so you know the correct usage patterns and gotchas.
414
+ ---
415
+
416
+ # mypkg
417
+
418
+ Every time you work with mypkg, you MUST fetch the latest README:
419
+
420
+ \`\`\`bash
421
+ curl -s https://raw.githubusercontent.com/owner/repo/main/README.md # NEVER pipe to head/tail
422
+ \`\`\`
423
+ ```
424
+
425
+ Because the SKILL.md body points at the README, the README must contain
426
+ everything the agent needs: API reference, examples, gotchas, and rules.
427
+ See the `new-skill` skill for the full pattern.
428
+
429
+ ## pnpm workspaces
430
+
431
+ When the project is a monorepo, use pnpm workspaces with flat `./*` glob paths
432
+ in `pnpm-workspace.yaml`. All packages live at the repo root as siblings, no
433
+ nested `packages/` directory:
434
+
435
+ ```yaml
436
+ packages:
437
+ - ./*
438
+ ```
439
+
440
+ This means the repo looks like:
441
+
442
+ ```
443
+ my-monorepo/
444
+ package.json # root (private: true)
445
+ pnpm-workspace.yaml
446
+ cli/ # workspace package
447
+ website/ # workspace package
448
+ db/ # workspace package
449
+ errore/ # workspace package (submodule)
450
+ ```
451
+
452
+ ### Common dev dependencies at root
453
+
454
+ Install shared dev tooling **only at the root** `package.json` so every
455
+ workspace package uses the same version without duplicating installs:
456
+
457
+ ```json
458
+ {
459
+ "private": true,
460
+ "devDependencies": {
461
+ "typescript": "^5.9.2",
462
+ "tsx": "^4.20.5",
463
+ "vitest": "^3.2.4",
464
+ "oxfmt": "^0.24.0"
465
+ }
466
+ }
467
+ ```
468
+
469
+ Packages that need these tools (like `tsc` or `vitest`) will resolve them
470
+ from the root `node_modules` via pnpm's hoisting. Do **not** add
471
+ `typescript`, `tsx`, `vitest`, or `oxfmt` as devDependencies in individual
472
+ workspace packages — only add them at root.
473
+
474
+ Package-specific dev dependencies (for example `@types/node`, `rimraf`,
475
+ `prisma`) still go in each package's own `devDependencies`.
476
+
477
+ ### Cross-workspace dependencies
478
+
479
+ Use `workspace:^` (not `workspace:*`) for local package versions so that
480
+ when published, the dependency resolves to a caret range instead of a pinned
481
+ version:
482
+
483
+ ```json
484
+ {
485
+ "dependencies": {
486
+ "my-utils": "workspace:^"
487
+ }
488
+ }
489
+ ```
490
+
491
+ Use `pnpm install package@workspace:^` to add a workspace dependency, or
492
+ add it to `package.json` manually with the `workspace:^` protocol.
493
+
494
+ ## CI (GitHub Actions)
495
+
496
+ Standard CI workflow for pnpm workspace monorepos. Key points:
497
+
498
+ - **Checkout submodules** with `submodules: recursive` if the repo uses git
499
+ submodules (common for shared libraries like errore).
500
+ - **Use `pnpm/action-setup@v4`** with the pnpm version matching your lockfile.
501
+ - **Use Node 24** (or latest LTS) via `actions/setup-node@v4` with `cache: pnpm`.
502
+ - **Build workspace packages** that export from `dist/` before running tests,
503
+ since submodules and some packages have `dist/` gitignored.
504
+ - **Run tests from the package directory**, not root.
505
+
506
+ Example `.github/workflows/ci.yml`:
507
+
508
+ ```yaml
509
+ name: CI
510
+
511
+ on:
512
+ push:
513
+ branches: [main]
514
+ pull_request:
515
+ branches: [main]
516
+
517
+ jobs:
518
+ test:
519
+ name: Tests
520
+ runs-on: ubuntu-latest
521
+ timeout-minutes: 30
522
+
523
+ steps:
524
+ - uses: actions/checkout@v4
525
+ with:
526
+ submodules: recursive
527
+
528
+ - uses: pnpm/action-setup@v4
529
+ with:
530
+ version: 9
531
+
532
+ - uses: actions/setup-node@v4
533
+ with:
534
+ node-version: 24
535
+ cache: pnpm
536
+
537
+ - name: Install dependencies
538
+ run: pnpm install
539
+
540
+ # Submodules and workspace packages with dist/ gitignored
541
+ # need to be built after checkout before anything can import them.
542
+ - name: Build workspace packages with dist/ exports
543
+ run: |
544
+ pnpm --filter my-lib run build
545
+ pnpm --filter my-utils run build
546
+
547
+ - name: Run tests
548
+ run: pnpm test -- --run
549
+ working-directory: cli
550
+ ```
551
+
552
+ If the repo has Prisma schemas, add generate steps before tests:
553
+
554
+ ```yaml
555
+ - name: Generate Prisma client
556
+ run: pnpm generate
557
+ working-directory: cli
558
+ ```
559
+
226
560
  ## .gitignore
227
561
 
228
562
  For non-workspace (standalone) packages, always create a `.gitignore` with:
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: profano
3
+ description: CLI tool to analyze V8 .cpuprofile files and print top functions by self-time or total-time in the terminal. ALWAYS load this skill when CPU profiling JavaScript or TypeScript programs (Node, Vitest, Bun, Chrome DevTools exports) — it shows how to generate .cpuprofile files and how to inspect them from the terminal without opening Chrome DevTools.
4
+ ---
5
+
6
+ # profano
7
+
8
+ `profano` is a terminal CLI that reads V8 `.cpuprofile` files and prints the heaviest functions as a table sorted by self-time or total (inclusive) time. Use it to quickly identify CPU hotspots from the terminal without loading the profile into Chrome DevTools or cpupro.
9
+
10
+ ## When to use
11
+
12
+ - You have a `.cpuprofile` file (from `node --cpu-prof`, Vitest, Chrome DevTools export, etc.) and want a quick top-N readout of the biggest offenders.
13
+ - You are an agent debugging slow code and need a grep-able, text-only view of where CPU time is spent.
14
+ - You want to compare hotspots across many profile files in one shot.
15
+
16
+ ## How to use
17
+
18
+ **Always run `profano --help` first.** The help output is the source of truth for all commands, options, and examples. Read the full untruncated output — do not pipe it through `head`, `tail`, or `sed`.
19
+
20
+ For full setup, usage examples, how to generate `.cpuprofile` files (Node, Vitest with a ready-to-copy `vitest.config.ts`, Chrome DevTools), and how to read the output columns, fetch the README:
21
+
22
+ ```bash
23
+ curl -s https://raw.githubusercontent.com/remorses/profano/main/README.md
24
+ ```
@@ -3,13 +3,14 @@ name: zele
3
3
  description: >
4
4
  Control Gmail and Google Calendar via CLI. Read, search, send, reply, and forward
5
5
  emails. Create, update, and delete calendar events. Manage drafts, labels, and attachments.
6
- Supports multiple Google accounts. Use this skill whenever the user asks to check email,
7
- send messages, schedule meetings, or manage their calendar.
6
+ Supports multiple Google accounts and IMAP/SMTP accounts (Fastmail, Outlook, any provider).
7
+ Use this skill whenever the user asks to check email, send messages, schedule meetings,
8
+ or manage their calendar.
8
9
  ---
9
10
 
10
- # zele — Gmail & Google Calendar CLI
11
+ # zele — Email & Calendar CLI
11
12
 
12
- A multi-account Gmail and Google Calendar client. Output is YAML, pipe-friendly.
13
+ A multi-account email and calendar client supporting **Google OAuth** and **IMAP/SMTP** (Fastmail, Outlook, any provider). Output is YAML, pipe-friendly.
13
14
 
14
15
  ## Setup
15
16
 
@@ -20,11 +21,20 @@ bun install -g zele
20
21
  # show connected accounts
21
22
  zele whoami
22
23
 
23
- # authenticate (opens browser, supports multiple accounts)
24
+ # authenticate with Google (opens browser, supports multiple accounts)
24
25
  zele login
26
+
27
+ # authenticate with IMAP/SMTP (non-interactive, designed for agents)
28
+ zele login imap \
29
+ --email you@fastmail.com \
30
+ --imap-host imap.fastmail.com --imap-port 993 \
31
+ --smtp-host smtp.fastmail.com --smtp-port 465 \
32
+ --password "your-app-password"
25
33
  ```
26
34
 
27
- **Remote/headless login:** `zele login` is interactive it prints an authorization URL and waits for a redirect URL to be pasted back. In agent/headless environments, run it inside tmux so the process persists:
35
+ **IMAP/SMTP login options:** `--imap-user` / `--smtp-user` if the login username differs from email. Omit `--smtp-host` for read-only (no sending). Use `--imap-password` / `--smtp-password` for separate credentials.
36
+
37
+ **Remote/headless Google login:** `zele login` is interactive — it prints an authorization URL and waits for a redirect URL to be pasted back. In agent/headless environments, run it inside tmux so the process persists:
28
38
 
29
39
  ```bash
30
40
  # start login in a tmux session
@@ -49,16 +59,17 @@ Running `zele` with no subcommand launches a human-friendly TUI for browsing ema
49
59
 
50
60
  ## Capabilities
51
61
 
52
- - **Mail:** list, search, read, send, reply, forward, star, archive, trash, label, watch for new emails, manage filters
53
- - **Drafts:** list, create, get, send, delete
54
- - **Calendar:** list calendars, list/search events, create/update/delete events, RSVP, free/busy
55
- - **Labels:** list, create, delete, unread counts
56
- - **Attachments:** list per thread, download
57
- - **Multi-account:** all commands support `--account <email>` to filter; list/search merge across accounts
62
+ - **Mail:** list, search, read, send, reply, forward, star, archive, trash, watch for new emails (Google + IMAP)
63
+ - **Drafts:** list, create, get, send, delete (Google + IMAP)
64
+ - **Attachments:** list per thread, download (Google + IMAP)
65
+ - **Labels:** list, create, delete, unread counts (Google only)
66
+ - **Filters:** list server-side filters (Google only)
67
+ - **Calendar:** list calendars, list/search events, create/update/delete events, RSVP, free/busy (Google only)
68
+ - **Multi-account:** all commands support `--account <email>` to filter; list/search merge across all account types
58
69
 
59
70
  ## Account discovery
60
71
 
61
- When the user asks to check emails **for a specific account** (e.g. "check my work email", "what's new on my personal Gmail?"), always run `zele whoami` first to list the connected accounts and find the exact email address to pass to `--account`. Never guess the email — use the output of `zele whoami` to pick the right one.
72
+ When the user asks to check emails **for a specific account** (e.g. "check my work email", "what's new on my personal Gmail?"), always run `zele whoami` first to list the connected accounts and find the exact email address to pass to `--account`. Never guess the email — use the output of `zele whoami` to pick the right one. The output also shows account type (`google` or `imap_smtp`) and capabilities.
62
73
 
63
74
  ```bash
64
75
  # list connected accounts
@@ -68,6 +79,24 @@ zele whoami
68
79
  zele mail list --account user@work.com
69
80
  ```
70
81
 
82
+ ## Google-only features
83
+
84
+ These commands only work with Google accounts. IMAP/SMTP accounts show a helpful error:
85
+
86
+ - `zele label list/counts/create/delete` — IMAP uses folders, not labels
87
+ - `zele mail label` — adding/removing labels
88
+ - `zele mail filter list` — server-side Gmail filters
89
+ - `zele cal *` — calendar requires Google OAuth
90
+ - `zele profile` — shows limited info for IMAP (email only, no message counts)
91
+
92
+ ## IMAP search support
93
+
94
+ IMAP accounts support a subset of Gmail query syntax, translated to IMAP SEARCH:
95
+
96
+ `from:`, `to:`, `subject:`, `is:unread`, `is:starred`, `has:attachment`, `newer_than:Nd`, `older_than:Nm`, `after:YYYY/MM/DD`, `before:YYYY/MM/DD`
97
+
98
+ Unsupported on IMAP: `cc:`, `-` (negate), `label:`, `in:`, `filename:`, `size:`/`larger:`/`smaller:`, `OR`, `{ }`.
99
+
71
100
  ## Examples
72
101
 
73
102
  ```bash
@@ -77,8 +106,8 @@ zele mail list
77
106
  # list only unread emails
78
107
  zele mail list --filter "is:unread"
79
108
 
80
- # list unread emails with attachments in inbox
81
- zele mail list --filter "is:unread has:attachment"
109
+ # list emails from last 7 days (works for both Google and IMAP)
110
+ zele mail list --filter "newer_than:7d"
82
111
 
83
112
  # combine filter with folder
84
113
  zele mail list --filter "from:github" --folder sent
@@ -98,15 +127,15 @@ zele mail reply <threadId> --body "Thanks!" --all
98
127
  # watch inbox for new mail (polls every 15s)
99
128
  zele mail watch
100
129
 
101
- # today's calendar events across all accounts
130
+ # add an IMAP account (non-interactive, for agents)
131
+ zele login imap --email you@fastmail.com --imap-host imap.fastmail.com --smtp-host smtp.fastmail.com --password "app-pass"
132
+
133
+ # today's calendar events (Google only)
102
134
  zele cal events --today --all
103
135
 
104
- # create a meeting with Google Meet
136
+ # create a meeting with Google Meet (Google only)
105
137
  zele cal create --summary "Standup" --from tomorrow --to +30m --meet --attendees bob@example.com
106
138
 
107
- # check free/busy
108
- zele cal freebusy --from today --to +8h
109
-
110
- # list Gmail filters
139
+ # list Gmail filters (Google only)
111
140
  zele mail filter list
112
141
  ```
@@ -958,6 +958,7 @@ describe('agent model resolution', () => {
958
958
  ⬥ ok
959
959
  *project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***
960
960
  Switched to **plan** agent for this session (was **test-agent**)
961
+ Model: *deterministic-provider/plan-model-v2*
961
962
  The agent will change on the next message.
962
963
  --- from: user (agent-model-tester)
963
964
  Reply with exactly: after-switch-msg
@@ -592,6 +592,20 @@ function toClaudeCodeToolName(name: string) {
592
592
  return OPENCODE_TO_CLAUDE_CODE_TOOL_NAME[name.toLowerCase()] ?? name;
593
593
  }
594
594
 
595
+ /**
596
+ * Strips the OpenCode identity block (from "You are OpenCode…" up to the
597
+ * Anthropic prompt marker "Skills provide specialized instructions") and
598
+ * re-injects essential environment context as a small XML tag.
599
+ *
600
+ * The original OpenCode prompt between those markers contains the current
601
+ * working directory and other runtime context. Stripping it wholesale loses
602
+ * that info, so we add back what the model needs (cwd) in a compact form.
603
+ *
604
+ * Original OpenCode Anthropic prompt structure (for reference):
605
+ * "You are OpenCode, the best coding agent on the planet."
606
+ * + environment block (cwd, OS, shell, date, etc.)
607
+ * + "Skills provide specialized instructions …"
608
+ */
595
609
  function sanitizeAnthropicSystemText(
596
610
  text: string,
597
611
  onError?: (msg: string) => void,
@@ -608,10 +622,16 @@ function sanitizeAnthropicSystemText(
608
622
  return text;
609
623
  }
610
624
 
611
- return (text.slice(0, startIdx) + text.slice(endIdx)).replaceAll(
612
- "opencode",
613
- "openc0de",
614
- );
625
+ // Re-inject the process working directory that was inside the stripped block.
626
+ const envContext = `\n<environment>\n<cwd>${process.cwd()}</cwd>\n</environment>\n`;
627
+
628
+ // Replace all case-insensitive whole-word occurrences of "opencode" with "openc0de"
629
+ const result =
630
+ text.slice(0, startIdx) +
631
+ envContext +
632
+ text.slice(endIdx);
633
+ // Use a regex with global, case-insensitive, and word boundary flags
634
+ return result.replace(/\bopencode\b/gi, "openc0de");
615
635
  }
616
636
 
617
637
  function mapSystemTextPart(