ada-agent 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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -0
  3. package/bench/README.md +88 -0
  4. package/bench/swebench.mjs +242 -0
  5. package/bin/ada-server.mjs +6 -0
  6. package/bin/ada.mjs +7 -0
  7. package/docs/agent-loop.svg +66 -0
  8. package/docs/architecture.md +139 -0
  9. package/docs/architecture.svg +73 -0
  10. package/docs/connectors.md +48 -0
  11. package/docs/integrations.md +59 -0
  12. package/docs/login-flow.svg +56 -0
  13. package/docs/orchestration.md +45 -0
  14. package/package.json +64 -0
  15. package/skills/accessibility/SKILL.md +23 -0
  16. package/skills/add-logging/SKILL.md +23 -0
  17. package/skills/add-metrics/SKILL.md +23 -0
  18. package/skills/adr/SKILL.md +24 -0
  19. package/skills/aesthetic-direction/SKILL.md +24 -0
  20. package/skills/agent-loop/SKILL.md +23 -0
  21. package/skills/alerting/SKILL.md +23 -0
  22. package/skills/alpha-compositing/SKILL.md +23 -0
  23. package/skills/android-compose/SKILL.md +23 -0
  24. package/skills/angular-module/SKILL.md +23 -0
  25. package/skills/ansible-playbook/SKILL.md +24 -0
  26. package/skills/api-docs/SKILL.md +24 -0
  27. package/skills/app-store-prep/SKILL.md +23 -0
  28. package/skills/architecture-diagram/SKILL.md +21 -0
  29. package/skills/architecture-doc/SKILL.md +24 -0
  30. package/skills/audit-log/SKILL.md +23 -0
  31. package/skills/authz-review/SKILL.md +23 -0
  32. package/skills/aws-lambda/SKILL.md +24 -0
  33. package/skills/bash-script/SKILL.md +23 -0
  34. package/skills/batch/SKILL.md +23 -0
  35. package/skills/bisect/SKILL.md +23 -0
  36. package/skills/bounding-box/SKILL.md +24 -0
  37. package/skills/branch-cleanup/SKILL.md +23 -0
  38. package/skills/bundle-analyze/SKILL.md +23 -0
  39. package/skills/cache/SKILL.md +23 -0
  40. package/skills/call-graph/SKILL.md +23 -0
  41. package/skills/canvas-debug/SKILL.md +23 -0
  42. package/skills/cdn-setup/SKILL.md +23 -0
  43. package/skills/changelog/SKILL.md +24 -0
  44. package/skills/cherry-pick/SKILL.md +23 -0
  45. package/skills/ci-setup/SKILL.md +23 -0
  46. package/skills/cleanup/SKILL.md +23 -0
  47. package/skills/cli-tool/SKILL.md +23 -0
  48. package/skills/cloudformation/SKILL.md +23 -0
  49. package/skills/code-examples/SKILL.md +24 -0
  50. package/skills/code-review/SKILL.md +23 -0
  51. package/skills/color-palette/SKILL.md +24 -0
  52. package/skills/color-space/SKILL.md +24 -0
  53. package/skills/comment-why/SKILL.md +23 -0
  54. package/skills/commit/SKILL.md +26 -0
  55. package/skills/complexity-audit/SKILL.md +23 -0
  56. package/skills/component/SKILL.md +23 -0
  57. package/skills/component-library/SKILL.md +23 -0
  58. package/skills/connect-github/SKILL.md +20 -0
  59. package/skills/connect-mcp/SKILL.md +21 -0
  60. package/skills/connect-postgres/SKILL.md +20 -0
  61. package/skills/connect-remote/SKILL.md +23 -0
  62. package/skills/connect-slack/SKILL.md +20 -0
  63. package/skills/contract-audit/SKILL.md +25 -0
  64. package/skills/contributing/SKILL.md +23 -0
  65. package/skills/cpp-raii/SKILL.md +23 -0
  66. package/skills/cron-job/SKILL.md +23 -0
  67. package/skills/cv-preprocess/SKILL.md +24 -0
  68. package/skills/dark-mode/SKILL.md +24 -0
  69. package/skills/dashboard/SKILL.md +23 -0
  70. package/skills/dashboard-ui/SKILL.md +23 -0
  71. package/skills/data-export/SKILL.md +23 -0
  72. package/skills/data-validation/SKILL.md +23 -0
  73. package/skills/dataframe/SKILL.md +23 -0
  74. package/skills/db-index/SKILL.md +24 -0
  75. package/skills/dead-code/SKILL.md +23 -0
  76. package/skills/debug/SKILL.md +24 -0
  77. package/skills/deck-review/SKILL.md +24 -0
  78. package/skills/dedupe/SKILL.md +23 -0
  79. package/skills/dedupe-deps/SKILL.md +23 -0
  80. package/skills/dependency-audit/SKILL.md +23 -0
  81. package/skills/dependency-update/SKILL.md +23 -0
  82. package/skills/deploy/SKILL.md +23 -0
  83. package/skills/design-system/SKILL.md +24 -0
  84. package/skills/design-tokens/SKILL.md +24 -0
  85. package/skills/diagram-as-code/SKILL.md +24 -0
  86. package/skills/diff-explain/SKILL.md +23 -0
  87. package/skills/django-view/SKILL.md +23 -0
  88. package/skills/doc-lint/SKILL.md +24 -0
  89. package/skills/docker-compose/SKILL.md +23 -0
  90. package/skills/dockerize/SKILL.md +23 -0
  91. package/skills/docstrings/SKILL.md +23 -0
  92. package/skills/dotfiles/SKILL.md +23 -0
  93. package/skills/dpi-scaling/SKILL.md +23 -0
  94. package/skills/e2e-test/SKILL.md +23 -0
  95. package/skills/embeddings/SKILL.md +23 -0
  96. package/skills/empty-states/SKILL.md +23 -0
  97. package/skills/env-setup/SKILL.md +23 -0
  98. package/skills/erc20/SKILL.md +24 -0
  99. package/skills/error-tracking/SKILL.md +23 -0
  100. package/skills/estimate/SKILL.md +23 -0
  101. package/skills/etl-pipeline/SKILL.md +24 -0
  102. package/skills/eval-harness/SKILL.md +23 -0
  103. package/skills/exif-orientation/SKILL.md +23 -0
  104. package/skills/explain-code/SKILL.md +23 -0
  105. package/skills/express-middleware/SKILL.md +23 -0
  106. package/skills/extract-function/SKILL.md +23 -0
  107. package/skills/faq/SKILL.md +24 -0
  108. package/skills/fastapi-endpoint/SKILL.md +23 -0
  109. package/skills/favicon/SKILL.md +23 -0
  110. package/skills/feature-engineering/SKILL.md +23 -0
  111. package/skills/few-shot/SKILL.md +23 -0
  112. package/skills/find-owner/SKILL.md +23 -0
  113. package/skills/firmware-driver/SKILL.md +23 -0
  114. package/skills/fix-flaky-tests/SKILL.md +23 -0
  115. package/skills/flutter-widget/SKILL.md +23 -0
  116. package/skills/font-rendering/SKILL.md +23 -0
  117. package/skills/form-validation/SKILL.md +23 -0
  118. package/skills/format/SKILL.md +23 -0
  119. package/skills/game-loop/SKILL.md +23 -0
  120. package/skills/gas-optimize/SKILL.md +25 -0
  121. package/skills/gdpr-review/SKILL.md +24 -0
  122. package/skills/github-actions/SKILL.md +23 -0
  123. package/skills/glossary/SKILL.md +24 -0
  124. package/skills/go-idioms/SKILL.md +23 -0
  125. package/skills/gpu-profile/SKILL.md +23 -0
  126. package/skills/graphify/SKILL.md +21 -0
  127. package/skills/graphql-resolver/SKILL.md +23 -0
  128. package/skills/grpc-service/SKILL.md +23 -0
  129. package/skills/guardrails/SKILL.md +23 -0
  130. package/skills/healthcheck/SKILL.md +23 -0
  131. package/skills/heisenbug/SKILL.md +23 -0
  132. package/skills/helm-chart/SKILL.md +24 -0
  133. package/skills/hero-section/SKILL.md +23 -0
  134. package/skills/html-email/SKILL.md +24 -0
  135. package/skills/html-form/SKILL.md +23 -0
  136. package/skills/html-sanitize/SKILL.md +23 -0
  137. package/skills/html-table/SKILL.md +23 -0
  138. package/skills/html-to-pdf/SKILL.md +23 -0
  139. package/skills/http-client/SKILL.md +23 -0
  140. package/skills/i18n/SKILL.md +23 -0
  141. package/skills/i2c-spi/SKILL.md +23 -0
  142. package/skills/image-decode/SKILL.md +24 -0
  143. package/skills/image-memory/SKILL.md +24 -0
  144. package/skills/image-perf/SKILL.md +24 -0
  145. package/skills/image-pipeline/SKILL.md +24 -0
  146. package/skills/image-upload/SKILL.md +24 -0
  147. package/skills/infra-cost/SKILL.md +24 -0
  148. package/skills/input-validation/SKILL.md +23 -0
  149. package/skills/issue-template/SKILL.md +23 -0
  150. package/skills/java-streams/SKILL.md +23 -0
  151. package/skills/k8s-manifest/SKILL.md +23 -0
  152. package/skills/kotlin-coroutines/SKILL.md +23 -0
  153. package/skills/landing-page/SKILL.md +24 -0
  154. package/skills/laravel-controller/SKILL.md +23 -0
  155. package/skills/lazy-load/SKILL.md +23 -0
  156. package/skills/license-check/SKILL.md +23 -0
  157. package/skills/license-header/SKILL.md +23 -0
  158. package/skills/lint-fix/SKILL.md +23 -0
  159. package/skills/llm-cost/SKILL.md +23 -0
  160. package/skills/lockfile-fix/SKILL.md +23 -0
  161. package/skills/low-power/SKILL.md +23 -0
  162. package/skills/makefile/SKILL.md +23 -0
  163. package/skills/man-page/SKILL.md +24 -0
  164. package/skills/mcp-server/SKILL.md +23 -0
  165. package/skills/memory-leak/SKILL.md +23 -0
  166. package/skills/mermaid-diagram/SKILL.md +23 -0
  167. package/skills/meta-tags/SKILL.md +23 -0
  168. package/skills/micro-interactions/SKILL.md +23 -0
  169. package/skills/migration/SKILL.md +23 -0
  170. package/skills/migration-guide/SKILL.md +24 -0
  171. package/skills/mkdocs-setup/SKILL.md +24 -0
  172. package/skills/mobile-permissions/SKILL.md +23 -0
  173. package/skills/mock-api/SKILL.md +23 -0
  174. package/skills/modernize/SKILL.md +23 -0
  175. package/skills/monorepo-setup/SKILL.md +23 -0
  176. package/skills/motion-design/SKILL.md +23 -0
  177. package/skills/n-plus-one/SKILL.md +23 -0
  178. package/skills/naming-review/SKILL.md +23 -0
  179. package/skills/nextjs-route/SKILL.md +23 -0
  180. package/skills/nginx-config/SKILL.md +23 -0
  181. package/skills/ocr-debug/SKILL.md +24 -0
  182. package/skills/onboard/SKILL.md +23 -0
  183. package/skills/onboarding-map/SKILL.md +23 -0
  184. package/skills/open-pr/SKILL.md +24 -0
  185. package/skills/openapi/SKILL.md +23 -0
  186. package/skills/opencv-debug/SKILL.md +24 -0
  187. package/skills/orm-model/SKILL.md +23 -0
  188. package/skills/owasp-check/SKILL.md +24 -0
  189. package/skills/page-transitions/SKILL.md +23 -0
  190. package/skills/pagination/SKILL.md +23 -0
  191. package/skills/perf-optimize/SKILL.md +23 -0
  192. package/skills/perf-profile/SKILL.md +23 -0
  193. package/skills/physics/SKILL.md +23 -0
  194. package/skills/pitch-deck/SKILL.md +24 -0
  195. package/skills/pixel-diff/SKILL.md +23 -0
  196. package/skills/ponytail/SKILL.md +46 -0
  197. package/skills/postmortem/SKILL.md +24 -0
  198. package/skills/pptx-deck/SKILL.md +23 -0
  199. package/skills/pptx-export/SKILL.md +23 -0
  200. package/skills/pptx-from-markdown/SKILL.md +23 -0
  201. package/skills/pptx-template/SKILL.md +24 -0
  202. package/skills/pr-review/SKILL.md +24 -0
  203. package/skills/precommit/SKILL.md +23 -0
  204. package/skills/pricing-page/SKILL.md +23 -0
  205. package/skills/project-overview/SKILL.md +22 -0
  206. package/skills/prompt-template/SKILL.md +23 -0
  207. package/skills/property-test/SKILL.md +23 -0
  208. package/skills/protobuf/SKILL.md +23 -0
  209. package/skills/py-async/SKILL.md +23 -0
  210. package/skills/py-typing/SKILL.md +23 -0
  211. package/skills/query-optimize/SKILL.md +23 -0
  212. package/skills/rag-pipeline/SKILL.md +23 -0
  213. package/skills/rails-resource/SKILL.md +23 -0
  214. package/skills/rate-limit/SKILL.md +23 -0
  215. package/skills/react-hooks/SKILL.md +23 -0
  216. package/skills/react-native-screen/SKILL.md +23 -0
  217. package/skills/react-perf/SKILL.md +23 -0
  218. package/skills/readme/SKILL.md +24 -0
  219. package/skills/rebase/SKILL.md +24 -0
  220. package/skills/refactor/SKILL.md +23 -0
  221. package/skills/regression-test/SKILL.md +23 -0
  222. package/skills/release-notes/SKILL.md +24 -0
  223. package/skills/rename-symbol/SKILL.md +23 -0
  224. package/skills/repro/SKILL.md +23 -0
  225. package/skills/resolve-conflicts/SKILL.md +23 -0
  226. package/skills/responsive/SKILL.md +23 -0
  227. package/skills/rest-endpoint/SKILL.md +23 -0
  228. package/skills/retro/SKILL.md +23 -0
  229. package/skills/rtos-task/SKILL.md +23 -0
  230. package/skills/runbook/SKILL.md +25 -0
  231. package/skills/rust-borrow/SKILL.md +23 -0
  232. package/skills/rust-unsafe-audit/SKILL.md +23 -0
  233. package/skills/sanitize/SKILL.md +23 -0
  234. package/skills/schema-design/SKILL.md +23 -0
  235. package/skills/screenshot-debug/SKILL.md +22 -0
  236. package/skills/scroll-animation/SKILL.md +23 -0
  237. package/skills/secret-scan/SKILL.md +23 -0
  238. package/skills/security-audit/SKILL.md +23 -0
  239. package/skills/security-review/SKILL.md +23 -0
  240. package/skills/seed-data/SKILL.md +23 -0
  241. package/skills/self-review/SKILL.md +23 -0
  242. package/skills/semantic-html/SKILL.md +23 -0
  243. package/skills/semver-bump/SKILL.md +24 -0
  244. package/skills/shader/SKILL.md +23 -0
  245. package/skills/shader-debug/SKILL.md +23 -0
  246. package/skills/simplify-conditionals/SKILL.md +23 -0
  247. package/skills/sitemap/SKILL.md +23 -0
  248. package/skills/skeleton-loader/SKILL.md +23 -0
  249. package/skills/slide-charts/SKILL.md +24 -0
  250. package/skills/slide-outline/SKILL.md +23 -0
  251. package/skills/snapshot-update/SKILL.md +23 -0
  252. package/skills/solidity-contract/SKILL.md +25 -0
  253. package/skills/speaker-notes/SKILL.md +23 -0
  254. package/skills/spike/SKILL.md +23 -0
  255. package/skills/split-file/SKILL.md +23 -0
  256. package/skills/spring-controller/SKILL.md +23 -0
  257. package/skills/sprite-anim/SKILL.md +23 -0
  258. package/skills/sql-report/SKILL.md +23 -0
  259. package/skills/squash/SKILL.md +24 -0
  260. package/skills/ssl-setup/SKILL.md +23 -0
  261. package/skills/stacktrace/SKILL.md +23 -0
  262. package/skills/static-site/SKILL.md +24 -0
  263. package/skills/structured-logging/SKILL.md +23 -0
  264. package/skills/svelte-store/SKILL.md +23 -0
  265. package/skills/swiftui-view/SKILL.md +23 -0
  266. package/skills/tailwind-theme/SKILL.md +24 -0
  267. package/skills/tcp-server/SKILL.md +23 -0
  268. package/skills/tdd/SKILL.md +23 -0
  269. package/skills/terraform-module/SKILL.md +24 -0
  270. package/skills/test-coverage/SKILL.md +23 -0
  271. package/skills/texture-debug/SKILL.md +23 -0
  272. package/skills/threat-model/SKILL.md +23 -0
  273. package/skills/thumbnail/SKILL.md +24 -0
  274. package/skills/todo-scan/SKILL.md +23 -0
  275. package/skills/tool-definition/SKILL.md +23 -0
  276. package/skills/trace-flow/SKILL.md +23 -0
  277. package/skills/tracing/SKILL.md +23 -0
  278. package/skills/train-model/SKILL.md +24 -0
  279. package/skills/tree-shake/SKILL.md +23 -0
  280. package/skills/ts-generics/SKILL.md +23 -0
  281. package/skills/ts-strict/SKILL.md +23 -0
  282. package/skills/tui-app/SKILL.md +23 -0
  283. package/skills/tutorial/SKILL.md +24 -0
  284. package/skills/type-tighten/SKILL.md +23 -0
  285. package/skills/typography/SKILL.md +24 -0
  286. package/skills/ui-bug-repro/SKILL.md +23 -0
  287. package/skills/ui-polish/SKILL.md +24 -0
  288. package/skills/ui-review/SKILL.md +24 -0
  289. package/skills/vendor/SKILL.md +23 -0
  290. package/skills/visual-diff-ci/SKILL.md +24 -0
  291. package/skills/visual-regression/SKILL.md +23 -0
  292. package/skills/vue-composition/SKILL.md +23 -0
  293. package/skills/web-component/SKILL.md +23 -0
  294. package/skills/web-fonts/SKILL.md +24 -0
  295. package/skills/web3-frontend/SKILL.md +25 -0
  296. package/skills/webgl-debug/SKILL.md +23 -0
  297. package/skills/webhook/SKILL.md +23 -0
  298. package/skills/websocket/SKILL.md +23 -0
  299. package/skills/write-tests/SKILL.md +19 -0
  300. package/src/client/agent.ts +803 -0
  301. package/src/client/background.ts +39 -0
  302. package/src/client/checkpoint.ts +48 -0
  303. package/src/client/cli.ts +1253 -0
  304. package/src/client/compaction.ts +86 -0
  305. package/src/client/extensions.ts +83 -0
  306. package/src/client/hooks.ts +40 -0
  307. package/src/client/image.ts +26 -0
  308. package/src/client/lsp.ts +0 -0
  309. package/src/client/mcp.ts +276 -0
  310. package/src/client/models-dev.ts +52 -0
  311. package/src/client/pkg.ts +41 -0
  312. package/src/client/platform.ts +94 -0
  313. package/src/client/prompts.ts +47 -0
  314. package/src/client/render.ts +138 -0
  315. package/src/client/session.ts +107 -0
  316. package/src/client/settings.ts +86 -0
  317. package/src/client/skill-router.ts +79 -0
  318. package/src/client/skills.ts +199 -0
  319. package/src/client/snapshot.ts +56 -0
  320. package/src/client/telemetry.ts +24 -0
  321. package/src/client/todos.ts +23 -0
  322. package/src/client/tools.ts +756 -0
  323. package/src/client/tui-mode.ts +41 -0
  324. package/src/client/tui.ts +224 -0
  325. package/src/sdk/index.ts +36 -0
  326. package/src/selfcheck.ts +364 -0
  327. package/src/server/config.ts +58 -0
  328. package/src/server/credentials.ts +89 -0
  329. package/src/server/identity.ts +58 -0
  330. package/src/server/index.ts +113 -0
  331. package/src/server/oauth.ts +93 -0
  332. package/src/server/providers/adapter.ts +25 -0
  333. package/src/server/providers/anthropic.ts +189 -0
  334. package/src/server/providers/openai-compat.ts +76 -0
  335. package/src/server/providers/registry.ts +31 -0
  336. package/src/server/router.ts +29 -0
  337. package/src/server/sse.ts +20 -0
  338. package/src/shared/types.ts +20 -0
  339. package/tsconfig.json +15 -0
