zoe-agent 0.3.1

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 (267) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/LICENSE +96 -0
  3. package/README.md +568 -0
  4. package/dist/adapters/cli/agent.d.ts +59 -0
  5. package/dist/adapters/cli/agent.js +232 -0
  6. package/dist/adapters/cli/bootstrap.d.ts +25 -0
  7. package/dist/adapters/cli/bootstrap.js +204 -0
  8. package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
  9. package/dist/adapters/cli/commands/build-registry.js +88 -0
  10. package/dist/adapters/cli/commands/clear.d.ts +7 -0
  11. package/dist/adapters/cli/commands/clear.js +10 -0
  12. package/dist/adapters/cli/commands/compact.d.ts +13 -0
  13. package/dist/adapters/cli/commands/compact.js +96 -0
  14. package/dist/adapters/cli/commands/exit.d.ts +7 -0
  15. package/dist/adapters/cli/commands/exit.js +9 -0
  16. package/dist/adapters/cli/commands/gateway.d.ts +7 -0
  17. package/dist/adapters/cli/commands/gateway.js +152 -0
  18. package/dist/adapters/cli/commands/help.d.ts +9 -0
  19. package/dist/adapters/cli/commands/help.js +12 -0
  20. package/dist/adapters/cli/commands/models.d.ts +10 -0
  21. package/dist/adapters/cli/commands/models.js +32 -0
  22. package/dist/adapters/cli/commands/registry.d.ts +70 -0
  23. package/dist/adapters/cli/commands/registry.js +111 -0
  24. package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
  25. package/dist/adapters/cli/commands/settings-utils.js +182 -0
  26. package/dist/adapters/cli/commands/settings.d.ts +9 -0
  27. package/dist/adapters/cli/commands/settings.js +395 -0
  28. package/dist/adapters/cli/commands/skills.d.ts +7 -0
  29. package/dist/adapters/cli/commands/skills.js +21 -0
  30. package/dist/adapters/cli/config-loader.d.ts +27 -0
  31. package/dist/adapters/cli/config-loader.js +48 -0
  32. package/dist/adapters/cli/docker-utils.d.ts +37 -0
  33. package/dist/adapters/cli/docker-utils.js +90 -0
  34. package/dist/adapters/cli/index.d.ts +2 -0
  35. package/dist/adapters/cli/index.js +88 -0
  36. package/dist/adapters/cli/repl.d.ts +22 -0
  37. package/dist/adapters/cli/repl.js +256 -0
  38. package/dist/adapters/cli/setup.d.ts +19 -0
  39. package/dist/adapters/cli/setup.js +613 -0
  40. package/dist/adapters/cli/system-prompts.d.ts +56 -0
  41. package/dist/adapters/cli/system-prompts.js +131 -0
  42. package/dist/adapters/cli/tui/app.d.ts +58 -0
  43. package/dist/adapters/cli/tui/app.js +314 -0
  44. package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
  45. package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
  46. package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
  47. package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
  48. package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
  49. package/dist/adapters/cli/tui/components/command-palette.js +50 -0
  50. package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
  51. package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
  52. package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
  53. package/dist/adapters/cli/tui/components/error-message.js +8 -0
  54. package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
  55. package/dist/adapters/cli/tui/components/footer.js +19 -0
  56. package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
  57. package/dist/adapters/cli/tui/components/goal-status.js +22 -0
  58. package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
  59. package/dist/adapters/cli/tui/components/info-message.js +8 -0
  60. package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
  61. package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
  62. package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
  63. package/dist/adapters/cli/tui/components/markdown.js +92 -0
  64. package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
  65. package/dist/adapters/cli/tui/components/message-area.js +55 -0
  66. package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
  67. package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
  68. package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
  69. package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
  70. package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
  71. package/dist/adapters/cli/tui/components/text-input.js +142 -0
  72. package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
  73. package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
  74. package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
  75. package/dist/adapters/cli/tui/components/user-message.js +8 -0
  76. package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
  77. package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
  78. package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
  79. package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
  80. package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
  81. package/dist/adapters/cli/tui/feed-serializer.js +70 -0
  82. package/dist/adapters/cli/tui/file-index.d.ts +8 -0
  83. package/dist/adapters/cli/tui/file-index.js +41 -0
  84. package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
  85. package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
  86. package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
  87. package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
  88. package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
  89. package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
  90. package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
  91. package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
  92. package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
  93. package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
  94. package/dist/adapters/cli/tui/index.d.ts +19 -0
  95. package/dist/adapters/cli/tui/index.js +206 -0
  96. package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
  97. package/dist/adapters/cli/tui/ink-reset.js +57 -0
  98. package/dist/adapters/cli/tui/layout.d.ts +15 -0
  99. package/dist/adapters/cli/tui/layout.js +15 -0
  100. package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
  101. package/dist/adapters/cli/tui/logo/gradient.js +31 -0
  102. package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
  103. package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
  104. package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
  105. package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
  106. package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
  107. package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
  108. package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
  109. package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
  110. package/dist/adapters/cli/tui/session-export.d.ts +21 -0
  111. package/dist/adapters/cli/tui/session-export.js +63 -0
  112. package/dist/adapters/cli/tui/theme.d.ts +23 -0
  113. package/dist/adapters/cli/tui/theme.js +22 -0
  114. package/dist/adapters/cli/tui/types.d.ts +52 -0
  115. package/dist/adapters/cli/tui/types.js +12 -0
  116. package/dist/adapters/sdk/agent.d.ts +20 -0
  117. package/dist/adapters/sdk/agent.js +356 -0
  118. package/dist/adapters/sdk/http.d.ts +43 -0
  119. package/dist/adapters/sdk/http.js +61 -0
  120. package/dist/adapters/sdk/index.d.ts +58 -0
  121. package/dist/adapters/sdk/index.js +209 -0
  122. package/dist/adapters/sdk/settings.d.ts +18 -0
  123. package/dist/adapters/sdk/settings.js +57 -0
  124. package/dist/adapters/sdk/tools.d.ts +7 -0
  125. package/dist/adapters/sdk/tools.js +13 -0
  126. package/dist/adapters/server/auth.d.ts +53 -0
  127. package/dist/adapters/server/auth.js +168 -0
  128. package/dist/adapters/server/index.d.ts +40 -0
  129. package/dist/adapters/server/index.js +255 -0
  130. package/dist/adapters/server/rest-gateway.d.ts +13 -0
  131. package/dist/adapters/server/rest-gateway.js +218 -0
  132. package/dist/adapters/server/rest.d.ts +37 -0
  133. package/dist/adapters/server/rest.js +341 -0
  134. package/dist/adapters/server/server-core.d.ts +55 -0
  135. package/dist/adapters/server/server-core.js +121 -0
  136. package/dist/adapters/server/session-store.d.ts +81 -0
  137. package/dist/adapters/server/session-store.js +272 -0
  138. package/dist/adapters/server/settings-handlers.d.ts +24 -0
  139. package/dist/adapters/server/settings-handlers.js +360 -0
  140. package/dist/adapters/server/standalone.d.ts +19 -0
  141. package/dist/adapters/server/standalone.js +113 -0
  142. package/dist/adapters/server/websocket.d.ts +26 -0
  143. package/dist/adapters/server/websocket.js +68 -0
  144. package/dist/adapters/server/ws-handlers.d.ts +32 -0
  145. package/dist/adapters/server/ws-handlers.js +523 -0
  146. package/dist/adapters/server/ws-types.d.ts +304 -0
  147. package/dist/adapters/server/ws-types.js +7 -0
  148. package/dist/core/agent-loop.d.ts +68 -0
  149. package/dist/core/agent-loop.js +423 -0
  150. package/dist/core/config.d.ts +115 -0
  151. package/dist/core/config.js +189 -0
  152. package/dist/core/errors.d.ts +58 -0
  153. package/dist/core/errors.js +88 -0
  154. package/dist/core/hooks.d.ts +35 -0
  155. package/dist/core/hooks.js +49 -0
  156. package/dist/core/index.d.ts +23 -0
  157. package/dist/core/index.js +29 -0
  158. package/dist/core/message-convert.d.ts +41 -0
  159. package/dist/core/message-convert.js +94 -0
  160. package/dist/core/middleware/auth.d.ts +24 -0
  161. package/dist/core/middleware/auth.js +28 -0
  162. package/dist/core/middleware/logging.d.ts +23 -0
  163. package/dist/core/middleware/logging.js +28 -0
  164. package/dist/core/middleware/rate-limit.d.ts +27 -0
  165. package/dist/core/middleware/rate-limit.js +38 -0
  166. package/dist/core/middleware/semantic-tools.d.ts +10 -0
  167. package/dist/core/middleware/semantic-tools.js +43 -0
  168. package/dist/core/middleware.d.ts +48 -0
  169. package/dist/core/middleware.js +38 -0
  170. package/dist/core/permission.d.ts +25 -0
  171. package/dist/core/permission.js +50 -0
  172. package/dist/core/provider-config.d.ts +129 -0
  173. package/dist/core/provider-config.js +273 -0
  174. package/dist/core/provider-env.d.ts +39 -0
  175. package/dist/core/provider-env.js +142 -0
  176. package/dist/core/provider-resolver.d.ts +12 -0
  177. package/dist/core/provider-resolver.js +12 -0
  178. package/dist/core/session-store.d.ts +75 -0
  179. package/dist/core/session-store.js +245 -0
  180. package/dist/core/settings-manager.d.ts +57 -0
  181. package/dist/core/settings-manager.js +359 -0
  182. package/dist/core/settings-schema.d.ts +38 -0
  183. package/dist/core/settings-schema.js +171 -0
  184. package/dist/core/skill-catalog.d.ts +6 -0
  185. package/dist/core/skill-catalog.js +17 -0
  186. package/dist/core/skill-invoker.d.ts +127 -0
  187. package/dist/core/skill-invoker.js +182 -0
  188. package/dist/core/stream-accumulator.d.ts +21 -0
  189. package/dist/core/stream-accumulator.js +51 -0
  190. package/dist/core/stream-manager.d.ts +58 -0
  191. package/dist/core/stream-manager.js +212 -0
  192. package/dist/core/tool-executor.d.ts +84 -0
  193. package/dist/core/tool-executor.js +256 -0
  194. package/dist/core/types.d.ts +259 -0
  195. package/dist/core/types.js +11 -0
  196. package/dist/gateway/gateway.d.ts +52 -0
  197. package/dist/gateway/gateway.js +537 -0
  198. package/dist/gateway/index.d.ts +21 -0
  199. package/dist/gateway/index.js +31 -0
  200. package/dist/gateway/openapi-importer.d.ts +15 -0
  201. package/dist/gateway/openapi-importer.js +66 -0
  202. package/dist/gateway/semantic-scorer.d.ts +7 -0
  203. package/dist/gateway/semantic-scorer.js +24 -0
  204. package/dist/gateway/settings-adapter.d.ts +49 -0
  205. package/dist/gateway/settings-adapter.js +137 -0
  206. package/dist/gateway/tool-factory.d.ts +9 -0
  207. package/dist/gateway/tool-factory.js +414 -0
  208. package/dist/gateway/types.d.ts +68 -0
  209. package/dist/gateway/types.js +7 -0
  210. package/dist/models-catalog.js +46 -0
  211. package/dist/providers/anthropic.d.ts +22 -0
  212. package/dist/providers/anthropic.js +148 -0
  213. package/dist/providers/factory.d.ts +10 -0
  214. package/dist/providers/factory.js +25 -0
  215. package/dist/providers/openai.d.ts +15 -0
  216. package/dist/providers/openai.js +71 -0
  217. package/dist/providers/types.d.ts +48 -0
  218. package/dist/providers/types.js +1 -0
  219. package/dist/skills/args.d.ts +37 -0
  220. package/dist/skills/args.js +99 -0
  221. package/dist/skills/index.d.ts +11 -0
  222. package/dist/skills/index.js +23 -0
  223. package/dist/skills/loader.d.ts +3 -0
  224. package/dist/skills/loader.js +59 -0
  225. package/dist/skills/parser.d.ts +7 -0
  226. package/dist/skills/parser.js +152 -0
  227. package/dist/skills/registry.d.ts +13 -0
  228. package/dist/skills/registry.js +74 -0
  229. package/dist/skills/resolver.d.ts +19 -0
  230. package/dist/skills/resolver.js +116 -0
  231. package/dist/skills/types.d.ts +74 -0
  232. package/dist/skills/types.js +50 -0
  233. package/dist/tools/browser.d.ts +2 -0
  234. package/dist/tools/browser.js +68 -0
  235. package/dist/tools/core.d.ts +20 -0
  236. package/dist/tools/core.js +244 -0
  237. package/dist/tools/email.d.ts +2 -0
  238. package/dist/tools/email.js +61 -0
  239. package/dist/tools/image.d.ts +2 -0
  240. package/dist/tools/image.js +257 -0
  241. package/dist/tools/index.d.ts +2 -0
  242. package/dist/tools/index.js +88 -0
  243. package/dist/tools/interface.d.ts +22 -0
  244. package/dist/tools/interface.js +1 -0
  245. package/dist/tools/notify.d.ts +2 -0
  246. package/dist/tools/notify.js +100 -0
  247. package/dist/tools/prompt-optimizer.d.ts +2 -0
  248. package/dist/tools/prompt-optimizer.js +65 -0
  249. package/dist/tools/screenshot.d.ts +2 -0
  250. package/dist/tools/screenshot.js +184 -0
  251. package/dist/tools/search.d.ts +2 -0
  252. package/dist/tools/search.js +78 -0
  253. package/dist/tools/todos.d.ts +10 -0
  254. package/dist/tools/todos.js +50 -0
  255. package/package.json +119 -0
  256. package/skills/docker-ops/SKILL.md +329 -0
  257. package/skills/k8s-deploy/SKILL.md +397 -0
  258. package/skills/log-analyzer/SKILL.md +331 -0
  259. package/skills/speckit-analyze/SKILL.md +260 -0
  260. package/skills/speckit-checklist/SKILL.md +374 -0
  261. package/skills/speckit-clarify/SKILL.md +286 -0
  262. package/skills/speckit-constitution/SKILL.md +157 -0
  263. package/skills/speckit-implement/SKILL.md +224 -0
  264. package/skills/speckit-plan/SKILL.md +171 -0
  265. package/skills/speckit-specify/SKILL.md +346 -0
  266. package/skills/speckit-tasks/SKILL.md +215 -0
  267. package/skills/speckit-taskstoissues/SKILL.md +107 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Zoe CLI — Config Loader
