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.
- package/README.md +96 -2
- package/dist/api/handlers/bloops.d.ts +4 -0
- package/dist/api/handlers/bloops.d.ts.map +1 -0
- package/dist/api/handlers/bloops.js +96 -0
- package/dist/api/handlers/bloops.js.map +1 -0
- package/dist/api/handlers/jobs.d.ts +4 -0
- package/dist/api/handlers/jobs.d.ts.map +1 -0
- package/dist/api/handlers/jobs.js +34 -0
- package/dist/api/handlers/jobs.js.map +1 -0
- package/dist/api/handlers/projects.d.ts +4 -0
- package/dist/api/handlers/projects.d.ts.map +1 -0
- package/dist/api/handlers/projects.js +75 -0
- package/dist/api/handlers/projects.js.map +1 -0
- package/dist/api/handlers/schedules.d.ts +4 -0
- package/dist/api/handlers/schedules.d.ts.map +1 -0
- package/dist/api/handlers/schedules.js +19 -0
- package/dist/api/handlers/schedules.js.map +1 -0
- package/dist/api/handlers/status.d.ts +4 -0
- package/dist/api/handlers/status.d.ts.map +1 -0
- package/dist/api/handlers/status.js +21 -0
- package/dist/api/handlers/status.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +17 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/utils.d.ts +5 -0
- package/dist/api/utils.d.ts.map +1 -0
- package/dist/api/utils.js +24 -0
- package/dist/api/utils.js.map +1 -0
- package/dist/chat/formatter.d.ts +43 -0
- package/dist/chat/formatter.d.ts.map +1 -0
- package/dist/chat/formatter.js +144 -0
- package/dist/chat/formatter.js.map +1 -0
- package/dist/chat/index.d.ts +33 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +253 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/intent.d.ts +12 -0
- package/dist/chat/intent.d.ts.map +1 -0
- package/dist/chat/intent.js +256 -0
- package/dist/chat/intent.js.map +1 -0
- package/dist/chat/providers/slack.d.ts +17 -0
- package/dist/chat/providers/slack.d.ts.map +1 -0
- package/dist/chat/providers/slack.js +90 -0
- package/dist/chat/providers/slack.js.map +1 -0
- package/dist/chat/providers/telegram.d.ts +15 -0
- package/dist/chat/providers/telegram.d.ts.map +1 -0
- package/dist/chat/providers/telegram.js +76 -0
- package/dist/chat/providers/telegram.js.map +1 -0
- package/dist/chat/providers/terminal.d.ts +14 -0
- package/dist/chat/providers/terminal.d.ts.map +1 -0
- package/dist/chat/providers/terminal.js +77 -0
- package/dist/chat/providers/terminal.js.map +1 -0
- package/dist/chat/providers/websocket.d.ts +16 -0
- package/dist/chat/providers/websocket.d.ts.map +1 -0
- package/dist/chat/providers/websocket.js +125 -0
- package/dist/chat/providers/websocket.js.map +1 -0
- package/dist/chat/skippy.d.ts +3 -0
- package/dist/chat/skippy.d.ts.map +1 -0
- package/dist/chat/skippy.js +47 -0
- package/dist/chat/skippy.js.map +1 -0
- package/dist/chat/types.d.ts +50 -0
- package/dist/chat/types.d.ts.map +1 -0
- package/dist/chat/types.js +2 -0
- package/dist/chat/types.js.map +1 -0
- package/dist/cli.js +232 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -2
- package/dist/config.js.map +1 -1
- package/dist/core/job-queue.d.ts +9 -1
- package/dist/core/job-queue.d.ts.map +1 -1
- package/dist/core/job-queue.js +33 -0
- package/dist/core/job-queue.js.map +1 -1
- package/dist/core/runner.d.ts +2 -0
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +37 -7
- package/dist/core/runner.js.map +1 -1
- package/dist/events/daemon.d.ts +1 -1
- package/dist/events/daemon.d.ts.map +1 -1
- package/dist/events/daemon.js +35 -1
- package/dist/events/daemon.js.map +1 -1
- package/dist/events/sources/webhook-source.d.ts +2 -1
- package/dist/events/sources/webhook-source.d.ts.map +1 -1
- package/dist/events/sources/webhook-source.js +77 -5
- package/dist/events/sources/webhook-source.js.map +1 -1
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -2
- package/dist/index.js.map +1 -1
- package/dist/storage/database.d.ts +20 -0
- package/dist/storage/database.d.ts.map +1 -1
- package/dist/storage/database.js +46 -0
- package/dist/storage/database.js.map +1 -1
- package/package.json +14 -4
- 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 @@
|
|
|
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
|
-
// ──
|
|
492
|
-
case "
|
|
493
|
-
|
|
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
|
-
//
|
|
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("
|
|
529
|
-
console.log(chalk.cyan("
|
|
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
|
|
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
|
-
|
|
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
|