@lightupai/polaris 0.0.34 → 0.0.35

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.
@@ -92,6 +92,30 @@ The skill instructions should tell the agent:
92
92
  - Use `<@slack_id>` format in `polaris_reply` messages
93
93
  - If unsure which person, present the matches and ask
94
94
 
95
+ ## UX Flow (v1)
96
+
97
+ Claude Code CLI's `@` is reserved for file references. Instead, use a conversational `/polaris tag` command:
98
+
99
+ ```
100
+ User: /polaris tag
101
+ Agent: Who would you like to tag?
102
+ 1. Krishna Patel (@krishna)
103
+ 2. Tuhin Roy (@tuhin)
104
+ 3. Laura Mowry (@laura)
105
+ User: 1
106
+ Agent: What message?
107
+ User: what do you think about this auth approach?
108
+ Agent: → Posted to #polaris-dev: @krishna what do you think about this auth approach?
109
+ ```
110
+
111
+ The agent calls `polaris_team` to get the list, presents it, and calls `polaris_reply` with the resolved Slack mention.
112
+
113
+ ### Shorthand (future)
114
+
115
+ Once the flow works, add inline shorthand so the user can skip the interactive steps:
116
+ - `/polaris tag krishna what do you think?` — resolves and posts in one step
117
+ - Custom Claude Code `@` completion provider (feature request to Anthropic)
118
+
95
119
  ## Future extensions
96
120
 
97
121
  - **Mention as invitation**: tagging someone could auto-invite them as an advisor to the session
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightupai/polaris",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "polaris": "bin/polaris",
@@ -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 polaris_rename polaris_backfill
5
- argument-hint: [join #channel | backfill [duration] | rename <new-name> | disconnect | (no args for status)]
4
+ allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename polaris_backfill polaris_team
5
+ argument-hint: [join #channel | tag [name] | backfill [duration] | rename <new-name> | disconnect | (no args for status)]
6
6
  ---
7
7
 
8
8
  ## Polaris — Multiplayer Collaboration
@@ -22,6 +22,13 @@ Based on the arguments provided, do ONE of the following:
22
22
  1. Call `polaris_rename` with the new name
23
23
  2. Report the result
24
24
 
25
+ **`/polaris tag [name]`** — Tag a teammate on Slack:
26
+ 1. Call `polaris_team` to get the team list with Slack identities
27
+ 2. If a name was given, find the matching member. If no name, present a numbered list for the user to pick.
28
+ 3. Ask the user what message to send (if not already provided)
29
+ 4. Call `polaris_reply` with the message, including the Slack mention in `<@SLACK_ID>` format
30
+ 5. Confirm the message was sent
31
+
25
32
  **`/polaris backfill [duration]`** — Recover lost events:
26
33
  1. Call `polaris_backfill` with the optional duration (e.g., `2h`, `30m`)
27
34
  2. Report how many events were recovered
@@ -110,6 +110,14 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
110
110
  required: ["session"],
111
111
  },
112
112
  },
113
+ {
114
+ name: "polaris_team",
115
+ description: "List team members with their Slack identities. Use this to resolve @mentions before posting to Slack.",
116
+ inputSchema: {
117
+ type: "object" as const,
118
+ properties: {},
119
+ },
120
+ },
113
121
  {
114
122
  name: "polaris_backfill",
115
123
  description: "Recover lost events from local daemon logs. Use when events were lost due to disconnection or API downtime.",
@@ -255,6 +263,25 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
255
263
  }
256
264
  }
257
265
 
