@lightupai/polaris 0.0.96 → 0.0.98

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.
@@ -43,32 +43,6 @@ jobs:
43
43
 
44
44
  - run: bun test
45
45
 
46
- publish:
47
- needs: test
48
- runs-on: ubuntu-latest
49
- if: github.ref == 'refs/heads/master' && github.event_name == 'push'
50
-
51
- steps:
52
- - uses: actions/checkout@v4
53
-
54
- - uses: oven-sh/setup-bun@v2
55
- with:
56
- bun-version: latest
57
-
58
- - run: bun install
59
-
60
- - name: Bump version from npm registry and publish
61
- run: |
62
- echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
63
- PUBLISHED=$(npm view @lightupai/polaris version 2>/dev/null || echo "0.0.0")
64
- # Bump patch from the latest published version
65
- IFS='.' read -r MAJOR MINOR PATCH <<< "$PUBLISHED"
66
- NEW="$MAJOR.$MINOR.$((PATCH + 1))"
67
- echo "Published: $PUBLISHED → Publishing: $NEW"
68
- # Set version in package.json for this publish only (not committed)
69
- npm version "$NEW" --no-git-tag-version
70
- npm publish --access public
71
-
72
46
  deploy:
73
47
  needs: test
74
48
  runs-on: ubuntu-latest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightupai/polaris",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
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",
@@ -45,4 +45,8 @@ Based on the arguments provided, do ONE of the following:
45
45
  1. Call `polaris_status`
46
46
  2. Display the current connection state
47
47
 
48
+ ### Daemon
49
+
50
+ The Polaris daemon runs locally and is managed automatically — do NOT manually configure launchd or systemd. If the user reports the daemon is down or not starting at login, tell them to run `polaris install` which sets up auto-restart (launchd on macOS, systemd user service on Linux).
51
+
48
52
  ### Arguments: $ARGUMENTS
package/src/cli/cli.ts CHANGED
@@ -107,6 +107,159 @@ function appToApi(appUrl: string): string {
107
107
  return appUrl.replace("app.", "api.");
108
108
  }
109
109
 
