@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.
- package/docs/design-mentions.md +24 -0
- package/package.json +1 -1
- package/skills/polaris/SKILL.md +9 -2
- package/src/client/client.ts +27 -0
- package/src/daemon/daemon.ts +15 -0
- package/src/service/db.ts +5 -0
- package/src/service/server.ts +44 -0
- package/src/web/pages.ts +4 -3
- package/tests/web.test.ts +1 -1
package/docs/design-mentions.md
CHANGED
|
@@ -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
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 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
|
package/src/client/client.ts
CHANGED
|
@@ -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." }] };
|
package/src/daemon/daemon.ts
CHANGED
|
@@ -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> {
|
package/src/service/server.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
+
Realtime multiplayer Claude Code<br>sessions and knowledge capture
|
|
15
15
|
</h1>
|
|
16
|
-
<p class="mt-
|
|
17
|
-
|
|
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("
|
|
278
|
+
expect(body).toContain("Polaris");
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
test("GET /preview returns 200", async () => {
|