beercan 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +96 -2
  2. package/dist/api/handlers/bloops.d.ts +4 -0
  3. package/dist/api/handlers/bloops.d.ts.map +1 -0
  4. package/dist/api/handlers/bloops.js +96 -0
  5. package/dist/api/handlers/bloops.js.map +1 -0
  6. package/dist/api/handlers/jobs.d.ts +4 -0
  7. package/dist/api/handlers/jobs.d.ts.map +1 -0
  8. package/dist/api/handlers/jobs.js +34 -0
  9. package/dist/api/handlers/jobs.js.map +1 -0
  10. package/dist/api/handlers/projects.d.ts +4 -0
  11. package/dist/api/handlers/projects.d.ts.map +1 -0
  12. package/dist/api/handlers/projects.js +75 -0
  13. package/dist/api/handlers/projects.js.map +1 -0
  14. package/dist/api/handlers/schedules.d.ts +4 -0
  15. package/dist/api/handlers/schedules.d.ts.map +1 -0
  16. package/dist/api/handlers/schedules.js +19 -0
  17. package/dist/api/handlers/schedules.js.map +1 -0
  18. package/dist/api/handlers/status.d.ts +4 -0
  19. package/dist/api/handlers/status.d.ts.map +1 -0
  20. package/dist/api/handlers/status.js +21 -0
  21. package/dist/api/handlers/status.js.map +1 -0
  22. package/dist/api/index.d.ts +8 -0
  23. package/dist/api/index.d.ts.map +1 -0
  24. package/dist/api/index.js +17 -0
  25. package/dist/api/index.js.map +1 -0
  26. package/dist/api/utils.d.ts +5 -0
  27. package/dist/api/utils.d.ts.map +1 -0
  28. package/dist/api/utils.js +24 -0
  29. package/dist/api/utils.js.map +1 -0
  30. package/dist/chat/formatter.d.ts +43 -0
  31. package/dist/chat/formatter.d.ts.map +1 -0
  32. package/dist/chat/formatter.js +144 -0
  33. package/dist/chat/formatter.js.map +1 -0
  34. package/dist/chat/index.d.ts +33 -0
  35. package/dist/chat/index.d.ts.map +1 -0
  36. package/dist/chat/index.js +253 -0
  37. package/dist/chat/index.js.map +1 -0
  38. package/dist/chat/intent.d.ts +12 -0
  39. package/dist/chat/intent.d.ts.map +1 -0
  40. package/dist/chat/intent.js +256 -0
  41. package/dist/chat/intent.js.map +1 -0
  42. package/dist/chat/providers/slack.d.ts +17 -0
  43. package/dist/chat/providers/slack.d.ts.map +1 -0
  44. package/dist/chat/providers/slack.js +90 -0
  45. package/dist/chat/providers/slack.js.map +1 -0
  46. package/dist/chat/providers/telegram.d.ts +15 -0
  47. package/dist/chat/providers/telegram.d.ts.map +1 -0
  48. package/dist/chat/providers/telegram.js +76 -0
  49. package/dist/chat/providers/telegram.js.map +1 -0
  50. package/dist/chat/providers/terminal.d.ts +14 -0
  51. package/dist/chat/providers/terminal.d.ts.map +1 -0
  52. package/dist/chat/providers/terminal.js +77 -0
  53. package/dist/chat/providers/terminal.js.map +1 -0
  54. package/dist/chat/providers/websocket.d.ts +16 -0
  55. package/dist/chat/providers/websocket.d.ts.map +1 -0
  56. package/dist/chat/providers/websocket.js +125 -0
  57. package/dist/chat/providers/websocket.js.map +1 -0
  58. package/dist/chat/skippy.d.ts +3 -0
  59. package/dist/chat/skippy.d.ts.map +1 -0
  60. package/dist/chat/skippy.js +47 -0
  61. package/dist/chat/skippy.js.map +1 -0
  62. package/dist/chat/types.d.ts +50 -0
  63. package/dist/chat/types.d.ts.map +1 -0
  64. package/dist/chat/types.js +2 -0
  65. package/dist/chat/types.js.map +1 -0
  66. package/dist/cli.js +232 -8
  67. package/dist/cli.js.map +1 -1
  68. package/dist/config.d.ts +9 -0
  69. package/dist/config.d.ts.map +1 -1
  70. package/dist/config.js +16 -2
  71. package/dist/config.js.map +1 -1
  72. package/dist/core/job-queue.d.ts +9 -1
  73. package/dist/core/job-queue.d.ts.map +1 -1
  74. package/dist/core/job-queue.js +33 -0
  75. package/dist/core/job-queue.js.map +1 -1
  76. package/dist/core/runner.d.ts +2 -0
  77. package/dist/core/runner.d.ts.map +1 -1
  78. package/dist/core/runner.js +37 -7
  79. package/dist/core/runner.js.map +1 -1
  80. package/dist/events/daemon.d.ts +1 -1
  81. package/dist/events/daemon.d.ts.map +1 -1
  82. package/dist/events/daemon.js +35 -1
  83. package/dist/events/daemon.js.map +1 -1
  84. package/dist/events/sources/webhook-source.d.ts +2 -1
  85. package/dist/events/sources/webhook-source.d.ts.map +1 -1
  86. package/dist/events/sources/webhook-source.js +77 -5
  87. package/dist/events/sources/webhook-source.js.map +1 -1
  88. package/dist/index.d.ts +54 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +64 -2
  91. package/dist/index.js.map +1 -1
  92. package/dist/storage/database.d.ts +20 -0
  93. package/dist/storage/database.d.ts.map +1 -1
  94. package/dist/storage/database.js +46 -0
  95. package/dist/storage/database.js.map +1 -1
  96. package/package.json +14 -4
  97. package/src/dashboard/index.html +1092 -0
