@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.
@@ -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
+ });
@@ -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
- export const vmCommand = new Command("vm")
5
- .description("Manage VM sessions");
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)", "claude-code")
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", "us-central1-a")
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 ? session.name.substring(0, 15) + "..." : session.name;
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}`);