@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
package/src/cli/cli.ts
CHANGED
|
@@ -1,135 +1,132 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// --- Polaris CLI ---
|
|
3
3
|
// Usage:
|
|
4
|
-
// polaris
|
|
5
|
-
// polaris
|
|
6
|
-
// polaris
|
|
7
|
-
// polaris
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// polaris — install + login (default onboarding)
|
|
5
|
+
// polaris install — install local components (no auth)
|
|
6
|
+
// polaris login — authenticate via Google SSO (production)
|
|
7
|
+
// polaris login --local — authenticate against localhost
|
|
8
|
+
// polaris login --url <url> — authenticate against a custom URL
|
|
9
|
+
// polaris login --profile <name> — explicit profile name
|
|
10
|
+
// polaris use <profile> — switch active profile
|
|
11
|
+
// polaris profiles — list profiles
|
|
12
|
+
// polaris daemon — start the local daemon
|
|
13
|
+
// polaris status — show connection status
|
|
14
|
+
// polaris logout — remove credentials
|
|
15
|
+
|
|
16
|
+
import { mkdir, writeFile, readFile, rm } from "node:fs/promises";
|
|
17
|
+
import { homedir, hostname } from "node:os";
|
|
11
18
|
import { join } from "node:path";
|
|
12
19
|
|
|
13
20
|
const POLARIS_DIR = join(homedir(), ".polaris");
|
|
14
|
-
const
|
|
21
|
+
const CONFIG_FILE = join(POLARIS_DIR, "config.json");
|
|
22
|
+
const LEGACY_CREDENTIALS_FILE = join(POLARIS_DIR, "credentials.json");
|
|
15
23
|
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
16
|
-
const CLAUDE_SETTINGS_DIR = join(CLAUDE_DIR);
|
|
17
24
|
|
|
18
|
-
const
|
|
25
|
+
const DEFAULT_APP_URL = "https://app.polaris.lightup.ai";
|
|
26
|
+
const LOCAL_APP_URL = "http://localhost:3000";
|
|
19
27
|
|
|
20
|
-
// ---
|
|
28
|
+
// --- Config ---
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
interface Profile {
|
|
31
|
+
api: string;
|
|
32
|
+
app: string;
|
|
33
|
+
token: string;
|
|
34
|
+
email: string;
|
|
35
|
+
name: string;
|
|
36
|
+
org_id: string;
|
|
37
|
+
participant_id: string;
|
|
38
|
+
}
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
interface Config {
|
|
41
|
+
active: string;
|
|
42
|
+
profiles: Record<string, Profile>;
|
|
43
|
+
}
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
async function loadConfig(): Promise<Config> {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(await readFile(CONFIG_FILE, "utf-8"));
|
|
48
|
+
} catch {
|
|
49
|
+
// Migrate from legacy credentials.json if it exists
|
|
50
|
+
try {
|
|
51
|
+
const legacy = JSON.parse(await readFile(LEGACY_CREDENTIALS_FILE, "utf-8"));
|
|
52
|
+
if (legacy.token) {
|
|
53
|
+
const appUrl = legacy.service_url ?? DEFAULT_APP_URL;
|
|
54
|
+
const profileName = deriveProfileName(appUrl);
|
|
55
|
+
const config: Config = {
|
|
56
|
+
active: profileName,
|
|
57
|
+
profiles: {
|
|
58
|
+
[profileName]: {
|
|
59
|
+
api: appToApi(appUrl),
|
|
60
|
+
app: appUrl,
|
|
61
|
+
token: legacy.token,
|
|
62
|
+
email: legacy.email ?? "",
|
|
63
|
+
name: legacy.name ?? "",
|
|
64
|
+
org_id: legacy.org_id ?? "",
|
|
65
|
+
participant_id: legacy.participant_id ?? "",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
await saveConfig(config);
|
|
70
|
+
return config;
|
|
43
71
|
}
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const callbackPort = callbackServer.port;
|
|
49
|
-
const authUrl = `${SERVICE_URL}/auth/cli?port=${callbackPort}`;
|
|
50
|
-
|
|
51
|
-
console.log("Opening browser for Google sign-in...");
|
|
52
|
-
console.log(`If the browser doesn't open, visit: ${authUrl}\n`);
|
|
53
|
-
|
|
54
|
-
// Open browser
|
|
55
|
-
const proc = Bun.spawn(
|
|
56
|
-
process.platform === "darwin" ? ["open", authUrl] :
|
|
57
|
-
process.platform === "win32" ? ["cmd", "/c", "start", authUrl] :
|
|
58
|
-
["xdg-open", authUrl],
|
|
59
|
-
{ stdout: "ignore", stderr: "ignore" }
|
|
60
|
-
);
|
|
61
|
-
await proc.exited;
|
|
72
|
+
} catch { /* no legacy file either */ }
|
|
73
|
+
return { active: "", profiles: {} };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
setTimeout(() => callbackServer.stop(true), 3000);
|
|
77
|
+
async function saveConfig(config: Config): Promise<void> {
|
|
78
|
+
await mkdir(POLARIS_DIR, { recursive: true });
|
|
79
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
80
|
+
}
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
const userInfo = (await res.json()) as {
|
|
76
|
-
sub: string;
|
|
77
|
-
email: string;
|
|
78
|
-
name: string;
|
|
79
|
-
org_id: string;
|
|
80
|
-
participant_id: string;
|
|
81
|
-
};
|
|
82
|
+
function getActiveProfile(config: Config): Profile | null {
|
|
83
|
+
if (!config.active || !config.profiles[config.active]) return null;
|
|
84
|
+
return config.profiles[config.active];
|
|
85
|
+
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
function deriveProfileName(appUrl: string): string {
|
|
88
|
+
if (appUrl.includes("localhost") || appUrl.includes("127.0.0.1")) return "local";
|
|
85
89
|
try {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
90
|
+
const host = new URL(appUrl).hostname;
|
|
91
|
+
// app.polaris.lightup.ai → prod
|
|
92
|
+
if (host.includes("polaris.lightup.ai")) return "prod";
|
|
93
|
+
// strip common prefixes
|
|
94
|
+
return host.replace(/^app\./, "").replace(/\./g, "-");
|
|
95
|
+
} catch {
|
|
96
|
+
return "default";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
function appToApi(appUrl: string): string {
|
|
101
|
+
if (appUrl.includes("localhost") || appUrl.includes("127.0.0.1")) {
|
|
102
|
+
return appUrl.replace(":3000", ":4321");
|
|
103
|
+
}
|
|
104
|
+
return appUrl.replace("app.", "api.");
|
|
105
|
+
}
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
await mkdir(POLARIS_DIR, { recursive: true });
|
|
101
|
-
await writeFile(CREDENTIALS_FILE, JSON.stringify({
|
|
102
|
-
token,
|
|
103
|
-
...userInfo,
|
|
104
|
-
service_url: SERVICE_URL,
|
|
105
|
-
}, null, 2));
|
|
106
|
-
console.log(`Credentials saved to ${CREDENTIALS_FILE}`);
|
|
107
|
+
// --- Install ---
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
async function install(participantId?: string) {
|
|
109
110
|
await mkdir(CLAUDE_DIR, { recursive: true });
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
+
// MCP server config
|
|
112
113
|
const clientPath = join(import.meta.dir, "..", "client", "client.ts");
|
|
113
|
-
|
|
114
114
|
const mcpConfig = {
|
|
115
115
|
mcpServers: {
|
|
116
116
|
polaris: {
|
|
117
|
-
command: "
|
|
118
|
-
args: [clientPath],
|
|
117
|
+
command: "npx",
|
|
118
|
+
args: ["bun", clientPath],
|
|
119
119
|
env: {
|
|
120
|
-
POLARIS_DAEMON_URL: "http://127.0.0.1:
|
|
121
|
-
POLARIS_SERVICE_URL: SERVICE_URL.replace(":3000", ":4321"), // API port
|
|
120
|
+
POLARIS_DAEMON_URL: "http://127.0.0.1:4322",
|
|
122
121
|
},
|
|
123
122
|
},
|
|
124
123
|
},
|
|
125
124
|
};
|
|
126
125
|
|
|
127
126
|
const mcpConfigPath = join(CLAUDE_DIR, ".mcp.json");
|
|
128
|
-
// Merge with existing config if present
|
|
129
127
|
let existingMcp: Record<string, unknown> = {};
|
|
130
128
|
try {
|
|
131
|
-
|
|
132
|
-
existingMcp = JSON.parse(existing);
|
|
129
|
+
existingMcp = JSON.parse(await readFile(mcpConfigPath, "utf-8"));
|
|
133
130
|
} catch { /* doesn't exist yet */ }
|
|
134
131
|
|
|
135
132
|
const mergedMcp = {
|
|
@@ -140,9 +137,9 @@ async function login() {
|
|
|
140
137
|
},
|
|
141
138
|
};
|
|
142
139
|
await writeFile(mcpConfigPath, JSON.stringify(mergedMcp, null, 2));
|
|
143
|
-
console.log(
|
|
140
|
+
console.log(" ✓ MCP server config written");
|
|
144
141
|
|
|
145
|
-
//
|
|
142
|
+
// Hooks
|
|
146
143
|
const captureShPath = join(import.meta.dir, "..", "..", "hooks", "capture.sh");
|
|
147
144
|
const hooksConfig = {
|
|
148
145
|
UserPromptSubmit: [{ hooks: [{ type: "command", command: captureShPath }] }],
|
|
@@ -154,28 +151,34 @@ async function login() {
|
|
|
154
151
|
const settingsPath = join(CLAUDE_DIR, "settings.json");
|
|
155
152
|
let existingSettings: Record<string, unknown> = {};
|
|
156
153
|
try {
|
|
157
|
-
|
|
158
|
-
existingSettings = JSON.parse(existing);
|
|
154
|
+
existingSettings = JSON.parse(await readFile(settingsPath, "utf-8"));
|
|
159
155
|
} catch { /* doesn't exist yet */ }
|
|
160
156
|
|
|
157
|
+
// Status line
|
|
158
|
+
const statusLinePath = join(import.meta.dir, "..", "..", "hooks", "statusline.sh");
|
|
161
159
|
const mergedSettings = {
|
|
162
160
|
...existingSettings,
|
|
163
161
|
hooks: {
|
|
164
162
|
...(existingSettings as { hooks?: Record<string, unknown> }).hooks,
|
|
165
163
|
...hooksConfig,
|
|
166
164
|
},
|
|
165
|
+
statusLine: {
|
|
166
|
+
type: "command",
|
|
167
|
+
command: statusLinePath,
|
|
168
|
+
},
|
|
167
169
|
};
|
|
168
170
|
await writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
169
|
-
console.log(
|
|
171
|
+
console.log(" ✓ Hooks + status line config written");
|
|
170
172
|
|
|
171
|
-
//
|
|
173
|
+
// /polaris skill
|
|
172
174
|
const skillDir = join(CLAUDE_DIR, "skills", "polaris");
|
|
173
175
|
await mkdir(skillDir, { recursive: true });
|
|
176
|
+
const identity = participantId ? `\`${participantId}\`` : "the user's participant ID (ask them if unknown)";
|
|
174
177
|
const skillContent = `---
|
|
175
178
|
name: polaris
|
|
176
179
|
description: Connect to a Polaris multiplayer collaboration session
|
|
177
|
-
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context
|
|
178
|
-
argument-hint: [join <project> <session> | disconnect | (no args for status)]
|
|
180
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename
|
|
181
|
+
argument-hint: [join <project> <session> | rename <new-name> | disconnect | (no args for status)]
|
|
179
182
|
---
|
|
180
183
|
|
|
181
184
|
## Polaris — Multiplayer Collaboration
|
|
@@ -187,9 +190,13 @@ Manage your connection to a Polaris collaboration session.
|
|
|
187
190
|
Based on the arguments provided, do ONE of the following:
|
|
188
191
|
|
|
189
192
|
**\`/polaris join <project> <session>\`** — Connect to a session:
|
|
190
|
-
1. Call \`polaris_connect\` with the given project, session, and user identity
|
|
193
|
+
1. Call \`polaris_connect\` with the given project, session, and user identity ${identity}
|
|
191
194
|
2. Report the connection status
|
|
192
195
|
|
|
196
|
+
**\`/polaris rename <new-name>\`** — Rename the current project:
|
|
197
|
+
1. Call \`polaris_rename\` with the new name
|
|
198
|
+
2. Report the result
|
|
199
|
+
|
|
193
200
|
**\`/polaris disconnect\`** — Disconnect:
|
|
194
201
|
1. Call \`polaris_disconnect\`
|
|
195
202
|
2. Confirm disconnection
|
|
@@ -201,24 +208,142 @@ Based on the arguments provided, do ONE of the following:
|
|
|
201
208
|
### Arguments: $ARGUMENTS
|
|
202
209
|
`;
|
|
203
210
|
await writeFile(join(skillDir, "SKILL.md"), skillContent);
|
|
204
|
-
console.log(
|
|
211
|
+
console.log(" ✓ /polaris skill written");
|
|
212
|
+
}
|
|
205
213
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
// --- Login ---
|
|
215
|
+
|
|
216
|
+
async function login(appUrl: string, profileName?: string) {
|
|
217
|
+
const derivedName = profileName ?? deriveProfileName(appUrl);
|
|
218
|
+
|
|
219
|
+
// Browser OAuth
|
|
220
|
+
let resolveToken: (token: string) => void;
|
|
221
|
+
const tokenPromise = new Promise<string>((resolve) => { resolveToken = resolve; });
|
|
222
|
+
|
|
223
|
+
const callbackServer = Bun.serve({
|
|
224
|
+
port: 0,
|
|
225
|
+
hostname: "127.0.0.1",
|
|
226
|
+
fetch(req) {
|
|
227
|
+
const url = new URL(req.url);
|
|
228
|
+
if (url.pathname === "/callback") {
|
|
229
|
+
const token = url.searchParams.get("token");
|
|
230
|
+
if (token) {
|
|
231
|
+
resolveToken!(token);
|
|
232
|
+
return new Response(
|
|
233
|
+
`<!DOCTYPE html><html><head><title>Polaris</title><style>body{font-family:-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;color:#1a1a1a;}</style></head><body><div style="text-align:center"><h2>Authenticated!</h2><p>You can close this tab and return to the terminal.</p></div></body></html>`,
|
|
234
|
+
{ headers: { "Content-Type": "text/html" } }
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return new Response("Not found", { status: 404 });
|
|
213
239
|
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const callbackPort = callbackServer.port;
|
|
243
|
+
const authUrl = `${appUrl}/auth/cli?port=${callbackPort}`;
|
|
244
|
+
|
|
245
|
+
console.log(` Opening browser for Google sign-in (${derivedName})...`);
|
|
246
|
+
console.log(` If the browser doesn't open, visit: ${authUrl}\n`);
|
|
247
|
+
|
|
248
|
+
const proc = Bun.spawn(
|
|
249
|
+
process.platform === "darwin" ? ["open", authUrl] :
|
|
250
|
+
process.platform === "win32" ? ["cmd", "/c", "start", authUrl] :
|
|
251
|
+
["xdg-open", authUrl],
|
|
252
|
+
{ stdout: "ignore", stderr: "ignore" }
|
|
253
|
+
);
|
|
254
|
+
await proc.exited;
|
|
255
|
+
|
|
256
|
+
console.log(" Waiting for authentication...");
|
|
257
|
+
const token = await tokenPromise;
|
|
258
|
+
setTimeout(() => callbackServer.stop(true), 3000);
|
|
259
|
+
|
|
260
|
+
// Validate token
|
|
261
|
+
const res = await fetch(`${appUrl}/auth/token?token=${token}`);
|
|
262
|
+
if (!res.ok) {
|
|
263
|
+
console.error(" ✗ Failed to validate token. Please try again.");
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
const userInfo = (await res.json()) as {
|
|
267
|
+
sub: string;
|
|
268
|
+
email: string;
|
|
269
|
+
name: string;
|
|
270
|
+
org_id: string;
|
|
271
|
+
participant_id: string;
|
|
214
272
|
};
|
|
215
|
-
await writeFile(settingsPath, JSON.stringify(mergedSettingsWithStatusLine, null, 2));
|
|
216
|
-
console.log(`Status line config written to ${settingsPath}`);
|
|
217
273
|
|
|
218
|
-
|
|
219
|
-
|
|
274
|
+
let orgName = userInfo.email.split("@")[1].split(".")[0];
|
|
275
|
+
orgName = orgName.charAt(0).toUpperCase() + orgName.slice(1);
|
|
276
|
+
|
|
277
|
+
console.log(`\n Authenticated as ${userInfo.name} (${userInfo.email})`);
|
|
278
|
+
console.log(` Organization: ${orgName}`);
|
|
279
|
+
console.log(` Participant ID: ${userInfo.participant_id}`);
|
|
280
|
+
|
|
281
|
+
// Save to profile
|
|
282
|
+
const config = await loadConfig();
|
|
283
|
+
config.profiles[derivedName] = {
|
|
284
|
+
api: appToApi(appUrl),
|
|
285
|
+
app: appUrl,
|
|
286
|
+
token,
|
|
287
|
+
email: userInfo.email,
|
|
288
|
+
name: userInfo.name,
|
|
289
|
+
org_id: userInfo.org_id,
|
|
290
|
+
participant_id: userInfo.participant_id,
|
|
291
|
+
};
|
|
292
|
+
config.active = derivedName;
|
|
293
|
+
await saveConfig(config);
|
|
294
|
+
|
|
295
|
+
// Also write legacy credentials.json for backward compat (daemon reads it)
|
|
296
|
+
await writeFile(LEGACY_CREDENTIALS_FILE, JSON.stringify({
|
|
297
|
+
token,
|
|
298
|
+
...userInfo,
|
|
299
|
+
service_url: appUrl,
|
|
300
|
+
}, null, 2));
|
|
301
|
+
|
|
302
|
+
console.log(` ✓ Profile "${derivedName}" saved and set as active`);
|
|
303
|
+
|
|
304
|
+
// Re-install skill with personalized participant ID
|
|
305
|
+
await mkdir(join(CLAUDE_DIR, "skills", "polaris"), { recursive: true });
|
|
306
|
+
const identity = `\`${userInfo.participant_id}\``;
|
|
307
|
+
const skillDir = join(CLAUDE_DIR, "skills", "polaris");
|
|
308
|
+
const skillContent = `---
|
|
309
|
+
name: polaris
|
|
310
|
+
description: Connect to a Polaris multiplayer collaboration session
|
|
311
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename
|
|
312
|
+
argument-hint: [join <project> <session> | rename <new-name> | disconnect | (no args for status)]
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Polaris — Multiplayer Collaboration
|
|
316
|
+
|
|
317
|
+
Manage your connection to a Polaris collaboration session.
|
|
318
|
+
|
|
319
|
+
### Commands
|
|
320
|
+
|
|
321
|
+
Based on the arguments provided, do ONE of the following:
|
|
322
|
+
|
|
323
|
+
**\`/polaris join <project> <session>\`** — Connect to a session:
|
|
324
|
+
1. Call \`polaris_connect\` with the given project, session, and user identity ${identity}
|
|
325
|
+
2. Report the connection status
|
|
326
|
+
|
|
327
|
+
**\`/polaris rename <new-name>\`** — Rename the current project:
|
|
328
|
+
1. Call \`polaris_rename\` with the new name
|
|
329
|
+
2. Report the result
|
|
330
|
+
|
|
331
|
+
**\`/polaris disconnect\`** — Disconnect:
|
|
332
|
+
1. Call \`polaris_disconnect\`
|
|
333
|
+
2. Confirm disconnection
|
|
334
|
+
|
|
335
|
+
**\`/polaris\`** (no arguments) — Show status:
|
|
336
|
+
1. Call \`polaris_status\`
|
|
337
|
+
2. Display the current connection state
|
|
338
|
+
|
|
339
|
+
### Arguments: $ARGUMENTS
|
|
340
|
+
`;
|
|
341
|
+
await writeFile(join(skillDir, "SKILL.md"), skillContent);
|
|
342
|
+
|
|
343
|
+
// Post system event (device connected)
|
|
344
|
+
const apiUrl = appToApi(appUrl);
|
|
220
345
|
try {
|
|
221
|
-
await fetch(`${
|
|
346
|
+
await fetch(`${apiUrl}/projects/_system/sessions/_system/events`, {
|
|
222
347
|
method: "POST",
|
|
223
348
|
headers: {
|
|
224
349
|
"Content-Type": "application/json",
|
|
@@ -229,21 +354,63 @@ Based on the arguments provided, do ONE of the following:
|
|
|
229
354
|
payload: {
|
|
230
355
|
hook_event_name: "Stop",
|
|
231
356
|
session_id: "_system",
|
|
232
|
-
stop_response: `Device connected: ${hostname} (${process.platform})`,
|
|
357
|
+
stop_response: `Device connected: ${hostname()} (${process.platform})`,
|
|
233
358
|
},
|
|
234
359
|
}),
|
|
235
360
|
});
|
|
236
|
-
// Notify web app dashboard to refresh
|
|
237
|
-
await fetch(`${SERVICE_URL}/api/notify-dashboard`, {
|
|
238
|
-
method: "POST",
|
|
239
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
240
|
-
});
|
|
241
361
|
} catch { /* non-fatal */ }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// --- Use ---
|
|
365
|
+
|
|
366
|
+
async function use(profileName: string) {
|
|
367
|
+
const config = await loadConfig();
|
|
368
|
+
if (!config.profiles[profileName]) {
|
|
369
|
+
console.error(`Profile "${profileName}" not found.`);
|
|
370
|
+
const names = Object.keys(config.profiles);
|
|
371
|
+
if (names.length > 0) {
|
|
372
|
+
console.error(`Available profiles: ${names.join(", ")}`);
|
|
373
|
+
} else {
|
|
374
|
+
console.error("No profiles configured. Run: polaris login");
|
|
375
|
+
}
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
config.active = profileName;
|
|
379
|
+
await saveConfig(config);
|
|
380
|
+
|
|
381
|
+
// Update legacy credentials.json for daemon
|
|
382
|
+
const profile = config.profiles[profileName];
|
|
383
|
+
await writeFile(LEGACY_CREDENTIALS_FILE, JSON.stringify({
|
|
384
|
+
token: profile.token,
|
|
385
|
+
email: profile.email,
|
|
386
|
+
name: profile.name,
|
|
387
|
+
org_id: profile.org_id,
|
|
388
|
+
participant_id: profile.participant_id,
|
|
389
|
+
service_url: profile.app,
|
|
390
|
+
}, null, 2));
|
|
242
391
|
|
|
243
|
-
console.log(
|
|
244
|
-
console.log("
|
|
245
|
-
|
|
246
|
-
|
|
392
|
+
console.log(`Active profile: ${profileName} (${profile.api})`);
|
|
393
|
+
console.log("Restart the daemon for this to take effect.");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// --- Profiles ---
|
|
397
|
+
|
|
398
|
+
async function profiles() {
|
|
399
|
+
const config = await loadConfig();
|
|
400
|
+
const names = Object.keys(config.profiles);
|
|
401
|
+
if (names.length === 0) {
|
|
402
|
+
console.log("No profiles configured. Run: polaris login");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
console.log("Profiles:\n");
|
|
406
|
+
for (const name of names) {
|
|
407
|
+
const p = config.profiles[name];
|
|
408
|
+
const active = name === config.active ? " (active)" : "";
|
|
409
|
+
console.log(` ${name}${active}`);
|
|
410
|
+
console.log(` API: ${p.api}`);
|
|
411
|
+
console.log(` User: ${p.name} (${p.email})`);
|
|
412
|
+
console.log("");
|
|
413
|
+
}
|
|
247
414
|
}
|
|
248
415
|
|
|
249
416
|
// --- Daemon ---
|
|
@@ -256,7 +423,6 @@ async function daemon() {
|
|
|
256
423
|
stderr: "inherit",
|
|
257
424
|
env: {
|
|
258
425
|
...process.env,
|
|
259
|
-
POLARIS_SERVICE_URL: SERVICE_URL.replace(":3000", ":4321"),
|
|
260
426
|
},
|
|
261
427
|
});
|
|
262
428
|
await proc.exited;
|
|
@@ -265,68 +431,143 @@ async function daemon() {
|
|
|
265
431
|
// --- Status ---
|
|
266
432
|
|
|
267
433
|
async function status() {
|
|
434
|
+
// Active profile
|
|
435
|
+
const config = await loadConfig();
|
|
436
|
+
const profile = getActiveProfile(config);
|
|
437
|
+
if (profile) {
|
|
438
|
+
console.log(`Profile: ${config.active} (${profile.api})`);
|
|
439
|
+
console.log(`User: ${profile.name} (${profile.email})`);
|
|
440
|
+
} else {
|
|
441
|
+
console.log("Not logged in. Run: polaris login");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Daemon
|
|
268
445
|
try {
|
|
269
|
-
const res = await fetch("http://127.0.0.1:
|
|
446
|
+
const res = await fetch("http://127.0.0.1:4322/status");
|
|
270
447
|
const data = (await res.json()) as { ok: boolean; sessions?: Array<{ ccSessionId: string; project: string; session: string; user: string }> };
|
|
271
448
|
if (data.ok) {
|
|
272
|
-
console.log("
|
|
449
|
+
console.log("\nDaemon: running");
|
|
273
450
|
if (data.sessions && data.sessions.length > 0) {
|
|
274
451
|
console.log(`Active sessions (${data.sessions.length}):`);
|
|
275
452
|
for (const s of data.sessions) {
|
|
276
|
-
console.log(` ${s.project}/${s.session} as ${s.user}
|
|
453
|
+
console.log(` ${s.project}/${s.session} as ${s.user}`);
|
|
277
454
|
}
|
|
278
455
|
} else {
|
|
279
456
|
console.log("No active sessions");
|
|
280
457
|
}
|
|
281
458
|
}
|
|
282
459
|
} catch {
|
|
283
|
-
console.log("
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Check credentials
|
|
287
|
-
try {
|
|
288
|
-
const creds = JSON.parse(await readFile(CREDENTIALS_FILE, "utf-8"));
|
|
289
|
-
console.log(`\nLogged in as: ${creds.name} (${creds.email})`);
|
|
290
|
-
console.log(`Org: ${creds.org_id}`);
|
|
291
|
-
console.log(`Service: ${creds.service_url}`);
|
|
292
|
-
} catch {
|
|
293
|
-
console.log("\nNot logged in. Run: polaris login");
|
|
460
|
+
console.log("\nDaemon: not running");
|
|
294
461
|
}
|
|
295
462
|
}
|
|
296
463
|
|
|
297
464
|
// --- Logout ---
|
|
298
465
|
|
|
299
|
-
async function logout() {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
466
|
+
async function logout(all = false) {
|
|
467
|
+
if (all) {
|
|
468
|
+
try {
|
|
469
|
+
await rm(POLARIS_DIR, { recursive: true });
|
|
470
|
+
console.log("All profiles and credentials removed.");
|
|
471
|
+
} catch { /* already gone */ }
|
|
472
|
+
} else {
|
|
473
|
+
const config = await loadConfig();
|
|
474
|
+
if (!config.active || !config.profiles[config.active]) {
|
|
475
|
+
console.log("No active profile to remove.");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const name = config.active;
|
|
479
|
+
delete config.profiles[name];
|
|
480
|
+
// Set active to first remaining profile, or empty
|
|
481
|
+
const remaining = Object.keys(config.profiles);
|
|
482
|
+
config.active = remaining.length > 0 ? remaining[0] : "";
|
|
483
|
+
await saveConfig(config);
|
|
484
|
+
console.log(`Profile "${name}" removed.`);
|
|
485
|
+
if (config.active) {
|
|
486
|
+
console.log(`Active profile switched to: ${config.active}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
console.log("MCP config and hooks are still installed — run `polaris install` to reset them.");
|
|
305
490
|
}
|
|
306
491
|
|
|
307
492
|
// --- Main ---
|
|
308
493
|
|
|
309
|
-
const
|
|
494
|
+
const args = process.argv.slice(2);
|
|
495
|
+
const command = args[0];
|
|
496
|
+
|
|
497
|
+
function getFlag(name: string): string | undefined {
|
|
498
|
+
const idx = args.indexOf(`--${name}`);
|
|
499
|
+
if (idx >= 0 && idx + 1 < args.length) return args[idx + 1];
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function hasFlag(name: string): boolean {
|
|
504
|
+
return args.includes(`--${name}`);
|
|
505
|
+
}
|
|
310
506
|
|
|
311
507
|
switch (command) {
|
|
312
|
-
case "
|
|
313
|
-
|
|
508
|
+
case "install":
|
|
509
|
+
console.log("Polaris — installing local components\n");
|
|
510
|
+
await install();
|
|
511
|
+
console.log("\nInstall complete.");
|
|
314
512
|
break;
|
|
513
|
+
|
|
514
|
+
case "login": {
|
|
515
|
+
const appUrl = hasFlag("local") ? LOCAL_APP_URL : (getFlag("url") ?? DEFAULT_APP_URL);
|
|
516
|
+
const profileName = getFlag("profile");
|
|
517
|
+
console.log("Polaris — authenticating\n");
|
|
518
|
+
await login(appUrl, profileName);
|
|
519
|
+
console.log("\n✓ Login complete!");
|
|
520
|
+
console.log("\nNext: start the daemon with `polaris daemon`, then `/polaris join <project> <session>` in your AI agent.");
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
case "use":
|
|
525
|
+
if (!args[1]) {
|
|
526
|
+
console.error("Usage: polaris use <profile>");
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
await use(args[1]);
|
|
530
|
+
break;
|
|
531
|
+
|
|
532
|
+
case "profiles":
|
|
533
|
+
await profiles();
|
|
534
|
+
break;
|
|
535
|
+
|
|
315
536
|
case "daemon":
|
|
316
537
|
await daemon();
|
|
317
538
|
break;
|
|
539
|
+
|
|
318
540
|
case "status":
|
|
319
541
|
await status();
|
|
320
542
|
break;
|
|
543
|
+
|
|
321
544
|
case "logout":
|
|
322
|
-
await logout();
|
|
545
|
+
await logout(hasFlag("all"));
|
|
546
|
+
break;
|
|
547
|
+
|
|
548
|
+
case undefined:
|
|
549
|
+
// Default: install + login
|
|
550
|
+
console.log("Polaris — setting up your machine\n");
|
|
551
|
+
console.log("[1/2] Installing local components...\n");
|
|
552
|
+
await install();
|
|
553
|
+
console.log("\n Install complete. Run `polaris install` to repeat this step independently.\n");
|
|
554
|
+
console.log("[2/2] Authenticating...\n");
|
|
555
|
+
await login(DEFAULT_APP_URL);
|
|
556
|
+
console.log("\n✓ Polaris is set up on this machine!");
|
|
557
|
+
console.log("\nNext: start the daemon with `polaris daemon`, then `/polaris join <project> <session>` in your AI agent.");
|
|
323
558
|
break;
|
|
559
|
+
|
|
324
560
|
default:
|
|
325
|
-
console.log("Polaris CLI");
|
|
326
|
-
console.log("");
|
|
561
|
+
console.log("Polaris CLI\n");
|
|
327
562
|
console.log("Usage:");
|
|
328
|
-
console.log(" polaris
|
|
329
|
-
console.log(" polaris
|
|
330
|
-
console.log(" polaris
|
|
331
|
-
console.log(" polaris
|
|
563
|
+
console.log(" polaris — install + login (default setup)");
|
|
564
|
+
console.log(" polaris install — install local components (no auth)");
|
|
565
|
+
console.log(" polaris login — authenticate (production)");
|
|
566
|
+
console.log(" polaris login --local — authenticate (local dev)");
|
|
567
|
+
console.log(" polaris use <profile> — switch active profile");
|
|
568
|
+
console.log(" polaris profiles — list all profiles");
|
|
569
|
+
console.log(" polaris daemon — start the local daemon");
|
|
570
|
+
console.log(" polaris status — show connection status");
|
|
571
|
+
console.log(" polaris logout — remove active profile");
|
|
572
|
+
console.log(" polaris logout --all — remove all credentials");
|
|
332
573
|
}
|