typeclaw 0.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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/auth.schema.json +63 -0
  4. package/cron.schema.json +96 -0
  5. package/package.json +72 -0
  6. package/scripts/emit-base-dockerfile.ts +5 -0
  7. package/scripts/generate-schema.ts +34 -0
  8. package/secrets.schema.json +63 -0
  9. package/src/agent/auth.ts +119 -0
  10. package/src/agent/compaction.ts +35 -0
  11. package/src/agent/git-nudge.ts +95 -0
  12. package/src/agent/index.ts +451 -0
  13. package/src/agent/plugin-tools.ts +269 -0
  14. package/src/agent/reload-tool.ts +71 -0
  15. package/src/agent/self.ts +45 -0
  16. package/src/agent/session-origin.ts +288 -0
  17. package/src/agent/subagents.ts +253 -0
  18. package/src/agent/system-prompt.ts +68 -0
  19. package/src/agent/tools/channel-fetch-attachment.ts +118 -0
  20. package/src/agent/tools/channel-history.ts +119 -0
  21. package/src/agent/tools/channel-reply.ts +182 -0
  22. package/src/agent/tools/channel-send.ts +212 -0
  23. package/src/agent/tools/ddg.ts +218 -0
  24. package/src/agent/tools/restart.ts +122 -0
  25. package/src/agent/tools/stream-snapshot.ts +181 -0
  26. package/src/agent/tools/webfetch/fetch.ts +102 -0
  27. package/src/agent/tools/webfetch/index.ts +1 -0
  28. package/src/agent/tools/webfetch/strategies/grep.ts +70 -0
  29. package/src/agent/tools/webfetch/strategies/jq.ts +31 -0
  30. package/src/agent/tools/webfetch/strategies/raw.ts +3 -0
  31. package/src/agent/tools/webfetch/strategies/readability.ts +30 -0
  32. package/src/agent/tools/webfetch/strategies/selector.ts +41 -0
  33. package/src/agent/tools/webfetch/strategies/snapshot.ts +135 -0
  34. package/src/agent/tools/webfetch/tool.ts +281 -0
  35. package/src/agent/tools/webfetch/types.ts +33 -0
  36. package/src/agent/tools/websearch.ts +96 -0
  37. package/src/agent/tools/wikipedia.ts +52 -0
  38. package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +170 -0
  39. package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +421 -0
  40. package/src/bundled-plugins/agent-browser/index.ts +179 -0
  41. package/src/bundled-plugins/agent-browser/shim-install.ts +158 -0
  42. package/src/bundled-plugins/agent-browser/shim.ts +152 -0
  43. package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +113 -0
  44. package/src/bundled-plugins/guard/index.ts +26 -0
  45. package/src/bundled-plugins/guard/policies/non-workspace-write.ts +98 -0
  46. package/src/bundled-plugins/guard/policies/skill-authoring.ts +185 -0
  47. package/src/bundled-plugins/guard/policies/uncommitted-changes.ts +85 -0
  48. package/src/bundled-plugins/guard/policy.ts +18 -0
  49. package/src/bundled-plugins/memory/README.md +71 -0
  50. package/src/bundled-plugins/memory/append-tool.ts +84 -0
  51. package/src/bundled-plugins/memory/dreaming-state.ts +86 -0
  52. package/src/bundled-plugins/memory/dreaming.ts +470 -0
  53. package/src/bundled-plugins/memory/fragment-parser.ts +67 -0
  54. package/src/bundled-plugins/memory/index.ts +238 -0
  55. package/src/bundled-plugins/memory/load-memory.ts +122 -0
  56. package/src/bundled-plugins/memory/memory-logger.ts +257 -0
  57. package/src/bundled-plugins/memory/secret-detector.ts +49 -0
  58. package/src/bundled-plugins/memory/watermark.ts +15 -0
  59. package/src/bundled-plugins/security/index.ts +35 -0
  60. package/src/bundled-plugins/security/policies/git-exfil.ts +120 -0
  61. package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +167 -0
  62. package/src/bundled-plugins/security/policies/prompt-injection.ts +488 -0
  63. package/src/bundled-plugins/security/policies/secret-exfil-bash.ts +99 -0
  64. package/src/bundled-plugins/security/policies/secret-exfil-read.ts +127 -0
  65. package/src/bundled-plugins/security/policies/session-search-secrets.ts +86 -0
  66. package/src/bundled-plugins/security/policies/ssrf.ts +196 -0
  67. package/src/bundled-plugins/security/policies/system-prompt-leak.ts +81 -0
  68. package/src/bundled-plugins/security/policy.ts +9 -0
  69. package/src/channels/adapters/discord-bot-channel-resolver.ts +77 -0
  70. package/src/channels/adapters/discord-bot-classify.ts +148 -0
  71. package/src/channels/adapters/discord-bot.ts +640 -0
  72. package/src/channels/adapters/kakaotalk-author-resolver.ts +78 -0
  73. package/src/channels/adapters/kakaotalk-channel-resolver.ts +105 -0
  74. package/src/channels/adapters/kakaotalk-classify.ts +77 -0
  75. package/src/channels/adapters/kakaotalk.ts +622 -0
  76. package/src/channels/adapters/slack-bot-author-resolver.ts +80 -0
  77. package/src/channels/adapters/slack-bot-channel-resolver.ts +84 -0
  78. package/src/channels/adapters/slack-bot-classify.ts +213 -0
  79. package/src/channels/adapters/slack-bot-dedupe.ts +51 -0
  80. package/src/channels/adapters/slack-bot-time.ts +10 -0
  81. package/src/channels/adapters/slack-bot.ts +881 -0
  82. package/src/channels/adapters/telegram-bot-classify.ts +155 -0
  83. package/src/channels/adapters/telegram-bot-format.ts +309 -0
  84. package/src/channels/adapters/telegram-bot.ts +604 -0
  85. package/src/channels/engagement.ts +227 -0
  86. package/src/channels/index.ts +21 -0
  87. package/src/channels/manager.ts +292 -0
  88. package/src/channels/membership-cache.ts +116 -0
  89. package/src/channels/membership-from-history.ts +53 -0
  90. package/src/channels/membership.ts +30 -0
  91. package/src/channels/participants.ts +47 -0
  92. package/src/channels/persistence.ts +209 -0
  93. package/src/channels/reloadable.ts +28 -0
  94. package/src/channels/router.ts +1570 -0
  95. package/src/channels/schema.ts +273 -0
  96. package/src/channels/types.ts +160 -0
  97. package/src/cli/channel.ts +403 -0
  98. package/src/cli/compose-status.ts +95 -0
  99. package/src/cli/compose.ts +240 -0
  100. package/src/cli/hostd.ts +163 -0
  101. package/src/cli/index.ts +27 -0
  102. package/src/cli/init.ts +592 -0
  103. package/src/cli/logs.ts +38 -0
  104. package/src/cli/reload.ts +68 -0
  105. package/src/cli/restart.ts +66 -0
  106. package/src/cli/run.ts +77 -0
  107. package/src/cli/shell.ts +33 -0
  108. package/src/cli/start.ts +57 -0
  109. package/src/cli/status.ts +178 -0
  110. package/src/cli/stop.ts +31 -0
  111. package/src/cli/tui.ts +35 -0
  112. package/src/cli/ui.ts +110 -0
  113. package/src/commands/index.ts +74 -0
  114. package/src/compose/discover.ts +43 -0
  115. package/src/compose/index.ts +25 -0
  116. package/src/compose/logs.ts +162 -0
  117. package/src/compose/restart.ts +69 -0
  118. package/src/compose/start.ts +62 -0
  119. package/src/compose/status.ts +28 -0
  120. package/src/compose/stop.ts +43 -0
  121. package/src/config/config.ts +424 -0
  122. package/src/config/index.ts +25 -0
  123. package/src/config/providers.ts +234 -0
  124. package/src/config/reloadable.ts +47 -0
  125. package/src/container/index.ts +27 -0
  126. package/src/container/logs.ts +37 -0
  127. package/src/container/port.ts +137 -0
  128. package/src/container/shared.ts +290 -0
  129. package/src/container/shell.ts +58 -0
  130. package/src/container/start.ts +670 -0
  131. package/src/container/status.ts +76 -0
  132. package/src/container/stop.ts +120 -0
  133. package/src/container/verify-running.ts +149 -0
  134. package/src/cron/consumer.ts +138 -0
  135. package/src/cron/index.ts +54 -0
  136. package/src/cron/reloadable.ts +64 -0
  137. package/src/cron/scheduler.ts +200 -0
  138. package/src/cron/schema.ts +96 -0
  139. package/src/hostd/client.ts +113 -0
  140. package/src/hostd/daemon.ts +587 -0
  141. package/src/hostd/index.ts +25 -0
  142. package/src/hostd/paths.ts +82 -0
  143. package/src/hostd/portbroker-manager.ts +101 -0
  144. package/src/hostd/protocol.ts +48 -0
  145. package/src/hostd/spawn.ts +224 -0
  146. package/src/hostd/supervisor.ts +60 -0
  147. package/src/hostd/tailscale.ts +172 -0
  148. package/src/hostd/version.ts +115 -0
  149. package/src/init/dockerfile.ts +327 -0
  150. package/src/init/ensure-deps.ts +152 -0
  151. package/src/init/gitignore.ts +46 -0
  152. package/src/init/hatching.ts +60 -0
  153. package/src/init/index.ts +786 -0
  154. package/src/init/kakaotalk-auth.ts +114 -0
  155. package/src/init/models-dev.ts +130 -0
  156. package/src/init/oauth-login.ts +74 -0
  157. package/src/init/packagejson.ts +94 -0
  158. package/src/init/paths.ts +2 -0
  159. package/src/init/run-bun-install.ts +20 -0
  160. package/src/markdown/chunk.ts +299 -0
  161. package/src/markdown/index.ts +1 -0
  162. package/src/plugin/context.ts +40 -0
  163. package/src/plugin/define.ts +35 -0
  164. package/src/plugin/hooks.ts +204 -0
  165. package/src/plugin/index.ts +63 -0
  166. package/src/plugin/loader.ts +111 -0
  167. package/src/plugin/manager.ts +136 -0
  168. package/src/plugin/registry.ts +145 -0
  169. package/src/plugin/skills.ts +62 -0
  170. package/src/plugin/types.ts +172 -0
  171. package/src/portbroker/bind-with-forward.ts +102 -0
  172. package/src/portbroker/container-server.ts +305 -0
  173. package/src/portbroker/forward-result-bus.ts +36 -0
  174. package/src/portbroker/hostd-client.ts +443 -0
  175. package/src/portbroker/index.ts +33 -0
  176. package/src/portbroker/policy.ts +24 -0
  177. package/src/portbroker/proc-net-tcp.ts +72 -0
  178. package/src/portbroker/protocol.ts +39 -0
  179. package/src/reload/client.ts +59 -0
  180. package/src/reload/index.ts +3 -0
  181. package/src/reload/registry.ts +60 -0
  182. package/src/reload/types.ts +13 -0
  183. package/src/run/bundled-plugins.ts +24 -0
  184. package/src/run/channel-session-factory.ts +105 -0
  185. package/src/run/index.ts +432 -0
  186. package/src/run/plugin-runtime.ts +43 -0
  187. package/src/run/schema-with-plugins.ts +14 -0
  188. package/src/secrets/index.ts +13 -0
  189. package/src/secrets/migrate.ts +95 -0
  190. package/src/secrets/schema.ts +75 -0
  191. package/src/secrets/storage.ts +231 -0
  192. package/src/server/index.ts +436 -0
  193. package/src/sessions/index.ts +23 -0
  194. package/src/shared/index.ts +9 -0
  195. package/src/shared/local-time.ts +21 -0
  196. package/src/shared/protocol.ts +25 -0
  197. package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +87 -0
  198. package/src/skills/typeclaw-channel-telegram-bot/SKILL.md +64 -0
  199. package/src/skills/typeclaw-config/SKILL.md +643 -0
  200. package/src/skills/typeclaw-cron/SKILL.md +159 -0
  201. package/src/skills/typeclaw-git/SKILL.md +89 -0
  202. package/src/skills/typeclaw-memory/SKILL.md +174 -0
  203. package/src/skills/typeclaw-monorepo/SKILL.md +175 -0
  204. package/src/skills/typeclaw-plugins/SKILL.md +594 -0
  205. package/src/skills/typeclaw-skills/SKILL.md +246 -0
  206. package/src/stream/broker.ts +161 -0
  207. package/src/stream/index.ts +16 -0
  208. package/src/stream/types.ts +69 -0
  209. package/src/tui/client.ts +45 -0
  210. package/src/tui/format.ts +317 -0
  211. package/src/tui/index.ts +225 -0
  212. package/src/tui/theme.ts +41 -0
  213. package/typeclaw.schema.json +826 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeon Suyeol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # TypeClaw