@@ -0,0 +1,94 @@
1
+ // Cross-platform niceties: detect WSL / tmux / Termux and read the OS clipboard accordingly.
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ export interface PlatformInfo {
9
+ os: NodeJS.Platform;
10
+ wsl: boolean;
11
+ tmux: boolean;
12
+ termux: boolean;
13
+ }
14
+
15
+ export function platformInfo(): PlatformInfo {
16
+ let wsl = false;
17
+ try {
18
+ wsl = process.platform === "linux" && existsSync("/proc/version") && /microsoft/i.test(readFileSync("/proc/version", "utf8"));
19
+ } catch {
20
+ /* ignore */
21
+ }
22
+ return {
23
+ os: process.platform,
24
+ wsl,
25
+ tmux: !!process.env.TMUX,
26
+ termux: !!process.env.TERMUX_VERSION || (process.env.PREFIX ?? "").includes("com.termux"),
27
+ };
28
+ }
29
+
30
+ /** Read text from the OS clipboard (best-effort; tries the right tool for the platform). */
31
+ export function readClipboard(): string {
32
+ const p = platformInfo();
33
+ const tries: Array<[string, string[]]> =
34
+ p.os === "win32" || p.wsl
35
+ ? [["powershell.exe", ["-NoProfile", "-Command", "Get-Clipboard"]]]
36
+ : p.os === "darwin"
37
+ ? [["pbpaste", []]]
38
+ : p.termux
39
+ ? [["termux-clipboard-get", []]]
40
+ : [
41
+ ["wl-paste", []],
42
+ ["xclip", ["-selection", "clipboard", "-o"]],
43
+ ["xsel", ["-b"]],
44
+ ];
45
+ for (const [cmd, args] of tries) {
46
+ try {
47
+ const r = spawnSync(cmd, args, { encoding: "utf8" });
48
+ if (r.status === 0 && r.stdout) return r.stdout.replace(/\r\n/g, "\n").replace(/\n$/, "");
49
+ } catch {
50
+ /* try the next tool */
51
+ }
52
+ }
53
+ return "";
54
+ }
55
+
56
+ /** Best-effort desktop notification (+ terminal bell); ignores failures. */
57
+ export function notify(title: string, body: string): void {
58
+ process.stdout.write("\x07"); // bell
59
+ const p = platformInfo();
60
+ try {
61
+ if (p.os === "darwin") spawnSync("osascript", ["-e", `display notification ${JSON.stringify(body)} with title ${JSON.stringify(title)}`]);
62
+ else if (p.termux) spawnSync("termux-notification", ["-t", title, "-c", body]);
63
+ else if (p.os === "linux") spawnSync("notify-send", [title, body]);
64
+ } catch {
65
+ /* ignore — the bell already fired */
66
+ }
67
+ }
68
+
69
+ /** Read an image from the OS clipboard as a PNG data URL (best-effort), or null. */
70
+ export function readClipboardImage(): string | null {
71
+ const p = platformInfo();
72
+ const tmp = join(tmpdir(), `ada-clip-${Date.now()}-${Math.floor(Math.random() * 1e6)}.png`);
73
+ try {
74
+ if (p.os === "win32" || p.wsl) {
75
+ const ps = `Add-Type -AssemblyName System.Windows.Forms,System.Drawing; $i=[System.Windows.Forms.Clipboard]::GetImage(); if($i){ $i.Save('${tmp}'); 'OK' } else { 'NONE' }`;
76
+ const r = spawnSync("powershell.exe", ["-NoProfile", "-Command", ps], { encoding: "utf8" });
77
+ if (!r.stdout || !r.stdout.includes("OK")) return null;
78
+ } else if (p.os === "darwin") {
79
+ if (spawnSync("pngpaste", [tmp]).status !== 0) return null;
80
+ } else {
81
+ spawnSync("sh", ["-c", `wl-paste --type image/png > '${tmp}' 2>/dev/null || xclip -selection clipboard -t image/png -o > '${tmp}' 2>/dev/null`]);
82
+ }
83
+ if (!existsSync(tmp) || statSync(tmp).size === 0) return null;
84
+ return `data:image/png;base64,${readFileSync(tmp).toString("base64")}`;
85
+ } catch {
86
+ return null;
87
+ } finally {
88
+ try {
89
+ rmSync(tmp, { force: true });
90
+ } catch {
91
+ /* ignore */
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,47 @@
1
+ // User prompt templates ("/commands"). Markdown files in .ada/prompts/ (project) or
2
+ // ~/.ada/prompts/ (global) become slash commands: a file `review.md` → `/review <args>`.
3
+ // Substitutions: $ARGUMENTS (everything after the command), $1, $2, … (whitespace-split args).
4
+
5
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { basename, join, resolve } from "node:path";
8
+
9
+ function dirs(includeProject: boolean): string[] {
10
+ const global = resolve(homedir(), ".ada", "prompts");
11
+ return includeProject ? [resolve(process.cwd(), ".ada", "prompts"), global] : [global];
12
+ }
13
+
14
+ export function loadPrompts(includeProject = true): Map<string, string> {
15
+ const out = new Map<string, string>();
16
+ for (const dir of dirs(includeProject)) {
17
+ if (!existsSync(dir)) continue;
18
+ let names: string[];
19
+ try {
20
+ names = readdirSync(dir);
21
+ } catch {
22
+ continue;
23
+ }
24
+ for (const f of names) {
25
+ if (!f.endsWith(".md")) continue;
26
+ const name = basename(f, ".md");
27
+ if (out.has(name)) continue; // project dir wins over global
28
+ try {
29
+ out.set(name, readFileSync(join(dir, f), "utf8"));
30
+ } catch {
31
+ /* ignore */
32
+ }
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /** If `input` is `/<name> args` and a template exists, return the expanded text; else null. */
39
+ export function expandPrompt(prompts: Map<string, string>, input: string): string | null {
40
+ if (!input.startsWith("/")) return null;
41
+ const name = input.slice(1).split(/\s+/)[0] ?? "";
42
+ const tmpl = prompts.get(name);
43
+ if (!tmpl) return null;
44
+ const argline = input.slice(1 + name.length).trim();
45
+ const parts = argline ? argline.split(/\s+/) : [];
46
+ return tmpl.replace(/\$ARGUMENTS/g, argline).replace(/\$(\d+)/g, (_, n) => parts[Number(n) - 1] ?? "");
47
+ }
@@ -0,0 +1,138 @@
1
+ // Terminal rendering: colored edit diffs + a streaming markdown styler with light,
2
+ // dependency-free syntax highlighting. Colors come from a theme (ADA_THEME=dark|light).
3
+
4
+ const ESC = "\x1b[";
5
+ const wrap = (code: string) => (s: string) => `${ESC}${code}m${s}${ESC}0m`;
6
+ export const red = wrap("31");
7
+ export const green = wrap("32");
8
+ export const yellow = wrap("33");
9
+ export const cyan = wrap("36");
10
+ export const dim = wrap("2");
11
+ export const bold = wrap("1");
12
+ const magenta = wrap("35");
13
+ const blue = wrap("34");
14
+
15
+ interface Theme {
16
+ heading: (s: string) => string;
17
+ bullet: (s: string) => string;
18
+ inlineCode: (s: string) => string;
19
+ keyword: (s: string) => string;
20
+ str: (s: string) => string;
21
+ comment: (s: string) => string;
22
+ num: (s: string) => string;
23
+ meta: (s: string) => string;
24
+ add: (s: string) => string;
25
+ del: (s: string) => string;
26
+ }
27
+
28
+ const THEMES: Record<string, Theme> = {
29
+ dark: { heading: bold, bullet: yellow, inlineCode: cyan, keyword: magenta, str: green, comment: dim, num: yellow, meta: dim, add: green, del: red },
30
+ light: { heading: bold, bullet: blue, inlineCode: blue, keyword: magenta, str: green, comment: dim, num: yellow, meta: dim, add: green, del: red },
31
+ };
32
+
33
+ export const theme: Theme = THEMES[process.env.ADA_THEME ?? "dark"] ?? THEMES.dark!;
34
+
35
+ // A small, language-agnostic keyword set — enough to make most code blocks readable.
36
+ const KEYWORDS = new Set(
37
+ ("const let var function return if else for while class new import export from async await try catch finally throw " +
38
+ "typeof instanceof of in do switch case break continue default extends implements interface type enum namespace " +
39
+ "public private protected static readonly void null undefined true false this super yield " +
40
+ "def lambda elif fn pub use struct impl match mut self None True False and or not is with as pass raise " +
41
+ "package func go defer chan map range select")
42
+ .split(" "),
43
+ );
44
+
45
+ /** Index of a line comment (`//` or `#`) not inside a string, or -1. */
46
+ function commentStart(line: string): number {
47
+ let q = "";
48
+ for (let i = 0; i < line.length; i++) {
49
+ const c = line[i]!;
50
+ if (q) {
51
+ if (c === q && line[i - 1] !== "\\") q = "";
52
+ continue;
53
+ }
54
+ if (c === '"' || c === "'" || c === "`") {
55
+ q = c;
56
+ continue;
57
+ }
58
+ if (c === "/" && line[i + 1] === "/") return i;
59
+ if (c === "#") return i;
60
+ }
61
+ return -1;
62
+ }
63
+
64
+ /** Heuristic syntax highlight for a single code line: strings, numbers, keywords, comments. */
65
+ export function highlight(line: string): string {
66
+ const ci = commentStart(line);
67
+ const code = ci >= 0 ? line.slice(0, ci) : line;
68
+ const comment = ci >= 0 ? line.slice(ci) : "";
69
+ const colored = code.replace(
70
+ /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)/g,
71
+ (m, str, num, word) => {
72
+ if (str) return theme.str(str);
73
+ if (num) return theme.num(num);
74
+ if (word && KEYWORDS.has(word)) return theme.keyword(word);
75
+ return m;
76
+ },
77
+ );
78
+ return colored + (comment ? theme.comment(comment) : "");
79
+ }
80
+
81
+ /** A colored diff for an exact edit (old_text → new_text), trimming shared head/tail lines
82
+ * so only the changed region is shown. */
83
+ export function renderEditDiff(path: string, oldText: string, newText: string): string {
84
+ const a = oldText.split("\n");
85
+ const b = newText.split("\n");
86
+ let pre = 0;
87
+ while (pre < a.length && pre < b.length && a[pre] === b[pre]) pre++;
88
+ let suf = 0;
89
+ while (suf < a.length - pre && suf < b.length - pre && a[a.length - 1 - suf] === b[b.length - 1 - suf]) suf++;
90
+
91
+ const out: string[] = [theme.meta(path)];
92
+ for (const l of a.slice(pre, a.length - suf)) out.push(theme.del(`- ${l}`));
93
+ for (const l of b.slice(pre, b.length - suf)) out.push(theme.add(`+ ${l}`));
94
+ return out.map((l) => ` ${l}`).join("\n");
95
+ }
96
+
97
+ /** Styles streamed assistant text line-by-line (markdown). Feed deltas to push(); it returns
98
+ * the styled, already-completed lines to print and buffers the in-progress line until its
99
+ * newline arrives. Call end() once the stream finishes to flush the last partial line. */
100
+ export class MarkdownStreamer {
101
+ private buf = "";
102
+ private inCode = false;
103
+
104
+ push(text: string): string {
105
+ this.buf += text;
106
+ let out = "";
107
+ let nl: number;
108
+ while ((nl = this.buf.indexOf("\n")) >= 0) {
109
+ out += `${this.styleLine(this.buf.slice(0, nl))}\n`;
110
+ this.buf = this.buf.slice(nl + 1);
111
+ }
112
+ return out;
113
+ }
114
+
115
+ end(): string {
116
+ if (!this.buf) return "";
117
+ const out = this.styleLine(this.buf);
118
+ this.buf = "";
119
+ return out;
120
+ }
121
+
122
+ private styleLine(line: string): string {
123
+ if (/^\s*```/.test(line)) {
124
+ this.inCode = !this.inCode;
125
+ return theme.meta(line);
126
+ }
127
+ if (this.inCode) return highlight(line);
128
+ const heading = line.match(/^#{1,6}\s+(.*)$/);
129
+ if (heading) return theme.heading(heading[1]!);
130
+ const bullet = line.match(/^(\s*)[-*]\s+(.*)$/);
131
+ if (bullet) return `${bullet[1]}${theme.bullet("•")} ${this.inline(bullet[2]!)}`;
132
+ return this.inline(line);
133
+ }
134
+
135
+ private inline(s: string): string {
136
+ return s.replace(/\*\*([^*]+)\*\*/g, (_, m) => bold(m)).replace(/`([^`]+)`/g, (_, m) => theme.inlineCode(m));
137
+ }
138
+ }
@@ -0,0 +1,107 @@
1
+ // Append-only JSONL session store under .ada/sessions/. One OpenAI message per line.
2
+
3
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+
6
+ const DIR = resolve(process.cwd(), ".ada", "sessions");
7
+
8
+ export type StoredMessage = Record<string, unknown>;
9
+
10
+ function ensureDir(): void {
11
+ mkdirSync(DIR, { recursive: true });
12
+ }
13
+
14
+ function newId(): string {
15
+ return `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(16).slice(2, 6)}`;
16
+ }
17
+
18
+ export interface SessionMeta {
19
+ file: string;
20
+ mtime: number;
21
+ title: string;
22
+ parent?: string; // file this branch was forked from
23
+ }
24
+
25
+ export class Session {
26
+ readonly file: string;
27
+
28
+ private constructor(file: string) {
29
+ this.file = file;
30
+ }
31
+
32
+ static create(): Session {
33
+ ensureDir();
34
+ return new Session(join(DIR, `${newId()}.jsonl`));
35
+ }
36
+
37
+ static open(file: string): Session {
38
+ return new Session(file);
39
+ }
40
+
41
+ static latest(): Session | null {
42
+ const metas = list();
43
+ return metas[0] ? Session.open(metas[0].file) : null;
44
+ }
45
+
46
+ /** Branch: a new session seeded with `messages`, recording its parent for /tree. */
47
+ static fork(parentFile: string, messages: unknown[]): Session {
48
+ ensureDir();
49
+ const s = new Session(join(DIR, `${newId()}.jsonl`));
50
+ s.append({ __meta: { parent: parentFile, branchedAt: messages.length } });
51
+ for (const m of messages) s.append(m);
52
+ return s;
53
+ }
54
+
55
+ append(msg: unknown): void {
56
+ try {
57
+ ensureDir();
58
+ appendFileSync(this.file, `${JSON.stringify(msg)}\n`, "utf8");
59
+ } catch {
60
+ /* persistence is best-effort; never crash the agent over it */
61
+ }
62
+ }
63
+
64
+ load(): StoredMessage[] {
65
+ if (!existsSync(this.file)) return [];
66
+ return readFileSync(this.file, "utf8")
67
+ .split("\n")
68
+ .filter((l) => l.trim())
69
+ .map((l) => {
70
+ try {
71
+ return JSON.parse(l) as StoredMessage;
72
+ } catch {
73
+ return null;
74
+ }
75
+ })
76
+ .filter((m): m is StoredMessage => m !== null && !("__meta" in m));
77
+ }
78
+ }
79
+
80
+ export function list(): SessionMeta[] {
81
+ ensureDir();
82
+ const out: SessionMeta[] = [];
83
+ for (const f of readdirSync(DIR)) {
84
+ if (!f.endsWith(".jsonl")) continue;
85
+ const file = join(DIR, f);
86
+ let title = "(empty)";
87
+ let parent: string | undefined;
88
+ try {
89
+ for (const l of readFileSync(file, "utf8").split("\n")) {
90
+ if (!l.trim()) continue;
91
+ const m = JSON.parse(l) as StoredMessage;
92
+ if (m.__meta) {
93
+ parent = (m.__meta as { parent?: string }).parent;
94
+ continue;
95
+ }
96
+ if (m.role === "user" && typeof m.content === "string") {
97
+ title = m.content.slice(0, 60);
98
+ break;
99
+ }
100
+ }
101
+ } catch {
102
+ /* ignore unreadable session */
103
+ }
104
+ out.push({ file, mtime: statSync(file).mtimeMs, title, parent });
105
+ }
106
+ return out.sort((a, b) => b.mtime - a.mtime);
107
+ }
@@ -0,0 +1,86 @@
1
+ // Layered settings: global (~/.ada/settings.json) merged with project (.ada/settings.json),
2
+ // project winning. Also the project-trust list — project files are only loaded for trusted dirs.
3
+
4
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join, resolve } from "node:path";
7
+
8
+ export type PermAction = "allow" | "ask" | "deny";
9
+ export interface PermRule {
10
+ tool?: string; // glob over the tool name (e.g. "bash", "web_*"); omit = any tool
11
+ pattern?: string; // glob/substring over the call summary (args); omit = any
12
+ action: PermAction;
13
+ }
14
+
15
+ export interface Settings {
16
+ model?: string;
17
+ reasoning?: "low" | "medium" | "high";
18
+ autoApprove?: boolean;
19
+ compactAt?: number;
20
+ trustedDirs?: string[];
21
+ keybindings?: { interrupt?: string };
22
+ protectedPaths?: string[];
23
+ confirmDestructive?: boolean;
24
+ permissions?: PermRule[]; // per-tool allow/ask/deny rules; last match wins
25
+ agents?: Record<string, { description?: string; prompt?: string; permissions?: PermRule[] }>; // named agent profiles
26
+ }
27
+
28
+ const GLOBAL = join(homedir(), ".ada", "settings.json");
29
+ const PROJECT = resolve(process.cwd(), ".ada", "settings.json");
30
+
31
+ function readJson(p: string): Settings {
32
+ try {
33
+ return JSON.parse(readFileSync(p, "utf8")) as Settings;
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+
39
+ function writeGlobal(s: Settings): void {
40
+ try {
41
+ mkdirSync(dirname(GLOBAL), { recursive: true });
42
+ writeFileSync(GLOBAL, JSON.stringify(s, null, 2), "utf8");
43
+ } catch {
44
+ /* best-effort */
45
+ }
46
+ }
47
+
48
+ /** Global settings, with project settings merged in (project overrides) when trusted. */
49
+ export function loadSettings(includeProject: boolean): Settings {
50
+ const g = readJson(GLOBAL);
51
+ return includeProject ? { ...g, ...readJson(PROJECT) } : g;
52
+ }
53
+
54
+ export function isTrusted(dir: string): boolean {
55
+ return (readJson(GLOBAL).trustedDirs ?? []).includes(dir);
56
+ }
57
+
58
+ function globMatch(pat: string, s: string): boolean {
59
+ const re = new RegExp(`^${pat.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`, "i");
60
+ return re.test(s);
61
+ }
62
+
63
+ // A named agent's permission rules override the configured ones while it's active.
64
+ let activeAgentPerms: PermRule[] | null = null;
65
+ export function setActiveAgentPermissions(rules: PermRule[] | null): void {
66
+ activeAgentPerms = rules;
67
+ }
68
+
69
+ /** Evaluate the configured permission rules for a tool call. null = no matching rule (use defaults). */
70
+ export function permissionFor(toolName: string, summary: string): PermAction | null {
71
+ const rules = activeAgentPerms ?? loadSettings(isTrusted(process.cwd())).permissions ?? [];
72
+ let result: PermAction | null = null;
73
+ for (const r of rules) {
74
+ const toolOk = !r.tool || r.tool === toolName || globMatch(r.tool, toolName);
75
+ const patOk = !r.pattern || summary.toLowerCase().includes(r.pattern.toLowerCase()) || globMatch(r.pattern, summary);
76
+ if (toolOk && patOk) result = r.action; // last match wins
77
+ }
78
+ return result;
79
+ }
80
+
81
+ export function addTrust(dir: string): void {
82
+ const g = readJson(GLOBAL);
83
+ const dirs = new Set(g.trustedDirs ?? []);
84
+ dirs.add(dir);
85
+ writeGlobal({ ...g, trustedDirs: [...dirs] });
86
+ }
@@ -0,0 +1,79 @@
1
+ // Skill routing: rank skills by relevance to a free-text request, so ada can surface the right
2
+ // skill proactively (or via the find_skill tool) instead of the model browsing the whole catalog.
3
+ //
4
+ // ponytail: this is a lexical TF-IDF-ish ranker (idf-weighted token overlap, name-boosted, with a
5
+ // shared-prefix match so plurals/derivations line up — "docker" ↔ "dockerfile", "migrate" ↔
6
+ // "migration"). No embedding model or dependency. A real embedding scorer can replace `rankSkills`
7
+ // later if synonym matching (e.g. "containerize" → dockerize) becomes worth a model round-trip.
8
+
9
+ export interface RankItem {
10
+ name: string;
11
+ description: string;
12
+ category?: string;
13
+ }
14
+
15
+ const STOP = new Set(
16
+ "a an and are as at be build by can create do for from how in into is it make of on or set the this to up use using with without your you".split(" "),
17
+ );
18
+
19
+ export function tokenize(s: string): string[] {
20
+ return s
21
+ .toLowerCase()
22
+ .split(/[^a-z0-9]+/)
23
+ .filter((t) => t.length > 1 && !STOP.has(t));
24
+ }
25
+
26
+ /** Two tokens match if equal or they share a 4+ char prefix (a cheap stand-in for stemming). */
27
+ function matches(a: string, b: string): boolean {
28
+ if (a === b) return true;
29
+ const m = Math.min(a.length, b.length);
30
+ let i = 0;
31
+ while (i < m && a[i] === b[i]) i++;
32
+ return i >= 4;
33
+ }
34
+
35
+ /** Top-n skills most relevant to `query`, scored by idf-weighted token overlap (name-boosted). */
36
+ export function rankSkills(query: string, items: RankItem[], n = 5): { name: string; description: string; score: number }[] {
37
+ const q = [...new Set(tokenize(query))];
38
+ if (!q.length || !items.length) return [];
39
+ const docs = items.map((it) => ({
40
+ name: tokenize(it.name),
41
+ all: tokenize(`${it.name} ${it.description} ${it.category ?? ""}`),
42
+ }));
43
+ const N = items.length;
44
+ const idf = (qt: string): number => {
45
+ let df = 0;
46
+ for (const d of docs) if (d.all.some((dt) => matches(qt, dt))) df++;
47
+ return Math.log(1 + N / (1 + df));
48
+ };
49
+ const weight = new Map(q.map((qt) => [qt, idf(qt)]));
50
+ const scored = items.map((it, i) => {
51
+ let s = 0;
52
+ for (const qt of q) {
53
+ if (docs[i].all.some((dt) => matches(qt, dt))) {
54
+ s += weight.get(qt)! * (docs[i].name.some((dt) => matches(qt, dt)) ? 2.5 : 1);
55
+ }
56
+ }
57
+ return { name: it.name, description: it.description, score: s };
58
+ });
59
+ return scored
60
+ .filter((x) => x.score > 0)
61
+ .sort((a, b) => b.score - a.score)
62
+ .slice(0, n);
63
+ }
64
+
65
+ /**
66
+ * The single clearly-dominant skill for a query, or null when the match is weak/ambiguous.
67
+ * Three gates, all required: a score floor, dominance over the runner-up, and — crucially — an
68
+ * EXACT whole-token overlap with the skill NAME. That last gate is the precision guard against
69
+ * lexical false positives: "make a powerpoint" prefix-matches "low-power" and even dominates, but
70
+ * "powerpoint" never equals the name token "power", so it's correctly rejected.
71
+ */
72
+ export function confidentSkill(query: string, items: RankItem[]): string | null {
73
+ const ranked = rankSkills(query, items, 2);
74
+ const top = ranked[0];
75
+ if (!top || top.score < 4) return null;
76
+ if (ranked[1] && top.score < ranked[1].score * 1.3) return null; // reject ties/near-ties; the name-exact gate below is the real precision guard
77
+ const q = new Set(tokenize(query));
78
+ return tokenize(top.name).some((t) => q.has(t)) ? top.name : null;
79
+ }