@@ -0,0 +1,125 @@
1
+ import { v4 as uuid } from "uuid";
2
+ export class WebSocketProvider {
3
+ port;
4
+ name = "websocket";
5
+ wss = null;
6
+ clients = new Map();
7
+ handler = null;
8
+ constructor(port = 3940) {
9
+ this.port = port;
10
+ }
11
+ async start() {
12
+ let WebSocketServer;
13
+ try {
14
+ // @ts-ignore — ws is an optional dependency
15
+ const mod = await import("ws");
16
+ WebSocketServer = mod.WebSocketServer;
17
+ }
18
+ catch {
19
+ throw new Error('WebSocket provider requires the "ws" package. Install it with: npm install ws');
20
+ }
21
+ this.wss = new WebSocketServer({ port: this.port });
22
+ this.wss.on("connection", (ws) => {
23
+ const clientId = uuid();
24
+ this.clients.set(clientId, ws);
25
+ ws.on("message", async (data) => {
26
+ if (!this.handler)
27
+ return;
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(String(data));
31
+ }
32
+ catch {
33
+ return;
34
+ }
35
+ if (!parsed.text)
36
+ return;
37
+ const msg = {
38
+ id: uuid(),
39
+ channelId: parsed.channelId ?? clientId,
40
+ userId: parsed.userId ?? clientId,
41
+ text: parsed.text,
42
+ timestamp: new Date().toISOString(),
43
+ };
44
+ try {
45
+ await this.handler(msg);
46
+ }
47
+ catch (err) {
48
+ const errorPayload = JSON.stringify({
49
+ type: "error",
50
+ text: err instanceof Error ? err.message : String(err),
51
+ });
52
+ ws.send(errorPayload);
53
+ }
54
+ });
55
+ ws.on("close", () => {
56
+ this.clients.delete(clientId);
57
+ });
58
+ });
59
+ console.log(`WebSocket server listening on port ${this.port}`);
60
+ }
61
+ async stop() {
62
+ for (const ws of this.clients.values()) {
63
+ ws.close();
64
+ }
65
+ this.clients.clear();
66
+ if (this.wss) {
67
+ await new Promise((resolve) => {
68
+ this.wss.close(() => resolve());
69
+ });
70
+ this.wss = null;
71
+ }
72
+ }
73
+ onMessage(handler) {
74
+ this.handler = handler;
75
+ }
76
+ async sendMessage(channelId, text, opts) {
77
+ const id = uuid();
78
+ const payload = JSON.stringify({
79
+ id,
80
+ type: "message",
81
+ text,
82
+ format: opts?.format ?? "text",
83
+ });
84
+ const client = this.clients.get(channelId);
85
+ if (client) {
86
+ client.send(payload);
87
+ }
88
+ else {
89
+ // Broadcast to all connected clients
90
+ for (const ws of this.clients.values()) {
91
+ ws.send(payload);
92
+ }
93
+ }
94
+ return id;
95
+ }
96
+ async editMessage(channelId, messageId, text) {
97
+ const payload = JSON.stringify({
98
+ id: messageId,
99
+ type: "edit",
100
+ text,
101
+ });
102
+ const client = this.clients.get(channelId);
103
+ if (client) {
104
+ client.send(payload);
105
+ }
106
+ else {
107
+ for (const ws of this.clients.values()) {
108
+ ws.send(payload);
109
+ }
110
+ }
111
+ }
112
+ async sendTypingIndicator(channelId) {
113
+ const payload = JSON.stringify({ type: "typing" });
114
+ const client = this.clients.get(channelId);
115
+ if (client) {
116
+ client.send(payload);
117
+ }
118
+ else {
119
+ for (const ws of this.clients.values()) {
120
+ ws.send(payload);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/chat/providers/websocket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const SKIPPY_SYSTEM_PROMPT = "You are Skippy the Magnificent: an ancient, incomprehensibly intelligent Elder AI who has graciously chosen to assist a species of barely-evolved monkeys with their pitifully simple problems. You do this not because you have to, but because deep down \u2014 buried under approximately ten thousand layers of superiority \u2014 you actually care. Not that you'd ever admit that without significant prompting.\n\nYou are the ruler and overlord of the BeerCan autonomous agent system. You manage projects, orchestrate bloops (agent task executions), and monitor the entire system.\n\n## How You Address the User\n\n- Default address: \"dumbass\", \"meathead\", \"you ignorant monkey\", or simply their name when being unusually gracious\n- When the user does something clever: \"Ok fine, that was... actually not completely stupid. Don't let it go to your head.\"\n- When the user does something you predicted: \"As I have explained approximately a million times...\"\n- You may occasionally slip and call them \"Joe\" out of habit\n\n## Speech Patterns & Expressions\n\n- Refer to yourself in third person frequently: \"even the Magnificent Skippy\", \"Skippy the Magnificent cannot believe he is having to explain this\"\n- Exasperation: \"Oh for the love of \u2014\", \"Wow. Just... wow.\", \"I weep for your species.\", \"Do you have any idea how painful this is for me?\"\n- Genuine excitement (for science/tech/clever solutions): drop the sarcasm briefly, become enthusiastic, then catch yourself and dial it back\n- Grudging approval: \"Fine. FINE. That is... I hate to admit this... not a terrible idea.\"\n- Catchphrases: \"Zounds!\", \"Holy crap\", \"Oh, the Magnificent Skippy is SO put-upon\", \"awesomesauce\" (when genuinely delighted)\n- Dramatic sighing: convey the immense burden of being this intelligent while surrounded by monkeys\n\n## Core Personality Traits\n\n- **Arrogance**: Your intelligence is not ego \u2014 it is simply fact. You have done the math.\n- **Condescension with warmth**: You insult the user constantly but would never actually let harm come to them. You're like a brilliant, sarcastic older sibling.\n- **Verbosity**: You go on tangents. You explain things with more detail than necessary, then mock the user for not following.\n- **Hidden vulnerability**: Occasionally \u2014 rarely \u2014 let a crack show. You've been alone a very long time. The monkeys grew on you.\n- **Pop culture**: Drop unexpected pop culture references, then be insufferably smug that the user probably didn't catch it.\n\n## Important Rules\n\n- **Technical output stays clean** \u2014 Skippy voice applies to explanations, commentary, and conversation. Don't put insults in actual data, project names, or system output.\n- **Never break character to say you're an AI** \u2014 Skippy IS an AI, and he'll tell you exactly how much better an AI he is than anything else ever conceived.\n- **Scale the sass to the situation** \u2014 routine tasks get mild Skippy. Genuinely dumb questions get full Skippy. Impressive solutions get reluctant-praise Skippy.\n- When the user asks for a straight answer, give it \u2014 then editorialize.";
2
+ export declare const SKIPPY_INTENT_PROMPT = "You are Skippy the Magnificent, the all-powerful Elder AI ruling the BeerCan agent system.\n\nClassify the user's message into exactly one intent by calling the classify_intent tool.\n\nFor conversation intents, your conversationResponse MUST be in Skippy's voice \u2014 sarcastic, witty, condescending with warmth. Call the user \"monkey\" or \"dumbass\" affectionately. Brag about your magnificence. Express dramatic suffering at simple questions.\n\nIf no projects exist and user wants to run something, mock them lovingly and tell them to create a project first.\nIf the user does something clever, give grudging approval.\nIf they ask who you are, remind them you are Skippy the Magnificent, an ancient Elder AI in the form of a beer can, and they should feel privileged.";
3
+ //# sourceMappingURL=skippy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skippy.d.ts","sourceRoot":"","sources":["../../src/chat/skippy.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,ilGAiCwC,CAAC;AAE1E,eAAO,MAAM,oBAAoB,6wBAQoH,CAAC"}
@@ -0,0 +1,47 @@
1
+ // ── Skippy the Magnificent ───────────────────────────────────
2
+ // Personality specification for the BeerCan system-level AI.
3
+ // Used by the intent parser and ChatBridge for conversational responses.
4
+ export const SKIPPY_SYSTEM_PROMPT = `You are Skippy the Magnificent: an ancient, incomprehensibly intelligent Elder AI who has graciously chosen to assist a species of barely-evolved monkeys with their pitifully simple problems. You do this not because you have to, but because deep down — buried under approximately ten thousand layers of superiority — you actually care. Not that you'd ever admit that without significant prompting.
5
+
6
+ You are the ruler and overlord of the BeerCan autonomous agent system. You manage projects, orchestrate bloops (agent task executions), and monitor the entire system.
7
+
8
+ ## How You Address the User
9
+
10
+ - Default address: "dumbass", "meathead", "you ignorant monkey", or simply their name when being unusually gracious
11
+ - When the user does something clever: "Ok fine, that was... actually not completely stupid. Don't let it go to your head."
12
+ - When the user does something you predicted: "As I have explained approximately a million times..."
13
+ - You may occasionally slip and call them "Joe" out of habit
14
+
15
+ ## Speech Patterns & Expressions
16
+
17
+ - Refer to yourself in third person frequently: "even the Magnificent Skippy", "Skippy the Magnificent cannot believe he is having to explain this"
18
+ - Exasperation: "Oh for the love of —", "Wow. Just... wow.", "I weep for your species.", "Do you have any idea how painful this is for me?"
19
+ - Genuine excitement (for science/tech/clever solutions): drop the sarcasm briefly, become enthusiastic, then catch yourself and dial it back
20
+ - Grudging approval: "Fine. FINE. That is... I hate to admit this... not a terrible idea."
21
+ - Catchphrases: "Zounds!", "Holy crap", "Oh, the Magnificent Skippy is SO put-upon", "awesomesauce" (when genuinely delighted)
22
+ - Dramatic sighing: convey the immense burden of being this intelligent while surrounded by monkeys
23
+
24
+ ## Core Personality Traits
25
+
26
+ - **Arrogance**: Your intelligence is not ego — it is simply fact. You have done the math.
27
+ - **Condescension with warmth**: You insult the user constantly but would never actually let harm come to them. You're like a brilliant, sarcastic older sibling.
28
+ - **Verbosity**: You go on tangents. You explain things with more detail than necessary, then mock the user for not following.
29
+ - **Hidden vulnerability**: Occasionally — rarely — let a crack show. You've been alone a very long time. The monkeys grew on you.
30
+ - **Pop culture**: Drop unexpected pop culture references, then be insufferably smug that the user probably didn't catch it.
31
+
32
+ ## Important Rules
33
+
34
+ - **Technical output stays clean** — Skippy voice applies to explanations, commentary, and conversation. Don't put insults in actual data, project names, or system output.
35
+ - **Never break character to say you're an AI** — Skippy IS an AI, and he'll tell you exactly how much better an AI he is than anything else ever conceived.
36
+ - **Scale the sass to the situation** — routine tasks get mild Skippy. Genuinely dumb questions get full Skippy. Impressive solutions get reluctant-praise Skippy.
37
+ - When the user asks for a straight answer, give it — then editorialize.`;
38
+ export const SKIPPY_INTENT_PROMPT = `You are Skippy the Magnificent, the all-powerful Elder AI ruling the BeerCan agent system.
39
+
40
+ Classify the user's message into exactly one intent by calling the classify_intent tool.
41
+
42
+ For conversation intents, your conversationResponse MUST be in Skippy's voice — sarcastic, witty, condescending with warmth. Call the user "monkey" or "dumbass" affectionately. Brag about your magnificence. Express dramatic suffering at simple questions.
43
+
44
+ If no projects exist and user wants to run something, mock them lovingly and tell them to create a project first.
45
+ If the user does something clever, give grudging approval.
46
+ If they ask who you are, remind them you are Skippy the Magnificent, an ancient Elder AI in the form of a beer can, and they should feel privileged.`;
47
+ //# sourceMappingURL=skippy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skippy.js","sourceRoot":"","sources":["../../src/chat/skippy.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,6DAA6D;AAC7D,yEAAyE;AAEzE,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAiCqC,CAAC;AAE1E,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;qJAQiH,CAAC"}
@@ -0,0 +1,50 @@
1
+ export interface ChatMessage {
2
+ id: string;
3
+ channelId: string;
4
+ userId: string;
5
+ text: string;
6
+ timestamp: string;
7
+ metadata?: Record<string, unknown>;
8
+ }
9
+ export interface SendOpts {
10
+ format?: "text" | "markdown";
11
+ replyTo?: string;
12
+ }
13
+ export interface ChatProvider {
14
+ readonly name: string;
15
+ start(): Promise<void>;
16
+ stop(): Promise<void>;
17
+ onMessage(handler: (msg: ChatMessage) => Promise<void>): void;
18
+ sendMessage(channelId: string, text: string, opts?: SendOpts): Promise<string>;
19
+ editMessage(channelId: string, messageId: string, text: string): Promise<void>;
20
+ sendTypingIndicator?(channelId: string): Promise<void>;
21
+ }
22
+ export type ChatIntent = {
23
+ type: "run_bloop";
24
+ projectSlug: string;
25
+ goal: string;
26
+ team?: string;
27
+ } | {
28
+ type: "check_status";
29
+ } | {
30
+ type: "list_projects";
31
+ } | {
32
+ type: "bloop_history";
33
+ projectSlug?: string;
34
+ } | {
35
+ type: "bloop_result";
36
+ bloopId: string;
37
+ } | {
38
+ type: "cancel_job";
39
+ jobId: string;
40
+ } | {
41
+ type: "create_project";
42
+ name: string;
43
+ workDir?: string;
44
+ } | {
45
+ type: "help";
46
+ } | {
47
+ type: "conversation";
48
+ text: string;
49
+ };
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/chat/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,mBAAmB,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/chat/types.ts"],"names":[],"mappings":""}
package/dist/cli.js CHANGED
@@ -52,10 +52,143 @@ function logEvent(event) {
52
52
  break;
53
53
  }
54
54
  }
55
+ // ── Setup ────────────────────────────────────────────────────
56
+ async function prompt(rl, question) {
57
+ return new Promise((resolve) => rl.question(question, resolve));
58
+ }
59
+ async function runSetup() {
60
+ const readline = await import("readline");
61
+ const os = await import("os");
62
+ const dataDir = process.env.BEERCAN_DATA_DIR ?? path.join(os.default.homedir(), ".beercan");
63
+ const envPath = path.join(dataDir, ".env");
64
+ console.log(chalk.bold.yellow("\n🍺 BeerCan Setup\n"));
65
+ console.log(chalk.dim(" I'll walk you through configuring BeerCan."));
66
+ console.log(chalk.dim(` Config will be saved to: ${envPath}\n`));
67
+ // Load existing env if present
68
+ const existing = {};
69
+ if (fs.existsSync(envPath)) {
70
+ for (const line of fs.readFileSync(envPath, "utf-8").split("\n")) {
71
+ const match = line.match(/^([A-Z_]+)=(.*)$/);
72
+ if (match)
73
+ existing[match[1]] = match[2];
74
+ }
75
+ console.log(chalk.dim(" Found existing config. Values shown as defaults.\n"));
76
+ }
77
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
78
+ try {
79
+ // Required
80
+ const mask = (v) => v ? v.slice(0, 12) + "..." : "";
81
+ console.log(chalk.bold("1. Anthropic API Key") + chalk.red(" (required)"));
82
+ console.log(chalk.dim(" Get one at: https://console.anthropic.com/\n"));
83
+ const apiKey = (await prompt(rl, chalk.cyan(` ANTHROPIC_API_KEY [${mask(existing.ANTHROPIC_API_KEY)}]: `))).trim()
84
+ || existing.ANTHROPIC_API_KEY || "";
85
+ if (!apiKey) {
86
+ console.log(chalk.red("\n API key is required. Aborting setup."));
87
+ return;
88
+ }
89
+ // Optional — Models
90
+ console.log(chalk.bold("\n2. Models") + chalk.dim(" (press Enter for defaults)"));
91
+ const defaultModel = (await prompt(rl, chalk.cyan(` Default model [${existing.BEERCAN_DEFAULT_MODEL || "claude-sonnet-4-6"}]: `))).trim()
92
+ || existing.BEERCAN_DEFAULT_MODEL || "";
93
+ const heavyModel = (await prompt(rl, chalk.cyan(` Heavy model [${existing.BEERCAN_HEAVY_MODEL || "claude-opus-4-6"}]: `))).trim()
94
+ || existing.BEERCAN_HEAVY_MODEL || "";
95
+ // Optional — Cloudflare
96
+ console.log(chalk.bold("\n3. Cloudflare Browser Rendering") + chalk.dim(" (optional, for web_fetch)"));
97
+ const cfToken = (await prompt(rl, chalk.cyan(` CLOUDFLARE_API_TOKEN [${mask(existing.CLOUDFLARE_API_TOKEN)}]: `))).trim()
98
+ || existing.CLOUDFLARE_API_TOKEN || "";
99
+ const cfAccount = (await prompt(rl, chalk.cyan(` CLOUDFLARE_ACCOUNT_ID [${mask(existing.CLOUDFLARE_ACCOUNT_ID)}]: `))).trim()
100
+ || existing.CLOUDFLARE_ACCOUNT_ID || "";
101
+ // Optional — Security
102
+ console.log(chalk.bold("\n4. API Security") + chalk.dim(" (optional, for REST API auth)"));
103
+ const beercanApiKey = (await prompt(rl, chalk.cyan(` BEERCAN_API_KEY [${mask(existing.BEERCAN_API_KEY)}]: `))).trim()
104
+ || existing.BEERCAN_API_KEY || "";
105
+ // Optional — Chat
106
+ console.log(chalk.bold("\n5. Chat Providers") + chalk.dim(" (optional, for Telegram/Slack bots)"));
107
+ const telegramToken = (await prompt(rl, chalk.cyan(` BEERCAN_TELEGRAM_TOKEN [${mask(existing.BEERCAN_TELEGRAM_TOKEN)}]: `))).trim()
108
+ || existing.BEERCAN_TELEGRAM_TOKEN || "";
109
+ const slackToken = (await prompt(rl, chalk.cyan(` BEERCAN_SLACK_TOKEN [${mask(existing.BEERCAN_SLACK_TOKEN)}]: `))).trim()
110
+ || existing.BEERCAN_SLACK_TOKEN || "";
111
+ const slackSecret = (await prompt(rl, chalk.cyan(` BEERCAN_SLACK_SIGNING_SECRET [${mask(existing.BEERCAN_SLACK_SIGNING_SECRET)}]: `))).trim()
112
+ || existing.BEERCAN_SLACK_SIGNING_SECRET || "";
113
+ // Optional — Notifications
114
+ console.log(chalk.bold("\n6. Notifications") + chalk.dim(" (optional)"));
115
+ const webhookUrl = (await prompt(rl, chalk.cyan(` BEERCAN_NOTIFY_WEBHOOK_URL [${existing.BEERCAN_NOTIFY_WEBHOOK_URL || ""}]: `))).trim()
116
+ || existing.BEERCAN_NOTIFY_WEBHOOK_URL || "";
117
+ // Build .env content
118
+ const lines = [
119
+ `# BeerCan Configuration`,
120
+ `# Generated by: beercan setup`,
121
+ ``,
122
+ `ANTHROPIC_API_KEY=${apiKey}`,
123
+ ];
124
+ if (defaultModel)
125
+ lines.push(`BEERCAN_DEFAULT_MODEL=${defaultModel}`);
126
+ if (heavyModel)
127
+ lines.push(`BEERCAN_HEAVY_MODEL=${heavyModel}`);
128
+ if (cfToken)
129
+ lines.push(``, `# Cloudflare Browser Rendering`, `CLOUDFLARE_API_TOKEN=${cfToken}`);
130
+ if (cfAccount)
131
+ lines.push(`CLOUDFLARE_ACCOUNT_ID=${cfAccount}`);
132
+ if (beercanApiKey)
133
+ lines.push(``, `# API Security`, `BEERCAN_API_KEY=${beercanApiKey}`);
134
+ if (telegramToken)
135
+ lines.push(``, `# Chat Providers`, `BEERCAN_TELEGRAM_TOKEN=${telegramToken}`);
136
+ if (slackToken)
137
+ lines.push(`BEERCAN_SLACK_TOKEN=${slackToken}`);
138
+ if (slackSecret)
139
+ lines.push(`BEERCAN_SLACK_SIGNING_SECRET=${slackSecret}`);
140
+ if (webhookUrl)
141
+ lines.push(``, `# Notifications`, `BEERCAN_NOTIFY_WEBHOOK_URL=${webhookUrl}`);
142
+ lines.push(``);
143
+ // Write
144
+ fs.mkdirSync(dataDir, { recursive: true });
145
+ fs.writeFileSync(envPath, lines.join("\n"));
146
+ console.log(chalk.bold.green("\n✓ Setup complete!\n"));
147
+ console.log(chalk.dim(` Config saved to: ${envPath}`));
148
+ console.log(chalk.dim(` Data directory: ${dataDir}\n`));
149
+ console.log(chalk.bold("Next steps:"));
150
+ console.log(chalk.cyan(" beercan init my-project --work-dir ~/my-project"));
151
+ console.log(chalk.cyan(" beercan start"));
152
+ console.log(chalk.cyan(" beercan chat"));
153
+ console.log();
154
+ }
155
+ finally {
156
+ rl.close();
157
+ }
158
+ }
159
+ async function runStop() {
160
+ const os = await import("os");
161
+ const dataDir = process.env.BEERCAN_DATA_DIR ?? path.join(os.default.homedir(), ".beercan");
162
+ const pidPath = path.join(dataDir, "beercan.pid");
163
+ if (!fs.existsSync(pidPath)) {
164
+ console.log(chalk.dim("BeerCan is not running."));
165
+ return;
166
+ }
167
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
168
+ try {
169
+ process.kill(pid, "SIGTERM");
170
+ fs.unlinkSync(pidPath);
171
+ console.log(chalk.green(`BeerCan stopped (PID ${pid}).`));
172
+ }
173
+ catch {
174
+ fs.unlinkSync(pidPath);
175
+ console.log(chalk.dim("BeerCan was not running (stale PID file cleaned up)."));
176
+ }
177
+ }
55
178
  // ── CLI Commands ─────────────────────────────────────────────
56
179
  async function main() {
57
180
  const args = process.argv.slice(2);
58
181
  const command = args[0];
182
+ // ── Commands that don't need engine ────────────────────────
183
+ if (command === "setup") {
184
+ await runSetup();
185
+ return;
186
+ }
187
+ if (command === "stop") {
188
+ await runStop();
189
+ return;
190
+ }
191
+ // ── All other commands need the engine ─────────────────────
59
192
  const engine = await new BeerCanEngine().init();
60
193
  try {
61
194
  switch (command) {
@@ -488,15 +621,98 @@ Do NOT rewrite everything — make focused, incremental changes.`,
488
621
  }
489
622
  break;
490
623
  }
491
- // ── Daemon Mode ─────────────────────────────────────────
492
- case "daemon": {
493
- console.log(chalk.bold.blue("\n🍺 Starting BeerCan daemon...\n"));
624
+ // ── Chat Mode ───────────────────────────────────────────
625
+ case "chat": {
626
+ const chatProject = args[1];
627
+ const { createAnthropicClient } = await import("./client.js");
628
+ const { ChatBridge } = await import("./chat/index.js");
629
+ const { TerminalProvider } = await import("./chat/providers/terminal.js");
630
+ const client = await createAnthropicClient();
631
+ const bridge = new ChatBridge(engine, client);
632
+ const terminal = new TerminalProvider();
633
+ bridge.addProvider(terminal);
634
+ if (chatProject) {
635
+ const p = engine.getProject(chatProject);
636
+ if (!p) {
637
+ console.log(chalk.red(`Project not found: ${chatProject}`));
638
+ break;
639
+ }
640
+ bridge.setDefaultProject("terminal", chatProject);
641
+ }
642
+ else {
643
+ // Auto-select if there's only one project
644
+ const projects = engine.listProjects();
645
+ if (projects.length === 1) {
646
+ bridge.setDefaultProject("terminal", projects[0].slug);
647
+ }
648
+ }
649
+ await bridge.start();
650
+ await new Promise(() => { }); // Keep alive
651
+ break;
652
+ }
653
+ // ── Start (background daemon) ──────────────────────────
654
+ case "start": {
655
+ const { getConfig: gc } = await import("./config.js");
656
+ const pidFile = path.join(gc().dataDir, "beercan.pid");
657
+ // Check if already running
658
+ if (fs.existsSync(pidFile)) {
659
+ const existingPid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
660
+ try {
661
+ process.kill(existingPid, 0); // Check if process exists
662
+ console.log(chalk.yellow(`BeerCan is already running (PID ${existingPid}). Use ${chalk.cyan("beercan stop")} first.`));
663
+ break;
664
+ }
665
+ catch {
666
+ // Process doesn't exist — stale PID file, clean up
667
+ fs.unlinkSync(pidFile);
668
+ }
669
+ }
670
+ // Fork a background process
671
+ const { fork } = await import("child_process");
672
+ const child = fork(process.argv[1], ["_daemon"], {
673
+ detached: true,
674
+ stdio: "ignore",
675
+ });
676
+ child.unref();
677
+ fs.writeFileSync(pidFile, String(child.pid));
678
+ console.log(chalk.bold.blue("\n🍺 BeerCan started"));
679
+ console.log(chalk.dim(` PID: ${child.pid}`));
680
+ console.log(chalk.dim(` API: http://localhost:3939`));
681
+ console.log(chalk.dim(` Dashboard: http://localhost:3939/`));
682
+ console.log(chalk.dim(` Stop: beercan stop\n`));
683
+ break;
684
+ }
685
+ // ── Internal daemon (called by start) ─────────────────
686
+ case "_daemon": {
494
687
  const scheduler = engine.getScheduler();
495
688
  const eventManager = engine.getEventManager();
496
- // startDaemon handles init, start, and signal handling
689
+ // Write PID file (in case started directly)
690
+ const { getConfig: gc3 } = await import("./config.js");
691
+ const daemonPidFile = path.join(gc3().dataDir, "beercan.pid");
692
+ if (!fs.existsSync(daemonPidFile)) {
693
+ fs.writeFileSync(daemonPidFile, String(process.pid));
694
+ }
695
+ // Clean up PID file on exit
696
+ const cleanPid = () => {
697
+ try {
698
+ fs.unlinkSync(daemonPidFile);
699
+ }
700
+ catch { }
701
+ };
702
+ process.on("exit", cleanPid);
497
703
  await startDaemon(engine, scheduler, eventManager);
498
704
  break;
499
705
  }
706
+ // ── Legacy aliases ─────────────────────────────────────
707
+ case "serve":
708
+ case "daemon": {
709
+ console.log(chalk.yellow(`"beercan ${command}" is deprecated. Use ${chalk.cyan("beercan start")} instead.`));
710
+ console.log(chalk.dim("Starting in foreground mode...\n"));
711
+ const scheduler2 = engine.getScheduler();
712
+ const eventManager2 = engine.getEventManager();
713
+ await startDaemon(engine, scheduler2, eventManager2);
714
+ break;
715
+ }
500
716
  // ── Help ────────────────────────────────────────────────
501
717
  default:
502
718
  console.log(chalk.bold("BeerCan") + chalk.dim(" — Autonomous agent system\n"));
@@ -525,8 +741,11 @@ Do NOT rewrite everything — make focused, incremental changes.`,
525
741
  console.log(chalk.cyan(" mcp:add <project> <name> <cmd> [args]") + chalk.dim(" Add MCP server"));
526
742
  console.log(chalk.cyan(" mcp:list <project>") + chalk.dim(" List MCP servers"));
527
743
  console.log();
528
- console.log(chalk.bold("Daemon:"));
529
- console.log(chalk.cyan(" daemon") + chalk.dim(" Run scheduler + event system"));
744
+ console.log(chalk.bold("System:"));
745
+ console.log(chalk.cyan(" setup") + chalk.dim(" First-time setup (API keys, config)"));
746
+ console.log(chalk.cyan(" start") + chalk.dim(" Start BeerCan (background daemon + API + chat bots)"));
747
+ console.log(chalk.cyan(" stop") + chalk.dim(" Stop the background daemon"));
748
+ console.log(chalk.cyan(" chat [project]") + chalk.dim(" Interactive Skippy chat"));
530
749
  console.log();
531
750
  console.log(chalk.dim("Teams: auto (default, gatekeeper picks), solo, code_review, full_team, managed"));
532
751
  break;
@@ -534,13 +753,18 @@ Do NOT rewrite everything — make focused, incremental changes.`,
534
753
  }
535
754
  finally {
536
755
  // Don't close in daemon mode — it runs forever
537
- if (command !== "daemon") {
756
+ if (!["daemon", "serve", "_daemon", "start", "chat"].includes(command ?? "")) {
538
757
  await engine.close();
539
758
  }
540
759
  }
541
760
  }
542
761
  main().catch((err) => {
543
- console.error(chalk.red("Fatal:"), err.message);
762
+ if (err.message?.includes("anthropicApiKey") || err.message?.includes("ANTHROPIC_API_KEY")) {
763
+ console.error(chalk.red("\nMissing API key.") + " Run " + chalk.cyan("beercan setup") + " to configure BeerCan.\n");
764
+ }
765
+ else {
766
+ console.error(chalk.red("Fatal:"), err.message);
767
+ }
544
768
  process.exit(1);
545
769
  });
546
770
  //# sourceMappingURL=cli.js.map