2
+
3
+ > A TypeScript-native, Bun-powered, Docker-friendly general-purpose agent runtime.
4
+
5
+ ## Why?
6
+
7
+ There are great agents out there. None of them were quite the shape I wanted:
8
+
9
+ - **OpenClaw** — feature-rich, but heavy
10
+ - **NanoClaw** — simple, but no plugin system
11
+ - **PicoClaw** — fast, but Go (so plugins live outside the runtime)
12
+ - **ZeroClaw** — light, but Rust (same problem, different ecosystem)
13
+ - **Hermes Agent** — awesome, but Python
14
+
15
+ None of that matters to most people. It matters to me. If you're like me, TypeClaw is the right choice.
16
+
17
+ TypeClaw is the agent I wanted to use:
18
+
19
+ - **TypeScript end to end** — agent core, plugins, channel adapters, CLI, TUI all in one language
20
+ - **Bun-native plugins** — plugins are just TS modules; no IPC, no FFI, hot-reloadable config
21
+ - **Docker-friendly by default** — every agent runs in its own container; the host CLI is purely a launcher
22
+ - **Multi-channel out of the box** — Slack, Discord, TUI, websocket — all routed through one in-process stream
23
+ - **Self-improving** — the agent observes its own work, distills it into long-term memory and reusable skills, and gets sharper over time without you writing prompts for it
24
+
25
+ ## Features
26
+
27
+ - 🐳 **Sandboxed by default** — every agent runs in its own Docker container, with an `.env` and bind-mounted host folders
28
+ - 🔌 **Plugin system** — plain TypeScript modules contribute tools, skills, subagents, channels, and typed config
29
+ - 💬 **Multi-channel** — Slack, Discord, and a websocket TUI out of the box; one agent, many inboxes
30
+ - 👥 **Group chat awareness** — knows who's in the room, distinguishes humans from bots, and stays engaged after a reply without re-mentioning
31
+ - ⏰ **Cron** — schedule prompts or shell commands; per-job coalescing so slow jobs don't pile up
32
+ - 📚 **Skills on demand** — markdown procedures the agent loads only when relevant; zero token cost until used
33
+ - 🌱 **Self-improving** — bundled memory plugin observes the agent's work and consolidates it into long-term memory (see below)
34
+ - 🧠 **Muscle memory** — repeated procedures get distilled into reusable skills that the agent writes for itself
35
+ - 🔄 **Hot reload** — change `typeclaw.json`, `typeclaw reload` — no restart for most fields
36
+ - 🔁 **Self-restart** — the agent can bounce its own container when it updates itself
37
+ - 🌐 **Auto port-forward** — dev servers inside the container appear on `localhost`, even loopback-only ones
38
+ - 🎼 **Compose** — orchestrate multiple agents across multiple folders
39
+
40
+ ### 🌱 Self-improving, in detail
41
+
42
+ The bundled `memory` plugin turns lived experience into reusable knowledge. No manual prompt engineering. No curated example library.
43
+
44
+ 1. **Observe.** After every idle turn, a `memory-logger` subagent reads the transcript and appends notable fragments to `memory/yyyy-MM-dd.md`. Cheap, frequent, lossy by design.
45
+ 2. **Dream.** On a cron schedule (default 4am), a `dreaming` subagent consolidates daily streams into `MEMORY.md`, and — when it spots a procedure worth remembering — writes it as **muscle memory**: a new skill at `memory/skills/<name>/SKILL.md`.
46
+ 3. **Apply.** Tomorrow's prompt sees the updated `MEMORY.md`. Muscle-memory skills sit alongside bundled and user-installed ones, loaded on demand. Every dream is `git commit -m Dream`'d, so growth is auditable.
47
+
48
+ See [`src/bundled-plugins/memory/README.md`](./src/bundled-plugins/memory/README.md) for the full contract.
49
+
50
+ ## Install
51
+
52
+ ```sh
53
+ bun add -g typeclaw
54
+ ```
55
+
56
+ Requires Bun ≥ 1.1 and Docker (or OrbStack) on the host.
57
+
58
+ ## Quickstart
59
+
60
+ ```sh
61
+ mkdir my-agent && cd my-agent
62
+ typeclaw init # scaffold typeclaw.json, .env, Dockerfile, package.json
63
+ typeclaw start # build + run the container
64
+ typeclaw tui # attach a terminal UI to the running agent
65
+ ```
66
+
67
+ That's it. The agent is now alive, listening on a websocket, ready to receive prompts from the TUI or any wired channel.
68
+
69
+ ## CLI
70
+
71
+ | Command | Purpose |
72
+ | ------------------ | ----------------------------------------------- |
73
+ | `typeclaw init` | Scaffold a new agent folder |
74
+ | `typeclaw start` | Build and run the container |
75
+ | `typeclaw stop` | Stop the container |
76
+ | `typeclaw restart` | `stop` then `start` |
77
+ | `typeclaw status` | Show container + daemon registration state |
78
+ | `typeclaw logs` | `docker logs` passthrough, `-f` to follow |
79
+ | `typeclaw tui` | Attach a terminal UI over the agent's websocket |
80
+ | `typeclaw shell` | Open a shell inside the running container |
81
+ | `typeclaw reload` | Push a live config reload to the running agent |
82
+ | `typeclaw compose` | Orchestrate multiple agents |
83
+
84
+ ## Configuration
85
+
86
+ Agent folder layout after `init`:
87
+
88
+ ```
89
+ my-agent/
90
+ ├── typeclaw.json # main config (schema-validated)
91
+ ├── cron.json # scheduled jobs (optional)
92
+ ├── .env # secrets, injected via --env-file
93
+ ├── Dockerfile # auto-managed by typeclaw, refreshed every `start`
94
+ ├── package.json # `typeclaw` as a dependency
95
+ ├── .gitignore # auto-managed
96
+ ├── workspace/ # agent's free-write zone (gitignored)
97
+ ├── sessions/ # JSONL session logs (gitignored, force-committed by auto-backup)
98
+ └── memory/ # MEMORY.md + muscle-memory skills (gitignored, force-committed by dreaming)
99
+ ```
100
+
101
+ `typeclaw.json` is JSON Schema–validated (see `typeclaw.schema.json`). Highlights:
102
+
103
+ - `port` — preferred host port (CLI falls back to ephemeral on conflict)
104
+ - `mounts` — host directories to expose inside the container
105
+ - `plugins` — list of plugin module specifiers
106
+ - `channels` — `slack-bot` / `discord-bot` config
107
+ - `portForward` — allow/deny list for auto port forwarding (default: `*`)
108
+ - `dockerfile` — toggles for `gh`, `python`, `tmux`, `ffmpeg`, plus `append` lines
109
+ - `memory` — idle window and dreaming schedule for the memory plugin
110
+
111
+ `Dockerfile` and `.gitignore` are owned by TypeClaw and rewritten on every `start` — edit `src/init/dockerfile.ts` and re-run `start --build` to ship template changes.
112
+
113
+ ## Development
114
+
115
+ ```sh
116
+ git clone https://github.com/typeclaw/typeclaw
117
+ cd typeclaw
118
+ bun install
119
+ bun test
120
+ ```
121
+
122
+ Pre-commit checks (must all pass — no exceptions):
123
+
124
+ ```sh
125
+ bun run typecheck
126
+ bun run lint
127
+ bun run format
128
+ ```
129
+
130
+ See [AGENTS.md](./AGENTS.md) for the long-form architecture notes — stages, hostd internals, message stream, plugin contracts, and the testing philosophy.
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "version": {
9
+ "type": "number",
10
+ "const": 1
11
+ },
12
+ "llm": {
13
+ "default": {},
14
+ "type": "object",
15
+ "propertyNames": {
16
+ "type": "string"
17
+ },
18
+ "additionalProperties": {
19
+ "oneOf": [
20
+ {
21
+ "type": "object",
22
+ "properties": {
23
+ "type": {
24
+ "type": "string",
25
+ "const": "api_key"
26
+ },
27
+ "key": {
28
+ "type": "string",
29
+ "minLength": 1
30
+ }
31
+ },
32
+ "required": [
33
+ "type",
34
+ "key"
35
+ ]
36
+ },
37
+ {
38
+ "type": "object",
39
+ "properties": {
40
+ "type": {
41
+ "type": "string",
42
+ "const": "oauth"
43
+ }
44
+ },
45
+ "required": [
46
+ "type"
47
+ ],
48
+ "additionalProperties": {}
49
+ }
50
+ ]
51
+ }
52
+ },
53
+ "channels": {
54
+ "default": {},
55
+ "type": "object",
56
+ "properties": {},
57
+ "additionalProperties": {}
58
+ }
59
+ },
60
+ "required": [
61
+ "version"
62
+ ]
63
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "jobs": {
9
+ "default": [],
10
+ "type": "array",
11
+ "items": {
12
+ "oneOf": [
13
+ {
14
+ "type": "object",
15
+ "properties": {
16
+ "id": {
17
+ "type": "string",
18
+ "minLength": 1,
19
+ "pattern": "^[a-zA-Z0-9_-]+$"
20
+ },
21
+ "schedule": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "enabled": {
26
+ "default": true,
27
+ "type": "boolean"
28
+ },
29
+ "timezone": {
30
+ "type": "string"
31
+ },
32
+ "kind": {
33
+ "type": "string",
34
+ "const": "prompt"
35
+ },
36
+ "prompt": {
37
+ "type": "string",
38
+ "minLength": 1
39
+ },
40
+ "subagent": {
41
+ "type": "string",
42
+ "minLength": 1
43
+ },
44
+ "payload": {}
45
+ },
46
+ "required": [
47
+ "id",
48
+ "schedule",
49
+ "kind",
50
+ "prompt"
51
+ ]
52
+ },
53
+ {
54
+ "type": "object",
55
+ "properties": {
56
+ "id": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "pattern": "^[a-zA-Z0-9_-]+$"
60
+ },
61
+ "schedule": {
62
+ "type": "string",
63
+ "minLength": 1
64
+ },
65
+ "enabled": {
66
+ "default": true,
67
+ "type": "boolean"
68
+ },
69
+ "timezone": {
70
+ "type": "string"
71
+ },
72
+ "kind": {
73
+ "type": "string",
74
+ "const": "exec"
75
+ },
76
+ "command": {
77
+ "minItems": 1,
78
+ "type": "array",
79
+ "items": {
80
+ "type": "string",
81
+ "minLength": 1
82
+ }
83
+ }
84
+ },
85
+ "required": [
86
+ "id",
87
+ "schedule",
88
+ "kind",
89
+ "command"
90
+ ]
91
+ }
92
+ ]
93
+ }
94
+ }
95
+ }
96
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "typeclaw",
3
+ "version": "0.1.0",
4
+ "homepage": "https://github.com/typeclaw/typeclaw#readme",
5
+ "bugs": {
6
+ "url": "https://github.com/typeclaw/typeclaw/issues"
7
+ },
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/typeclaw/typeclaw.git"
12
+ },
13
+ "bin": {
14
+ "typeclaw": "src/cli/index.ts"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "scripts",
19
+ "typeclaw.schema.json",
20
+ "cron.schema.json",
21
+ "secrets.schema.json",
22
+ "auth.schema.json",
23
+ "!**/*.test.ts"
24
+ ],
25
+ "type": "module",
26
+ "exports": {
27
+ "./plugin": "./src/plugin/index.ts"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "typecheck": "tsgo --noEmit",
34
+ "lint": "oxlint",
35
+ "format": "oxfmt --write .",
36
+ "format:check": "oxfmt --check .",
37
+ "check": "bun run typecheck && bun run lint && bun run format:check",
38
+ "test": "bun test",
39
+ "generate:schema": "bun run scripts/generate-schema.ts",
40
+ "postinstall": "bun run scripts/generate-schema.ts"
41
+ },
42
+ "dependencies": {
43
+ "@clack/prompts": "^1.2.0",
44
+ "@mariozechner/pi-coding-agent": "^0.67.3",
45
+ "@mariozechner/pi-tui": "^0.67.3",
46
+ "@mozilla/readability": "^0.6.0",
47
+ "agent-messenger": "2.14.1",
48
+ "cheerio": "^1.2.0",
49
+ "citty": "^0.2.2",
50
+ "cron-parser": "^5.5.0",
51
+ "jq-wasm": "^1.1.0-jq-1.8.1",
52
+ "jsdom": "^29.0.2",
53
+ "turndown": "^7.2.4",
54
+ "zod": "^4.3.6"
55
+ },
56
+ "devDependencies": {
57
+ "@types/bun": "latest",
58
+ "@types/jsdom": "^28.0.1",
59
+ "@types/proper-lockfile": "^4.1.4",
60
+ "@types/turndown": "^5.0.6",
61
+ "@types/ws": "^8.18.1",
62
+ "@typescript/native-preview": "^7.0.0-dev.20260416.1",
63
+ "oxfmt": "^0.45.0",
64
+ "oxlint": "^1.60.0"
65
+ },
66
+ "peerDependencies": {
67
+ "typescript": "^5"
68
+ },
69
+ "engines": {
70
+ "bun": ">=1.1.0"
71
+ }
72
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { buildBaseDockerfile } from '../src/init/dockerfile'
4
+
5
+ process.stdout.write(buildBaseDockerfile())
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { writeFile } from 'node:fs/promises'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { z } from 'zod'
8
+
9
+ import { configSchema as coreConfigSchema } from '../src/config/config'
10
+ import { cronFileSchema } from '../src/cron/schema'
11
+ import { buildConfigSchemaWithBundledPlugins } from '../src/run/schema-with-plugins'
12
+ import { secretsFileSchema } from '../src/secrets/schema'
13
+
14
+ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
15
+
16
+ // auth.schema.json is the permanent compatibility alias for secrets.schema.json.
17
+ // Pre-rename `auth.json` files (or migrated `secrets.json` files that still
18
+ // carry the legacy `$schema` URL — `mergeLlmIntoEnvelope` preserves an
19
+ // existing `$schema`, so the legacy pointer survives the file rename) need
20
+ // it to resolve in editors. The alias is tiny and re-emitting it has no
21
+ // maintenance cost, so we keep it indefinitely rather than coordinating a
22
+ // content-rewrite migration for every old agent folder.
23
+ const targets: Array<{ path: string; schema: z.ZodType }> = [
24
+ { path: join(repoRoot, 'typeclaw.schema.json'), schema: buildConfigSchemaWithBundledPlugins(coreConfigSchema) },
25
+ { path: join(repoRoot, 'cron.schema.json'), schema: cronFileSchema },
26
+ { path: join(repoRoot, 'secrets.schema.json'), schema: secretsFileSchema },
27
+ { path: join(repoRoot, 'auth.schema.json'), schema: secretsFileSchema },
28
+ ]
29
+
30
+ for (const { path, schema } of targets) {
31
+ const json = z.toJSONSchema(schema, { io: 'input', reused: 'inline' })
32
+ await writeFile(path, `${JSON.stringify(json, null, 2)}\n`)
33
+ console.log(`Wrote ${path}`)
34
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "version": {
9
+ "type": "number",
10
+ "const": 1
11
+ },
12
+ "llm": {
13
+ "default": {},
14
+ "type": "object",
15
+ "propertyNames": {
16
+ "type": "string"
17
+ },
18
+ "additionalProperties": {
19
+ "oneOf": [
20
+ {
21
+ "type": "object",
22
+ "properties": {
23
+ "type": {
24
+ "type": "string",
25
+ "const": "api_key"
26
+ },
27
+ "key": {
28
+ "type": "string",
29
+ "minLength": 1
30
+ }
31
+ },
32
+ "required": [
33
+ "type",
34
+ "key"
35
+ ]
36
+ },
37
+ {
38
+ "type": "object",
39
+ "properties": {
40
+ "type": {
41
+ "type": "string",
42
+ "const": "oauth"
43
+ }
44
+ },
45
+ "required": [
46
+ "type"
47
+ ],
48
+ "additionalProperties": {}
49
+ }
50
+ ]
51
+ }
52
+ },
53
+ "channels": {
54
+ "default": {},
55
+ "type": "object",
56
+ "properties": {},
57
+ "additionalProperties": {}
58
+ }
59
+ },
60
+ "required": [
61
+ "version"
62
+ ]
63
+ }
@@ -0,0 +1,119 @@
1
+ import { join } from 'node:path'
2
+
3
+ import { AuthStorage, ModelRegistry } from '@mariozechner/pi-coding-agent'
4
+
5
+ import { getConfig } from '@/config'
6
+ import {
7
+ KNOWN_PROVIDERS,
8
+ providerForModelRef,
9
+ supportsApiKey,
10
+ supportsOAuth,
11
+ type KnownProviderId,
12
+ } from '@/config/providers'
13
+ import { createSecretsStoreForAgent } from '@/secrets'
14
+
15
+ type Auth = {
16
+ authStorage: AuthStorage
17
+ modelRegistry: ModelRegistry
18
+ }
19
+
20
+ const TEST_DUMMY_API_KEY = 'test_dummy_key'
21
+
22
+ // In container stage, /agent is the bind-mounted agent folder; in host stage
23
+ // (only used by `typeclaw init` itself), it falls back to process.cwd(). The
24
+ // host writes secrets.json at init time and the container reads + refreshes
25
+ // it at runtime — both paths point at the same file on the host filesystem.
26
+ function secretsJsonPath(): string {
27
+ return join(process.cwd(), 'secrets.json')
28
+ }
29
+
30
+ let cached: Auth | null = null
31
+
32
+ export function getAuth(): Auth {
33
+ if (cached) return cached
34
+
35
+ const providerId = providerForModelRef(getConfig().model)
36
+ const provider = KNOWN_PROVIDERS[providerId]
37
+
38
+ // Bun sets NODE_ENV=test automatically under `bun test`. The dummy path
39
+ // bypasses both secrets.json and process.env so suites that build sessions
40
+ // but never hit the LLM don't need real credentials; production still
41
+ // hard-exits to surface misconfiguration.
42
+ if (process.env.NODE_ENV === 'test' && !hasAnyCredentialInEnv(provider.apiKeyEnv)) {
43
+ const authStorage = AuthStorage.inMemory()
44
+ if (supportsApiKey(provider)) {
45
+ authStorage.setRuntimeApiKey(provider.id, TEST_DUMMY_API_KEY)
46
+ }
47
+ const modelRegistry = ModelRegistry.create(authStorage)
48
+ cached = { authStorage, modelRegistry }
49
+ return cached
50
+ }
51
+
52
+ const authStorage = createSecretsStoreForAgent(secretsJsonPath())
53
+
54
+ // Persist the .env API key into secrets.json so the file is the single
55
+ // source of truth for credentials. Upstream pi-ai's `getEnvApiKey()` only
56
+ // knows about a hardcoded set of providers (anthropic, openai, etc.) and
57
+ // does NOT know about Fireworks, so `hasAuth("fireworks")` returns false
58
+ // unless a credential is materialized into AuthStorage's data map. Before
59
+ // this migration the code used `setRuntimeApiKey`, which papered over the
60
+ // gap in-memory but never wrote secrets.json — leaving `llm` empty for every
61
+ // downstream consumer (rotation, audit, transport over the daemon
62
+ // boundary) that treats the file as authoritative.
63
+ //
64
+ // Policy: never overwrite an existing OAuth credential. The user
65
+ // explicitly logged in at init, and an unrelated `.env` value must not
66
+ // silently displace it. Only write when no credential exists, or when an
67
+ // existing api-key value drifted from the env var (the user rotated the
68
+ // key in .env and expects the next boot to pick it up).
69
+ if (supportsApiKey(provider) && provider.apiKeyEnv) {
70
+ const envKey = process.env[provider.apiKeyEnv]
71
+ if (envKey) {
72
+ const existing = authStorage.get(provider.id)
73
+ const needsWrite = existing === undefined || (existing.type === 'api_key' && existing.key !== envKey)
74
+ if (needsWrite) {
75
+ authStorage.set(provider.id, { type: 'api_key', key: envKey })
76
+ }
77
+ }
78
+ }
79
+
80
+ // OAuth providers persist via `oauth-login.ts` at init time; api-key
81
+ // providers persist via the migration block above. By this point
82
+ // secrets.json is authoritative — a missing entry means the user skipped
83
+ // login at init, deleted the file, or never set the provider's env var.
84
+ if (!authStorage.hasAuth(provider.id)) {
85
+ console.error(missingCredentialMessage(providerId))
86
+ process.exit(1)
87
+ }
88
+
89
+ const modelRegistry = ModelRegistry.create(authStorage)
90
+ cached = { authStorage, modelRegistry }
91
+ return cached
92
+ }
93
+
94
+ export function resetAuthForTesting(): void {
95
+ cached = null
96
+ }
97
+
98
+ function hasAnyCredentialInEnv(apiKeyEnv: string | null): boolean {
99
+ return apiKeyEnv !== null && process.env[apiKeyEnv] !== undefined && process.env[apiKeyEnv] !== ''
100
+ }
101
+
102
+ function missingCredentialMessage(providerId: KnownProviderId): string {
103
+ const provider = KNOWN_PROVIDERS[providerId]
104
+ const ref = getConfig().model
105
+ const slash = ref.indexOf('/')
106
+ const modelName =
107
+ (provider.models as Record<string, { name: string }>)[ref.slice(slash + 1)]?.name ?? ref.slice(slash + 1)
108
+
109
+ const oauthOnly = supportsOAuth(provider) && !supportsApiKey(provider)
110
+ const apiKeyOnly = supportsApiKey(provider) && !supportsOAuth(provider)
111
+
112
+ if (oauthOnly) {
113
+ return `No credentials for ${provider.name}. Run \`typeclaw init\` and pick "OAuth" to log in to ${modelName}.`
114
+ }
115
+ if (apiKeyOnly && provider.apiKeyEnv) {
116
+ return `Set ${provider.apiKeyEnv} in .env to use ${modelName} via ${provider.name}.`
117
+ }
118
+ return `No credentials for ${provider.name}. Either set ${provider.apiKeyEnv ?? '<api-key-env>'} in .env or run \`typeclaw init\` and pick "OAuth".`
119
+ }
@@ -0,0 +1,35 @@
1
+ import type { KnownApi, Model } from '@mariozechner/pi-ai'
2
+ import { SettingsManager } from '@mariozechner/pi-coding-agent'
3
+
4
+ // Compaction trigger threshold expressed as a percentage of the model's
5
+ // context window. pi-coding-agent's auto-compaction fires when
6
+ // `contextTokens > contextWindow - reserveTokens`. To honor a percentage-
7
+ // based intent across models with very different window sizes (200K Claude
8
+ // vs. 1M Gemini vs. 256K Kimi), we derive `reserveTokens` per-model from
9
+ // the model's `contextWindow`. SDK defaults (16384 reserve) are a fixed
10
+ // number of tokens that drift in relative terms across models — at 256K
11
+ // that's ~6% headroom (94% trigger), at 1M it's ~1.6% (98% trigger). A
12
+ // percentage-derived reserve trips at the same fraction regardless of
13
+ // model, which is what we actually want.
14
+ export const COMPACTION_TRIGGER_PERCENT = 0.8
15
+
16
+ // Tokens to keep in the recent window after compaction. Fixed (not a
17
+ // percentage) because "recent context" is a property of conversation
18
+ // shape, not model capacity — the same recent ~20K is roughly the right
19
+ // amount of history regardless of whether the model has 200K or 1M total.
20
+ // Mirrors pi's DEFAULT_COMPACTION_SETTINGS.keepRecentTokens.
21
+ export const COMPACTION_KEEP_RECENT_TOKENS = 20_000
22
+
23
+ export function reserveTokensForModel<TApi extends KnownApi>(model: Model<TApi>): number {
24
+ return Math.max(1, Math.round(model.contextWindow * (1 - COMPACTION_TRIGGER_PERCENT)))
25
+ }
26
+
27
+ export function createCompactionSettingsManager<TApi extends KnownApi>(model: Model<TApi>): SettingsManager {
28
+ return SettingsManager.inMemory({
29
+ compaction: {
30
+ enabled: true,
31
+ reserveTokens: reserveTokensForModel(model),
32
+ keepRecentTokens: COMPACTION_KEEP_RECENT_TOKENS,
33
+ },
34
+ })
35
+ }