3
+ *
4
+ * Re-exports core config utilities and adds CLI-specific chalk output.
5
+ */
6
+ import chalk from 'chalk';
7
+ import { loadJsonConfig as coreLoadJsonConfig, loadMergedConfig as coreLoadMergedConfig, applyEnvOverrides, getConfigPath, getConfigDir, getConfigPaths, migrateLegacyFormat, resolveActiveProviderType, saveConfig as coreSaveConfig, writeConfigToPath as coreWriteConfigToPath, maskSecret, } from '../../core/config.js';
8
+ // ── Re-exports (unchanged API for CLI consumers) ───────────────────────
9
+ export { applyEnvOverrides, getConfigPath, getConfigDir, getConfigPaths, migrateLegacyFormat, resolveActiveProviderType, maskSecret, };
10
+ // ── CLI wrappers with chalk output ─────────────────────────────────────
11
+ /**
12
+ * Load and parse a JSON config file, returning {} on failure.
13
+ * Logs parse warnings to console with chalk.
14
+ */
15
+ export function loadJsonConfig(filePath) {
16
+ const { config, warning } = coreLoadJsonConfig(filePath);
17
+ if (warning) {
18
+ console.error(chalk.yellow(warning));
19
+ }
20
+ return config;
21
+ }
22
+ /**
23
+ * Load global and local configs and merge them.
24
+ * Priority: local > global.
25
+ */
26
+ export function loadMergedConfig() {
27
+ // Use the core version directly — it calls coreLoadJsonConfig internally
28
+ // and doesn't produce warnings the CLI needs to display at this level.
29
+ return coreLoadMergedConfig();
30
+ }
31
+ /**
32
+ * Save config to disk. If a local config exists, saves there; otherwise global.
33
+ */
34
+ export function saveConfig(config) {
35
+ coreSaveConfig(config);
36
+ }
37
+ /**
38
+ * Save config to a specific path.
39
+ * Logs errors to console with chalk.
40
+ */
41
+ export function writeConfigToPath(config, targetFile) {
42
+ try {
43
+ coreWriteConfigToPath(config, targetFile);
44
+ }
45
+ catch (e) {
46
+ console.error(chalk.red(`Failed to save config: ${e.message}`));
47
+ }
48
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Zoe CLI — Docker & Non-Interactive Detection
3
+ *
4
+ * Utilities for detecting Docker environments and non-interactive TTY contexts.
5
+ * Used to guard inquirer prompts and readline settings.
6
+ */
7
+ /**
8
+ * Detect if the current process is running inside a Docker container.
9
+ * Checks:
10
+ * 1. /.dockerenv file existence
11
+ * 2. /proc/1/cgroup contains "docker" or "containerd"
12
+ * 3. ZOE_DOCKER env var is "true"
13
+ */
14
+ export declare function isDockerContainer(): boolean;
15
+ /**
16
+ * Determine if the CLI is running in a non-interactive context.
17
+ * Returns true if:
18
+ * - stdin is not a TTY
19
+ * - --no-interactive flag was passed (ZOE_NO_INTERACTIVE=true)
20
+ * - Running inside Docker (unless ZOE_INTERACTIVE=true overrides it)
21
+ */
22
+ export declare function isNonInteractive(): boolean;
23
+ /**
24
+ * Check if all required provider API keys are available via environment variables
25
+ * or the provided config. If so, the setup wizard can be safely skipped.
26
+ *
27
+ * Environment variable mappings:
28
+ * - openai: OPENAI_API_KEY
29
+ * - openai-compatible: OPENAI_COMPAT_API_KEY
30
+ * - anthropic: ANTHROPIC_API_KEY
31
+ * - glm: GLM_API_KEY
32
+ *
33
+ * Also checks LLM_PROVIDER to know which one is needed.
34
+ */
35
+ export declare function hasRequiredProviderEnv(config: {
36
+ models?: Record<string, any>;
37
+ }): boolean;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Zoe CLI — Docker & Non-Interactive Detection
3
+ *
4
+ * Utilities for detecting Docker environments and non-interactive TTY contexts.
5
+ * Used to guard inquirer prompts and readline settings.
6
+ */
7
+ import * as fs from 'fs';
8
+ /**
9
+ * Detect if the current process is running inside a Docker container.
10
+ * Checks:
11
+ * 1. /.dockerenv file existence
12
+ * 2. /proc/1/cgroup contains "docker" or "containerd"
13
+ * 3. ZOE_DOCKER env var is "true"
14
+ */
15
+ export function isDockerContainer() {
16
+ // Explicit env var override (used by --docker flag too)
17
+ if (process.env.ZOE_DOCKER === 'true')
18
+ return true;
19
+ try {
20
+ if (fs.existsSync('/.dockerenv'))
21
+ return true;
22
+ }
23
+ catch {
24
+ // Filesystem access may fail in restricted environments
25
+ }
26
+ try {
27
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf-8');
28
+ if (cgroup.includes('docker') || cgroup.includes('containerd'))
29
+ return true;
30
+ }
31
+ catch {
32
+ // /proc may not exist on non-Linux systems
33
+ }
34
+ return false;
35
+ }
36
+ /**
37
+ * Determine if the CLI is running in a non-interactive context.
38
+ * Returns true if:
39
+ * - stdin is not a TTY
40
+ * - --no-interactive flag was passed (ZOE_NO_INTERACTIVE=true)
41
+ * - Running inside Docker (unless ZOE_INTERACTIVE=true overrides it)
42
+ */
43
+ export function isNonInteractive() {
44
+ // Explicit opt-in to interactive mode overrides everything
45
+ if (process.env.ZOE_INTERACTIVE === 'true')
46
+ return false;
47
+ // Explicit non-interactive flag
48
+ if (process.env.ZOE_NO_INTERACTIVE === 'true')
49
+ return true;
50
+ // No TTY detected
51
+ if (!process.stdin.isTTY)
52
+ return true;
53
+ // Running in Docker without explicit interactive override
54
+ if (isDockerContainer())
55
+ return true;
56
+ return false;
57
+ }
58
+ /**
59
+ * Check if all required provider API keys are available via environment variables
60
+ * or the provided config. If so, the setup wizard can be safely skipped.
61
+ *
62
+ * Environment variable mappings:
63
+ * - openai: OPENAI_API_KEY
64
+ * - openai-compatible: OPENAI_COMPAT_API_KEY
65
+ * - anthropic: ANTHROPIC_API_KEY
66
+ * - glm: GLM_API_KEY
67
+ *
68
+ * Also checks LLM_PROVIDER to know which one is needed.
69
+ */
70
+ export function hasRequiredProviderEnv(config) {
71
+ const provider = process.env.LLM_PROVIDER || config.models && Object.keys(config.models).find(k => config.models[k]?.apiKey);
72
+ if (!provider) {
73
+ return !!(process.env.OPENAI_API_KEY ||
74
+ process.env.OPENAI_COMPAT_API_KEY ||
75
+ process.env.ANTHROPIC_API_KEY ||
76
+ process.env.GLM_API_KEY);
77
+ }
78
+ switch (provider) {
79
+ case 'openai':
80
+ return !!(process.env.OPENAI_API_KEY || config.models?.[provider]?.apiKey);
81
+ case 'openai-compatible':
82
+ return !!(process.env.OPENAI_COMPAT_API_KEY || config.models?.[provider]?.apiKey);
83
+ case 'anthropic':
84
+ return !!(process.env.ANTHROPIC_API_KEY || config.models?.[provider]?.apiKey);
85
+ case 'glm':
86
+ return !!(process.env.GLM_API_KEY || config.models?.[provider]?.apiKey);
87
+ default:
88
+ return !!(config.models?.[provider]?.apiKey);
89
+ }
90
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import dotenv from 'dotenv';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { isNonInteractive } from './docker-utils.js';
9
+ import { runSetup } from './setup.js';
10
+ import { runChat } from './repl.js';
11
+ import { resolveLaunchMode } from './system-prompts.js';
12
+ // Handle Ctrl+C gracefully
13
+ function handleExit() {
14
+ console.log(chalk.cyan("\n\nGoodbye! (Interrupted)"));
15
+ if (process.stdin.isTTY) {
16
+ process.stdin.setRawMode(false);
17
+ }
18
+ process.stdin.pause();
19
+ process.exit(0);
20
+ }
21
+ process.on('SIGINT', handleExit);
22
+ process.on('SIGTERM', handleExit);
23
+ // Load local env vars (lowest priority of env vars, but env vars override JSON)
24
+ dotenv.config();
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+ const pkgPath = path.join(__dirname, '..', 'package.json');
27
+ let version = '0.2.2';
28
+ try {
29
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
30
+ version = pkg.version;
31
+ }
32
+ catch (e) {
33
+ // Fallback if package.json not found in expected location
34
+ }
35
+ const program = new Command();
36
+ program
37
+ .name('zoe')
38
+ .description('A lightweight AI agent CLI tool')
39
+ .version(version)
40
+ .option('-m, --model <model>', 'Model to use')
41
+ .option('-p, --provider <provider>', 'Provider to use (openai-compatible|openai|anthropic|glm)')
42
+ .option('-n, --no-interactive', 'Exit after processing the initial query (Headless mode)')
43
+ .option('--docker', 'Docker mode: implies --no-interactive, disables all prompts, uses env vars and config only')
44
+ .option('-y, --yes', 'Auto-confirm all tool executions (e.g., shell commands)')
45
+ .option('--headless', 'Bypass permission matrix: auto-approve all tools (for CI/Docker/scripts)')
46
+ .option('--strict', 'Permission level: auto-approve safe tools only')
47
+ .option('--moderate', 'Permission level: auto-approve safe + edit + communications (default)')
48
+ .option('--yolo', 'Permission level: auto-approve all tools')
49
+ .option('-r, --resume <id>', 'Resume a previous session by id (or "last")');
50
+ program
51
+ .command('setup')
52
+ .description('Run the interactive setup wizard to configure API keys')
53
+ .option('-p, --project', 'Save configuration to project-level (.zoe/setting.json)')
54
+ .action(async (options) => {
55
+ // Setup wizard cannot run in non-interactive mode
56
+ if (isNonInteractive()) {
57
+ console.log(chalk.yellow('Setup wizard requires an interactive terminal.'));
58
+ console.log(chalk.dim('Set API keys via environment variables instead:'));
59
+ console.log(chalk.dim(' OPENAI_API_KEY, ANTHROPIC_API_KEY, GLM_API_KEY'));
60
+ console.log(chalk.dim(' LLM_PROVIDER (openai-compatible|openai|anthropic|glm)'));
61
+ console.log(chalk.dim('Or mount a config file at ~/.zoe/setting.json'));
62
+ process.exit(1);
63
+ }
64
+ await runSetup(options);
65
+ });
66
+ program
67
+ .command('chat [query...]', { isDefault: true })
68
+ .description('Start the AI agent (default)')
69
+ .action(async (queryParts) => {
70
+ const options = program.opts();
71
+ // Dispatch on the SAME predicate that selects the system prompt, so launch
72
+ // mode and UI mode can never diverge (FR-001). The TUI is lazy-imported
73
+ // only in interactive mode; headless / piped / --docker never load React.
74
+ if (resolveLaunchMode(options) === 'interactive') {
75
+ const { startTui } = await import('./tui/index.js');
76
+ await startTui({ queryParts, options });
77
+ }
78
+ else {
79
+ await runChat(queryParts, options);
80
+ }
81
+ });
82
+ // Apply --docker flag effects early from raw argv (before Commander parses)
83
+ // This ensures isNonInteractive() works correctly during the parse phase
84
+ if (process.argv.includes('--docker')) {
85
+ process.env.ZOE_DOCKER = 'true';
86
+ process.env.ZOE_NO_INTERACTIVE = 'true';
87
+ }
88
+ program.parse(process.argv);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Zoe CLI — REPL Functions
3
+ *
4
+ * Interrupt handling, chat-with-interrupt, and the main runChat loop.
5
+ * Extracted from index.ts for single-responsibility.
6
+ */
7
+ import { Agent } from './agent.js';
8
+ import type { ApproveToolFn, PermissionLevel } from '../../core/types.js';
9
+ export interface InterruptHandle {
10
+ signal: AbortSignal;
11
+ /** Temporarily disable ESC detection (e.g. during approval prompts) */
12
+ suspend: () => void;
13
+ /** Re-enable ESC detection after suspend */
14
+ resume: () => void;
15
+ /** Permanently clean up the interrupt handler */
16
+ teardown: () => void;
17
+ }
18
+ export declare function setupInterrupt(agent: Agent): InterruptHandle;
19
+ /** Build the adapter-level approveTool callback for the CLI. */
20
+ export declare function createCliApproveTool(config: any, handle: InterruptHandle, permissionLevel?: PermissionLevel): ApproveToolFn;
21
+ export declare function chatWithInterrupt(agent: Agent, input: string, config?: any, permissionLevel?: PermissionLevel): Promise<void>;
22
+ export declare function runChat(queryParts: string[], options: any): Promise<void>;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Zoe CLI — REPL Functions
3
+ *
4
+ * Interrupt handling, chat-with-interrupt, and the main runChat loop.
5
+ * Extracted from index.ts for single-responsibility.
6
+ */
7
+ import chalk from 'chalk';
8
+ import * as readline from 'node:readline/promises';
9
+ import inquirer from 'inquirer';
10
+ import { bootstrapCliSession } from './bootstrap.js';
11
+ import { buildCommandRegistry } from './commands/build-registry.js';
12
+ export function setupInterrupt(agent) {
13
+ const signal = agent.createAbortSignal();
14
+ const stdin = process.stdin;
15
+ if (!stdin.isTTY) {
16
+ return {
17
+ signal,
18
+ suspend: () => { },
19
+ resume: () => { },
20
+ teardown: () => agent.clearAbortController(),
21
+ };
22
+ }
23
+ const ESC = '\x1b';
24
+ let wasRaw = stdin.isRaw;
25
+ const onData = (data) => {
26
+ if (data[0] === ESC.charCodeAt(0)) {
27
+ agent.abort();
28
+ }
29
+ };
30
+ // Start ESC detection
31
+ stdin.setRawMode(true);
32
+ stdin.resume();
33
+ stdin.on('data', onData);
34
+ return {
35
+ signal,
36
+ suspend: () => {
37
+ stdin.removeListener('data', onData);
38
+ if (stdin.isRaw)
39
+ stdin.setRawMode(false);
40
+ },
41
+ resume: () => {
42
+ stdin.setRawMode(true);
43
+ stdin.resume();
44
+ stdin.on('data', onData);
45
+ },
46
+ teardown: () => {
47
+ stdin.removeListener('data', onData);
48
+ if (!wasRaw && stdin.isRaw) {
49
+ stdin.setRawMode(false);
50
+ }
51
+ agent.clearAbortController();
52
+ },
53
+ };
54
+ }
55
+ // ── Shell approval mode ──────────────────────────────────────────────
56
+ function getShellApprovalMode(config, newPermissionSystemActive) {
57
+ if (config?.autoConfirm)
58
+ return 'auto';
59
+ // When the new permission system is active, ignore the legacy ZOE_SHELL_APPROVE env var
60
+ // so it cannot bypass the permission matrix.
61
+ if (!newPermissionSystemActive) {
62
+ const envMode = process.env.ZOE_SHELL_APPROVE;
63
+ if (envMode === 'auto' || envMode === 'true' || envMode === '1')
64
+ return 'auto';
65
+ if (envMode === 'deny' || envMode === 'false' || envMode === '0')
66
+ return 'deny';
67
+ }
68
+ if (process.stdin.isTTY)
69
+ return 'prompt';
70
+ return 'deny';
71
+ }
72
+ /** Build the adapter-level approveTool callback for the CLI. */
73
+ export function createCliApproveTool(config, handle, permissionLevel) {
74
+ // New permission system is active when an explicit permission level was resolved
75
+ const newPermissionSystemActive = permissionLevel !== undefined;
76
+ return async (call) => {
77
+ // Display what the tool wants to do
78
+ if (call.name === 'execute_shell_command') {
79
+ const cmd = typeof call.args.command === 'string' ? call.args.command : JSON.stringify(call.args.command);
80
+ const rationale = typeof call.args.rationale === 'string' ? call.args.rationale : '';
81
+ console.log(chalk.yellow(`\nAI wants to execute: `) + chalk.bold(cmd));
82
+ if (rationale)
83
+ console.log(chalk.dim(`Reason: ${rationale}`));
84
+ }
85
+ else {
86
+ console.log(chalk.yellow(`\nAI wants to use tool: `) + chalk.bold(call.name));
87
+ }
88
+ const mode = getShellApprovalMode(config, newPermissionSystemActive);
89
+ if (mode === 'deny') {
90
+ console.log(chalk.red('Command denied (non-interactive mode).'));
91
+ console.log(chalk.dim('Set ZOE_SHELL_APPROVE=auto to auto-approve, or use --yes flag.'));
92
+ return false;
93
+ }
94
+ if (mode === 'prompt') {
95
+ // Suspend ESC handler so inquirer can use stdin normally
96
+ handle.suspend();
97
+ try {
98
+ const { confirm } = await inquirer.prompt([
99
+ {
100
+ type: 'confirm',
101
+ name: 'confirm',
102
+ message: 'Do you want to run this command?',
103
+ default: false,
104
+ },
105
+ ]);
106
+ return confirm;
107
+ }
108
+ catch {
109
+ // Prompt cancelled (Ctrl+C or inquirer error)
110
+ return false;
111
+ }
112
+ finally {
113
+ handle.resume();
114
+ }
115
+ }
116
+ // Auto-approved
117
+ console.log(chalk.gray(`(Auto-approved: ${config?.autoConfirm ? '--yes flag' : 'ZOE_SHELL_APPROVE=auto'})`));
118
+ return true;
119
+ };
120
+ }
121
+ export async function chatWithInterrupt(agent, input, config, permissionLevel) {
122
+ const handle = setupInterrupt(agent);
123
+ const approveTool = config ? createCliApproveTool(config, handle, permissionLevel) : undefined;
124
+ try {
125
+ await agent.chat(input, handle.signal, approveTool, permissionLevel);
126
+ }
127
+ finally {
128
+ handle.teardown();
129
+ }
130
+ }
131
+ // ── Main chat runner ─────────────────────────────────────────────────
132
+ export async function runChat(queryParts, options) {
133
+ if (options.interactive) {
134
+ console.log(chalk.bold.cyan("Welcome to Zoe Agent CLI"));
135
+ }
136
+ const initialQuery = queryParts.join(' ');
137
+ const ctx = await bootstrapCliSession(options);
138
+ const { agent, fullConfig, activeProviderType, providerConfig, permissionLevel, gatewayInstance } = ctx;
139
+ if (options.interactive) {
140
+ console.log(chalk.green(`Agent initialized with ${activeProviderType} (${providerConfig.model})`));
141
+ console.log(chalk.gray("Type /help for commands, /exit to leave."));
142
+ }
143
+ // @path resolver — hoisted so the initial query resolves at the caller,
144
+ // not inside Agent.chat() (T022). The resolver is idempotent.
145
+ const { resolveReferences } = await import('../../skills/resolver.js');
146
+ // Handle initial query if present
147
+ if (initialQuery) {
148
+ if (options.interactive) {
149
+ console.log(chalk.blue("\nProcessing initial request: ") + chalk.bold(initialQuery));
150
+ }
151
+ let resolvedInitial = initialQuery;
152
+ if (initialQuery.includes('@')) {
153
+ try {
154
+ resolvedInitial = await resolveReferences(initialQuery);
155
+ }
156
+ catch { /* resolver not available */ }
157
+ }
158
+ await chatWithInterrupt(agent, resolvedInitial, fullConfig, permissionLevel);
159
+ // Headless mode exit
160
+ if (!options.interactive) {
161
+ process.exit(0);
162
+ }
163
+ }
164
+ // Main chat loop
165
+ const rl = readline.createInterface({
166
+ input: process.stdin,
167
+ output: process.stdout,
168
+ terminal: process.stdin.isTTY === true
169
+ });
170
+ // Build command registry
171
+ const cmdRegistry = buildCommandRegistry(agent, fullConfig, activeProviderType, gatewayInstance);
172
+ // Lazy-loaded modules (hoisted outside the loop to avoid repeated import overhead)
173
+ const { invokeSkill, createSkillProviderSwitcher } = await import('../../core/skill-invoker.js');
174
+ try {
175
+ while (true) {
176
+ const userInput = await rl.question(chalk.green('?') + ' You > ');
177
+ // Bare exit/quit (without /)
178
+ if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') {
179
+ console.log(chalk.cyan('Goodbye!'));
180
+ break;
181
+ }
182
+ if (userInput.trim() === '')
183
+ continue;
184
+ // Slash commands — dispatch through registry
185
+ if (userInput.startsWith('/')) {
186
+ rl.pause();
187
+ try {
188
+ const { status, output } = await cmdRegistry.dispatch(userInput, { agent, args: '', config: fullConfig }, agent.getSkillRegistry());
189
+ if (output)
190
+ console.log(output);
191
+ if (status === 'exit')
192
+ break;
193
+ if (status === 'handled')
194
+ continue;
195
+ // 'fallthrough' — try skill invocation
196
+ const skillResult = await invokeSkill({ input: userInput, registry: agent.getSkillRegistry() });
197
+ if (skillResult) {
198
+ console.log(chalk.cyan(`Loading skill: ${skillResult.skill.name}`));
199
+ const switcher = createSkillProviderSwitcher({
200
+ provider: agent.getProvider(),
201
+ model: agent.getModel(),
202
+ models: fullConfig.models ?? {},
203
+ });
204
+ const switched = await switcher.switchIfNeeded(skillResult);
205
+ if (switched) {
206
+ agent.switchProvider(switcher.activeProvider, switcher.activeModel);
207
+ }
208
+ try {
209
+ await chatWithInterrupt(agent, skillResult.prompt, fullConfig, permissionLevel);
210
+ }
211
+ finally {
212
+ if (switched) {
213
+ switcher.restore();
214
+ agent.switchProvider(switcher.activeProvider, switcher.activeModel);
215
+ }
216
+ }
217
+ continue;
218
+ }
219
+ // No matching command or skill
220
+ console.log(chalk.yellow(`Unknown command: ${userInput.split(' ')[0]}`));
221
+ console.log(chalk.dim('Type /help for available commands.'));
222
+ }
223
+ finally {
224
+ rl.resume();
225
+ }
226
+ continue;
227
+ }
228
+ // Resolve @path file references in user input
229
+ let resolvedInput = userInput;
230
+ if (userInput.includes('@')) {
231
+ try {
232
+ resolvedInput = await resolveReferences(userInput);
233
+ }
234
+ catch { /* resolver not available, use raw input */ }
235
+ }
236
+ rl.pause();
237
+ try {
238
+ await chatWithInterrupt(agent, resolvedInput, fullConfig, permissionLevel);
239
+ }
240
+ finally {
241
+ rl.resume();
242
+ }
243
+ }
244
+ }
245
+ catch (err) {
246
+ if (err.message && (err.message.includes('User force closed') || err.message.includes('Prompt was canceled'))) {
247
+ console.log(chalk.cyan("\nGoodbye!"));
248
+ }
249
+ else {
250
+ console.error(chalk.red("Error in chat loop:"), err);
251
+ }
252
+ }
253
+ finally {
254
+ rl.close();
255
+ }
256
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Zoe CLI — Setup Wizard
3
+ *
4
+ * Interactive setup wizard for configuring API keys and providers.
5
+ * Extracted from index.ts for separation of concerns.
6
+ */
7
+ import { ProviderType } from '../../providers/types.js';
8
+ import { Agent } from './agent.js';
9
+ import { type AppConfig } from './config-loader.js';
10
+ /**
11
+ * Run the interactive setup wizard.
12
+ * @param options.project - If true, save to project-level config instead of global.
13
+ */
14
+ export declare function runSetup(options?: any): Promise<void>;
15
+ /**
16
+ * Handle the /models interactive command.
17
+ * Allows switching, editing, adding, and removing providers at runtime.
18
+ */
19
+ export declare function handleModelsCommand(agent: Agent, config: AppConfig, activeProvider: ProviderType): Promise<ProviderType>;