266
+ if (name === "polaris_team") {
267
+ try {
268
+ const res = await daemonGet("/team");
269
+ if (res.ok) {
270
+ const body = await res.json() as { members: Array<{ name: string; participant_id: string; slack_id: string | null; slack_display: string | null }> };
271
+ if (body.members.length === 0) {
272
+ return { content: [{ type: "text", text: "No team members found." }] };
273
+ }
274
+ const list = body.members.map((m, i) =>
275
+ ` ${i + 1}. ${m.name} (${m.participant_id})${m.slack_display ? ` — @${m.slack_display}` : ""}${m.slack_id ? ` [${m.slack_id}]` : ""}`
276
+ ).join("\n");
277
+ return { content: [{ type: "text", text: `Team members:\n${list}` }] };
278
+ }
279
+ return { content: [{ type: "text", text: "Failed to fetch team list." }] };
280
+ } catch {
281
+ return { content: [{ type: "text", text: "Failed to reach the daemon." }] };
282
+ }
283
+ }
284
+
258
285
  if (name === "polaris_backfill") {
259
286
  if (!currentProject) {
260
287
  return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
@@ -669,6 +669,21 @@ export function startDaemon(port = Number(process.env.POLARIS_DAEMON_PORT ?? 432
669
669
  }
670
670
  }
671
671
 
672
+ // GET /team — list team members with Slack identities
673
+ if (method === "GET" && pathname === "/team") {
674
+ try {
675
+ const serviceUrl = getServiceUrl();
676
+ const res = await fetch(`${serviceUrl}/team`, {
677
+ headers: await authHeaders(),
678
+ });
679
+ if (!res.ok) return error("Failed to fetch team", res.status);
680
+ const data = await res.json();
681
+ return json(data);
682
+ } catch {
683
+ return error("API unreachable", 503);
684
+ }
685
+ }
686
+
672
687
  // POST /backfill — recover lost events from daemon log
673
688
  if (method === "POST" && pathname === "/backfill") {
674
689
  try {
package/src/service/db.ts CHANGED
@@ -189,6 +189,11 @@ export async function upsertUser(sql: Sql, id: string, email: string, name: stri
189
189
  return { ...row, created_at: row.created_at.toISOString() } as User;
190
190
  }
191
191
 
192
+ export async function listUsers(sql: Sql, orgId: string): Promise<User[]> {
193
+ const rows = await sql`SELECT * FROM users WHERE org_id = ${orgId} ORDER BY name ASC`;
194
+ return rows.map((r) => ({ ...r, created_at: r.created_at.toISOString() }) as User);
195
+ }
196
+
192
197
  // --- Projects (org-scoped) ---
193
198
 
194
199
  export async function createProject(sql: Sql, orgId: string, name: string): Promise<Project> {
@@ -444,6 +444,50 @@ export async function startServer(opts: {
444
444
  return json({ status: "claimed", driver: parsed.data.driver, session: params.sess });
445
445
  }
446
446
 
447
+ // GET /team — list org members with Slack identities
448
+ if (method === "GET" && pathname === "/team") {
449
+ const { listUsers, getOrg: getOrgFn } = await import("./db");
450
+ const users = await listUsers(sql, orgId);
451
+ const org = await getOrgFn(sql, orgId);
452
+
453
+ // Resolve Slack user info if bot token available
454
+ let slackMembers: Array<{ id: string; name: string; display_name: string }> = [];
455
+ if (org?.slack_bot_token) {
456
+ try {
457
+ const slackRes = await fetch("https://slack.com/api/users.list?limit=200", {
458
+ headers: { Authorization: `Bearer ${org.slack_bot_token}` },
459
+ });
460
+ if (slackRes.ok) {
461
+ const slackData = (await slackRes.json()) as { members?: Array<{ id: string; real_name?: string; profile?: { display_name?: string }; deleted?: boolean; is_bot?: boolean }> };
462
+ slackMembers = (slackData.members ?? [])
463
+ .filter((m) => !m.deleted && !m.is_bot)
464
+ .map((m) => ({
465
+ id: m.id,
466
+ name: m.real_name ?? "",
467
+ display_name: m.profile?.display_name ?? "",
468
+ }));
469
+ }
470
+ } catch { /* Slack API unavailable */ }
471
+ }
472
+
473
+ // Join Polaris users with Slack members by email match or name match
474
+ const team = users.map((u) => {
475
+ const slack = slackMembers.find((m) =>
476
+ m.name.toLowerCase() === u.name.toLowerCase() ||
477
+ m.display_name.toLowerCase() === u.name.toLowerCase().replace(/\s+/g, ".")
478
+ );
479
+ return {
480
+ name: u.name,
481
+ participant_id: u.participant_id,
482
+ email: u.email,
483
+ slack_id: slack?.id ?? null,
484
+ slack_display: slack?.display_name || slack?.name || null,
485
+ };
486
+ });
487
+
488
+ return json({ members: team });
489
+ }
490
+
447
491
  if (method === "GET" && pathname === "/status") {
448
492
  return json({ ok: true, version: "0.0.1" });
449
493
  }
package/src/web/pages.ts CHANGED
@@ -11,10 +11,11 @@ export function renderLandingPage(): string {
11
11
  <!-- Hero -->
12
12
  <div class="pt-24 pb-16">
13
13
  <h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
14
- Multiplayer collaboration<br>for Claude Code
14
+ Realtime multiplayer Claude Code<br>sessions and knowledge capture
15
15
  </h1>
16
- <p class="mt-4 text-lg text-gray-500 max-w-xl">
17
- Your teammates see what your agent is doing. They can jump in from Slack and steer it in real time.
16
+ <p class="mt-2 text-sm font-medium text-polaris-600">It's like Gong for AI.</p>
17
+ <p class="mt-4 text-lg text-gray-500 max-w-2xl">
18
+ Move your Claude Code sessions out of private windows and into a shared workspace in Slack. Polaris runs quietly in the background to document your sessions automatically, turning isolated chats into a continuous stream of team intelligence.
18
19
  </p>
19
20
  <div class="mt-8 flex items-center gap-4">
20
21
  <a href="#get-started" class="px-5 py-2.5 bg-gray-900 text-white text-sm font-medium rounded-lg hover:bg-gray-800 transition">Get started</a>
package/tests/web.test.ts CHANGED
@@ -275,7 +275,7 @@ describe("routes", () => {
275
275
  const res = await app.request("/");
276
276
  expect(res.status).toBe(200);
277
277
  const body = await res.text();
278
- expect(body).toContain("Multiplayer collaboration");
278
+ expect(body).toContain("Polaris");
279
279
  });
280
280
 
281
281
  test("GET /preview returns 200", async () => {