@lightupai/polaris 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -0
- package/.github/workflows/ci.yml +38 -0
- package/.mcp.json +3 -3
- package/Makefile +20 -3
- package/README.md +124 -0
- package/bin/polaris +2 -2
- package/bun.lock +289 -0
- package/deploy.sh +18 -0
- package/docker/Caddyfile +7 -0
- package/docker/Dockerfile +13 -0
- package/docker/bridge-entrypoint.sh +17 -0
- package/docker-compose.prod.yml +85 -0
- package/docs/deploy-hetzner.md +99 -0
- package/hooks/capture-stop.sh +6 -0
- package/hooks/capture-stop.ts +122 -0
- package/hooks/capture.sh +1 -1
- package/hooks/statusline.sh +22 -11
- package/package.json +3 -1
- package/skills/polaris/SKILL.md +6 -2
- package/src/bridge-discover-org.ts +5 -0
- package/src/cli/cli.ts +401 -160
- package/src/client/client.ts +37 -24
- package/src/daemon/daemon.ts +250 -8
- package/src/service/db.ts +159 -28
- package/src/service/server.ts +47 -0
- package/src/slack/bridge.ts +399 -0
- package/src/slack/format.ts +115 -0
- package/src/types.ts +7 -1
- package/src/web/app.ts +40 -10
- package/src/web/layout.ts +16 -2
- package/src/web/views.ts +63 -77
- package/tests/bridge.test.ts +205 -0
- package/tests/client.test.ts +3 -13
- package/tests/daemon.test.ts +5 -14
- package/tests/e2e.test.ts +4 -13
- package/tests/format.test.ts +103 -0
- package/tests/helpers.ts +71 -0
- package/tests/service.test.ts +2 -13
- package/tests/types.test.ts +2 -2
- package/tests/web.test.ts +17 -31
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "[bridge] Waiting for Postgres..."
|
|
5
|
+
until bun run src/bridge-discover-org.ts >/dev/null 2>&1; do
|
|
6
|
+
sleep 2
|
|
7
|
+
done
|
|
8
|
+
|
|
9
|
+
ORG_ID=$(bun run src/bridge-discover-org.ts)
|
|
10
|
+
if [ -z "$ORG_ID" ]; then
|
|
11
|
+
echo "[bridge] No Slack-connected org found. Retrying in 30s..."
|
|
12
|
+
sleep 30
|
|
13
|
+
exec "$0"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
echo "[bridge] Starting bridge for org: $ORG_ID"
|
|
17
|
+
exec bun run src/slack/bridge.ts "$ORG_ID"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:17
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_USER: polaris
|
|
7
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
8
|
+
POSTGRES_DB: polaris
|
|
9
|
+
volumes:
|
|
10
|
+
- pgdata:/var/lib/postgresql/data
|
|
11
|
+
healthcheck:
|
|
12
|
+
test: ["CMD-SHELL", "pg_isready -U polaris"]
|
|
13
|
+
interval: 5s
|
|
14
|
+
timeout: 3s
|
|
15
|
+
retries: 5
|
|
16
|
+
|
|
17
|
+
api:
|
|
18
|
+
build:
|
|
19
|
+
context: .
|
|
20
|
+
dockerfile: docker/Dockerfile
|
|
21
|
+
restart: unless-stopped
|
|
22
|
+
command: ["bun", "run", "src/service/server.ts"]
|
|
23
|
+
environment:
|
|
24
|
+
DATABASE_URL: postgres://polaris:${POSTGRES_PASSWORD}@postgres:5432/polaris
|
|
25
|
+
POLARIS_JWT_SECRET: ${POLARIS_JWT_SECRET}
|
|
26
|
+
WEB_HOST: web
|
|
27
|
+
WEB_PORT: 3000
|
|
28
|
+
depends_on:
|
|
29
|
+
postgres:
|
|
30
|
+
condition: service_healthy
|
|
31
|
+
|
|
32
|
+
web:
|
|
33
|
+
build:
|
|
34
|
+
context: .
|
|
35
|
+
dockerfile: docker/Dockerfile
|
|
36
|
+
restart: unless-stopped
|
|
37
|
+
command: ["bun", "run", "src/web/serve.ts"]
|
|
38
|
+
environment:
|
|
39
|
+
DATABASE_URL: postgres://polaris:${POSTGRES_PASSWORD}@postgres:5432/polaris
|
|
40
|
+
POLARIS_JWT_SECRET: ${POLARIS_JWT_SECRET}
|
|
41
|
+
WEB_PORT: 3000
|
|
42
|
+
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
|
|
43
|
+
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
|
|
44
|
+
GOOGLE_REDIRECT_URI: https://app.polaris.lightup.ai/auth/google/callback
|
|
45
|
+
SLACK_CLIENT_ID: ${SLACK_CLIENT_ID}
|
|
46
|
+
SLACK_CLIENT_SECRET: ${SLACK_CLIENT_SECRET}
|
|
47
|
+
SLACK_REDIRECT_URI: https://app.polaris.lightup.ai/slack/callback
|
|
48
|
+
depends_on:
|
|
49
|
+
postgres:
|
|
50
|
+
condition: service_healthy
|
|
51
|
+
|
|
52
|
+
bridge:
|
|
53
|
+
build:
|
|
54
|
+
context: .
|
|
55
|
+
dockerfile: docker/Dockerfile
|
|
56
|
+
restart: unless-stopped
|
|
57
|
+
entrypoint: ["/bin/sh", "/app/docker/bridge-entrypoint.sh"]
|
|
58
|
+
environment:
|
|
59
|
+
DATABASE_URL: postgres://polaris:${POSTGRES_PASSWORD}@postgres:5432/polaris
|
|
60
|
+
SLACK_APP_TOKEN: ${SLACK_APP_TOKEN}
|
|
61
|
+
POLARIS_API_URL: http://api:4321
|
|
62
|
+
POLARIS_LONG_MSG: ${POLARIS_LONG_MSG:-snippet}
|
|
63
|
+
depends_on:
|
|
64
|
+
postgres:
|
|
65
|
+
condition: service_healthy
|
|
66
|
+
|
|
67
|
+
caddy:
|
|
68
|
+
image: caddy:2-alpine
|
|
69
|
+
restart: unless-stopped
|
|
70
|
+
ports:
|
|
71
|
+
- "80:80"
|
|
72
|
+
- "443:443"
|
|
73
|
+
- "443:443/udp"
|
|
74
|
+
volumes:
|
|
75
|
+
- ./docker/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
76
|
+
- caddy_data:/data
|
|
77
|
+
- caddy_config:/config
|
|
78
|
+
depends_on:
|
|
79
|
+
- api
|
|
80
|
+
- web
|
|
81
|
+
|
|
82
|
+
volumes:
|
|
83
|
+
pgdata:
|
|
84
|
+
caddy_data:
|
|
85
|
+
caddy_config:
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Deploy Polaris to Hetzner Cloud VPS
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Polaris runs entirely on localhost. We need a production deployment so the API, web dashboard, Slack bridge, and Postgres all run on a Hetzner Cloud VPS with proper HTTPS. The daemon and MCP client stay local on each developer's machine — they talk to the cloud API.
|
|
6
|
+
|
|
7
|
+
Domain: `polaris.lightup.ai` (subdomains: `api.polaris.lightup.ai`, `app.polaris.lightup.ai`)
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
**On Hetzner VPS (Docker Compose):**
|
|
12
|
+
- **Caddy** — reverse proxy, automatic Let's Encrypt HTTPS (ports 80/443)
|
|
13
|
+
- **API** — `src/service/server.ts` (port 4321 internal)
|
|
14
|
+
- **Web** — `src/web/serve.ts` (port 3000 internal)
|
|
15
|
+
- **Bridge** — `src/slack/bridge.ts` (no port, outbound only)
|
|
16
|
+
- **Postgres 17** — persistent volume
|
|
17
|
+
|
|
18
|
+
**Stays local (each dev machine):**
|
|
19
|
+
- Daemon (port 4322)
|
|
20
|
+
- MCP client
|
|
21
|
+
- Hooks
|
|
22
|
+
|
|
23
|
+
## Files to Create
|
|
24
|
+
|
|
25
|
+
| File | Purpose |
|
|
26
|
+
|------|---------|
|
|
27
|
+
| `docker/Dockerfile` | Single Bun image, each service overrides CMD |
|
|
28
|
+
| `docker/Caddyfile` | Reverse proxy: app.polaris.lightup.ai → web:3000, api.polaris.lightup.ai → api:4321 |
|
|
29
|
+
| `docker/bridge-entrypoint.sh` | Wait for Postgres, discover org ID, start bridge |
|
|
30
|
+
| `src/bridge-discover-org.ts` | Tiny script: query DB for Slack-connected org ID |
|
|
31
|
+
| `docker-compose.prod.yml` | Full production orchestration |
|
|
32
|
+
| `.env.example` | Template of required env vars (no secrets) |
|
|
33
|
+
| `deploy.sh` | SSH deploy script: git pull + docker compose up |
|
|
34
|
+
|
|
35
|
+
## Code Change
|
|
36
|
+
|
|
37
|
+
**`src/service/server.ts`** — The API calls `http://localhost:${WEB_PORT}/api/notify-dashboard` to push SSE updates to the web app. In Docker, `localhost` doesn't reach other containers. Change to:
|
|
38
|
+
```
|
|
39
|
+
http://${process.env.WEB_HOST ?? "localhost"}:${WEB_PORT}/api/notify-dashboard
|
|
40
|
+
```
|
|
41
|
+
Set `WEB_HOST=web` in the production compose. Backward compatible for local dev.
|
|
42
|
+
|
|
43
|
+
## Docker Strategy
|
|
44
|
+
|
|
45
|
+
**Single Dockerfile**, multi-service via different `command:` overrides in compose:
|
|
46
|
+
```dockerfile
|
|
47
|
+
FROM oven/bun:1
|
|
48
|
+
WORKDIR /app
|
|
49
|
+
COPY package.json bun.lock* ./
|
|
50
|
+
RUN bun install --frozen-lockfile
|
|
51
|
+
COPY src/ ./src/
|
|
52
|
+
COPY docker/bridge-entrypoint.sh ./docker/
|
|
53
|
+
CMD ["bun", "run", "src/service/server.ts"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Bridge entrypoint** waits for Postgres, queries `orgs` table for first Slack-connected org, starts bridge with that ID. Retries every 30s if no org found.
|
|
57
|
+
|
|
58
|
+
## Caddy Config
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
app.polaris.lightup.ai {
|
|
62
|
+
reverse_proxy web:3000
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
api.polaris.lightup.ai {
|
|
66
|
+
reverse_proxy api:4321
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Caddy auto-provisions Let's Encrypt certs. WebSocket upgrades pass through transparently.
|
|
71
|
+
|
|
72
|
+
## Production Compose (key decisions)
|
|
73
|
+
|
|
74
|
+
- Only Caddy exposes ports (80, 443). All other services are internal.
|
|
75
|
+
- Postgres has healthcheck; API and web depend on it.
|
|
76
|
+
- Bridge depends on API being healthy.
|
|
77
|
+
- Secrets via `.env` file on server (Docker Compose reads it automatically).
|
|
78
|
+
- Persistent volumes: `pgdata` (Postgres), `caddy_data` (TLS certs).
|
|
79
|
+
|
|
80
|
+
## Server Setup (one-time)
|
|
81
|
+
|
|
82
|
+
1. Provision Hetzner CX22 (2 vCPU, 4 GB RAM)
|
|
83
|
+
2. Install Docker + Docker Compose
|
|
84
|
+
3. Create deploy user, add SSH key
|
|
85
|
+
4. Clone repo to `/opt/polaris`
|
|
86
|
+
5. Create `.env` with production secrets
|
|
87
|
+
6. DNS: A records for `api.polaris.lightup.ai` and `app.polaris.lightup.ai` → VPS IP
|
|
88
|
+
7. Update Google OAuth + Slack redirect URIs to `https://app.polaris.lightup.ai/...`
|
|
89
|
+
8. Firewall: allow 80, 443, 22 only
|
|
90
|
+
9. `docker compose -f docker-compose.prod.yml up -d`
|
|
91
|
+
|
|
92
|
+
## Verification
|
|
93
|
+
|
|
94
|
+
1. `docker compose -f docker-compose.prod.yml up --build` locally — all services start
|
|
95
|
+
2. `curl https://api.polaris.lightup.ai/status` returns `{"ok":true}`
|
|
96
|
+
3. `https://app.polaris.lightup.ai` loads login page
|
|
97
|
+
4. Google SSO login works end-to-end
|
|
98
|
+
5. Slack bridge connects and posts to channels
|
|
99
|
+
6. Local daemon connects to `https://api.polaris.lightup.ai` and events flow
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// hooks/capture-stop.ts — Extract the full assistant turn from the transcript
|
|
3
|
+
// and POST it to the daemon as a Stop event with the complete response.
|
|
4
|
+
//
|
|
5
|
+
// A single Claude turn can span multiple assistant entries in the transcript
|
|
6
|
+
// (text → tool_use → tool_result → text → tool_use → ...). We collect ALL
|
|
7
|
+
// assistant text parts since the last user message.
|
|
8
|
+
|
|
9
|
+
const POLARIS_PORT = process.env.POLARIS_PORT ?? "4322";
|
|
10
|
+
const POLARIS_URL = `http://127.0.0.1:${POLARIS_PORT}/events`;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const input = JSON.parse(await Bun.stdin.text());
|
|
14
|
+
|
|
15
|
+
if (input.hook_event_name !== "Stop") {
|
|
16
|
+
// Not a Stop event — forward as-is
|
|
17
|
+
await fetch(POLARIS_URL, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify(input),
|
|
21
|
+
}).catch(() => {});
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Read the transcript to get the full assistant turn
|
|
26
|
+
const transcriptPath = input.transcript_path;
|
|
27
|
+
let fullResponse = input.last_assistant_message ?? "";
|
|
28
|
+
|
|
29
|
+
if (transcriptPath) {
|
|
30
|
+
try {
|
|
31
|
+
const file = Bun.file(transcriptPath);
|
|
32
|
+
const text = await file.text();
|
|
33
|
+
const lines = text.trim().split("\n");
|
|
34
|
+
|
|
35
|
+
// Walk backwards to find the last user message, then collect all
|
|
36
|
+
// assistant text parts between that user message and the end.
|
|
37
|
+
let userIdx = -1;
|
|
38
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
39
|
+
try {
|
|
40
|
+
const entry = JSON.parse(lines[i]);
|
|
41
|
+
if ((entry.type === "user" || entry.type === "human") && typeof entry.message?.content === "string") {
|
|
42
|
+
userIdx = i;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Collect everything since the last user message:
|
|
49
|
+
// 1. rawTurn: full structured data for zero-loss DB logging
|
|
50
|
+
// 2. displayResponse: formatted text for Slack display
|
|
51
|
+
const rawTurn: unknown[] = [];
|
|
52
|
+
const displayParts: string[] = [];
|
|
53
|
+
const startIdx = userIdx >= 0 ? userIdx + 1 : 0;
|
|
54
|
+
|
|
55
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
56
|
+
try {
|
|
57
|
+
const entry = JSON.parse(lines[i]);
|
|
58
|
+
// Capture raw entries for full fidelity
|
|
59
|
+
if (entry.type === "assistant" || (entry.type === "user" && Array.isArray(entry.message?.content))) {
|
|
60
|
+
rawTurn.push(entry);
|
|
61
|
+
}
|
|
62
|
+
// Build display text
|
|
63
|
+
if (entry.type === "assistant" && entry.message?.content) {
|
|
64
|
+
for (const c of entry.message.content) {
|
|
65
|
+
if (c.type === "text" && c.text) {
|
|
66
|
+
displayParts.push(c.text);
|
|
67
|
+
} else if (c.type === "tool_use" && c.name) {
|
|
68
|
+
const inputSummary = c.input?.command?.slice(0, 80)
|
|
69
|
+
?? c.input?.file_path?.slice(0, 80)
|
|
70
|
+
?? c.input?.pattern?.slice(0, 80)
|
|
71
|
+
?? "";
|
|
72
|
+
displayParts.push(`> _\`${c.name}\`${inputSummary ? ": " + inputSummary : ""}_`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (displayParts.length > 0) {
|
|
80
|
+
fullResponse = displayParts.join("\n\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Attach raw turn data to the payload for zero-loss storage
|
|
84
|
+
if (rawTurn.length > 0) {
|
|
85
|
+
// Sanitize: strip null bytes and invalid Unicode surrogates
|
|
86
|
+
const sanitized = JSON.parse(
|
|
87
|
+
JSON.stringify(rawTurn).replace(/\\u0000/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "")
|
|
88
|
+
);
|
|
89
|
+
(input as Record<string, unknown>).raw_turn = sanitized;
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Fall back to last_assistant_message
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// POST the Stop event with the full response
|
|
97
|
+
const payload = {
|
|
98
|
+
...input,
|
|
99
|
+
stop_response: fullResponse,
|
|
100
|
+
last_assistant_message: fullResponse,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const res = await fetch(POLARIS_URL, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify(payload),
|
|
107
|
+
}).catch(() => null);
|
|
108
|
+
|
|
109
|
+
// If the POST failed (e.g., Unicode issue in raw_turn), retry without it
|
|
110
|
+
if (!res || !res.ok) {
|
|
111
|
+
delete (payload as Record<string, unknown>).raw_turn;
|
|
112
|
+
await fetch(POLARIS_URL, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify(payload),
|
|
116
|
+
}).catch(() => {});
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Always exit 0 to avoid blocking the coding agent
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
process.exit(0);
|
package/hooks/capture.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Reads hook JSON from stdin, POSTs to the local polaris client.
|
|
4
4
|
# Always exits 0 to avoid blocking the coding agent.
|
|
5
5
|
|
|
6
|
-
POLARIS_PORT="${POLARIS_PORT:-
|
|
6
|
+
POLARIS_PORT="${POLARIS_PORT:-4322}"
|
|
7
7
|
POLARIS_URL="http://127.0.0.1:${POLARIS_PORT}/events"
|
|
8
8
|
|
|
9
9
|
# Read all of stdin
|
package/hooks/statusline.sh
CHANGED
|
@@ -2,29 +2,40 @@
|
|
|
2
2
|
# hooks/statusline.sh — Polaris status line for coding agent CLI
|
|
3
3
|
# Reads session JSON from stdin, queries daemon for connection state.
|
|
4
4
|
|
|
5
|
-
POLARIS_DAEMON_PORT="${POLARIS_DAEMON_PORT:-
|
|
5
|
+
POLARIS_DAEMON_PORT="${POLARIS_DAEMON_PORT:-4322}"
|
|
6
6
|
|
|
7
7
|
# Read stdin (session JSON from the coding agent)
|
|
8
8
|
INPUT=$(cat)
|
|
9
9
|
|
|
10
|
-
# Extract the CC session ID if available
|
|
11
|
-
CC_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "
|
|
10
|
+
# Extract the CC session ID if available
|
|
11
|
+
CC_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "")
|
|
12
12
|
|
|
13
|
-
# Query daemon
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
echo "polaris: daemon offline"
|
|
18
|
-
exit 0
|
|
13
|
+
# Query daemon — try specific session first, fall back to any connected session
|
|
14
|
+
if [ -n "$CC_SESSION_ID" ]; then
|
|
15
|
+
STATUS=$(curl -s "http://127.0.0.1:${POLARIS_DAEMON_PORT}/status/${CC_SESSION_ID}" 2>/dev/null)
|
|
16
|
+
CONNECTED=$(echo "$STATUS" | jq -r '.connected' 2>/dev/null || echo "false")
|
|
19
17
|
fi
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
# If no match, check if there's any active session
|
|
20
|
+
if [ "$CONNECTED" != "true" ]; then
|
|
21
|
+
STATUS=$(curl -s "http://127.0.0.1:${POLARIS_DAEMON_PORT}/status" 2>/dev/null)
|
|
22
|
+
FIRST_SESSION=$(echo "$STATUS" | jq -r '.sessions[0] // empty' 2>/dev/null)
|
|
23
|
+
if [ -n "$FIRST_SESSION" ]; then
|
|
24
|
+
STATUS="{\"connected\":true,\"project\":$(echo "$STATUS" | jq '.sessions[0].project'),\"session\":$(echo "$STATUS" | jq '.sessions[0].session'),\"user\":$(echo "$STATUS" | jq '.sessions[0].user')}"
|
|
25
|
+
CONNECTED="true"
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
22
28
|
|
|
23
29
|
if [ "$CONNECTED" = "true" ]; then
|
|
24
30
|
PROJECT=$(echo "$STATUS" | jq -r '.project' 2>/dev/null)
|
|
25
31
|
SESSION=$(echo "$STATUS" | jq -r '.session' 2>/dev/null)
|
|
26
32
|
USER=$(echo "$STATUS" | jq -r '.user' 2>/dev/null)
|
|
27
|
-
echo "
|
|
33
|
+
SLACK=$(echo "$STATUS" | jq -r '.slackChannel // empty' 2>/dev/null)
|
|
34
|
+
if [ -n "$SLACK" ]; then
|
|
35
|
+
echo "polaris: ${PROJECT}/${SESSION} (${USER}) #${SLACK}"
|
|
36
|
+
else
|
|
37
|
+
echo "polaris: ${PROJECT}/${SESSION} (${USER})"
|
|
38
|
+
fi
|
|
28
39
|
else
|
|
29
40
|
echo "polaris: not connected"
|
|
30
41
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightupai/polaris",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"polaris": "bin/polaris"
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
13
|
+
"@slack/socket-mode": "^2.0.0",
|
|
14
|
+
"@slack/web-api": "^7.0.0",
|
|
13
15
|
"arctic": "^3.5.0",
|
|
14
16
|
"hono": "^4.7.0",
|
|
15
17
|
"jose": "^6.0.0",
|
package/skills/polaris/SKILL.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: polaris
|
|
3
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)]
|
|
4
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename
|
|
5
|
+
argument-hint: [join <project> <session> | rename <new-name> | disconnect | (no args for status)]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Polaris — Multiplayer Collaboration
|
|
@@ -18,6 +18,10 @@ Based on the arguments provided, do ONE of the following:
|
|
|
18
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
19
|
3. Report the connection status
|
|
20
20
|
|
|
21
|
+
**`/polaris rename <new-name>`** — Rename the current project:
|
|
22
|
+
1. Call `polaris_rename` with the new name
|
|
23
|
+
2. Report the result
|
|
24
|
+
|
|
21
25
|
**`/polaris disconnect`** — Disconnect:
|
|
22
26
|
1. Call `polaris_disconnect`
|
|
23
27
|
2. Confirm disconnection
|