@lightupai/polaris 0.0.77 → 0.0.78

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,60 @@
1
+ #!/usr/bin/env bun
2
+ // hooks/capture-prompt.ts — Forward UserPromptSubmit hook events to the daemon
3
+ // and deliver any queued advisor injects to the agent via additionalContext.
4
+ //
5
+ // The daemon queues inject events arriving over its cloud WebSocket (see
6
+ // injectQueues in src/daemon/daemon.ts); the /events response for a
7
+ // UserPromptSubmit hook drains that queue as pendingInjects, which we surface
8
+ // to Claude Code as hookSpecificOutput. Always exits 0; never blocks.
9
+
10
+ const POLARIS_PORT = process.env.POLARIS_PORT ?? "4322";
11
+ const POLARIS_URL = `http://127.0.0.1:${POLARIS_PORT}/events`;
12
+
13
+ // Daemon auth: include the shared local secret when provided (wired into the
14
+ // hook command env by `polaris install`)
15
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
16
+ if (process.env.POLARIS_DAEMON_SECRET) {
17
+ headers["x-polaris-daemon-secret"] = process.env.POLARIS_DAEMON_SECRET;
18
+ }
19
+
20
+ try {
21
+ const input = JSON.parse(await Bun.stdin.text());
22
+
23
+ const res = await fetch(POLARIS_URL, {
24
+ method: "POST",
25
+ headers,
26
+ body: JSON.stringify(input),
27
+ }).catch(() => null);
28
+
29
+ if (res && res.ok) {
30
+ const body = (await res.json().catch(() => null)) as {
31
+ pendingInjects?: Array<{ from: string; content: string; timestamp: string }>;
32
+ } | null;
33
+ const items = body?.pendingInjects;
34
+ if (Array.isArray(items) && items.length > 0) {
35
+ // Present injects with honest provenance and let the model apply its own
36
+ // judgment. NOTE (verified live): the model treats content arriving via
37
+ // this channel as not-from-the-developer and will surface/consider it but
38
+ // will NOT obey imperative instructions through it — no hook wording
39
+ // reliably changes that, nor should it. Use injects to SHARE CONTEXT with
40
+ // the agent, not to remote-control its actions. (For trusted imperative
41
+ // steering, the claude/channel path is the right mechanism — deferred.)
42
+ const formatted =
43
+ "Context shared by teammates collaborating in this session via Polaris " +
44
+ "(for your awareness; it did not come directly from the developer):\n" +
45
+ items.map((i) => `- ${i.from}: ${i.content}`).join("\n");
46
+ process.stdout.write(
47
+ JSON.stringify({
48
+ hookSpecificOutput: {
49
+ hookEventName: "UserPromptSubmit",
50
+ additionalContext: formatted,
51
+ },
52
+ })
53
+ );
54
+ }
55
+ }
56
+ } catch {
57
+ // Always exit 0 to avoid blocking the coding agent
58
+ }
59
+
60
+ process.exit(0);
@@ -9,6 +9,13 @@
9
9
  const POLARIS_PORT = process.env.POLARIS_PORT ?? "4322";
10
10
  const POLARIS_URL = `http://127.0.0.1:${POLARIS_PORT}/events`;
11
11
 
