@poolzin/pool-bot 2026.3.13 → 2026.3.14

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 (183) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/agents/checkpoint-manager.js +291 -0
  3. package/dist/agents/poolbot-tools.js +5 -0
  4. package/dist/agents/subagent-announce-reliability.js +160 -0
  5. package/dist/agents/tool-result-truncation.js +299 -0
  6. package/dist/agents/tools/nodes-file-tool.js +197 -0
  7. package/dist/build-info.json +3 -3
  8. package/dist/cli/config-cli.js +60 -0
  9. package/dist/cron/cron-improvements.js +195 -0
  10. package/dist/discord/discord-improvements.js +167 -0
  11. package/dist/gateway/auth-rate-limit.js +19 -0
  12. package/dist/gateway/auth.js +41 -0
  13. package/dist/gateway/gateway-improvements.js +294 -0
  14. package/dist/gateway/node-command-policy.js +7 -2
  15. package/dist/infra/net/ssrf.js +15 -2
  16. package/dist/infra/shell-security.js +201 -0
  17. package/dist/memory/memory-improvements.js +239 -0
  18. package/dist/node-host/runner.js +146 -79
  19. package/dist/security/prototype-pollution.js +141 -0
  20. package/dist/security/webhook-security.js +253 -0
  21. package/dist/shared/net/ip.js +52 -1
  22. package/dist/slack/slack-improvements.js +225 -0
  23. package/dist/telegram/telegram-improvements.js +220 -0
  24. package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
  25. package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
  26. package/docs/competitive-analysis.md +421 -0
  27. package/docs/implementation-analysis.md +393 -0
  28. package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
  29. package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
  30. package/extensions/agency-agents/README.md +301 -0
  31. package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
  32. package/extensions/agency-agents/agents/README.md +602 -0
  33. package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
  34. package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
  35. package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
  36. package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
  37. package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
  38. package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
  39. package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
  40. package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
  41. package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
  42. package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
  43. package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
  44. package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
  45. package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
  46. package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
  47. package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
  48. package/extensions/agency-agents/agents/examples/README.md +48 -0
  49. package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
  50. package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
  51. package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
  52. package/extensions/agency-agents/agents/integrations/README.md +117 -0
  53. package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
  54. package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
  55. package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
  56. package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
  57. package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
  58. package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
  59. package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
  60. package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
  61. package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
  62. package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
  63. package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
  64. package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
  65. package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
  66. package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
  67. package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
  68. package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
  69. package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
  70. package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
  71. package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
  72. package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
  73. package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
  74. package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
  75. package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
  76. package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
  77. package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
  78. package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
  79. package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
  80. package/extensions/agency-agents/agents/scripts/install.sh +465 -0
  81. package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
  82. package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
  83. package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
  84. package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
  85. package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
  86. package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
  87. package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
  88. package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
  89. package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
  90. package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
  91. package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
  92. package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
  93. package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
  94. package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
  95. package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
  96. package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
  97. package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
  98. package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
  99. package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
  100. package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
  101. package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
  102. package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
  103. package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
  104. package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
  105. package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
  106. package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
  107. package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
  108. package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
  109. package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
  110. package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
  111. package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
  112. package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
  113. package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
  114. package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
  115. package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
  116. package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
  117. package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
  118. package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
  119. package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
  120. package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
  121. package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
  122. package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
  123. package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
  124. package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
  125. package/extensions/agency-agents/index.ts +733 -0
  126. package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
  127. package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
  128. package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
  129. package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
  130. package/extensions/agency-agents/node_modules/.bin/vite +21 -0
  131. package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
  132. package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
  133. package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  134. package/extensions/agency-agents/package.json +25 -0
  135. package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
  136. package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
  137. package/extensions/agency-agents/src/types.ts +147 -0
  138. package/extensions/agency-agents/vitest.config.ts +8 -0
  139. package/extensions/hexstrike-ai/README.md +98 -0
  140. package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
  141. package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
  142. package/extensions/hexstrike-ai/package.json +29 -0
  143. package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
  144. package/extensions/hexstrike-ai/src/client.ts +91 -0
  145. package/extensions/hexstrike-ai/src/index.ts +170 -0
  146. package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
  147. package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
  148. package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
  149. package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
  150. package/extensions/hexstrike-ai/tsconfig.json +20 -0
  151. package/extensions/page-agent/README.md +159 -0
  152. package/extensions/page-agent/index.ts +595 -0
  153. package/extensions/page-agent/node_modules/.bin/jiti +21 -0
  154. package/extensions/page-agent/node_modules/.bin/playwright +21 -0
  155. package/extensions/page-agent/node_modules/.bin/tsc +21 -0
  156. package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
  157. package/extensions/page-agent/node_modules/.bin/tsx +21 -0
  158. package/extensions/page-agent/node_modules/.bin/vitest +21 -0
  159. package/extensions/page-agent/node_modules/.bin/yaml +21 -0
  160. package/extensions/page-agent/package.json +43 -0
  161. package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
  162. package/extensions/page-agent/src/PageAgentService.ts +636 -0
  163. package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
  164. package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
  165. package/extensions/page-agent/src/index.ts +20 -0
  166. package/extensions/page-agent/src/tools.test.ts +231 -0
  167. package/extensions/page-agent/src/tools.ts +167 -0
  168. package/extensions/page-agent/src/types.ts +198 -0
  169. package/extensions/xyops/README.md +227 -0
  170. package/extensions/xyops/index.ts +342 -0
  171. package/extensions/xyops/node_modules/.bin/jiti +21 -0
  172. package/extensions/xyops/node_modules/.bin/tsc +21 -0
  173. package/extensions/xyops/node_modules/.bin/tsserver +21 -0
  174. package/extensions/xyops/node_modules/.bin/tsx +21 -0
  175. package/extensions/xyops/node_modules/.bin/vitest +21 -0
  176. package/extensions/xyops/node_modules/.bin/yaml +21 -0
  177. package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  178. package/extensions/xyops/package.json +39 -0
  179. package/extensions/xyops/src/client.test.ts +467 -0
  180. package/extensions/xyops/src/client.ts +157 -0
  181. package/extensions/xyops/src/types.ts +147 -0
  182. package/extensions/xyops/vitest.config.ts +8 -0
  183. package/package.json +1 -1
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/yaml@2.8.2/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/yaml@2.8.2/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/bin.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../../../../node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/bin.mjs" "$@"
21
+ fi
@@ -0,0 +1 @@
1
+ {"version":"4.0.18","results":[[":src/client.test.ts",{"duration":29.100499999999982,"failed":false}]]}
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@poolbot/xyops",
3
+ "version": "2026.3.13",
4
+ "description": "XYOps integration for PoolBot - Workflow automation and job scheduling",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./client": "./dist/client.js",
11
+ "./types": "./dist/types.js"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest"
18
+ },
19
+ "dependencies": {
20
+ "@sinclair/typebox": "^0.34.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.0.0",
24
+ "typescript": "^5.0.0",
25
+ "vitest": "^3.0.0"
26
+ },
27
+ "peerDependencies": {
28
+ "poolbot": "workspace:*"
29
+ },
30
+ "poolbot": {
31
+ "plugin": true,
32
+ "config": {
33
+ "schema": "./dist/config/schema.js"
34
+ }
35
+ },
36
+ "keywords": ["xyops", "workflow", "automation", "scheduler", "jobs"],
37
+ "author": "PoolBot Team",
38
+ "license": "MIT"
39
+ }
@@ -0,0 +1,467 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { XYOpsClient } from './client.js'
3
+ import type { XYOpsConfig, XYOpsJob, XYOpsSchedule, XYOpsEvent, XYOpsStatus, XYOpsPlugin } from './types.js'
4
+
5
+ describe('XYOpsClient', () => {
6
+ let client: XYOpsClient
7
+ let mockFetch: ReturnType<typeof vi.fn>
8
+
9
+ const config: XYOpsConfig = {
10
+ baseUrl: 'http://localhost:8080',
11
+ apiKey: 'test-api-key',
12
+ timeout: 5000,
13
+ }
14
+
15
+ beforeEach(() => {
16
+ vi.clearAllMocks()
17
+ client = new XYOpsClient(config)
18
+ mockFetch = vi.fn()
19
+ global.fetch = mockFetch as unknown as typeof fetch
20
+ })
21
+
22
+ describe('constructor', () => {
23
+ it('should create client with config', () => {
24
+ expect(client).toBeDefined()
25
+ })
26
+
27
+ it('should remove trailing slash from baseUrl', () => {
28
+ const clientWithSlash = new XYOpsClient({
29
+ ...config,
30
+ baseUrl: 'http://localhost:8080/',
31
+ })
32
+ expect(clientWithSlash).toBeDefined()
33
+ })
34
+
35
+ it('should use default timeout', () => {
36
+ const clientWithoutTimeout = new XYOpsClient({
37
+ baseUrl: 'http://localhost:8080',
38
+ })
39
+ expect(clientWithoutTimeout).toBeDefined()
40
+ })
41
+ })
42
+
43
+ describe('request', () => {
44
+ it('should make GET request with auth header', async () => {
45
+ const mockJob: XYOpsJob = {
46
+ id: 'job-1',
47
+ title: 'Test Job',
48
+ category: 'test',
49
+ plugin: 'shell',
50
+ params: { command: 'echo test' },
51
+ enabled: true,
52
+ created: Date.now(),
53
+ modified: Date.now(),
54
+ }
55
+
56
+ mockFetch.mockResolvedValue({
57
+ ok: true,
58
+ headers: new Headers({ 'content-type': 'application/json' }),
59
+ json: () => Promise.resolve(mockJob),
60
+ })
61
+
62
+ const result = await client.getJob('job-1')
63
+
64
+ expect(mockFetch).toHaveBeenCalledWith(
65
+ 'http://localhost:8080/api/jobs/job-1',
66
+ expect.objectContaining({
67
+ method: 'GET',
68
+ headers: expect.objectContaining({
69
+ 'Authorization': 'Bearer test-api-key',
70
+ 'Content-Type': 'application/json',
71
+ }),
72
+ })
73
+ )
74
+ expect(result).toEqual(mockJob)
75
+ })
76
+
77
+ it('should make POST request with body', async () => {
78
+ const newJob = {
79
+ title: 'New Job',
80
+ category: 'test',
81
+ plugin: 'shell',
82
+ params: { command: 'echo hello' },
83
+ enabled: true,
84
+ }
85
+
86
+ const mockResponse: XYOpsJob = {
87
+ id: 'job-2',
88
+ ...newJob,
89
+ created: Date.now(),
90
+ modified: Date.now(),
91
+ }
92
+
93
+ mockFetch.mockResolvedValue({
94
+ ok: true,
95
+ headers: new Headers({ 'content-type': 'application/json' }),
96
+ json: () => Promise.resolve(mockResponse),
97
+ })
98
+
99
+ const result = await client.createJob(newJob)
100
+
101
+ expect(mockFetch).toHaveBeenCalledWith(
102
+ 'http://localhost:8080/api/jobs',
103
+ expect.objectContaining({
104
+ method: 'POST',
105
+ body: JSON.stringify(newJob),
106
+ })
107
+ )
108
+ expect(result).toEqual(mockResponse)
109
+ })
110
+
111
+ it('should handle API errors', async () => {
112
+ mockFetch.mockResolvedValue({
113
+ ok: false,
114
+ status: 404,
115
+ statusText: 'Not Found',
116
+ text: () => Promise.resolve('Job not found'),
117
+ })
118
+
119
+ await expect(client.getJob('nonexistent')).rejects.toThrow('XYOps API error')
120
+ })
121
+
122
+ it('should handle timeout', async () => {
123
+ mockFetch.mockImplementation(() => {
124
+ const error = new Error('AbortError')
125
+ error.name = 'AbortError'
126
+ return Promise.reject(error)
127
+ })
128
+
129
+ await expect(client.getJob('job-1')).rejects.toThrow('timeout')
130
+ })
131
+
132
+ it('should handle non-JSON responses', async () => {
133
+ mockFetch.mockResolvedValue({
134
+ ok: true,
135
+ headers: new Headers({ 'content-type': 'text/plain' }),
136
+ })
137
+
138
+ const result = await client.deleteJob('job-1')
139
+ expect(result).toBeUndefined()
140
+ })
141
+
142
+ it('should work without API key', async () => {
143
+ const clientNoAuth = new XYOpsClient({
144
+ baseUrl: 'http://localhost:8080',
145
+ })
146
+
147
+ mockFetch.mockResolvedValue({
148
+ ok: true,
149
+ headers: new Headers({ 'content-type': 'application/json' }),
150
+ json: () => Promise.resolve([]),
151
+ })
152
+
153
+ await clientNoAuth.listJobs()
154
+
155
+ expect(mockFetch).toHaveBeenCalledWith(
156
+ expect.any(String),
157
+ expect.objectContaining({
158
+ headers: expect.not.objectContaining({
159
+ 'Authorization': expect.any(String),
160
+ }),
161
+ })
162
+ )
163
+ })
164
+ })
165
+
166
+ describe('Jobs', () => {
167
+ const mockJob: XYOpsJob = {
168
+ id: 'job-1',
169
+ title: 'Test Job',
170
+ category: 'test',
171
+ plugin: 'shell',
172
+ params: { command: 'echo test' },
173
+ enabled: true,
174
+ created: Date.now(),
175
+ modified: Date.now(),
176
+ }
177
+
178
+ it('should list jobs', async () => {
179
+ mockFetch.mockResolvedValue({
180
+ ok: true,
181
+ headers: new Headers({ 'content-type': 'application/json' }),
182
+ json: () => Promise.resolve([mockJob]),
183
+ })
184
+
185
+ const result = await client.listJobs(0, 50)
186
+
187
+ expect(mockFetch).toHaveBeenCalledWith(
188
+ 'http://localhost:8080/api/jobs?offset=0&limit=50',
189
+ expect.any(Object)
190
+ )
191
+ expect(result).toEqual([mockJob])
192
+ })
193
+
194
+ it('should get job by id', async () => {
195
+ mockFetch.mockResolvedValue({
196
+ ok: true,
197
+ headers: new Headers({ 'content-type': 'application/json' }),
198
+ json: () => Promise.resolve(mockJob),
199
+ })
200
+
201
+ const result = await client.getJob('job-1')
202
+
203
+ expect(result).toEqual(mockJob)
204
+ })
205
+
206
+ it('should create job', async () => {
207
+ const jobData = {
208
+ title: 'New Job',
209
+ category: 'test',
210
+ plugin: 'shell',
211
+ params: { command: 'echo hello' },
212
+ enabled: true,
213
+ }
214
+
215
+ mockFetch.mockResolvedValue({
216
+ ok: true,
217
+ headers: new Headers({ 'content-type': 'application/json' }),
218
+ json: () => Promise.resolve({ id: 'job-2', ...jobData, created: Date.now(), modified: Date.now() }),
219
+ })
220
+
221
+ const result = await client.createJob(jobData)
222
+
223
+ expect(result.title).toBe('New Job')
224
+ })
225
+
226
+ it('should update job', async () => {
227
+ const updates = { title: 'Updated Job' }
228
+
229
+ mockFetch.mockResolvedValue({
230
+ ok: true,
231
+ headers: new Headers({ 'content-type': 'application/json' }),
232
+ json: () => Promise.resolve({ ...mockJob, ...updates }),
233
+ })
234
+
235
+ const result = await client.updateJob('job-1', updates)
236
+
237
+ expect(result.title).toBe('Updated Job')
238
+ })
239
+
240
+ it('should delete job', async () => {
241
+ mockFetch.mockResolvedValue({
242
+ ok: true,
243
+ headers: new Headers({ 'content-type': 'application/json' }),
244
+ json: () => Promise.resolve(undefined),
245
+ })
246
+
247
+ await client.deleteJob('job-1')
248
+
249
+ expect(mockFetch).toHaveBeenCalledWith(
250
+ 'http://localhost:8080/api/jobs/job-1',
251
+ expect.objectContaining({ method: 'DELETE' })
252
+ )
253
+ })
254
+
255
+ it('should run job', async () => {
256
+ mockFetch.mockResolvedValue({
257
+ ok: true,
258
+ headers: new Headers({ 'content-type': 'application/json' }),
259
+ json: () => Promise.resolve({ eventId: 'event-1' }),
260
+ })
261
+
262
+ const result = await client.runJob('job-1', { param1: 'value1' })
263
+
264
+ expect(result.eventId).toBe('event-1')
265
+ expect(mockFetch).toHaveBeenCalledWith(
266
+ 'http://localhost:8080/api/jobs/job-1/run',
267
+ expect.objectContaining({
268
+ method: 'POST',
269
+ body: JSON.stringify({ params: { param1: 'value1' } }),
270
+ })
271
+ )
272
+ })
273
+
274
+ it('should run job without params', async () => {
275
+ mockFetch.mockResolvedValue({
276
+ ok: true,
277
+ headers: new Headers({ 'content-type': 'application/json' }),
278
+ json: () => Promise.resolve({ eventId: 'event-1' }),
279
+ })
280
+
281
+ await client.runJob('job-1')
282
+
283
+ expect(mockFetch).toHaveBeenCalledWith(
284
+ expect.any(String),
285
+ expect.objectContaining({
286
+ body: undefined,
287
+ })
288
+ )
289
+ })
290
+ })
291
+
292
+ describe('Schedules', () => {
293
+ const mockSchedule: XYOpsSchedule = {
294
+ id: 'schedule-1',
295
+ jobId: 'job-1',
296
+ cron: '0 0 * * *',
297
+ enabled: true,
298
+ created: Date.now(),
299
+ }
300
+
301
+ it('should list schedules', async () => {
302
+ mockFetch.mockResolvedValue({
303
+ ok: true,
304
+ headers: new Headers({ 'content-type': 'application/json' }),
305
+ json: () => Promise.resolve([mockSchedule]),
306
+ })
307
+
308
+ const result = await client.listSchedules()
309
+
310
+ expect(result).toEqual([mockSchedule])
311
+ })
312
+
313
+ it('should list schedules for specific job', async () => {
314
+ mockFetch.mockResolvedValue({
315
+ ok: true,
316
+ headers: new Headers({ 'content-type': 'application/json' }),
317
+ json: () => Promise.resolve([mockSchedule]),
318
+ })
319
+
320
+ await client.listSchedules('job-1')
321
+
322
+ expect(mockFetch).toHaveBeenCalledWith(
323
+ 'http://localhost:8080/api/schedules?jobId=job-1',
324
+ expect.any(Object)
325
+ )
326
+ })
327
+
328
+ it('should create schedule', async () => {
329
+ const scheduleData = {
330
+ jobId: 'job-1',
331
+ cron: '0 0 * * *',
332
+ enabled: true,
333
+ }
334
+
335
+ mockFetch.mockResolvedValue({
336
+ ok: true,
337
+ headers: new Headers({ 'content-type': 'application/json' }),
338
+ json: () => Promise.resolve({ id: 'schedule-2', ...scheduleData, created: Date.now() }),
339
+ })
340
+
341
+ const result = await client.createSchedule(scheduleData)
342
+
343
+ expect(result.jobId).toBe('job-1')
344
+ })
345
+
346
+ it('should delete schedule', async () => {
347
+ mockFetch.mockResolvedValue({
348
+ ok: true,
349
+ headers: new Headers({ 'content-type': 'application/json' }),
350
+ json: () => Promise.resolve(undefined),
351
+ })
352
+
353
+ await client.deleteSchedule('schedule-1')
354
+
355
+ expect(mockFetch).toHaveBeenCalledWith(
356
+ 'http://localhost:8080/api/schedules/schedule-1',
357
+ expect.objectContaining({ method: 'DELETE' })
358
+ )
359
+ })
360
+ })
361
+
362
+ describe('Events', () => {
363
+ const mockEvent: XYOpsEvent = {
364
+ id: 'event-1',
365
+ jobId: 'job-1',
366
+ status: 'running',
367
+ startTime: Date.now(),
368
+ }
369
+
370
+ it('should list events', async () => {
371
+ mockFetch.mockResolvedValue({
372
+ ok: true,
373
+ headers: new Headers({ 'content-type': 'application/json' }),
374
+ json: () => Promise.resolve([mockEvent]),
375
+ })
376
+
377
+ const result = await client.listEvents()
378
+
379
+ expect(mockFetch).toHaveBeenCalledWith(
380
+ 'http://localhost:8080/api/events?limit=50&offset=0',
381
+ expect.any(Object)
382
+ )
383
+ expect(result).toEqual([mockEvent])
384
+ })
385
+
386
+ it('should list events with filters', async () => {
387
+ mockFetch.mockResolvedValue({
388
+ ok: true,
389
+ headers: new Headers({ 'content-type': 'application/json' }),
390
+ json: () => Promise.resolve([mockEvent]),
391
+ })
392
+
393
+ await client.listEvents('job-1', 10, 20)
394
+
395
+ expect(mockFetch).toHaveBeenCalledWith(
396
+ 'http://localhost:8080/api/events?jobId=job-1&limit=10&offset=20',
397
+ expect.any(Object)
398
+ )
399
+ })
400
+
401
+ it('should get event by id', async () => {
402
+ mockFetch.mockResolvedValue({
403
+ ok: true,
404
+ headers: new Headers({ 'content-type': 'application/json' }),
405
+ json: () => Promise.resolve(mockEvent),
406
+ })
407
+
408
+ const result = await client.getEvent('event-1')
409
+
410
+ expect(result).toEqual(mockEvent)
411
+ })
412
+
413
+ it('should abort event', async () => {
414
+ mockFetch.mockResolvedValue({
415
+ ok: true,
416
+ headers: new Headers({ 'content-type': 'application/json' }),
417
+ json: () => Promise.resolve(undefined),
418
+ })
419
+
420
+ await client.abortEvent('event-1')
421
+
422
+ expect(mockFetch).toHaveBeenCalledWith(
423
+ 'http://localhost:8080/api/events/event-1/abort',
424
+ expect.objectContaining({ method: 'POST' })
425
+ )
426
+ })
427
+ })
428
+
429
+ describe('System', () => {
430
+ it('should get status', async () => {
431
+ const mockStatus: XYOpsStatus = {
432
+ version: '1.0.0',
433
+ uptime: 3600,
434
+ jobCount: 5,
435
+ scheduleCount: 3,
436
+ activeEvents: 1,
437
+ }
438
+
439
+ mockFetch.mockResolvedValue({
440
+ ok: true,
441
+ headers: new Headers({ 'content-type': 'application/json' }),
442
+ json: () => Promise.resolve(mockStatus),
443
+ })
444
+
445
+ const result = await client.getStatus()
446
+
447
+ expect(result).toEqual(mockStatus)
448
+ })
449
+
450
+ it('should list plugins', async () => {
451
+ const mockPlugins: XYOpsPlugin[] = [
452
+ { name: 'plugin-1', title: 'Plugin 1', description: 'Test plugin 1' },
453
+ { name: 'plugin-2', title: 'Plugin 2', description: 'Test plugin 2' },
454
+ ]
455
+
456
+ mockFetch.mockResolvedValue({
457
+ ok: true,
458
+ headers: new Headers({ 'content-type': 'application/json' }),
459
+ json: () => Promise.resolve(mockPlugins),
460
+ })
461
+
462
+ const result = await client.listPlugins()
463
+
464
+ expect(result).toEqual(mockPlugins)
465
+ })
466
+ })
467
+ })
@@ -0,0 +1,157 @@
1
+ import type {
2
+ XYOpsConfig,
3
+ XYOpsJob,
4
+ XYOpsSchedule,
5
+ XYOpsEvent,
6
+ XYOpsPlugin,
7
+ XYOpsStatus,
8
+ } from "./types.js";
9
+
10
+ export class XYOpsClient {
11
+ private baseUrl: string;
12
+ private apiKey?: string;
13
+ private timeout: number;
14
+
15
+ constructor(config: XYOpsConfig) {
16
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
17
+ this.apiKey = config.apiKey;
18
+ this.timeout = config.timeout ?? 30000;
19
+ }
20
+
21
+ private async request<T>(
22
+ method: string,
23
+ path: string,
24
+ body?: unknown
25
+ ): Promise<T> {
26
+ const url = `${this.baseUrl}/api${path}`;
27
+ const headers: Record<string, string> = {
28
+ "Content-Type": "application/json",
29
+ Accept: "application/json",
30
+ };
31
+
32
+ if (this.apiKey) {
33
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
34
+ }
35
+
36
+ const controller = new AbortController();
37
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
38
+
39
+ try {
40
+ const response = await fetch(url, {
41
+ method,
42
+ headers,
43
+ body: body ? JSON.stringify(body) : undefined,
44
+ signal: controller.signal,
45
+ });
46
+
47
+ clearTimeout(timeoutId);
48
+
49
+ if (!response.ok) {
50
+ const errorText = await response.text();
51
+ throw new Error(
52
+ `XYOps API error: ${response.status} ${response.statusText} - ${errorText}`
53
+ );
54
+ }
55
+
56
+ // Handle empty responses
57
+ const contentType = response.headers.get("content-type");
58
+ if (!contentType?.includes("application/json")) {
59
+ return undefined as T;
60
+ }
61
+
62
+ return (await response.json()) as T;
63
+ } catch (error) {
64
+ clearTimeout(timeoutId);
65
+ if (error instanceof Error && error.name === "AbortError") {
66
+ throw new Error(`XYOps request timeout after ${this.timeout}ms`);
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ // Jobs
73
+ async listJobs(offset = 0, limit = 50): Promise<XYOpsJob[]> {
74
+ return this.request<XYOpsJob[]>(
75
+ "GET",
76
+ `/jobs?offset=${offset}&limit=${limit}`
77
+ );
78
+ }
79
+
80
+ async getJob(jobId: string): Promise<XYOpsJob> {
81
+ return this.request<XYOpsJob>("GET", `/jobs/${jobId}`);
82
+ }
83
+
84
+ async createJob(
85
+ jobData: Omit<XYOpsJob, "id" | "created" | "modified">
86
+ ): Promise<XYOpsJob> {
87
+ return this.request<XYOpsJob>("POST", "/jobs", jobData);
88
+ }
89
+
90
+ async updateJob(
91
+ jobId: string,
92
+ updates: Partial<Omit<XYOpsJob, "id" | "created" | "modified">>
93
+ ): Promise<XYOpsJob> {
94
+ return this.request<XYOpsJob>("PUT", `/jobs/${jobId}`, updates);
95
+ }
96
+
97
+ async deleteJob(jobId: string): Promise<void> {
98
+ await this.request<void>("DELETE", `/jobs/${jobId}`);
99
+ }
100
+
101
+ async runJob(
102
+ jobId: string,
103
+ params?: Record<string, unknown>
104
+ ): Promise<{ eventId: string }> {
105
+ return this.request<{ eventId: string }>(
106
+ "POST",
107
+ `/jobs/${jobId}/run`,
108
+ params ? { params } : undefined
109
+ );
110
+ }
111
+
112
+ // Schedules
113
+ async listSchedules(jobId?: string): Promise<XYOpsSchedule[]> {
114
+ const query = jobId ? `?jobId=${jobId}` : "";
115
+ return this.request<XYOpsSchedule[]>("GET", `/schedules${query}`);
116
+ }
117
+
118
+ async createSchedule(
119
+ scheduleData: Omit<XYOpsSchedule, "id" | "created">
120
+ ): Promise<XYOpsSchedule> {
121
+ return this.request<XYOpsSchedule>("POST", "/schedules", scheduleData);
122
+ }
123
+
124
+ async deleteSchedule(scheduleId: string): Promise<void> {
125
+ await this.request<void>("DELETE", `/schedules/${scheduleId}`);
126
+ }
127
+
128
+ // Events
129
+ async listEvents(
130
+ jobId?: string,
131
+ limit = 50,
132
+ offset = 0
133
+ ): Promise<XYOpsEvent[]> {
134
+ const params = new URLSearchParams();
135
+ if (jobId) params.append("jobId", jobId);
136
+ params.append("limit", String(limit));
137
+ params.append("offset", String(offset));
138
+ return this.request<XYOpsEvent[]>("GET", `/events?${params.toString()}`);
139
+ }
140
+
141
+ async getEvent(eventId: string): Promise<XYOpsEvent> {
142
+ return this.request<XYOpsEvent>("GET", `/events/${eventId}`);
143
+ }
144
+
145
+ async abortEvent(eventId: string): Promise<void> {
146
+ await this.request<void>("POST", `/events/${eventId}/abort`);
147
+ }
148
+
149
+ // System
150
+ async getStatus(): Promise<XYOpsStatus> {
151
+ return this.request<XYOpsStatus>("GET", "/status");
152
+ }
153
+
154
+ async listPlugins(): Promise<XYOpsPlugin[]> {
155
+ return this.request<XYOpsPlugin[]>("GET", "/plugins");
156
+ }
157
+ }