@simonfestl/husky-cli 0.9.5 → 0.9.7

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.
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import * as path from "path";
3
3
  import { WorktreeManager } from "../lib/worktree.js";
4
4
  import { MergeLock, withMergeLock } from "../lib/merge-lock.js";
5
+ import { getConfig } from "./config.js";
5
6
  export const worktreeCommand = new Command("worktree")
6
7
  .description("Manage Git worktrees for isolated agent workspaces");
7
8
  /**
@@ -23,13 +24,46 @@ worktreeCommand
23
24
  .description("Create a new worktree for a session")
24
25
  .option("-b, --base-branch <branch>", "Base branch to create from (default: main/master)")
25
26
  .option("-p, --project <path>", "Project directory (default: current directory)")
27
+ .option("--task-id <id>", "Link worktree to task and register with dashboard API")
26
28
  .option("--json", "Output as JSON")
27
29
  .action(async (sessionName, options) => {
28
30
  try {
29
31
  const manager = getManager(options);
30
32
  const info = manager.createWorktree(sessionName);
33
+ // Register with dashboard API if task-id provided
34
+ if (options.taskId) {
35
+ const config = getConfig();
36
+ if (!config.apiUrl) {
37
+ console.warn("Warning: API URL not configured. Worktree created but not registered.");
38
+ console.warn("Run: husky config set api-url <url>");
39
+ }
40
+ else {
41
+ try {
42
+ const res = await fetch(`${config.apiUrl}/api/tasks/${options.taskId}`, {
43
+ method: "PATCH",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
47
+ },
48
+ body: JSON.stringify({
49
+ worktreePath: info.path,
50
+ worktreeBranch: info.branch,
51
+ }),
52
+ });
53
+ if (!res.ok) {
54
+ console.warn(`Warning: Failed to register worktree with task (API ${res.status})`);
55
+ }
56
+ else if (!options.json) {
57
+ console.log(`✓ Registered worktree with task ${options.taskId}`);
58
+ }
59
+ }
60
+ catch (err) {
61
+ console.warn(`Warning: Failed to register worktree: ${err instanceof Error ? err.message : err}`);
62
+ }
63
+ }
64
+ }
31
65
  if (options.json) {
32
- console.log(JSON.stringify(info, null, 2));
66
+ console.log(JSON.stringify({ ...info, taskId: options.taskId }, null, 2));
33
67
  }
34
68
  else {
35
69
  console.log(`\nWorktree created successfully!`);
@@ -403,3 +437,449 @@ function printWorktreeStatus(info, changedFiles, hasUncommitted) {
403
437
  }
404
438
  }
405
439
  }
440
+ // ============================================
441
+ // DASHBOARD SYNC COMMANDS
442
+ // ============================================
443
+ // husky worktree sync-stats <session-name> --task-id <id>
444
+ worktreeCommand
445
+ .command("sync-stats <session-name>")
446
+ .description("Sync worktree stats to dashboard for a task")
447
+ .requiredOption("--task-id <id>", "Task ID to update")
448
+ .option("-p, --project <path>", "Project directory (default: current directory)")
449
+ .option("--json", "Output as JSON")
450
+ .action(async (sessionName, options) => {
451
+ const config = getConfig();
452
+ if (!config.apiUrl) {
453
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
454
+ process.exit(1);
455
+ }
456
+ try {
457
+ const manager = getManager(options);
458
+ const info = manager.getWorktree(sessionName);
459
+ if (!info) {
460
+ console.error(`Error: No worktree found for session: ${sessionName}`);
461
+ process.exit(1);
462
+ }
463
+ // Prepare stats payload
464
+ const worktreeStats = {
465
+ commitCount: info.stats.commitCount,
466
+ filesChanged: info.stats.filesChanged,
467
+ additions: info.stats.additions,
468
+ deletions: info.stats.deletions,
469
+ lastUpdated: new Date().toISOString(),
470
+ };
471
+ // Update task via API
472
+ const res = await fetch(`${config.apiUrl}/api/tasks/${options.taskId}`, {
473
+ method: "PATCH",
474
+ headers: {
475
+ "Content-Type": "application/json",
476
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
477
+ },
478
+ body: JSON.stringify({
479
+ worktreePath: info.path,
480
+ worktreeBranch: info.branch,
481
+ worktreeStats,
482
+ }),
483
+ });
484
+ if (!res.ok) {
485
+ throw new Error(`API error: ${res.status}`);
486
+ }
487
+ if (options.json) {
488
+ console.log(JSON.stringify({ success: true, taskId: options.taskId, worktreeStats }, null, 2));
489
+ }
490
+ else {
491
+ console.log(`✓ Synced stats for ${sessionName} to task ${options.taskId}`);
492
+ console.log(` Commits: ${worktreeStats.commitCount}`);
493
+ console.log(` Files: ${worktreeStats.filesChanged}`);
494
+ console.log(` Changes: +${worktreeStats.additions}/-${worktreeStats.deletions}`);
495
+ }
496
+ }
497
+ catch (error) {
498
+ console.error("Error syncing stats:", error instanceof Error ? error.message : error);
499
+ process.exit(1);
500
+ }
501
+ });
502
+ // husky worktree check-conflicts <session-name> --task-id <id>
503
+ worktreeCommand
504
+ .command("check-conflicts <session-name>")
505
+ .description("Check for merge conflicts and sync to dashboard")
506
+ .requiredOption("--task-id <id>", "Task ID to update")
507
+ .option("-p, --project <path>", "Project directory (default: current directory)")
508
+ .option("--json", "Output as JSON")
509
+ .action(async (sessionName, options) => {
510
+ const config = getConfig();
511
+ if (!config.apiUrl) {
512
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
513
+ process.exit(1);
514
+ }
515
+ try {
516
+ const manager = getManager(options);
517
+ const info = manager.getWorktree(sessionName);
518
+ if (!info) {
519
+ console.error(`Error: No worktree found for session: ${sessionName}`);
520
+ process.exit(1);
521
+ }
522
+ // Check for conflicts
523
+ const conflictResult = manager.checkMergeConflicts(sessionName);
524
+ // Update task via API
525
+ const res = await fetch(`${config.apiUrl}/api/tasks/${options.taskId}`, {
526
+ method: "PATCH",
527
+ headers: {
528
+ "Content-Type": "application/json",
529
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
530
+ },
531
+ body: JSON.stringify({
532
+ worktreeHasConflicts: conflictResult.hasConflicts,
533
+ worktreeConflictFiles: conflictResult.conflictFiles,
534
+ worktreeConflictCheckedAt: conflictResult.checkedAt.toISOString(),
535
+ }),
536
+ });
537
+ if (!res.ok) {
538
+ throw new Error(`API error: ${res.status}`);
539
+ }
540
+ if (options.json) {
541
+ console.log(JSON.stringify({
542
+ success: true,
543
+ taskId: options.taskId,
544
+ hasConflicts: conflictResult.hasConflicts,
545
+ conflictFiles: conflictResult.conflictFiles,
546
+ checkedAt: conflictResult.checkedAt.toISOString(),
547
+ }, null, 2));
548
+ }
549
+ else {
550
+ if (conflictResult.hasConflicts) {
551
+ console.log(`⚠ Conflicts detected in ${sessionName}`);
552
+ console.log(` Files with conflicts:`);
553
+ for (const file of conflictResult.conflictFiles.slice(0, 5)) {
554
+ console.log(` - ${file}`);
555
+ }
556
+ if (conflictResult.conflictFiles.length > 5) {
557
+ console.log(` ... and ${conflictResult.conflictFiles.length - 5} more`);
558
+ }
559
+ }
560
+ else {
561
+ console.log(`✓ No conflicts in ${sessionName}`);
562
+ }
563
+ console.log(` Updated task ${options.taskId}`);
564
+ }
565
+ }
566
+ catch (error) {
567
+ console.error("Error checking conflicts:", error instanceof Error ? error.message : error);
568
+ process.exit(1);
569
+ }
570
+ });
571
+ // husky worktree poll-actions
572
+ worktreeCommand
573
+ .command("poll-actions")
574
+ .description("Poll for pending worktree actions from dashboard")
575
+ .option("-p, --project <path>", "Project directory (default: current directory)")
576
+ .option("--execute", "Execute pending actions immediately")
577
+ .option("--json", "Output as JSON")
578
+ .action(async (options) => {
579
+ const config = getConfig();
580
+ if (!config.apiUrl) {
581
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
582
+ process.exit(1);
583
+ }
584
+ try {
585
+ // Fetch pending actions
586
+ const res = await fetch(`${config.apiUrl}/api/worktrees/pending`, {
587
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
588
+ });
589
+ if (!res.ok) {
590
+ throw new Error(`API error: ${res.status}`);
591
+ }
592
+ const data = await res.json();
593
+ const pendingActions = data.pendingActions || [];
594
+ if (options.json && !options.execute) {
595
+ console.log(JSON.stringify({ pendingActions }, null, 2));
596
+ return;
597
+ }
598
+ if (pendingActions.length === 0) {
599
+ if (!options.json) {
600
+ console.log("No pending actions.");
601
+ }
602
+ return;
603
+ }
604
+ if (!options.execute) {
605
+ console.log(`\n PENDING WORKTREE ACTIONS`);
606
+ console.log(" " + "-".repeat(60));
607
+ for (const action of pendingActions) {
608
+ const icon = action.action === "merge" ? "↑" : "✕";
609
+ console.log(` ${icon} ${action.action.toUpperCase().padEnd(8)} ${action.taskId}`);
610
+ console.log(` Branch: ${action.worktreeBranch}`);
611
+ console.log(` Path: ${action.worktreePath}`);
612
+ }
613
+ console.log("");
614
+ console.log(" Use --execute to process these actions");
615
+ return;
616
+ }
617
+ // Execute pending actions
618
+ const projectDir = getProjectDir(options);
619
+ const results = [];
620
+ for (const action of pendingActions) {
621
+ console.log(`\nExecuting ${action.action} for task ${action.taskId}...`);
622
+ // Extract session name from branch (e.g., "husky/task-abc123" -> "task-abc123")
623
+ const sessionName = action.worktreeBranch.replace("husky/", "");
624
+ const manager = new WorktreeManager(projectDir);
625
+ let success = false;
626
+ let error;
627
+ try {
628
+ if (action.action === "merge") {
629
+ // Execute merge with lock
630
+ success = await withMergeLock(projectDir, sessionName, async () => {
631
+ return manager.mergeWorktree(sessionName, {
632
+ deleteAfter: false,
633
+ message: `husky: Merge ${action.worktreeBranch} (via dashboard)`,
634
+ });
635
+ });
636
+ if (!success) {
637
+ error = "Merge failed - check for conflicts";
638
+ }
639
+ }
640
+ else if (action.action === "cleanup") {
641
+ // Execute cleanup
642
+ manager.removeWorktree(sessionName, true);
643
+ success = true;
644
+ }
645
+ }
646
+ catch (err) {
647
+ success = false;
648
+ error = err instanceof Error ? err.message : "Unknown error";
649
+ }
650
+ // Report result to dashboard
651
+ const completeRes = await fetch(`${config.apiUrl}/api/worktrees/${action.taskId}/action`, {
652
+ method: "PATCH",
653
+ headers: {
654
+ "Content-Type": "application/json",
655
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
656
+ },
657
+ body: JSON.stringify({
658
+ status: success ? "completed" : "failed",
659
+ error,
660
+ }),
661
+ });
662
+ if (!completeRes.ok) {
663
+ console.error(` Warning: Failed to report status to dashboard`);
664
+ }
665
+ results.push({ taskId: action.taskId, action: action.action, success, error });
666
+ if (success) {
667
+ console.log(` ✓ ${action.action} completed`);
668
+ }
669
+ else {
670
+ console.log(` ✗ ${action.action} failed: ${error}`);
671
+ }
672
+ }
673
+ if (options.json) {
674
+ console.log(JSON.stringify({ results }, null, 2));
675
+ }
676
+ }
677
+ catch (error) {
678
+ console.error("Error polling actions:", error instanceof Error ? error.message : error);
679
+ process.exit(1);
680
+ }
681
+ });
682
+ // husky worktree push <session-name>
683
+ worktreeCommand
684
+ .command("push <session-name>")
685
+ .description("Push worktree branch to remote")
686
+ .option("-p, --project <path>", "Project directory (default: current directory)")
687
+ .option("-f, --force", "Force push")
688
+ .option("--json", "Output as JSON")
689
+ .action(async (sessionName, options) => {
690
+ try {
691
+ const manager = getManager(options);
692
+ const info = manager.getWorktree(sessionName);
693
+ if (!info) {
694
+ console.error(`Error: No worktree found for session: ${sessionName}`);
695
+ process.exit(1);
696
+ }
697
+ const success = manager.pushWorktreeBranch(sessionName, options.force);
698
+ if (options.json) {
699
+ console.log(JSON.stringify({ success, sessionName, branch: info.branch }, null, 2));
700
+ }
701
+ if (!success) {
702
+ process.exit(1);
703
+ }
704
+ }
705
+ catch (error) {
706
+ console.error("Error pushing branch:", error instanceof Error ? error.message : error);
707
+ process.exit(1);
708
+ }
709
+ });
710
+ // husky worktree pr <session-name>
711
+ worktreeCommand
712
+ .command("pr <session-name>")
713
+ .description("Create a pull request for worktree branch")
714
+ .option("-p, --project <path>", "Project directory (default: current directory)")
715
+ .requiredOption("-t, --title <title>", "PR title")
716
+ .option("-b, --body <body>", "PR body/description")
717
+ .option("--draft", "Create as draft PR")
718
+ .option("--push", "Push branch before creating PR")
719
+ .option("--task-id <id>", "Task ID to update with PR URL")
720
+ .option("--json", "Output as JSON")
721
+ .action(async (sessionName, options) => {
722
+ const config = getConfig();
723
+ try {
724
+ const manager = getManager(options);
725
+ const info = manager.getWorktree(sessionName);
726
+ if (!info) {
727
+ console.error(`Error: No worktree found for session: ${sessionName}`);
728
+ process.exit(1);
729
+ }
730
+ // Push first if requested
731
+ if (options.push) {
732
+ const pushSuccess = manager.pushWorktreeBranch(sessionName);
733
+ if (!pushSuccess) {
734
+ console.error("Failed to push branch");
735
+ process.exit(1);
736
+ }
737
+ }
738
+ // Create PR
739
+ const result = manager.createPullRequest(sessionName, {
740
+ title: options.title,
741
+ body: options.body,
742
+ draft: options.draft,
743
+ });
744
+ if (!result.success) {
745
+ console.error(`Error creating PR: ${result.error}`);
746
+ process.exit(1);
747
+ }
748
+ // Update task with PR URL if specified
749
+ if (options.taskId && config.apiUrl && result.prUrl) {
750
+ try {
751
+ await fetch(`${config.apiUrl}/api/tasks/${options.taskId}`, {
752
+ method: "PATCH",
753
+ headers: {
754
+ "Content-Type": "application/json",
755
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
756
+ },
757
+ body: JSON.stringify({
758
+ result: {
759
+ prUrl: result.prUrl,
760
+ completedAt: new Date().toISOString(),
761
+ },
762
+ }),
763
+ });
764
+ }
765
+ catch {
766
+ console.warn("Warning: Could not update task with PR URL");
767
+ }
768
+ }
769
+ if (options.json) {
770
+ console.log(JSON.stringify({
771
+ success: true,
772
+ sessionName,
773
+ branch: info.branch,
774
+ prUrl: result.prUrl,
775
+ }, null, 2));
776
+ }
777
+ else {
778
+ console.log(`✓ Pull request created: ${result.prUrl}`);
779
+ if (options.taskId) {
780
+ console.log(` Updated task ${options.taskId}`);
781
+ }
782
+ }
783
+ }
784
+ catch (error) {
785
+ console.error("Error creating PR:", error instanceof Error ? error.message : error);
786
+ process.exit(1);
787
+ }
788
+ });
789
+ // husky worktree sync-all
790
+ worktreeCommand
791
+ .command("sync-all")
792
+ .description("Sync stats and check conflicts for all worktrees with linked tasks")
793
+ .option("-p, --project <path>", "Project directory (default: current directory)")
794
+ .option("--json", "Output as JSON")
795
+ .action(async (options) => {
796
+ const config = getConfig();
797
+ if (!config.apiUrl) {
798
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
799
+ process.exit(1);
800
+ }
801
+ try {
802
+ const manager = getManager(options);
803
+ const worktrees = manager.listWorktrees();
804
+ if (worktrees.length === 0) {
805
+ console.log("No worktrees found.");
806
+ return;
807
+ }
808
+ // Fetch tasks with worktrees to find linked tasks
809
+ const tasksRes = await fetch(`${config.apiUrl}/api/worktrees`, {
810
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
811
+ });
812
+ if (!tasksRes.ok) {
813
+ throw new Error(`API error: ${tasksRes.status}`);
814
+ }
815
+ const tasksData = await tasksRes.json();
816
+ const tasksByBranch = new Map();
817
+ for (const wt of tasksData.worktrees || []) {
818
+ tasksByBranch.set(wt.worktreeBranch, wt.taskId);
819
+ }
820
+ const results = [];
821
+ for (const wt of worktrees) {
822
+ const taskId = tasksByBranch.get(wt.branch);
823
+ if (!taskId) {
824
+ results.push({ sessionName: wt.sessionName, synced: false });
825
+ continue;
826
+ }
827
+ // Check conflicts
828
+ const conflictResult = manager.checkMergeConflicts(wt.sessionName);
829
+ // Prepare update
830
+ const updatePayload = {
831
+ worktreePath: wt.path,
832
+ worktreeBranch: wt.branch,
833
+ worktreeStats: {
834
+ commitCount: wt.stats.commitCount,
835
+ filesChanged: wt.stats.filesChanged,
836
+ additions: wt.stats.additions,
837
+ deletions: wt.stats.deletions,
838
+ lastUpdated: new Date().toISOString(),
839
+ },
840
+ worktreeHasConflicts: conflictResult.hasConflicts,
841
+ worktreeConflictFiles: conflictResult.conflictFiles,
842
+ worktreeConflictCheckedAt: conflictResult.checkedAt.toISOString(),
843
+ };
844
+ // Update task
845
+ const updateRes = await fetch(`${config.apiUrl}/api/tasks/${taskId}`, {
846
+ method: "PATCH",
847
+ headers: {
848
+ "Content-Type": "application/json",
849
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
850
+ },
851
+ body: JSON.stringify(updatePayload),
852
+ });
853
+ results.push({
854
+ sessionName: wt.sessionName,
855
+ taskId,
856
+ synced: updateRes.ok,
857
+ hasConflicts: conflictResult.hasConflicts,
858
+ });
859
+ }
860
+ if (options.json) {
861
+ console.log(JSON.stringify({ results }, null, 2));
862
+ }
863
+ else {
864
+ console.log(`\n WORKTREE SYNC RESULTS`);
865
+ console.log(" " + "-".repeat(60));
866
+ for (const r of results) {
867
+ const icon = r.synced ? "✓" : r.taskId ? "✗" : "○";
868
+ const conflictIcon = r.hasConflicts ? " ⚠" : "";
869
+ const taskInfo = r.taskId ? ` → ${r.taskId}` : " (no linked task)";
870
+ console.log(` ${icon} ${r.sessionName}${taskInfo}${conflictIcon}`);
871
+ }
872
+ const synced = results.filter(r => r.synced).length;
873
+ const conflicts = results.filter(r => r.hasConflicts).length;
874
+ console.log("");
875
+ console.log(` Synced: ${synced}/${results.length}`);
876
+ if (conflicts > 0) {
877
+ console.log(` With conflicts: ${conflicts}`);
878
+ }
879
+ }
880
+ }
881
+ catch (error) {
882
+ console.error("Error syncing worktrees:", error instanceof Error ? error.message : error);
883
+ process.exit(1);
884
+ }
885
+ });
package/dist/index.js CHANGED
@@ -20,13 +20,15 @@ import { completionCommand } from "./commands/completion.js";
20
20
  import { worktreeCommand } from "./commands/worktree.js";
21
21
  import { workerCommand } from "./commands/worker.js";
22
22
  import { bizCommand } from "./commands/biz.js";
23
+ import { servicesCommand } from "./commands/services.js";
23
24
  import { printLLMContext, llmCommand } from "./commands/llm-context.js";
24
25
  import { runInteractiveMode } from "./commands/interactive.js";
26
+ import { serviceAccountCommand } from "./commands/service-account.js";
25
27
  const program = new Command();
26
28
  program
27
29
  .name("husky")
28
30
  .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
29
- .version("0.9.5")
31
+ .version("0.9.7")
30
32
  .option("--llm", "Output LLM reference documentation (markdown)");
31
33
  program.addCommand(taskCommand);
32
34
  program.addCommand(configCommand);
@@ -48,6 +50,8 @@ program.addCommand(completionCommand);
48
50
  program.addCommand(worktreeCommand);
49
51
  program.addCommand(workerCommand);
50
52
  program.addCommand(bizCommand);
53
+ program.addCommand(servicesCommand);
54
+ program.addCommand(serviceAccountCommand);
51
55
  program.addCommand(llmCommand);
52
56
  // Handle --llm flag specially
53
57
  if (process.argv.includes("--llm")) {
@@ -0,0 +1,20 @@
1
+ export interface AgentTypeConfig {
2
+ directories: string[];
3
+ dependencies: string[];
4
+ defaultPrompt: string;
5
+ geminiMdTemplate?: string;
6
+ }
7
+ export interface AgentType {
8
+ id: string;
9
+ departmentId: string;
10
+ name: string;
11
+ slug: string;
12
+ description: string;
13
+ agentConfig: AgentTypeConfig;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+ export declare const DEFAULT_AGENT_CONFIGS: Record<string, AgentTypeConfig>;
18
+ export declare function generateStartupScript(agentType: AgentType | string, huskyApiUrl?: string, huskyApiKey?: string, gcpProject?: string): string;
19
+ export declare function getDefaultAgentConfig(slug: string): AgentTypeConfig | undefined;
20
+ export declare function listDefaultAgentTypes(): string[];
@@ -0,0 +1,142 @@
1
+ export const DEFAULT_AGENT_CONFIGS = {
2
+ support: {
3
+ directories: [
4
+ "sops",
5
+ "tickets",
6
+ "customers",
7
+ "templates",
8
+ "knowledge/auto",
9
+ "learning",
10
+ "logs",
11
+ ],
12
+ dependencies: ["google-cloud-firestore"],
13
+ defaultPrompt: "Du bist ein Support Agent. Starte mit /shift-start um die Schicht zu beginnen.",
14
+ },
15
+ accounting: {
16
+ directories: [
17
+ "inbox/unprocessed",
18
+ "inbox/processed",
19
+ "inbox/failed",
20
+ "documents/invoices",
21
+ "documents/receipts",
22
+ "exports",
23
+ "logs",
24
+ ],
25
+ dependencies: ["google-cloud-firestore", "google-cloud-storage"],
26
+ defaultPrompt: "Du bist ein Accounting Agent. Pruefe /inbox fuer neue Belege.",
27
+ },
28
+ marketing: {
29
+ directories: [
30
+ "campaigns/active",
31
+ "campaigns/drafts",
32
+ "newsletters/templates",
33
+ "newsletters/sent",
34
+ "assets/images",
35
+ "analytics",
36
+ "logs",
37
+ ],
38
+ dependencies: ["google-cloud-firestore"],
39
+ defaultPrompt: "Du bist ein Marketing Agent. Pruefe /campaigns fuer aktuelle Kampagnen.",
40
+ },
41
+ research: {
42
+ directories: [
43
+ "sources/youtube",
44
+ "sources/competitors",
45
+ "products/images/raw",
46
+ "products/images/processed",
47
+ "products/data",
48
+ "reports",
49
+ "logs",
50
+ ],
51
+ dependencies: ["google-cloud-firestore", "yt-dlp"],
52
+ defaultPrompt: "Du bist ein Research Agent. Pruefe /youtube fuer neue Videos zu analysieren.",
53
+ },
54
+ };
55
+ export function generateStartupScript(agentType, huskyApiUrl, huskyApiKey, gcpProject) {
56
+ let config;
57
+ let typeName;
58
+ let typeSlug;
59
+ if (typeof agentType === "string") {
60
+ config = DEFAULT_AGENT_CONFIGS[agentType] || DEFAULT_AGENT_CONFIGS.support;
61
+ typeName =
62
+ agentType.charAt(0).toUpperCase() + agentType.slice(1) + " Agent";
63
+ typeSlug = agentType;
64
+ }
65
+ else {
66
+ config = agentType.agentConfig;
67
+ typeName = agentType.name;
68
+ typeSlug = agentType.slug;
69
+ }
70
+ const dirsToCreate = config.directories
71
+ .map((d) => `"$WORKSPACE/${d}"`)
72
+ .join(" ");
73
+ const pipDeps = config.dependencies.join(" ");
74
+ return `#!/bin/bash
75
+ exec > /var/log/agent-init.log 2>&1
76
+ set -e
77
+
78
+ echo "=== AGENT VM STARTUP ==="
79
+ echo "Type: ${typeSlug}"
80
+ echo "Name: ${typeName}"
81
+ echo "Time: $(date)"
82
+
83
+ AGENT_TYPE="${typeSlug}"
84
+ AGENT_NAME="${typeName}"
85
+ HUSKY_API_URL="${huskyApiUrl || ""}"
86
+ HUSKY_API_KEY="${huskyApiKey || ""}"
87
+ GCP_PROJECT="${gcpProject || "$(curl -s http://metadata.google.internal/computeMetadata/v1/project/project-id -H Metadata-Flavor:Google)"}"
88
+
89
+ AGENT_USER="agent"
90
+ WORKSPACE="/home/$AGENT_USER/workspace"
91
+
92
+ if ! id "$AGENT_USER" &>/dev/null; then
93
+ useradd -m -s /bin/bash "$AGENT_USER"
94
+ usermod -aG sudo "$AGENT_USER"
95
+ echo "$AGENT_USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
96
+ fi
97
+
98
+ echo "[1/5] Installing system packages..."
99
+ apt-get update -qq
100
+ apt-get install -y -qq nodejs npm jq python3-pip imagemagick curl
101
+
102
+ echo "[2/5] Installing CLI tools..."
103
+ npm install -g @google/gemini-cli @huskyv0/cli 2>/dev/null || true
104
+
105
+ echo "[3/5] Installing Python dependencies..."
106
+ pip3 install ${pipDeps} 2>/dev/null || true
107
+
108
+ echo "[4/5] Creating workspace..."
109
+ sudo -u "$AGENT_USER" mkdir -p ${dirsToCreate}
110
+ sudo -u "$AGENT_USER" mkdir -p "$WORKSPACE/.gemini/commands" "$WORKSPACE/scripts"
111
+
112
+ echo "[5/5] Configuring environment..."
113
+ cat >> "/home/$AGENT_USER/.bashrc" << 'BASHRC'
114
+ export WORKSPACE="$HOME/workspace"
115
+ export AGENT_TYPE="${typeSlug}"
116
+ export AGENT_NAME="${typeName}"
117
+ export GOOGLE_CLOUD_PROJECT="$GCP_PROJECT"
118
+ export PATH="$WORKSPACE/scripts:$PATH"
119
+ alias ws="cd $WORKSPACE"
120
+ alias logs="tail -f $WORKSPACE/logs/$(date +%Y-%m-%d).log"
121
+ BASHRC
122
+
123
+ if [ -n "$HUSKY_API_URL" ] && [ -n "$HUSKY_API_KEY" ]; then
124
+ sudo -u "$AGENT_USER" bash -c "husky config set api-url '$HUSKY_API_URL'"
125
+ sudo -u "$AGENT_USER" bash -c "husky config set api-key '$HUSKY_API_KEY'"
126
+ fi
127
+
128
+ chown -R "$AGENT_USER:$AGENT_USER" "/home/$AGENT_USER"
129
+
130
+ echo "=== AGENT READY ==="
131
+ echo "Type: $AGENT_TYPE"
132
+ echo "Name: $AGENT_NAME"
133
+ echo "User: $AGENT_USER"
134
+ echo "Workspace: $WORKSPACE"
135
+ `;
136
+ }
137
+ export function getDefaultAgentConfig(slug) {
138
+ return DEFAULT_AGENT_CONFIGS[slug];
139
+ }
140
+ export function listDefaultAgentTypes() {
141
+ return Object.keys(DEFAULT_AGENT_CONFIGS);
142
+ }