12
+ // Daemon auth: include the shared local secret when provided (wired into the
13
+ // hook command env by `polaris install`)
14
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
15
+ if (process.env.POLARIS_DAEMON_SECRET) {
16
+ headers["x-polaris-daemon-secret"] = process.env.POLARIS_DAEMON_SECRET;
17
+ }
18
+
12
19
  try {
13
20
  const input = JSON.parse(await Bun.stdin.text());
14
21
 
@@ -16,7 +23,7 @@ try {
16
23
  // Not a Stop event — forward as-is
17
24
  await fetch(POLARIS_URL, {
18
25
  method: "POST",
19
- headers: { "Content-Type": "application/json" },
26
+ headers,
20
27
  body: JSON.stringify(input),
21
28
  }).catch(() => {});
22
29
  process.exit(0);
@@ -102,7 +109,7 @@ try {
102
109
 
103
110
  const res = await fetch(POLARIS_URL, {
104
111
  method: "POST",
105
- headers: { "Content-Type": "application/json" },
112
+ headers,
106
113
  body: JSON.stringify(payload),
107
114
  }).catch(() => null);
108
115
 
@@ -111,7 +118,7 @@ try {
111
118
  delete (payload as Record<string, unknown>).raw_turn;
112
119
  await fetch(POLARIS_URL, {
113
120
  method: "POST",
114
- headers: { "Content-Type": "application/json" },
121
+ headers,
115
122
  body: JSON.stringify(payload),
116
123
  }).catch(() => {});
117
124
  }
package/hooks/capture.sh CHANGED
@@ -2,6 +2,11 @@
2
2
  # hooks/capture.sh - Forward Claude Code hook events to polaris local client
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
+ #
6
+ # The raw hook payload is forwarded verbatim — no fields are stripped — so any
7
+ # tool approval/rejection or permission decision fields Claude Code includes
8
+ # (e.g. permission_mode, permission decisions on PreToolUse/PostToolUse) pass
9
+ # through to the daemon untouched.
5
10
 
6
11
  POLARIS_PORT="${POLARIS_PORT:-4322}"
7
12
  POLARIS_URL="http://127.0.0.1:${POLARIS_PORT}/events"
@@ -9,9 +14,16 @@ POLARIS_URL="http://127.0.0.1:${POLARIS_PORT}/events"
9
14
  # Read all of stdin
10
15
  INPUT=$(cat)
11
16
 
17
+ # Shared local daemon secret (wired by `polaris install`); sent when available
18
+ SECRET_HEADER=""
19
+ if [ -n "$POLARIS_DAEMON_SECRET" ]; then
20
+ SECRET_HEADER="x-polaris-daemon-secret: $POLARIS_DAEMON_SECRET"
21
+ fi
22
+
12
23
  # POST to polaris local client, fail silently
13
24
  curl -s -X POST \
14
25
  -H "Content-Type: application/json" \
26
+ ${SECRET_HEADER:+-H "$SECRET_HEADER"} \
15
27
  -d "$INPUT" \
16
28
  "$POLARIS_URL" \
17
29
  >/dev/null 2>&1 || true
@@ -4,6 +4,12 @@
4
4
 
5
5
  POLARIS_DAEMON_PORT="${POLARIS_DAEMON_PORT:-4322}"
6
6
 
7
+ # Shared local daemon secret (wired by `polaris install`); sent when available
8
+ SECRET_HEADER=""
9
+ if [ -n "$POLARIS_DAEMON_SECRET" ]; then
10
+ SECRET_HEADER="x-polaris-daemon-secret: $POLARIS_DAEMON_SECRET"
11
+ fi
12
+
7
13
  # Read stdin (session JSON from the coding agent)
8
14
  INPUT=$(cat)
9
15
 
@@ -12,13 +18,13 @@ CC_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo ""
12
18
 
13
19
  # Query daemon — try specific session first, fall back to any connected session
14
20
  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)
21
+ STATUS=$(curl -s ${SECRET_HEADER:+-H "$SECRET_HEADER"} "http://127.0.0.1:${POLARIS_DAEMON_PORT}/status/${CC_SESSION_ID}" 2>/dev/null)
16
22
  CONNECTED=$(echo "$STATUS" | jq -r '.connected' 2>/dev/null || echo "false")
17
23
  fi
18
24
 
19
25
  # If no match, check if there's any active session
20
26
  if [ "$CONNECTED" != "true" ]; then
21
- STATUS=$(curl -s "http://127.0.0.1:${POLARIS_DAEMON_PORT}/status" 2>/dev/null)
27
+ STATUS=$(curl -s ${SECRET_HEADER:+-H "$SECRET_HEADER"} "http://127.0.0.1:${POLARIS_DAEMON_PORT}/status" 2>/dev/null)
22
28
  FIRST_SESSION=$(echo "$STATUS" | jq -r '.sessions[0] // empty' 2>/dev/null)
23
29
  if [ -n "$FIRST_SESSION" ]; then
24
30
  STATUS="{\"connected\":true,\"project\":$(echo "$STATUS" | jq '.sessions[0].project'),\"session\":$(echo "$STATUS" | jq '.sessions[0].session'),\"user\":$(echo "$STATUS" | jq '.sessions[0].user')}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightupai/polaris",
3
- "version": "0.0.77",
3
+ "version": "0.0.78",
4
4
  "description": "Record and stream Claude Code sessions to Slack. Like Gong for AI coding — capture every prompt, response, and tool call. Multiplayer collaboration across AI coding agents.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Polaris Postgres backup: pg_dump to a timestamped file.
5
+ #
6
+ # Never destructive — this script only creates new files. It never deletes,
7
+ # rotates, or overwrites anything (it refuses to clobber an existing dump).
8
+ #
9
+ # Usage:
10
+ # DATABASE_URL=postgres://user:pass@host:5432/polaris ./scripts/backup.sh [output-dir]
11
+ #
12
+ # Parameters (env):
13
+ # DATABASE_URL (required) Postgres connection string to back up.
14
+ # BACKUP_DIR (optional) Output directory (default: ./backups).
15
+ # A positional arg, if given, takes precedence.
16
+ # BACKUP_S3_URI (optional) Object-storage target, e.g. s3://my-bucket/polaris.
17
+ # If set and the `aws` CLI is installed, the dump is
18
+ # also uploaded there (the local copy is kept).
19
+ #
20
+ # Cron wiring (on the prod host, e.g. nightly at 03:17 UTC):
21
+ # crontab -e
22
+ # 17 3 * * * DATABASE_URL=postgres://polaris:PASS@127.0.0.1:5432/polaris \
23
+ # BACKUP_DIR=/var/backups/polaris /opt/polaris/scripts/backup.sh \
24
+ # >> /var/log/polaris-backup.log 2>&1
25
+ #
26
+ # Retention is intentionally NOT handled here (this script never deletes).
27
+ # If you want it, pair the cron entry with something like:
28
+ # find /var/backups/polaris -name 'polaris-*.dump' -mtime +30 -delete
29
+ #
30
+ # Restore with:
31
+ # pg_restore --clean --if-exists -d "$DATABASE_URL" /path/to/polaris-<stamp>.dump
32
+ #
33
+ # Note: pg_dump's major version must be >= the server's (pg_dump aborts
34
+ # otherwise). On a host without a matching client, run via the container:
35
+ # docker exec <postgres-container> pg_dump --format=custom "$DATABASE_URL" > out.dump
36
+
37
+ DATABASE_URL="${DATABASE_URL:?DATABASE_URL is required (postgres://user:pass@host:port/db)}"
38
+ BACKUP_DIR="${1:-${BACKUP_DIR:-./backups}}"
39
+ BACKUP_S3_URI="${BACKUP_S3_URI:-}"
40
+
41
+ STAMP="$(date -u +%Y%m%d-%H%M%S)"
42
+ OUT="$BACKUP_DIR/polaris-$STAMP.dump"
43
+
44
+ mkdir -p "$BACKUP_DIR"
45
+
46
+ if [ -e "$OUT" ]; then
47
+ echo "Refusing to overwrite existing $OUT" >&2
48
+ exit 1
49
+ fi
50
+
51
+ echo "Backing up to $OUT ..."
52
+
53
+ # Custom format (-Fc): compressed, restorable with pg_restore.
54
+ # Dump to a .partial first so an interrupted dump is never mistaken for a
55
+ # complete backup, then atomically rename.
56
+ pg_dump --format=custom --file="$OUT.partial" "$DATABASE_URL"
57
+ mv "$OUT.partial" "$OUT"
58
+
59
+ echo "Backup written: $OUT ($(du -h "$OUT" | cut -f1))"
60
+
61
+ if [ -n "$BACKUP_S3_URI" ]; then
62
+ if command -v aws >/dev/null 2>&1; then
63
+ echo "Uploading to $BACKUP_S3_URI/ ..."
64
+ aws s3 cp "$OUT" "$BACKUP_S3_URI/$(basename "$OUT")"
65
+ echo "Upload complete."
66
+ else
67
+ echo "BACKUP_S3_URI is set but the aws CLI is not installed; skipping upload (local copy kept)." >&2
68
+ fi
69
+ fi
70
+
71
+ echo "Done."
@@ -1,5 +1,31 @@
1
1
  import postgres from "postgres";
2
- const sql = postgres(process.env.DATABASE_URL ?? "");
3
- const rows = await sql`SELECT id FROM orgs WHERE slack_team_id IS NOT NULL LIMIT 1`;
4
- if (rows.length > 0) console.log(rows[0].id);
5
- await sql.end();
2
+ import type { Sql } from "./service/db";
3
+
4
+ // All orgs eligible for a Slack bridge: Slack-connected (team id) with a bot token.
5
+ export async function discoverBridgeOrgs(sql: Sql): Promise<string[]> {
6
+ const rows = await sql`
7
+ SELECT id FROM orgs
8
+ WHERE slack_team_id IS NOT NULL AND slack_bot_token IS NOT NULL
9
+ ORDER BY created_at ASC
10
+ `;
11
+ return rows.map((row) => row.id as string);
12
+ }
13
+
14
+ // Back-compat: first eligible org (legacy single-org callers).
15
+ export async function discoverBridgeOrg(sql: Sql): Promise<string | null> {
16
+ const orgs = await discoverBridgeOrgs(sql);
17
+ return orgs[0] ?? null;
18
+ }
19
+
20
+ // CLI mode (docker/bridge-entrypoint.sh): print the first eligible org id.
21
+ // Kept single-line so `ORG_ID=$(bun run src/bridge-discover-org.ts)` stays valid;
22
+ // multi-org deployments should run bridge.ts with no args (startAllBridges) instead.
23
+ if (import.meta.main) {
24
+ const sql = postgres(process.env.DATABASE_URL ?? "");
25
+ try {
26
+ const orgId = await discoverBridgeOrg(sql);
27
+ if (orgId) console.log(orgId);
28
+ } finally {
29
+ await sql.end();
30
+ }
31
+ }