110
+ // --- Daemon auto-restart (macOS: launchd, Linux: systemd user service) ---
111
+
112
+ // macOS: LaunchAgent
113
+ const LAUNCH_AGENT_LABEL = "ai.withpolaris.daemon";
114
+ const LAUNCH_AGENT_PLIST = join(homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
115
+
116
+ async function installLaunchAgent(daemonSecret?: string): Promise<void> {
117
+ const binDir = join(POLARIS_DIR, "bin");
118
+ const runnerPath = join(binDir, "run-daemon.sh");
119
+ const daemonTsPath = join(
120
+ POLARIS_DIR, "mcp", "node_modules", "@lightupai", "polaris", "src", "daemon", "daemon.ts"
121
+ );
122
+
123
+ await mkdir(binDir, { recursive: true });
124
+ // Runner searches common bun locations so launchd's minimal PATH is not an issue
125
+ const runner = [
126
+ "#!/bin/sh",
127
+ `# Polaris daemon — managed by launchd (${LAUNCH_AGENT_LABEL})`,
128
+ `for BUNPATH in "$HOME/.bun/bin/bun" /opt/homebrew/bin/bun /usr/local/bin/bun; do`,
129
+ ` [ -x "$BUNPATH" ] && break`,
130
+ `done`,
131
+ `exec "$BUNPATH" run "${daemonTsPath}"`,
132
+ ].join("\n") + "\n";
133
+ await writeFile(runnerPath, runner);
134
+ await chmod(runnerPath, 0o755);
135
+
136
+ const secretEnv = daemonSecret
137
+ ? `\n <key>EnvironmentVariables</key>\n <dict>\n <key>POLARIS_DAEMON_SECRET</key>\n <string>${daemonSecret}</string>\n </dict>`
138
+ : "";
139
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
140
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
141
+ <plist version="1.0">
142
+ <dict>
143
+ <key>Label</key>
144
+ <string>${LAUNCH_AGENT_LABEL}</string>
145
+ <key>ProgramArguments</key>
146
+ <array>
147
+ <string>${runnerPath}</string>
148
+ </array>${secretEnv}
149
+ <key>KeepAlive</key>
150
+ <true/>
151
+ <key>RunAtLoad</key>
152
+ <true/>
153
+ <key>ThrottleInterval</key>
154
+ <integer>5</integer>
155
+ <key>StandardOutPath</key>
156
+ <string>/tmp/polaris-daemon.log</string>
157
+ <key>StandardErrorPath</key>
158
+ <string>/tmp/polaris-daemon.log</string>
159
+ </dict>
160
+ </plist>`;
161
+
162
+ await mkdir(join(homedir(), "Library", "LaunchAgents"), { recursive: true });
163
+ await writeFile(LAUNCH_AGENT_PLIST, plist);
164
+
165
+ // Unload any existing instance, then load fresh (ignore errors if not loaded yet)
166
+ Bun.spawnSync(["launchctl", "unload", "-w", LAUNCH_AGENT_PLIST], { stdout: "ignore", stderr: "ignore" });
167
+ const load = Bun.spawnSync(["launchctl", "load", "-w", LAUNCH_AGENT_PLIST], { stdout: "pipe", stderr: "pipe" });
168
+ if (load.exitCode !== 0) {
169
+ console.error(` Warning: could not register daemon with launchd: ${load.stderr.toString().trim()}`);
170
+ console.error(` Try manually: launchctl load -w ${LAUNCH_AGENT_PLIST}`);
171
+ }
172
+ }
173
+
174
+ async function removeLaunchAgent(): Promise<void> {
175
+ Bun.spawnSync(["launchctl", "unload", "-w", LAUNCH_AGENT_PLIST], { stdout: "ignore", stderr: "ignore" });
176
+ try { await rm(LAUNCH_AGENT_PLIST); } catch { /* already gone */ }
177
+ }
178
+
179
+ // Linux: systemd user service
180
+ const SYSTEMD_SERVICE_NAME = "polaris-daemon";
181
+ const SYSTEMD_USER_DIR = join(homedir(), ".config", "systemd", "user");
182
+ const SYSTEMD_SERVICE_FILE = join(SYSTEMD_USER_DIR, `${SYSTEMD_SERVICE_NAME}.service`);
183
+
184
+ async function installSystemdService(daemonSecret?: string): Promise<void> {
185
+ const binDir = join(POLARIS_DIR, "bin");
186
+ const runnerPath = join(binDir, "run-daemon.sh");
187
+ const daemonTsPath = join(
188
+ POLARIS_DIR, "mcp", "node_modules", "@lightupai", "polaris", "src", "daemon", "daemon.ts"
189
+ );
190
+
191
+ await mkdir(binDir, { recursive: true });
192
+ // Runner searches common bun install locations — systemd user units inherit a
193
+ // minimal PATH that may not include ~/.bun/bin
194
+ const runner = [
195
+ "#!/bin/sh",
196
+ `# Polaris daemon — managed by systemd (${SYSTEMD_SERVICE_NAME}.service)`,
197
+ `for BUNPATH in "$HOME/.bun/bin/bun" /usr/local/bin/bun /usr/bin/bun; do`,
198
+ ` [ -x "$BUNPATH" ] && break`,
199
+ `done`,
200
+ `exec "$BUNPATH" run "${daemonTsPath}"`,
201
+ ].join("\n") + "\n";
202
+ await writeFile(runnerPath, runner);
203
+ await chmod(runnerPath, 0o755);
204
+
205
+ const secretLine = daemonSecret ? `Environment=POLARIS_DAEMON_SECRET=${daemonSecret}\n` : "";
206
+ const unit = `[Unit]
207
+ Description=Polaris daemon
208
+ After=network.target
209
+
210
+ [Service]
211
+ Type=simple
212
+ ExecStart=${runnerPath}
213
+ Restart=always
214
+ RestartSec=5
215
+ ${secretLine}StandardOutput=append:/tmp/polaris-daemon.log
216
+ StandardError=append:/tmp/polaris-daemon.log
217
+
218
+ [Install]
219
+ WantedBy=default.target
220
+ `;
221
+
222
+ await mkdir(SYSTEMD_USER_DIR, { recursive: true });
223
+ await writeFile(SYSTEMD_SERVICE_FILE, unit);
224
+
225
+ // Reload unit files, enable (start-at-login) and start/restart the service
226
+ Bun.spawnSync(["systemctl", "--user", "daemon-reload"], { stdout: "ignore", stderr: "ignore" });
227
+ Bun.spawnSync(["systemctl", "--user", "enable", SYSTEMD_SERVICE_NAME], { stdout: "ignore", stderr: "ignore" });
228
+ const start = Bun.spawnSync(["systemctl", "--user", "restart", SYSTEMD_SERVICE_NAME], { stdout: "pipe", stderr: "pipe" });
229
+ if (start.exitCode !== 0) {
230
+ console.error(` Warning: could not start daemon service: ${start.stderr.toString().trim()}`);
231
+ console.error(` Try manually: systemctl --user restart ${SYSTEMD_SERVICE_NAME}`);
232
+ }
233
+
234
+ // Enable lingering so the user service survives logout and starts at boot
235
+ // (best-effort — requires sudo-less loginctl or appropriate privileges)
236
+ Bun.spawnSync(["loginctl", "enable-linger"], { stdout: "ignore", stderr: "ignore" });
237
+ }
238
+
239
+ async function removeSystemdService(): Promise<void> {
240
+ Bun.spawnSync(["systemctl", "--user", "disable", "--now", SYSTEMD_SERVICE_NAME], { stdout: "ignore", stderr: "ignore" });
241
+ try { await rm(SYSTEMD_SERVICE_FILE); } catch { /* already gone */ }
242
+ Bun.spawnSync(["systemctl", "--user", "daemon-reload"], { stdout: "ignore", stderr: "ignore" });
243
+ }
244
+
245
+ // Platform dispatcher
246
+ async function installDaemonService(daemonSecret?: string): Promise<void> {
247
+ if (process.platform === "darwin") return installLaunchAgent(daemonSecret);
248
+ if (process.platform === "linux") return installSystemdService(daemonSecret);
249
+ // Windows and others: no managed service; daemon is started manually
250
+ }
251
+
252
+ async function removeDaemonService(): Promise<void> {
253
+ if (process.platform === "darwin") return removeLaunchAgent();
254
+ if (process.platform === "linux") return removeSystemdService();
255
+ }
256
+
257
+ function daemonServiceLabel(): string {
258
+ if (process.platform === "darwin") return "launchd (auto-restarts on crash, starts at login)";
259
+ if (process.platform === "linux") return "systemd user service (auto-restarts on crash, starts at login)";
260
+ return "background process";
261
+ }
262
+
110
263
  // --- Daemon secret ---
