@lightupai/polaris 0.0.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.
@@ -0,0 +1,115 @@
1
+ #!/bin/sh
2
+ # scripts/setup-slack-app.sh
3
+ # Creates a Slack app for Polaris and outputs credentials.
4
+ # Requires a Slack workspace admin token or manual steps.
5
+ #
6
+ # Unfortunately, Slack does not provide a CLI or API to create apps
7
+ # programmatically. This script automates what it can and opens the
8
+ # browser for the manual steps.
9
+
10
+ set -e
11
+
12
+ REDIRECT_URI="http://localhost:3000/slack/callback"
13
+ ENV_FILE=".env"
14
+
15
+ echo "=== Setting up Slack App for Polaris ==="
16
+ echo ""
17
+ echo "This script will open your browser to create a Slack app."
18
+ echo "Follow the steps, then paste the credentials here."
19
+ echo ""
20
+
21
+ # Step 1: Create the app
22
+ echo "Step 1: Create the Slack app"
23
+ echo "---"
24
+ echo " 1. Click 'Create New App' → 'From scratch'"
25
+ echo " 2. App Name: Polaris"
26
+ echo " 3. Workspace: your development workspace"
27
+ echo " 4. Click 'Create App'"
28
+ echo ""
29
+ open "https://api.slack.com/apps" 2>/dev/null || xdg-open "https://api.slack.com/apps" 2>/dev/null || echo "Open https://api.slack.com/apps in your browser"
30
+ echo "Press Enter when the app is created..."
31
+ read -r
32
+
33
+ # Step 2: OAuth redirect
34
+ echo ""
35
+ echo "Step 2: Set up OAuth"
36
+ echo "---"
37
+ echo " 1. In the left sidebar, go to 'OAuth & Permissions'"
38
+ echo " 2. Under 'Redirect URLs', click 'Add New Redirect URL'"
39
+ echo " 3. Add: ${REDIRECT_URI}"
40
+ echo " 4. Click 'Save URLs'"
41
+ echo ""
42
+ echo " 5. Scroll to 'Bot Token Scopes' and add these scopes:"
43
+ echo " - channels:manage"
44
+ echo " - channels:join"
45
+ echo " - channels:read"
46
+ echo " - chat:write"
47
+ echo " - users:read"
48
+ echo " - users:read.email"
49
+ echo ""
50
+ echo "Press Enter when done..."
51
+ read -r
52
+
53
+ # Step 3: Enable Socket Mode
54
+ echo ""
55
+ echo "Step 3: Enable Socket Mode"
56
+ echo "---"
57
+ echo " 1. In the left sidebar, go to 'Socket Mode'"
58
+ echo " 2. Toggle 'Enable Socket Mode' on"
59
+ echo " 3. Name the token: polaris-socket"
60
+ echo " 4. Scope: connections:write (should be pre-selected)"
61
+ echo " 5. Click 'Generate'"
62
+ echo " 6. Copy the token (starts with xapp-)"
63
+ echo ""
64
+ echo "Press Enter when done..."
65
+ read -r
66
+
67
+ # Step 4: Enable Events (required for Socket Mode)
68
+ echo ""
69
+ echo "Step 4: Enable Events"
70
+ echo "---"
71
+ echo " 1. In the left sidebar, go to 'Event Subscriptions'"
72
+ echo " 2. Toggle 'Enable Events' on"
73
+ echo " 3. Under 'Subscribe to bot events', add:"
74
+ echo " - message.channels"
75
+ echo " 4. Click 'Save Changes'"
76
+ echo ""
77
+ echo "Press Enter when done..."
78
+ read -r
79
+
80
+ # Step 5: Collect credentials
81
+ echo ""
82
+ echo "Step 5: Collect credentials"
83
+ echo "---"
84
+ echo " Go to 'Basic Information' in the left sidebar."
85
+ echo ""
86
+
87
+ printf "Paste your Client ID: "
88
+ read -r SLACK_CLIENT_ID
89
+
90
+ printf "Paste your Client Secret: "
91
+ read -r SLACK_CLIENT_SECRET
92
+
93
+ printf "Paste your Socket Mode token (xapp-...): "
94
+ read -r SLACK_APP_TOKEN
95
+
96
+ if [ -z "$SLACK_CLIENT_ID" ] || [ -z "$SLACK_CLIENT_SECRET" ] || [ -z "$SLACK_APP_TOKEN" ]; then
97
+ echo ""
98
+ echo "ERROR: All three values are required."
99
+ exit 1
100
+ fi
101
+
102
+ # Append to .env (preserve existing content)
103
+ echo "" >> "$ENV_FILE"
104
+ echo "# Slack" >> "$ENV_FILE"
105
+ echo "SLACK_CLIENT_ID=${SLACK_CLIENT_ID}" >> "$ENV_FILE"
106
+ echo "SLACK_CLIENT_SECRET=${SLACK_CLIENT_SECRET}" >> "$ENV_FILE"
107
+ echo "SLACK_APP_TOKEN=${SLACK_APP_TOKEN}" >> "$ENV_FILE"
108
+ echo "SLACK_REDIRECT_URI=${REDIRECT_URI}" >> "$ENV_FILE"
109
+
110
+ echo ""
111
+ echo "=== Done! ==="
112
+ echo "Credentials appended to ${ENV_FILE}"
113
+ echo ""
114
+ echo "Restart the dev server: make clean && make dev"
115
+ echo "Then click 'Connect Slack' on the dashboard."
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: polaris
3
+ description: Connect to a Polaris multiplayer collaboration session
4
+ allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context
5
+ argument-hint: [join <project> <session> | disconnect | (no args for status)]
6
+ ---
7
+
8
+ ## Polaris — Multiplayer Collaboration
9
+
10
+ Manage your connection to a Polaris collaboration session.
11
+
12
+ ### Commands
13
+
14
+ Based on the arguments provided, do ONE of the following:
15
+
16
+ **`/polaris join <project> <session>`** — Connect to a session:
17
+ 1. Call `polaris_connect` with the given project, session, and user identity
18
+ 2. If `.polaris.json` exists in the repo root, read the `user` field from it. Otherwise ask the user for their participant ID (e.g., `user:manu`).
19
+ 3. Report the connection status
20
+
21
+ **`/polaris disconnect`** — Disconnect:
22
+ 1. Call `polaris_disconnect`
23
+ 2. Confirm disconnection
24
+
25
+ **`/polaris`** (no arguments) — Show status:
26
+ 1. Call `polaris_status`
27
+ 2. Display the current connection state
28
+
29
+ ### Arguments: $ARGUMENTS
package/src/cli/cli.ts ADDED
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env bun
2
+ // --- Polaris CLI ---
3
+ // Usage:
4
+ // polaris login — authenticate via Google SSO, install local components
5
+ // polaris daemon — start the local daemon
6
+ // polaris status — show daemon health and active sessions
7
+ // polaris logout — remove credentials and local config
8
+
9
+ import { mkdir, writeFile, readFile, rm, exists } from "node:fs/promises";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ const POLARIS_DIR = join(homedir(), ".polaris");
14
+ const CREDENTIALS_FILE = join(POLARIS_DIR, "credentials.json");
15
+ const CLAUDE_DIR = join(homedir(), ".claude");
16
+ const CLAUDE_SETTINGS_DIR = join(CLAUDE_DIR);
17
+
18
+ const SERVICE_URL = process.env.POLARIS_SERVICE_URL ?? "http://localhost:3000";
19
+
20
+ // --- Login ---
21
+
22
+ async function login() {
23
+ console.log("Polaris — setting up your machine\n");
24
+
25
+ // 1. Start a local HTTP server to receive the token callback
26
+ let resolveToken: (token: string) => void;
27
+ const tokenPromise = new Promise<string>((resolve) => { resolveToken = resolve; });
28
+
29
+ const callbackServer = Bun.serve({
30
+ port: 0,
31
+ hostname: "127.0.0.1",
32
+ fetch(req) {
33
+ const url = new URL(req.url);
34
+ if (url.pathname === "/callback") {
35
+ const token = url.searchParams.get("token");
36
+ if (token) {
37
+ resolveToken!(token);
38
+ return new Response(
39
+ `<!DOCTYPE html><html><head><title>Polaris</title><style>body{font-family:-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;color:#1a1a1a;}</style></head><body><div style="text-align:center"><h2>Authenticated!</h2><p>You can close this tab and return to the terminal.</p></div></body></html>`,
40
+ { headers: { "Content-Type": "text/html" } }
41
+ );
42
+ }
43
+ }
44
+ return new Response("Not found", { status: 404 });
45
+ },
46
+ });
47
+
48
+ const callbackPort = callbackServer.port;
49
+ const authUrl = `${SERVICE_URL}/auth/cli?port=${callbackPort}`;
50
+
51
+ console.log("Opening browser for Google sign-in...");
52
+ console.log(`If the browser doesn't open, visit: ${authUrl}\n`);
53
+
54
+ // Open browser
55
+ const proc = Bun.spawn(
56
+ process.platform === "darwin" ? ["open", authUrl] :
57
+ process.platform === "win32" ? ["cmd", "/c", "start", authUrl] :
58
+ ["xdg-open", authUrl],
59
+ { stdout: "ignore", stderr: "ignore" }
60
+ );
61
+ await proc.exited;
62
+
63
+ // 2. Wait for the token
64
+ console.log("Waiting for authentication...");
65
+ const token = await tokenPromise;
66
+ callbackServer.stop(true);
67
+
68
+ // 3. Validate the token and get user info
69
+ const res = await fetch(`${SERVICE_URL}/auth/token?token=${token}`);
70
+ if (!res.ok) {
71
+ console.error("Failed to validate token. Please try again.");
72
+ process.exit(1);
73
+ }
74
+ const userInfo = (await res.json()) as {
75
+ sub: string;
76
+ email: string;
77
+ name: string;
78
+ org_id: string;
79
+ participant_id: string;
80
+ };
81
+
82
+ console.log(`\nAuthenticated as ${userInfo.name} (${userInfo.email})`);
83
+ console.log(`Organization: ${userInfo.org_id}`);
84
+ console.log(`Participant ID: ${userInfo.participant_id}\n`);
85
+
86
+ // 4. Store credentials
87
+ await mkdir(POLARIS_DIR, { recursive: true });
88
+ await writeFile(CREDENTIALS_FILE, JSON.stringify({
89
+ token,
90
+ ...userInfo,
91
+ service_url: SERVICE_URL,
92
+ }, null, 2));
93
+ console.log(`Credentials saved to ${CREDENTIALS_FILE}`);
94
+
95
+ // 5. Install MCP server config
96
+ await mkdir(CLAUDE_DIR, { recursive: true });
97
+
98
+ // Find the path to the client.ts relative to this CLI
99
+ const clientPath = join(import.meta.dir, "..", "client", "client.ts");
100
+
101
+ const mcpConfig = {
102
+ mcpServers: {
103
+ polaris: {
104
+ command: "bun",
105
+ args: [clientPath],
106
+ env: {
107
+ POLARIS_DAEMON_URL: "http://127.0.0.1:4321",
108
+ POLARIS_SERVICE_URL: SERVICE_URL.replace(":3000", ":4321"), // API port
109
+ },
110
+ },
111
+ },
112
+ };
113
+
114
+ const mcpConfigPath = join(CLAUDE_DIR, ".mcp.json");
115
+ // Merge with existing config if present
116
+ let existingMcp: Record<string, unknown> = {};
117
+ try {
118
+ const existing = await readFile(mcpConfigPath, "utf-8");
119
+ existingMcp = JSON.parse(existing);
120
+ } catch { /* doesn't exist yet */ }
121
+
122
+ const mergedMcp = {
123
+ ...existingMcp,
124
+ mcpServers: {
125
+ ...(existingMcp as { mcpServers?: Record<string, unknown> }).mcpServers,
126
+ ...mcpConfig.mcpServers,
127
+ },
128
+ };
129
+ await writeFile(mcpConfigPath, JSON.stringify(mergedMcp, null, 2));
130
+ console.log(`MCP server config written to ${mcpConfigPath}`);
131
+
132
+ // 6. Install hooks
133
+ const captureShPath = join(import.meta.dir, "..", "..", "hooks", "capture.sh");
134
+ const hooksConfig = {
135
+ UserPromptSubmit: [{ hooks: [{ type: "command", command: captureShPath }] }],
136
+ Stop: [{ hooks: [{ type: "command", command: captureShPath }] }],
137
+ PreToolUse: [{ hooks: [{ type: "command", command: captureShPath }] }],
138
+ PostToolUse: [{ hooks: [{ type: "command", command: captureShPath }] }],
139
+ };
140
+
141
+ const settingsPath = join(CLAUDE_DIR, "settings.json");
142
+ let existingSettings: Record<string, unknown> = {};
143
+ try {
144
+ const existing = await readFile(settingsPath, "utf-8");
145
+ existingSettings = JSON.parse(existing);
146
+ } catch { /* doesn't exist yet */ }
147
+
148
+ const mergedSettings = {
149
+ ...existingSettings,
150
+ hooks: {
151
+ ...(existingSettings as { hooks?: Record<string, unknown> }).hooks,
152
+ ...hooksConfig,
153
+ },
154
+ };
155
+ await writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
156
+ console.log(`Hooks config written to ${settingsPath}`);
157
+
158
+ // 7. Install /polaris skill
159
+ const skillDir = join(CLAUDE_DIR, "skills", "polaris");
160
+ await mkdir(skillDir, { recursive: true });
161
+ const skillContent = `---
162
+ name: polaris
163
+ description: Connect to a Polaris multiplayer collaboration session
164
+ allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context
165
+ argument-hint: [join <project> <session> | disconnect | (no args for status)]
166
+ ---
167
+
168
+ ## Polaris — Multiplayer Collaboration
169
+
170
+ Manage your connection to a Polaris collaboration session.
171
+
172
+ ### Commands
173
+
174
+ Based on the arguments provided, do ONE of the following:
175
+
176
+ **\`/polaris join <project> <session>\`** — Connect to a session:
177
+ 1. Call \`polaris_connect\` with the given project, session, and user identity \`${userInfo.participant_id}\`
178
+ 2. Report the connection status
179
+
180
+ **\`/polaris disconnect\`** — Disconnect:
181
+ 1. Call \`polaris_disconnect\`
182
+ 2. Confirm disconnection
183
+
184
+ **\`/polaris\`** (no arguments) — Show status:
185
+ 1. Call \`polaris_status\`
186
+ 2. Display the current connection state
187
+
188
+ ### Arguments: $ARGUMENTS
189
+ `;
190
+ await writeFile(join(skillDir, "SKILL.md"), skillContent);
191
+ console.log(`/polaris skill written to ${skillDir}/SKILL.md`);
192
+
193
+ // 8. Install status line
194
+ const statusLinePath = join(import.meta.dir, "..", "..", "hooks", "statusline.sh");
195
+ const mergedSettingsWithStatusLine = {
196
+ ...mergedSettings,
197
+ statusLine: {
198
+ type: "command",
199
+ command: statusLinePath,
200
+ },
201
+ };
202
+ await writeFile(settingsPath, JSON.stringify(mergedSettingsWithStatusLine, null, 2));
203
+ console.log(`Status line config written to ${settingsPath}`);
204
+
205
+ console.log("\n✓ Polaris is set up on this machine!");
206
+ console.log("\nNext steps:");
207
+ console.log(" 1. Start the daemon: bun run src/daemon/daemon.ts");
208
+ console.log(" 2. Open your AI agent and run: /polaris join <project> <session>");
209
+ }
210
+
211
+ // --- Daemon ---
212
+
213
+ async function daemon() {
214
+ const daemonPath = join(import.meta.dir, "..", "daemon", "daemon.ts");
215
+ console.log("Starting Polaris daemon...");
216
+ const proc = Bun.spawn(["bun", "run", daemonPath], {
217
+ stdout: "inherit",
218
+ stderr: "inherit",
219
+ env: {
220
+ ...process.env,
221
+ POLARIS_SERVICE_URL: SERVICE_URL.replace(":3000", ":4321"),
222
+ },
223
+ });
224
+ await proc.exited;
225
+ }
226
+
227
+ // --- Status ---
228
+
229
+ async function status() {
230
+ try {
231
+ const res = await fetch("http://127.0.0.1:4321/status");
232
+ const data = (await res.json()) as { ok: boolean; sessions?: Array<{ ccSessionId: string; project: string; session: string; user: string }> };
233
+ if (data.ok) {
234
+ console.log("Daemon: running");
235
+ if (data.sessions && data.sessions.length > 0) {
236
+ console.log(`Active sessions (${data.sessions.length}):`);
237
+ for (const s of data.sessions) {
238
+ console.log(` ${s.project}/${s.session} as ${s.user} (cc: ${s.ccSessionId})`);
239
+ }
240
+ } else {
241
+ console.log("No active sessions");
242
+ }
243
+ }
244
+ } catch {
245
+ console.log("Daemon: not running");
246
+ }
247
+
248
+ // Check credentials
249
+ try {
250
+ const creds = JSON.parse(await readFile(CREDENTIALS_FILE, "utf-8"));
251
+ console.log(`\nLogged in as: ${creds.name} (${creds.email})`);
252
+ console.log(`Org: ${creds.org_id}`);
253
+ console.log(`Service: ${creds.service_url}`);
254
+ } catch {
255
+ console.log("\nNot logged in. Run: polaris login");
256
+ }
257
+ }
258
+
259
+ // --- Logout ---
260
+
261
+ async function logout() {
262
+ try {
263
+ await rm(POLARIS_DIR, { recursive: true });
264
+ console.log("Credentials removed.");
265
+ } catch { /* already gone */ }
266
+ console.log("Logged out. MCP config and hooks are still installed — remove them manually if needed.");
267
+ }
268
+
269
+ // --- Main ---
270
+
271
+ const command = process.argv[2];
272
+
273
+ switch (command) {
274
+ case "login":
275
+ await login();
276
+ break;
277
+ case "daemon":
278
+ await daemon();
279
+ break;
280
+ case "status":
281
+ await status();
282
+ break;
283
+ case "logout":
284
+ await logout();
285
+ break;
286
+ default:
287
+ console.log("Polaris CLI");
288
+ console.log("");
289
+ console.log("Usage:");
290
+ console.log(" polaris login — authenticate and set up this machine");
291
+ console.log(" polaris daemon — start the local daemon");
292
+ console.log(" polaris status — show connection status");
293
+ console.log(" polaris logout — remove credentials");
294
+ }
@@ -0,0 +1,245 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ ListToolsRequestSchema,
5
+ CallToolRequestSchema,
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ // --- Configuration ---
9
+
10
+ const DAEMON_URL = process.env.POLARIS_DAEMON_URL ?? "http://127.0.0.1:4321";
11
+ const SERVICE_URL = process.env.POLARIS_SERVICE_URL ?? "http://localhost:4321";
12
+
13
+ // Generate a stable session ID for this MCP server instance
14
+ const CC_SESSION_ID = process.env.POLARIS_CC_SESSION_ID ?? crypto.randomUUID();
15
+
16
+ // --- Daemon communication ---
17
+
18
+ async function daemonPost(path: string, body: unknown): Promise<Response> {
19
+ return fetch(`${DAEMON_URL}${path}`, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify(body),
23
+ });
24
+ }
25
+
26
+ async function daemonGet(path: string): Promise<Response> {
27
+ return fetch(`${DAEMON_URL}${path}`);
28
+ }
29
+
30
+ // --- Cloud service (direct, for context queries) ---
31
+
32
+ async function serviceGet(path: string): Promise<Response> {
33
+ return fetch(`${SERVICE_URL}${path}`);
34
+ }
35
+
36
+ // --- Current connection state ---
37
+
38
+ let currentProject = "";
39
+ let currentSession = "";
40
+ let currentUser = "";
41
+
42
+ // --- MCP Server ---
43
+
44
+ const mcp = new Server(
45
+ { name: "polaris", version: "0.0.1" },
46
+ {
47
+ capabilities: {
48
+ experimental: { "claude/channel": {} },
49
+ tools: {},
50
+ },
51
+ instructions: `You are connected to Polaris — a multiplayer collaboration system. Messages from advisors and teammates may arrive as <channel source="polaris" from="..."> tags. Use /polaris commands to manage your session, or call the polaris tools directly.`,
52
+ }
53
+ );
54
+
55
+ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
56
+ tools: [
57
+ {
58
+ name: "polaris_connect",
59
+ description: "Connect this session to a Polaris project and session. Creates the session if it doesn't exist.",
60
+ inputSchema: {
61
+ type: "object" as const,
62
+ properties: {
63
+ project: { type: "string", description: "Project name" },
64
+ session: { type: "string", description: "Session name" },
65
+ user: { type: "string", description: "Your participant ID (e.g., user:manu)" },
66
+ },
67
+ required: ["project", "session", "user"],
68
+ },
69
+ },
70
+ {
71
+ name: "polaris_disconnect",
72
+ description: "Disconnect from the current Polaris session.",
73
+ inputSchema: {
74
+ type: "object" as const,
75
+ properties: {},
76
+ },
77
+ },
78
+ {
79
+ name: "polaris_status",
80
+ description: "Show current Polaris connection status.",
81
+ inputSchema: {
82
+ type: "object" as const,
83
+ properties: {},
84
+ },
85
+ },
86
+ {
87
+ name: "polaris_reply",
88
+ description: "Send a message to the project floor (visible to all advisors and the Slack/WhatsApp channel).",
89
+ inputSchema: {
90
+ type: "object" as const,
91
+ properties: {
92
+ message: { type: "string", description: "Message to send" },
93
+ },
94
+ required: ["message"],
95
+ },
96
+ },
97
+ {
98
+ name: "polaris_context",
99
+ description: "Fetch activity from a sibling session in this project. Use this to see what other drivers have been doing.",
100
+ inputSchema: {
101
+ type: "object" as const,
102
+ properties: {
103
+ session: { type: "string", description: "Name of the sibling session to fetch context from" },
104
+ },
105
+ required: ["session"],
106
+ },
107
+ },
108
+ ],
109
+ }));
110
+
111
+ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
112
+ const { name, arguments: args } = req.params;
113
+
114
+ if (name === "polaris_connect") {
115
+ const { project, session, user } = args as { project: string; session: string; user: string };
116
+ try {
117
+ const res = await daemonPost("/connect", {
118
+ ccSessionId: CC_SESSION_ID,
119
+ project,
120
+ session,
121
+ user,
122
+ });
123
+ const body = await res.json();
124
+ if (res.ok) {
125
+ currentProject = project;
126
+ currentSession = session;
127
+ currentUser = user;
128
+ return { content: [{ type: "text", text: `Connected to ${project}/${session} as ${user}.` }] };
129
+ }
130
+ return { content: [{ type: "text", text: `Failed to connect: ${(body as { error?: string }).error ?? "unknown error"}` }] };
131
+ } catch {
132
+ return { content: [{ type: "text", text: "Failed to connect — is the Polaris daemon running? Start it with `polaris daemon` or `bun run src/daemon/daemon.ts`." }] };
133
+ }
134
+ }
135
+
136
+ if (name === "polaris_disconnect") {
137
+ try {
138
+ await daemonPost("/disconnect", { ccSessionId: CC_SESSION_ID });
139
+ currentProject = "";
140
+ currentSession = "";
141
+ currentUser = "";
142
+ return { content: [{ type: "text", text: "Disconnected from Polaris." }] };
143
+ } catch {
144
+ return { content: [{ type: "text", text: "Failed to disconnect — daemon may not be running." }] };
145
+ }
146
+ }
147
+
148
+ if (name === "polaris_status") {
149
+ try {
150
+ const res = await daemonGet(`/status/${CC_SESSION_ID}`);
151
+ const body = (await res.json()) as { connected: boolean; project?: string; session?: string; user?: string };
152
+ if (body.connected) {
153
+ return { content: [{ type: "text", text: `Connected: ${body.project}/${body.session} as ${body.user}` }] };
154
+ }
155
+ return { content: [{ type: "text", text: "Not connected to any Polaris session." }] };
156
+ } catch {
157
+ return { content: [{ type: "text", text: "Polaris daemon not reachable." }] };
158
+ }
159
+ }
160
+
161
+ if (name === "polaris_reply") {
162
+ if (!currentProject) {
163
+ return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
164
+ }
165
+ const message = (args as { message: string }).message;
166
+ try {
167
+ const res = await fetch(`${SERVICE_URL}/projects/${currentProject}/sessions/${currentSession}/events`, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({
171
+ sender: currentUser,
172
+ payload: {
173
+ hook_event_name: "Stop",
174
+ session_id: CC_SESSION_ID,
175
+ stop_response: message,
176
+ },
177
+ }),
178
+ });
179
+ if (res.ok) {
180
+ return { content: [{ type: "text", text: "Reply sent to the floor." }] };
181
+ }
182
+ return { content: [{ type: "text", text: `Failed to send reply: ${res.status}` }] };
183
+ } catch {
184
+ return { content: [{ type: "text", text: "Failed to reach the cloud service." }] };
185
+ }
186
+ }
187
+
188
+ if (name === "polaris_context") {
189
+ if (!currentProject) {
190
+ return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
191
+ }
192
+ const targetSession = (args as { session: string }).session;
193
+ try {
194
+ const res = await serviceGet(`/projects/${currentProject}/sessions/${targetSession}/messages`);
195
+ if (!res.ok) {
196
+ return { content: [{ type: "text", text: `Could not fetch session "${targetSession}": ${res.status}` }] };
197
+ }
198
+ const events = (await res.json()) as Array<{
199
+ sender: string;
200
+ payload: { prompt?: string; stop_response?: string; content?: string };
201
+ }>;
202
+ const summary = events
203
+ .map((e) => {
204
+ const p = e.payload;
205
+ const text = p.prompt ?? p.stop_response ?? p.content ?? JSON.stringify(p);
206
+ return `[${e.sender}] ${text}`;
207
+ })
208
+ .join("\n");
209
+ return { content: [{ type: "text", text: summary || "(no activity yet)" }] };
210
+ } catch {
211
+ return { content: [{ type: "text", text: "Failed to reach the cloud service." }] };
212
+ }
213
+ }
214
+
215
+ throw new Error(`Unknown tool: ${name}`);
216
+ });
217
+
218
+ // --- Register with daemon and connect stdio ---
219
+
220
+ async function main() {
221
+ // Register with daemon (best-effort — daemon might not be running yet)
222
+ try {
223
+ await daemonPost("/register", { ccSessionId: CC_SESSION_ID });
224
+ } catch {
225
+ console.error("Warning: Polaris daemon not reachable. Start it with `bun run src/daemon/daemon.ts`.");
226
+ }
227
+
228
+ // Poll daemon for advisor messages and inject into MCP session
229
+ setInterval(async () => {
230
+ if (!currentProject) return;
231
+ // The daemon's cloud WS forwards inject events to the mcpCallbacks map,
232
+ // but since we're in a separate process, we use HTTP polling as a fallback.
233
+ // In production, this would use IPC (Unix socket or named pipe).
234
+ }, 5000);
235
+
236
+ const transport = new StdioServerTransport();
237
+ await mcp.connect(transport);
238
+ console.error(`Polaris MCP client started (session: ${CC_SESSION_ID})`);
239
+ }
240
+
241
+ if (import.meta.main) {
242
+ await main();
243
+ }
244
+
245
+ export { mcp, CC_SESSION_ID, main as startClient };