@simonfestl/husky-cli 0.9.3 β 0.9.6
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.
- package/dist/commands/llm-context.d.ts +2 -0
- package/dist/commands/llm-context.js +105 -3
- package/dist/commands/service-account.d.ts +2 -0
- package/dist/commands/service-account.js +180 -0
- package/dist/commands/services.d.ts +2 -0
- package/dist/commands/services.js +381 -0
- package/dist/commands/vm.js +230 -6
- package/dist/commands/worktree.js +481 -1
- package/dist/index.js +7 -2
- package/dist/lib/agent-templates.d.ts +20 -0
- package/dist/lib/agent-templates.js +142 -0
- package/dist/lib/worktree.d.ts +26 -0
- package/dist/lib/worktree.js +127 -0
- package/package.json +1 -1
|
@@ -0,0 +1,381 @@
|
|
|
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
|
+
});
|
package/dist/commands/vm.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
3
|
import * as readline from "readline";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { DEFAULT_AGENT_CONFIGS, generateStartupScript, listDefaultAgentTypes, getDefaultAgentConfig, } from "../lib/agent-templates.js";
|
|
7
|
+
export const vmCommand = new Command("vm").description("Manage VM sessions");
|
|
6
8
|
// Helper: Ensure API is configured
|
|
7
9
|
function ensureConfig() {
|
|
8
10
|
const config = getConfig();
|
|
@@ -12,6 +14,50 @@ function ensureConfig() {
|
|
|
12
14
|
}
|
|
13
15
|
return config;
|
|
14
16
|
}
|
|
17
|
+
async function fetchAgentTypes() {
|
|
18
|
+
const config = getConfig();
|
|
19
|
+
if (!config.apiUrl)
|
|
20
|
+
return null;
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${config.apiUrl}/api/agent-types`, {
|
|
23
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
return null;
|
|
27
|
+
return await res.json();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function fetchAgentTypeBySlug(slug) {
|
|
34
|
+
const config = getConfig();
|
|
35
|
+
if (!config.apiUrl)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(`${config.apiUrl}/api/agent-types?slug=${slug}`, {
|
|
39
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok)
|
|
42
|
+
return null;
|
|
43
|
+
return await res.json();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function getAgentTypeConfig(slug) {
|
|
50
|
+
const apiType = await fetchAgentTypeBySlug(slug);
|
|
51
|
+
if (apiType) {
|
|
52
|
+
return { config: apiType.agentConfig, name: apiType.name };
|
|
53
|
+
}
|
|
54
|
+
const defaultConfig = getDefaultAgentConfig(slug);
|
|
55
|
+
if (defaultConfig) {
|
|
56
|
+
const name = slug.charAt(0).toUpperCase() + slug.slice(1) + " Agent";
|
|
57
|
+
return { config: defaultConfig, name };
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
15
61
|
// Helper: Prompt for confirmation
|
|
16
62
|
async function confirm(message) {
|
|
17
63
|
const rl = readline.createInterface({
|
|
@@ -71,19 +117,40 @@ vmCommand
|
|
|
71
117
|
.command("create <name>")
|
|
72
118
|
.description("Create a new VM session")
|
|
73
119
|
.option("-p, --prompt <prompt>", "Initial prompt for the agent")
|
|
74
|
-
.option("--agent <agent>", "Agent type (claude-code, gemini-cli, aider, custom)", "
|
|
120
|
+
.option("--agent <agent>", "Agent type (claude-code, gemini-cli, aider, custom)", "gemini-cli")
|
|
121
|
+
.option("-t, --type <type>", "Business agent type (support, accounting, marketing, research)")
|
|
75
122
|
.option("--config <configId>", "VM config to use")
|
|
76
123
|
.option("--project <projectId>", "Link to project")
|
|
77
124
|
.option("--task <taskId>", "Link to task")
|
|
78
125
|
.option("--repo <repoUrl>", "Git repository URL")
|
|
79
126
|
.option("--branch <branch>", "Git branch to use")
|
|
80
127
|
.option("--machine-type <machineType>", "GCP machine type", "e2-medium")
|
|
81
|
-
.option("--zone <zone>", "GCP zone", "
|
|
128
|
+
.option("--zone <zone>", "GCP zone", "europe-west1-b")
|
|
82
129
|
.option("--json", "Output as JSON")
|
|
83
130
|
.action(async (name, options) => {
|
|
84
131
|
const config = ensureConfig();
|
|
132
|
+
const validBusinessTypes = [
|
|
133
|
+
"support",
|
|
134
|
+
"accounting",
|
|
135
|
+
"marketing",
|
|
136
|
+
"research",
|
|
137
|
+
];
|
|
138
|
+
if (options.type && !validBusinessTypes.includes(options.type)) {
|
|
139
|
+
console.error(`Error: Invalid business agent type '${options.type}'`);
|
|
140
|
+
console.error(`Valid types: ${validBusinessTypes.join(", ")}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
if (options.type && !options.prompt) {
|
|
144
|
+
const defaultPrompts = {
|
|
145
|
+
support: "Du bist ein Support Agent. Starte mit /shift-start um die Schicht zu beginnen.",
|
|
146
|
+
accounting: "Du bist ein Accounting Agent. Pruefe /inbox fuer neue Belege.",
|
|
147
|
+
marketing: "Du bist ein Marketing Agent. Pruefe /campaigns fuer aktuelle Kampagnen.",
|
|
148
|
+
research: "Du bist ein Research Agent. Pruefe /youtube fuer neue Videos zu analysieren.",
|
|
149
|
+
};
|
|
150
|
+
options.prompt = defaultPrompts[options.type] || "Agent bereit.";
|
|
151
|
+
}
|
|
85
152
|
if (!options.prompt) {
|
|
86
|
-
console.error("Error: --prompt is required");
|
|
153
|
+
console.error("Error: --prompt is required (or use --type for default prompt)");
|
|
87
154
|
process.exit(1);
|
|
88
155
|
}
|
|
89
156
|
try {
|
|
@@ -97,6 +164,7 @@ vmCommand
|
|
|
97
164
|
name,
|
|
98
165
|
prompt: options.prompt,
|
|
99
166
|
agentType: options.agent,
|
|
167
|
+
businessAgentType: options.type,
|
|
100
168
|
taskId: options.task,
|
|
101
169
|
workflowId: options.project,
|
|
102
170
|
repoUrl: options.repo,
|
|
@@ -118,9 +186,16 @@ vmCommand
|
|
|
118
186
|
console.log(`Created VM session: ${session.name}`);
|
|
119
187
|
console.log(` ID: ${session.id}`);
|
|
120
188
|
console.log(` Agent: ${session.agentType}`);
|
|
189
|
+
if (session.businessAgentType) {
|
|
190
|
+
console.log(` Type: ${session.businessAgentType}`);
|
|
191
|
+
}
|
|
121
192
|
console.log(` Status: ${formatStatus(session.vmStatus)}`);
|
|
122
193
|
console.log(` VM Name: ${session.vmName}`);
|
|
194
|
+
console.log(` Zone: ${options.zone}`);
|
|
123
195
|
console.log(`\nTo start the VM, run: husky vm start ${session.id}`);
|
|
196
|
+
if (session.businessAgentType) {
|
|
197
|
+
console.log(`\nAfter VM is ready, the ${session.businessAgentType} agent will be auto-configured.`);
|
|
198
|
+
}
|
|
124
199
|
}
|
|
125
200
|
}
|
|
126
201
|
catch (error) {
|
|
@@ -523,6 +598,150 @@ vmCommand
|
|
|
523
598
|
process.exit(1);
|
|
524
599
|
}
|
|
525
600
|
});
|
|
601
|
+
vmCommand
|
|
602
|
+
.command("types")
|
|
603
|
+
.description("List available business agent types")
|
|
604
|
+
.option("--json", "Output as JSON")
|
|
605
|
+
.action(async (options) => {
|
|
606
|
+
const apiTypes = await fetchAgentTypes();
|
|
607
|
+
if (apiTypes && apiTypes.length > 0) {
|
|
608
|
+
if (options.json) {
|
|
609
|
+
console.log(JSON.stringify(apiTypes, null, 2));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
console.log("\n Available Agent Types (from API)");
|
|
613
|
+
console.log(" " + "-".repeat(70));
|
|
614
|
+
for (const type of apiTypes) {
|
|
615
|
+
console.log(`\n ${type.slug.toUpperCase()} - ${type.name}`);
|
|
616
|
+
console.log(` ${type.description}`);
|
|
617
|
+
console.log(` Directories: ${type.agentConfig.directories.slice(0, 3).join(", ")}...`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const types = listDefaultAgentTypes();
|
|
622
|
+
if (options.json) {
|
|
623
|
+
console.log(JSON.stringify(DEFAULT_AGENT_CONFIGS, null, 2));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
console.log("\n Available Agent Types (defaults)");
|
|
627
|
+
console.log(" " + "-".repeat(70));
|
|
628
|
+
for (const type of types) {
|
|
629
|
+
const config = DEFAULT_AGENT_CONFIGS[type];
|
|
630
|
+
console.log(`\n ${type.toUpperCase()}`);
|
|
631
|
+
console.log(` Default prompt: ${config.defaultPrompt}`);
|
|
632
|
+
console.log(` Directories: ${config.directories.slice(0, 3).join(", ")}...`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
console.log("\n Usage:");
|
|
636
|
+
console.log(" husky vm create my-agent --type support");
|
|
637
|
+
console.log(" husky vm init --type accounting\n");
|
|
638
|
+
});
|
|
639
|
+
vmCommand
|
|
640
|
+
.command("init")
|
|
641
|
+
.description("Initialize agent workspace on current machine (run this ON the VM)")
|
|
642
|
+
.requiredOption("-t, --type <type>", "Business agent type slug (e.g., support, accounting, marketing, research)")
|
|
643
|
+
.option("--workspace <path>", "Workspace directory", process.env.HOME + "/workspace")
|
|
644
|
+
.option("--json", "Output as JSON")
|
|
645
|
+
.action(async (options) => {
|
|
646
|
+
const agentTypeSlug = options.type;
|
|
647
|
+
const typeData = await getAgentTypeConfig(agentTypeSlug);
|
|
648
|
+
if (!typeData) {
|
|
649
|
+
const validTypes = listDefaultAgentTypes();
|
|
650
|
+
console.error(`Error: Unknown agent type '${agentTypeSlug}'`);
|
|
651
|
+
console.error(`Default types: ${validTypes.join(", ")}`);
|
|
652
|
+
console.error(`Or configure API to fetch custom types.`);
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
const { config, name: agentName } = typeData;
|
|
656
|
+
const workspace = options.workspace;
|
|
657
|
+
console.log(`\n Initializing ${agentName}...`);
|
|
658
|
+
console.log(` Workspace: ${workspace}\n`);
|
|
659
|
+
try {
|
|
660
|
+
if (!fs.existsSync(workspace)) {
|
|
661
|
+
fs.mkdirSync(workspace, { recursive: true });
|
|
662
|
+
}
|
|
663
|
+
for (const dir of config.directories) {
|
|
664
|
+
const fullPath = path.join(workspace, dir);
|
|
665
|
+
if (!fs.existsSync(fullPath)) {
|
|
666
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
667
|
+
console.log(` Created: ${dir}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const geminiDir = path.join(workspace, ".gemini", "commands");
|
|
671
|
+
if (!fs.existsSync(geminiDir)) {
|
|
672
|
+
fs.mkdirSync(geminiDir, { recursive: true });
|
|
673
|
+
}
|
|
674
|
+
const scriptsDir = path.join(workspace, "scripts");
|
|
675
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
676
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
677
|
+
}
|
|
678
|
+
const geminiMd = `# ${agentName} VM - Project Rules
|
|
679
|
+
|
|
680
|
+
## Rolle
|
|
681
|
+
Du bist ein autonomer ${agentName}.
|
|
682
|
+
|
|
683
|
+
## Tech Stack
|
|
684
|
+
- Runtime: Google Cloud VM
|
|
685
|
+
- AI Model: Google Vertex AI (europe-west1) - DSGVO-konform
|
|
686
|
+
- CLI: Husky CLI fuer alle Business-Operationen
|
|
687
|
+
|
|
688
|
+
## Workspace
|
|
689
|
+
${config.directories.map((d) => `- ${d}/`).join("\n")}
|
|
690
|
+
|
|
691
|
+
## Status Updates
|
|
692
|
+
\`\`\`bash
|
|
693
|
+
husky task message <task-id> -m "<status>"
|
|
694
|
+
\`\`\`
|
|
695
|
+
`;
|
|
696
|
+
fs.writeFileSync(path.join(workspace, "GEMINI.md"), geminiMd);
|
|
697
|
+
console.log(` Created: GEMINI.md`);
|
|
698
|
+
if (options.json) {
|
|
699
|
+
console.log(JSON.stringify({ success: true, workspace, type: agentTypeSlug, name: agentName }, null, 2));
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
console.log(`\n ${agentName} initialized!`);
|
|
703
|
+
console.log(`\n Next steps:`);
|
|
704
|
+
console.log(` cd ${workspace}`);
|
|
705
|
+
console.log(` gemini`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
console.error("Error initializing agent:", error);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
vmCommand
|
|
714
|
+
.command("startup-script")
|
|
715
|
+
.description("Generate VM startup script for agent type")
|
|
716
|
+
.requiredOption("-t, --type <type>", "Business agent type slug")
|
|
717
|
+
.option("--husky-url <url>", "Husky API URL")
|
|
718
|
+
.option("--husky-key <key>", "Husky API Key")
|
|
719
|
+
.option("--project <project>", "GCP Project ID")
|
|
720
|
+
.action(async (options) => {
|
|
721
|
+
const apiType = await fetchAgentTypeBySlug(options.type);
|
|
722
|
+
if (apiType) {
|
|
723
|
+
const script = generateStartupScript({
|
|
724
|
+
id: apiType.id,
|
|
725
|
+
departmentId: apiType.departmentId || "",
|
|
726
|
+
name: apiType.name,
|
|
727
|
+
slug: apiType.slug,
|
|
728
|
+
description: apiType.description,
|
|
729
|
+
agentConfig: apiType.agentConfig,
|
|
730
|
+
createdAt: apiType.createdAt,
|
|
731
|
+
updatedAt: apiType.updatedAt,
|
|
732
|
+
}, options.huskyUrl, options.huskyKey, options.project);
|
|
733
|
+
console.log(script);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const validTypes = listDefaultAgentTypes();
|
|
737
|
+
if (!validTypes.includes(options.type)) {
|
|
738
|
+
console.error(`Error: Unknown agent type '${options.type}'`);
|
|
739
|
+
console.error(`Default types: ${validTypes.join(", ")}`);
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
const script = generateStartupScript(options.type, options.huskyUrl, options.huskyKey, options.project);
|
|
743
|
+
console.log(script);
|
|
744
|
+
});
|
|
526
745
|
// Print helpers
|
|
527
746
|
function printVMSessions(sessions, stats) {
|
|
528
747
|
if (sessions.length === 0) {
|
|
@@ -538,7 +757,9 @@ function printVMSessions(sessions, stats) {
|
|
|
538
757
|
console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(20)} ${"STATUS".padEnd(16)} ${"AGENT".padEnd(14)} CREATED`);
|
|
539
758
|
console.log(" " + "-".repeat(90));
|
|
540
759
|
for (const session of sessions) {
|
|
541
|
-
const truncatedName = session.name.length > 18
|
|
760
|
+
const truncatedName = session.name.length > 18
|
|
761
|
+
? session.name.substring(0, 15) + "..."
|
|
762
|
+
: session.name;
|
|
542
763
|
const status = formatStatus(session.vmStatus).padEnd(16);
|
|
543
764
|
const createdAt = new Date(session.createdAt).toLocaleDateString();
|
|
544
765
|
console.log(` ${session.id.padEnd(24)} ${truncatedName.padEnd(20)} ${status} ${session.agentType.padEnd(14)} ${createdAt}`);
|
|
@@ -551,6 +772,9 @@ function printVMSessionDetail(session) {
|
|
|
551
772
|
console.log(` ID: ${session.id}`);
|
|
552
773
|
console.log(` Status: ${formatStatus(session.vmStatus)}`);
|
|
553
774
|
console.log(` Agent: ${session.agentType}`);
|
|
775
|
+
if (session.businessAgentType) {
|
|
776
|
+
console.log(` Type: ${session.businessAgentType}`);
|
|
777
|
+
}
|
|
554
778
|
console.log(` VM Name: ${session.vmName}`);
|
|
555
779
|
console.log(` Zone: ${session.vmZone}`);
|
|
556
780
|
console.log(` Machine Type: ${session.machineType}`);
|