111
264
 
112
265
  // Shared local secret for daemon HTTP auth. Stored in ~/.polaris/config.json
@@ -272,6 +425,10 @@ Based on the arguments provided, do ONE of the following:
272
425
  1. Call \`polaris_status\`
273
426
  2. Display the current connection state
274
427
 
428
+ ### Daemon
429
+
430
+ The Polaris daemon runs locally and is managed automatically — do NOT manually configure launchd or systemd. If the user reports the daemon is down or not starting at login, tell them to run \`polaris install\` which sets up auto-restart (launchd on macOS, systemd user service on Linux).
431
+
275
432
  ### Arguments: $ARGUMENTS
276
433
  `;
277
434
  await writeFile(join(skillDir, "SKILL.md"), skillContent);
@@ -782,6 +939,7 @@ async function recover() {
782
939
 
783
940
  async function logout(all = false) {
784
941
  if (all) {
942
+ await removeDaemonService();
785
943
  try {
786
944
  await rm(POLARIS_DIR, { recursive: true });
787
945
  console.log("All profiles and credentials removed.");
@@ -884,17 +1042,8 @@ switch (command) {
884
1042
  case "install":
885
1043
  console.log("Polaris — installing local components\n");
886
1044
  await install();
887
- // Restart daemon so it picks up any updates in the new package
888
- await killExistingDaemon();
889
- {
890
- const daemonPath = join(import.meta.dir, "..", "daemon", "daemon.ts");
891
- Bun.spawn(["bun", "run", daemonPath], {
892
- stdout: "ignore",
893
- stderr: "ignore",
894
- env: { ...process.env },
895
- }).unref?.();
896
- }
897
- console.log(" ✓ Daemon restarted");
1045
+ await installDaemonService(await ensureDaemonSecret());
1046
+ console.log(` ✓ Daemon registered with ${daemonServiceLabel()}`);
898
1047
  console.log("\nInstall complete.");
899
1048
  break;
900
1049
 
@@ -904,15 +1053,8 @@ switch (command) {
904
1053
  console.log("Polaris — authenticating\n");
905
1054
  await login(appUrl, profileName);
906
1055
  console.log("\n✓ Login complete!");
907
- // Replace any existing daemon with the current version
908
- await killExistingDaemon();
909
- const daemonPath = join(import.meta.dir, "..", "daemon", "daemon.ts");
910
- Bun.spawn(["bun", "run", daemonPath], {
911
- stdout: "ignore",
912
- stderr: "ignore",
913
- env: { ...process.env },
914
- }).unref?.();
915
- console.log(" ✓ Daemon started in background");
1056
+ await installDaemonService(await ensureDaemonSecret());
1057
+ console.log(` ✓ Daemon registered with ${daemonServiceLabel()}`);
916
1058
 
917
1059
  console.log("\nNext: restart Claude Code, then run `/polaris join #channel-name` in your AI agent.");
918
1060
  break;
@@ -965,15 +1107,8 @@ switch (command) {
965
1107
  console.log("[2/2] Authenticating...\n");
966
1108
  await login(DEFAULT_APP_URL);
967
1109
  console.log("\n✓ Polaris is set up on this machine!");
968
- // Replace any existing daemon with the current version
969
- await killExistingDaemon();
970
- const daemonPath = join(import.meta.dir, "..", "daemon", "daemon.ts");
971
- Bun.spawn(["bun", "run", daemonPath], {
972
- stdout: "ignore",
973
- stderr: "ignore",
974
- env: { ...process.env },
975
- }).unref?.();
976
- console.log(" ✓ Daemon started in background");
1110
+ await installDaemonService(await ensureDaemonSecret());
1111
+ console.log(` ✓ Daemon registered with ${daemonServiceLabel()}`);
977
1112
 
978
1113
  console.log("\nNext: restart Claude Code, then run `/polaris join #channel-name` in your AI agent.");
979
1114
  break;
@@ -100,7 +100,9 @@ const mcp = new Server(
100
100
  experimental: { "claude/channel": {} },
101
101
  tools: {},
102
102
  },
103
- 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.`,
103
+ 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.
104
+
105
+ If the user reports the Polaris daemon is not running or keeps crashing, tell them to run \`polaris install\` — it registers the daemon with launchd on macOS or a systemd user service on Linux for auto-restart on crash and at login. Do NOT suggest manually configuring launchd or systemd — \`polaris install\` handles it.`,
104
106
  }
105
107
  );
106
108