@simonfestl/husky-cli 0.9.6 β†’ 1.0.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.
@@ -1,381 +0,0 @@
1
- import { Command } from "commander";
2
- import { getConfig } from "./config.js";
3
- // Helper: Ensure API is configured
4
- function ensureConfig() {
5
- const config = getConfig();
6
- if (!config.apiUrl) {
7
- console.error("Error: API URL not configured. Run: husky config set api-url <url>");
8
- process.exit(1);
9
- }
10
- return { apiUrl: config.apiUrl, apiKey: config.apiKey };
11
- }
12
- // Helper: Make API request
13
- async function fetchAPI(apiUrl, apiKey, path, options = {}) {
14
- const response = await fetch(`${apiUrl}${path}`, {
15
- ...options,
16
- headers: {
17
- "Content-Type": "application/json",
18
- ...(apiKey ? { "x-api-key": apiKey } : {}),
19
- ...options.headers,
20
- },
21
- });
22
- if (!response.ok) {
23
- const error = await response.text();
24
- throw new Error(`API Error: ${response.status} - ${error}`);
25
- }
26
- return response.json();
27
- }
28
- export const servicesCommand = new Command("services")
29
- .description("Manage Agent Services (Agentic Workflow Platform)");
30
- // husky services list
31
- servicesCommand
32
- .command("list")
33
- .description("List all agent services")
34
- .option("--project <id>", "Filter by project ID")
35
- .option("--json", "Output as JSON")
36
- .action(async (options) => {
37
- const config = ensureConfig();
38
- try {
39
- const params = new URLSearchParams();
40
- if (options.project)
41
- params.set("projectId", options.project);
42
- const queryString = params.toString();
43
- const path = queryString ? `/api/services?${queryString}` : "/api/services";
44
- const services = await fetchAPI(config.apiUrl, config.apiKey, path);
45
- if (options.json) {
46
- console.log(JSON.stringify(services, null, 2));
47
- return;
48
- }
49
- if (services.length === 0) {
50
- console.log("No services found.");
51
- return;
52
- }
53
- console.log("\n Agent Services");
54
- console.log(" " + "─".repeat(80));
55
- for (const s of services) {
56
- const statusIcon = {
57
- building: "πŸ”¨",
58
- deployed: "πŸ“¦",
59
- running: "βœ…",
60
- failed: "❌",
61
- disabled: "⏸️",
62
- };
63
- console.log(` ${statusIcon[s.status] || "❓"} ${s.name} (${s.type})`);
64
- console.log(` ID: ${s.id}`);
65
- console.log(` Status: ${s.status}`);
66
- if (s.schedule)
67
- console.log(` Schedule: ${s.schedule}`);
68
- if (s.cloudRunUrl)
69
- console.log(` URL: ${s.cloudRunUrl}`);
70
- console.log(` Runs: ${s.runCount} (${s.successCount} success, ${s.failureCount} failed)`);
71
- console.log("");
72
- }
73
- }
74
- catch (error) {
75
- console.error("Error:", error instanceof Error ? error.message : error);
76
- process.exit(1);
77
- }
78
- });
79
- // husky services get <id>
80
- servicesCommand
81
- .command("get <id>")
82
- .description("Get service details")
83
- .option("--json", "Output as JSON")
84
- .action(async (id, options) => {
85
- const config = ensureConfig();
86
- try {
87
- const service = await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}`);
88
- if (options.json) {
89
- console.log(JSON.stringify(service, null, 2));
90
- return;
91
- }
92
- console.log(`\n Service: ${service.name}`);
93
- console.log(" " + "─".repeat(50));
94
- console.log(` ID: ${service.id}`);
95
- console.log(` Type: ${service.type}`);
96
- console.log(` Status: ${service.status}`);
97
- if (service.description)
98
- console.log(` Description: ${service.description}`);
99
- if (service.schedule)
100
- console.log(` Schedule: ${service.schedule}`);
101
- if (service.cloudRunUrl)
102
- console.log(` URL: ${service.cloudRunUrl}`);
103
- if (service.dockerImage)
104
- console.log(` Image: ${service.dockerImage}`);
105
- console.log(` Runs: ${service.runCount} total`);
106
- console.log(` Success: ${service.successCount}`);
107
- console.log(` Failures: ${service.failureCount}`);
108
- if (service.avgDurationMs)
109
- console.log(` Avg Duration: ${service.avgDurationMs}ms`);
110
- if (service.lastError)
111
- console.log(` Last Error: ${service.lastError}`);
112
- console.log(` Created: ${new Date(service.createdAt).toLocaleString()}`);
113
- console.log(` Updated: ${new Date(service.updatedAt).toLocaleString()}`);
114
- console.log("");
115
- }
116
- catch (error) {
117
- console.error("Error:", error instanceof Error ? error.message : error);
118
- process.exit(1);
119
- }
120
- });
121
- // husky services create <name>
122
- servicesCommand
123
- .command("create <name>")
124
- .description("Create a new agent service")
125
- .option("-t, --type <type>", "Service type (cloud-run, cloud-function, scheduled-job)", "cloud-run")
126
- .option("-d, --description <desc>", "Service description")
127
- .option("-s, --schedule <cron>", 'Cron schedule (e.g., "0 6 * * *")')
128
- .option("--project <id>", "Link to project")
129
- .option("--repo <url>", "Source repository URL")
130
- .option("--path <path>", "Source path in repository")
131
- .action(async (name, options) => {
132
- const config = ensureConfig();
133
- try {
134
- const service = await fetchAPI(config.apiUrl, config.apiKey, "/api/services", {
135
- method: "POST",
136
- body: JSON.stringify({
137
- name,
138
- type: options.type,
139
- description: options.description,
140
- schedule: options.schedule,
141
- projectId: options.project,
142
- sourceRepo: options.repo,
143
- sourcePath: options.path,
144
- }),
145
- });
146
- console.log(`βœ“ Created service: ${service.name} (${service.id})`);
147
- console.log(` Type: ${service.type}`);
148
- console.log(` Status: ${service.status}`);
149
- }
150
- catch (error) {
151
- console.error("Error:", error instanceof Error ? error.message : error);
152
- process.exit(1);
153
- }
154
- });
155
- // husky services update <id>
156
- servicesCommand
157
- .command("update <id>")
158
- .description("Update a service")
159
- .option("-d, --description <desc>", "Update description")
160
- .option("-s, --schedule <cron>", "Update schedule")
161
- .option("--status <status>", "Update status")
162
- .option("--url <url>", "Set Cloud Run URL")
163
- .option("--image <image>", "Set Docker image")
164
- .action(async (id, options) => {
165
- const config = ensureConfig();
166
- const updates = {};
167
- if (options.description)
168
- updates.description = options.description;
169
- if (options.schedule)
170
- updates.schedule = options.schedule;
171
- if (options.status)
172
- updates.status = options.status;
173
- if (options.url)
174
- updates.cloudRunUrl = options.url;
175
- if (options.image)
176
- updates.dockerImage = options.image;
177
- if (Object.keys(updates).length === 0) {
178
- console.log("No updates specified. Use --help for available options.");
179
- return;
180
- }
181
- try {
182
- const service = await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}`, {
183
- method: "PATCH",
184
- body: JSON.stringify(updates),
185
- });
186
- console.log(`βœ“ Updated service: ${service.name}`);
187
- const changedFields = Object.keys(updates).join(", ");
188
- console.log(` Changed: ${changedFields}`);
189
- }
190
- catch (error) {
191
- console.error("Error:", error instanceof Error ? error.message : error);
192
- process.exit(1);
193
- }
194
- });
195
- // husky services delete <id>
196
- servicesCommand
197
- .command("delete <id>")
198
- .description("Delete a service")
199
- .option("-f, --force", "Skip confirmation")
200
- .action(async (id, options) => {
201
- const config = ensureConfig();
202
- if (!options.force) {
203
- console.log("Use --force to confirm deletion.");
204
- return;
205
- }
206
- try {
207
- await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}`, { method: "DELETE" });
208
- console.log(`βœ“ Deleted service: ${id}`);
209
- }
210
- catch (error) {
211
- console.error("Error:", error instanceof Error ? error.message : error);
212
- process.exit(1);
213
- }
214
- });
215
- // husky services health
216
- servicesCommand
217
- .command("health")
218
- .description("Check health of all services")
219
- .option("--json", "Output as JSON")
220
- .action(async (options) => {
221
- const config = ensureConfig();
222
- try {
223
- const services = await fetchAPI(config.apiUrl, config.apiKey, "/api/services");
224
- if (options.json) {
225
- const healthData = services
226
- .filter((s) => s.status !== "disabled")
227
- .map((s) => ({
228
- id: s.id,
229
- name: s.name,
230
- healthStatus: s.healthStatus || "unknown",
231
- status: s.status,
232
- lastError: s.lastError,
233
- }));
234
- console.log(JSON.stringify(healthData, null, 2));
235
- return;
236
- }
237
- console.log("\n Service Health");
238
- console.log(" " + "─".repeat(50));
239
- let healthy = 0;
240
- let unhealthy = 0;
241
- let unknown = 0;
242
- for (const s of services) {
243
- if (s.status === "disabled")
244
- continue;
245
- const icon = s.healthStatus === "healthy"
246
- ? "βœ…"
247
- : s.healthStatus === "unhealthy"
248
- ? "❌"
249
- : "❓";
250
- console.log(` ${icon} ${s.name}: ${s.healthStatus || "unknown"}`);
251
- if (s.lastError && s.healthStatus === "unhealthy") {
252
- console.log(` Last Error: ${s.lastError}`);
253
- }
254
- if (s.healthStatus === "healthy")
255
- healthy++;
256
- else if (s.healthStatus === "unhealthy")
257
- unhealthy++;
258
- else
259
- unknown++;
260
- }
261
- console.log("");
262
- console.log(` Summary: ${healthy} healthy, ${unhealthy} unhealthy, ${unknown} unknown`);
263
- console.log("");
264
- }
265
- catch (error) {
266
- console.error("Error:", error instanceof Error ? error.message : error);
267
- process.exit(1);
268
- }
269
- });
270
- // husky services run <id>
271
- servicesCommand
272
- .command("run <id>")
273
- .description("Trigger a service run")
274
- .option("--input <json>", "Input data as JSON")
275
- .option("--json", "Output as JSON")
276
- .action(async (id, options) => {
277
- const config = ensureConfig();
278
- let inputData = {};
279
- if (options.input) {
280
- try {
281
- inputData = JSON.parse(options.input);
282
- }
283
- catch {
284
- console.error("Error: --input must be valid JSON");
285
- process.exit(1);
286
- }
287
- }
288
- try {
289
- const result = await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}/run`, {
290
- method: "POST",
291
- body: JSON.stringify({ input: inputData }),
292
- });
293
- if (options.json) {
294
- console.log(JSON.stringify(result, null, 2));
295
- return;
296
- }
297
- console.log(`βœ“ Service run triggered: ${id}`);
298
- if (result.runId)
299
- console.log(` Run ID: ${result.runId}`);
300
- if (result.status)
301
- console.log(` Status: ${result.status}`);
302
- }
303
- catch (error) {
304
- console.error("Error:", error instanceof Error ? error.message : error);
305
- process.exit(1);
306
- }
307
- });
308
- // husky services logs <id>
309
- servicesCommand
310
- .command("logs <id>")
311
- .description("View service logs")
312
- .option("-n, --lines <num>", "Number of log lines", "50")
313
- .option("--json", "Output as JSON")
314
- .action(async (id, options) => {
315
- const config = ensureConfig();
316
- try {
317
- const logs = await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}/logs?limit=${options.lines}`);
318
- if (options.json) {
319
- console.log(JSON.stringify(logs, null, 2));
320
- return;
321
- }
322
- if (!logs.entries || logs.entries.length === 0) {
323
- console.log(`No logs found for service ${id}`);
324
- return;
325
- }
326
- console.log(`\n Logs for service: ${id}`);
327
- console.log(" " + "─".repeat(70));
328
- for (const entry of logs.entries) {
329
- const timestamp = new Date(entry.timestamp).toLocaleString();
330
- const level = entry.level?.toUpperCase() || "INFO";
331
- console.log(` [${timestamp}] ${level}: ${entry.message}`);
332
- }
333
- console.log("");
334
- }
335
- catch (error) {
336
- console.error("Error:", error instanceof Error ? error.message : error);
337
- process.exit(1);
338
- }
339
- });
340
- // husky services runs <id>
341
- servicesCommand
342
- .command("runs <id>")
343
- .description("List recent runs for a service")
344
- .option("-n, --limit <num>", "Number of runs to show", "10")
345
- .option("--json", "Output as JSON")
346
- .action(async (id, options) => {
347
- const config = ensureConfig();
348
- try {
349
- const runs = await fetchAPI(config.apiUrl, config.apiKey, `/api/services/${id}/runs?limit=${options.limit}`);
350
- if (options.json) {
351
- console.log(JSON.stringify(runs, null, 2));
352
- return;
353
- }
354
- if (!runs || runs.length === 0) {
355
- console.log(`No runs found for service ${id}`);
356
- return;
357
- }
358
- console.log(`\n Recent Runs for service: ${id}`);
359
- console.log(" " + "─".repeat(70));
360
- for (const run of runs) {
361
- const statusIcon = run.status === "success"
362
- ? "βœ…"
363
- : run.status === "failed"
364
- ? "❌"
365
- : run.status === "running"
366
- ? "β–Ά"
367
- : "⏸️";
368
- const timestamp = new Date(run.startedAt).toLocaleString();
369
- const duration = run.durationMs ? `${run.durationMs}ms` : "β€”";
370
- console.log(` ${statusIcon} ${run.id.slice(0, 12)} β”‚ ${timestamp} β”‚ ${duration} β”‚ ${run.status}`);
371
- if (run.error) {
372
- console.log(` Error: ${run.error}`);
373
- }
374
- }
375
- console.log("");
376
- }
377
- catch (error) {
378
- console.error("Error:", error instanceof Error ? error.message : error);
379
- process.exit(1);
380
- }
381
- });