palmier 0.2.4 → 0.2.5

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.
Files changed (41) hide show
  1. package/README.md +12 -16
  2. package/dist/agents/claude.js +5 -2
  3. package/dist/agents/codex.js +4 -2
  4. package/dist/agents/gemini.js +4 -2
  5. package/dist/commands/info.js +17 -10
  6. package/dist/commands/init.d.ts +2 -10
  7. package/dist/commands/init.js +102 -154
  8. package/dist/commands/run.js +2 -13
  9. package/dist/commands/sessions.js +1 -4
  10. package/dist/config.js +1 -1
  11. package/dist/index.js +7 -7
  12. package/dist/platform/index.d.ts +4 -0
  13. package/dist/platform/index.js +12 -0
  14. package/dist/platform/linux.d.ts +11 -0
  15. package/dist/platform/linux.js +186 -0
  16. package/dist/platform/platform.d.ts +20 -0
  17. package/dist/platform/platform.js +2 -0
  18. package/dist/platform/windows.d.ts +11 -0
  19. package/dist/platform/windows.js +201 -0
  20. package/dist/rpc-handler.js +13 -30
  21. package/dist/spawn-command.d.ts +5 -2
  22. package/dist/spawn-command.js +2 -0
  23. package/dist/transports/http-transport.js +2 -7
  24. package/package.json +1 -1
  25. package/src/agents/claude.ts +6 -2
  26. package/src/agents/codex.ts +5 -2
  27. package/src/agents/gemini.ts +5 -2
  28. package/src/commands/info.ts +18 -10
  29. package/src/commands/init.ts +131 -180
  30. package/src/commands/run.ts +3 -15
  31. package/src/commands/sessions.ts +1 -4
  32. package/src/config.ts +1 -1
  33. package/src/index.ts +8 -7
  34. package/src/platform/index.ts +16 -0
  35. package/src/platform/linux.ts +207 -0
  36. package/src/platform/platform.ts +25 -0
  37. package/src/platform/windows.ts +223 -0
  38. package/src/rpc-handler.ts +13 -37
  39. package/src/spawn-command.ts +7 -2
  40. package/src/transports/http-transport.ts +2 -5
  41. package/src/systemd.ts +0 -164
package/README.md CHANGED
@@ -8,9 +8,9 @@ The host supports three connection modes:
8
8
 
9
9
  | Mode | Transport | Setup | Features |
10
10
  |------|-----------|-------|----------|
11
- | **`nats`** | NATS only | `palmier init --server <url>` select nats | All features |
12
- | **`auto`** | Both NATS + HTTP | `palmier init --server <url>` select auto | All features. PWA auto-detects best route. |
13
- | **`lan`** | HTTP only | `palmier init --lan` | No push notifications or email MCP. No server needed. |
11
+ | **`nats`** | NATS only | `palmier init` Full access, skip LAN | All features |
12
+ | **`auto`** | Both NATS + HTTP | `palmier init` Full access + LAN | All features. PWA auto-detects best route. |
13
+ | **`lan`** | HTTP only | `palmier init` → LAN only | No push notifications or email MCP. No server needed. |
14
14
 
15
15
  In **auto** mode, the PWA connects directly to the host via HTTP when on the same LAN (lower latency), and falls back to NATS when the host is unreachable. Push notifications and email relay always flow through NATS/server, so no features are lost.
16
16
 
@@ -32,8 +32,7 @@ npm install -g palmier
32
32
 
33
33
  | Command | Description |
34
34
  |---|---|
35
- | `palmier init --server <url>` | Provision the host via the server (prompts for nats/auto mode) |
36
- | `palmier init --lan` | Provision standalone LAN host (no server needed) |
35
+ | `palmier init` | Interactive setup wizard (full access or LAN only) |
37
36
  | `palmier pair` | Generate an OTP code to pair a new device |
38
37
  | `palmier sessions list` | List active session tokens |
39
38
  | `palmier sessions revoke <token>` | Revoke a specific session token |
@@ -46,20 +45,17 @@ npm install -g palmier
46
45
 
47
46
  ## Setup
48
47
 
49
- ### Server-provisioned (nats or auto mode)
48
+ ### Quick Start
50
49
 
51
50
  1. Install the host: `npm install -g palmier`
52
- 2. Run `palmier init --server https://your-server-url` in your project directory.
53
- 3. Select connection mode: `auto` (recommended) or `nats`.
54
- 4. The host registers with the server, saves config, installs a systemd service, and generates a pairing code.
51
+ 2. Run `palmier init` in your project directory.
52
+ 3. The wizard walks you through:
53
+ - **Full access** or **LAN only** mode
54
+ - Server URL (for full access, defaults to `https://www.palmier.me`)
55
+ - Optional LAN access (for full access — auto-detects best route when enabled)
56
+ 4. The host saves config, installs a systemd service, and generates a pairing code.
55
57
  5. Enter the pairing code in the Palmier PWA to connect your device.
56
58
 
57
- ### Standalone LAN mode
58
-
59
- 1. Install the host: `npm install -g palmier`
60
- 2. Run `palmier init --lan` in your project directory.
61
- 3. A pairing code and address are displayed. Enter both in the PWA.
62
-
63
59
  ### Pairing additional devices
64
60
 
65
61
  Run `palmier pair` on the host to generate a new OTP code. Each paired device gets its own session token.
@@ -142,7 +138,7 @@ src/
142
138
  codex.ts # Codex CLI agent implementation
143
139
  openclaw.ts # OpenClaw agent implementation
144
140
  commands/
145
- init.ts # Provisioning logic (--server and --lan flows, auto-pair)
141
+ init.ts # Interactive setup wizard (auto-pair)
146
142
  pair.ts # OTP code generation and pairing handler
147
143
  sessions.ts # Session token management CLI (list, revoke, revoke-all)
148
144
  info.ts # Print host connection info
@@ -1,5 +1,8 @@
1
1
  import { execSync } from "child_process";
2
2
  import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
3
+ // execSync's shell option takes a string (shell path), not boolean.
4
+ // On Windows we need a shell so .cmd shims resolve correctly.
5
+ const SHELL = process.platform === "win32" ? "cmd.exe" : undefined;
3
6
  export class ClaudeAgent {
4
7
  getPlanGenerationCommandLine(prompt) {
5
8
  return {
@@ -18,13 +21,13 @@ export class ClaudeAgent {
18
21
  }
19
22
  async init() {
20
23
  try {
21
- execSync("claude --version");
24
+ execSync("claude --version", { shell: SHELL });
22
25
  }
23
26
  catch {
24
27
  return false;
25
28
  }
26
29
  try {
27
- execSync("claude mcp add --transport stdio palmier --scope user -- palmier mcpserver");
30
+ execSync("claude mcp add --transport stdio palmier --scope user -- palmier mcpserver", { shell: SHELL });
28
31
  }
29
32
  catch (err) {
30
33
  console.warn("Warning: failed to install MCP for Claude:", err instanceof Error ? err.message : err);
@@ -1,5 +1,7 @@
1
1
  import { execSync } from "child_process";
2
2
  import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
3
+ // On Windows we need a shell so .cmd shims resolve correctly.
4
+ const SHELL = process.platform === "win32" ? "cmd.exe" : undefined;
3
5
  export class CodexAgent {
4
6
  getPlanGenerationCommandLine(prompt) {
5
7
  // TODO: fill in
@@ -24,13 +26,13 @@ export class CodexAgent {
24
26
  }
25
27
  async init() {
26
28
  try {
27
- execSync("codex --version");
29
+ execSync("codex --version", { shell: SHELL });
28
30
  }
29
31
  catch {
30
32
  return false;
31
33
  }
32
34
  try {
33
- execSync("codex mcp add palmier palmier mcpserver");
35
+ execSync("codex mcp add palmier palmier mcpserver", { shell: SHELL });
34
36
  }
35
37
  catch (err) {
36
38
  console.warn("Warning: failed to install MCP for Codex:", err instanceof Error ? err.message : err);
@@ -1,5 +1,7 @@
1
1
  import { execSync } from "child_process";
2
2
  import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
3
+ // On Windows we need a shell so .cmd shims resolve correctly.
4
+ const SHELL = process.platform === "win32" ? "cmd.exe" : undefined;
3
5
  export class GeminiAgent {
4
6
  getPlanGenerationCommandLine(prompt) {
5
7
  // TODO: fill in
@@ -22,13 +24,13 @@ export class GeminiAgent {
22
24
  }
23
25
  async init() {
24
26
  try {
25
- execSync("gemini --version");
27
+ execSync("gemini --version", { shell: SHELL });
26
28
  }
27
29
  catch {
28
30
  return false;
29
31
  }
30
32
  try {
31
- execSync("gemini mcp add --scope user palmier palmier mcpserver");
33
+ execSync("gemini mcp add --scope user palmier palmier mcpserver", { shell: SHELL });
32
34
  }
33
35
  catch (err) {
34
36
  console.warn("Warning: failed to install MCP for Gemini:", err instanceof Error ? err.message : err);
@@ -1,5 +1,6 @@
1
1
  import * as os from "os";
2
2
  import { loadConfig } from "../config.js";
3
+ import { loadSessions } from "../session-store.js";
3
4
  /**
4
5
  * Detect the first non-internal IPv4 address.
5
6
  */
@@ -20,21 +21,27 @@ function detectLanIp() {
20
21
  export async function infoCommand() {
21
22
  const config = loadConfig();
22
23
  const mode = config.mode ?? "nats";
23
- console.log(`Mode: ${mode}`);
24
- console.log(`Host ID: ${config.hostId}`);
24
+ const sessions = loadSessions();
25
+ console.log(`Host ID: ${config.hostId}`);
26
+ console.log(`Mode: ${mode}`);
27
+ console.log(`Project root: ${config.projectRoot}`);
25
28
  if (mode === "lan" || mode === "auto") {
26
29
  const lanIp = detectLanIp();
27
30
  const port = config.directPort ?? 7400;
28
- console.log("");
29
- console.log("Direct connection info:");
30
- console.log(` Address: ${lanIp}:${port}`);
31
- console.log(` Token: ${config.directToken}`);
31
+ console.log(`LAN address: ${lanIp}:${port}`);
32
+ }
33
+ // Detected agents
34
+ if (config.agents && config.agents.length > 0) {
35
+ console.log(`Agents: ${config.agents.map((a) => a.label).join(", ")}`);
36
+ }
37
+ else {
38
+ console.log(`Agents: (none detected — run \`palmier agents\`)`);
32
39
  }
33
- if (mode === "nats" || mode === "auto") {
40
+ // Sessions
41
+ console.log(`Sessions: ${sessions.length} active`);
42
+ if (sessions.length === 0) {
34
43
  console.log("");
35
- console.log("NATS connection info:");
36
- console.log(` URL: ${config.natsUrl}`);
37
- console.log(` WS: ${config.natsWsUrl}`);
44
+ console.log("No paired clients. Run `palmier pair` to connect a device.");
38
45
  }
39
46
  }
40
47
  //# sourceMappingURL=info.js.map
@@ -1,13 +1,5 @@
1
- export interface InitOptions {
2
- server?: string;
3
- lan?: boolean;
4
- host?: string;
5
- port?: number;
6
- }
7
1
  /**
8
- * Provision this host. Two flows:
9
- * - palmier init --lan → standalone LAN mode, no server needed
10
- * - palmier init --server <url> → register with server, prompts for nats/auto mode
2
+ * Interactive wizard to provision this host.
11
3
  */
12
- export declare function initCommand(options: InitOptions): Promise<void>;
4
+ export declare function initCommand(): Promise<void>;
13
5
  //# sourceMappingURL=init.d.ts.map
@@ -1,63 +1,91 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
1
  import * as readline from "readline";
5
2
  import { randomUUID, randomBytes } from "crypto";
6
- import { execSync } from "child_process";
7
- import { homedir } from "os";
8
3
  import { saveConfig } from "../config.js";
9
4
  import { detectAgents } from "../agents/agent.js";
5
+ import { detectLanIp } from "../transports/http-transport.js";
6
+ import { getPlatform } from "../platform/index.js";
10
7
  import { pairCommand } from "./pair.js";
11
8
  /**
12
- * Provision this host. Two flows:
13
- * - palmier init --lan → standalone LAN mode, no server needed
14
- * - palmier init --server <url> → register with server, prompts for nats/auto mode
9
+ * Interactive wizard to provision this host.
15
10
  */
16
- export async function initCommand(options) {
17
- if (options.lan) {
18
- await initLan(options);
19
- }
20
- else if (options.server) {
21
- await initWithServer(options);
11
+ export async function initCommand() {
12
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
13
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
14
+ try {
15
+ console.log("\n=== Palmier Host Setup ===\n");
16
+ let step = 1;
17
+ // Step 1: Access mode
18
+ const accessMode = await promptAccessMode(ask, step++);
19
+ let registerResponse;
20
+ if (accessMode === "full") {
21
+ // Step 2: Server URL
22
+ const serverUrl = await promptServerUrl(ask, step++);
23
+ // Register with server
24
+ console.log(`\nRegistering host with ${serverUrl}...`);
25
+ registerResponse = await registerHost(serverUrl);
26
+ console.log("Host registered successfully.");
27
+ }
28
+ let enableLan = accessMode === "lan";
29
+ if (accessMode === "full") {
30
+ // Step N: Optional LAN
31
+ enableLan = await promptEnableLan(ask, step++);
32
+ }
33
+ // Determine mode
34
+ const mode = accessMode === "lan" ? "lan" : enableLan ? "auto" : "nats";
35
+ // Build config
36
+ const config = {
37
+ hostId: registerResponse?.hostId ?? randomUUID(),
38
+ projectRoot: process.cwd(),
39
+ mode,
40
+ };
41
+ if (registerResponse) {
42
+ config.natsUrl = registerResponse.natsUrl;
43
+ config.natsWsUrl = registerResponse.natsWsUrl;
44
+ config.natsToken = registerResponse.natsToken;
45
+ }
46
+ if (enableLan) {
47
+ setupLan(config, step++);
48
+ }
49
+ console.log("\nDetecting installed agents...");
50
+ config.agents = await detectAgents();
51
+ saveConfig(config);
52
+ console.log(`\nHost provisioned (${mode} mode). ID: ${config.hostId}`);
53
+ console.log("Config saved to ~/.config/palmier/host.json");
54
+ getPlatform().installDaemon(config);
55
+ console.log("\nStarting pairing...");
56
+ rl.close();
57
+ await pairCommand();
22
58
  }
23
- else {
24
- console.error("Either --lan or --server <url> is required.");
25
- process.exit(1);
59
+ catch (err) {
60
+ rl.close();
61
+ throw err;
26
62
  }
27
63
  }
28
- /**
29
- * Flow A: Standalone LAN mode. No web server needed.
30
- */
31
- async function initLan(options) {
32
- const hostId = randomUUID();
33
- const directToken = randomBytes(32).toString("hex");
34
- const directPort = options.port ?? 7400;
35
- const lanIp = options.host ?? detectLanIp();
36
- const config = {
37
- hostId,
38
- mode: "lan",
39
- directPort,
40
- directToken,
41
- projectRoot: process.cwd(),
42
- };
43
- console.log("Detecting installed agents...");
44
- config.agents = await detectAgents();
45
- saveConfig(config);
46
- console.log(`Host provisioned (LAN mode). ID: ${hostId}`);
47
- console.log("Config saved to ~/.config/palmier/host.json");
48
- installSystemdService(config);
49
- // Auto-enter pair mode so user can connect their PWA immediately
64
+ async function promptAccessMode(ask, step) {
65
+ console.log(`Step ${step}: Choose access mode\n`);
66
+ console.log(" 1) Full access — built-in email and push notification support");
67
+ console.log(" 2) LAN only — no data relayed by palmier server, requires same network");
50
68
  console.log("");
51
- console.log("Starting pairing...");
52
- await pairCommand();
69
+ const answer = await ask("Select [1]: ");
70
+ const trimmed = answer.trim();
71
+ if (trimmed === "2") {
72
+ return "lan";
73
+ }
74
+ return "full";
53
75
  }
54
- /**
55
- * Flow B: Register directly with server. No provisioning token needed.
56
- */
57
- async function initWithServer(options) {
58
- const serverUrl = options.server;
59
- console.log(`Registering host at ${serverUrl}...`);
60
- let registerResponse;
76
+ async function promptServerUrl(ask, step) {
77
+ const defaultUrl = "https://www.palmier.me";
78
+ console.log(`\nStep ${step}: Enter your Palmier server URL\n`);
79
+ while (true) {
80
+ const answer = await ask(`Server URL [${defaultUrl}]: `);
81
+ const trimmed = (answer.trim() || defaultUrl).replace(/\/+$/, "");
82
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
83
+ return trimmed;
84
+ }
85
+ console.log(" URL must start with http:// or https://. Please try again.\n");
86
+ }
87
+ }
88
+ async function registerHost(serverUrl) {
61
89
  try {
62
90
  const res = await fetch(`${serverUrl}/api/hosts/register`, {
63
91
  method: "POST",
@@ -69,121 +97,41 @@ async function initWithServer(options) {
69
97
  console.error(`Failed to register host: ${res.status} ${res.statusText}\n${body}`);
70
98
  process.exit(1);
71
99
  }
72
- registerResponse = (await res.json());
100
+ return (await res.json());
73
101
  }
74
102
  catch (err) {
75
103
  console.error(`Failed to reach server: ${err}`);
76
104
  process.exit(1);
77
105
  }
78
- // Prompt for connection mode
79
- const mode = await promptMode();
80
- // Build config
81
- const config = {
82
- hostId: registerResponse.hostId,
83
- projectRoot: process.cwd(),
84
- mode,
85
- natsUrl: registerResponse.natsUrl,
86
- natsWsUrl: registerResponse.natsWsUrl,
87
- natsToken: registerResponse.natsToken,
88
- };
89
- if (mode === "auto") {
90
- const directToken = randomBytes(32).toString("hex");
91
- const directPort = options.port ?? 7400;
92
- const lanIp = options.host ?? detectLanIp();
93
- config.directPort = directPort;
94
- config.directToken = directToken;
95
- console.log("");
96
- console.log("Direct connection info (for LAN clients):");
97
- console.log(` Address: ${lanIp}:${directPort}`);
98
- console.log(` Token: ${directToken}`);
99
- }
100
- console.log("Detecting installed agents...");
101
- config.agents = await detectAgents();
102
- saveConfig(config);
103
- console.log(`Host provisioned (${mode} mode). ID: ${config.hostId}`);
104
- console.log("Config saved to ~/.config/palmier/host.json");
105
- installSystemdService(config);
106
- // Auto-enter pair mode so user can connect their PWA immediately
107
- console.log("");
108
- console.log("Starting pairing...");
109
- await pairCommand();
110
- }
111
- /**
112
- * Prompt user to select connection mode.
113
- */
114
- async function promptMode() {
115
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
116
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
117
- console.log("");
118
- console.log("Connection mode:");
119
- console.log(" 1) auto — Both LAN and NATS (recommended)");
120
- console.log(" 2) nats — NATS only");
121
- console.log("");
122
- const answer = await question("Select [1]: ");
123
- rl.close();
124
- if (answer.trim() === "2" || answer.trim().toLowerCase() === "nats") {
125
- return "nats";
126
- }
127
- return "auto";
128
106
  }
129
- /**
130
- * Detect the first non-internal IPv4 address.
131
- */
132
- function detectLanIp() {
133
- const interfaces = os.networkInterfaces();
134
- for (const name of Object.keys(interfaces)) {
135
- for (const iface of interfaces[name] ?? []) {
136
- if (iface.family === "IPv4" && !iface.internal) {
137
- return iface.address;
138
- }
139
- }
140
- }
141
- return "127.0.0.1";
107
+ async function promptEnableLan(ask, step) {
108
+ console.log(`\nStep ${step}: Enable LAN access?\n`);
109
+ console.log(" When enabled, the app will automatically use a direct LAN connection");
110
+ console.log(" when on the same network, and fall back to the server otherwise.\n");
111
+ const answer = await ask("Enable LAN access? (Y/n): ");
112
+ return answer.trim().toLowerCase() !== "n";
142
113
  }
143
- /**
144
- * Install systemd user service for palmier serve.
145
- */
146
- function installSystemdService(config) {
147
- const unitDir = path.join(homedir(), ".config", "systemd", "user");
148
- fs.mkdirSync(unitDir, { recursive: true });
149
- const palmierBin = process.argv[1] || "palmier";
150
- const serviceContent = `[Unit]
151
- Description=Palmier Host
152
- After=network-online.target
153
- Wants=network-online.target
154
-
155
- [Service]
156
- Type=simple
157
- ExecStart=${palmierBin} serve
158
- WorkingDirectory=${config.projectRoot}
159
- Restart=on-failure
160
- RestartSec=5
161
- Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
162
-
163
- [Install]
164
- WantedBy=default.target
165
- `;
166
- const servicePath = path.join(unitDir, "palmier.service");
167
- fs.writeFileSync(servicePath, serviceContent, "utf-8");
168
- console.log("Systemd service installed at:", servicePath);
169
- // Enable and start the service
170
- try {
171
- execSync("systemctl --user daemon-reload", { stdio: "inherit" });
172
- execSync("systemctl --user enable --now palmier.service", { stdio: "inherit" });
173
- console.log("Palmier host service enabled and started.");
174
- }
175
- catch (err) {
176
- console.error(`Warning: failed to enable systemd service: ${err}`);
177
- console.error("You may need to start it manually: systemctl --user enable --now palmier.service");
114
+ function setupLan(config, step) {
115
+ const directPort = 7400;
116
+ const directToken = randomBytes(32).toString("hex");
117
+ const lanIp = detectLanIp();
118
+ config.directPort = directPort;
119
+ config.directToken = directToken;
120
+ console.log(`\nStep ${step}: LAN setup\n`);
121
+ console.log(` LAN IP: ${lanIp}`);
122
+ console.log(` Port: ${directPort}`);
123
+ console.log("");
124
+ const platform = process.platform;
125
+ if (platform === "win32") {
126
+ console.log(" Firewall: You may need to allow incoming connections on this port:");
127
+ console.log(` netsh advfirewall firewall add rule name="Palmier" dir=in action=allow protocol=TCP localport=${directPort}`);
178
128
  }
179
- // Enable lingering so service runs without active login session
180
- try {
181
- execSync(`loginctl enable-linger ${process.env.USER || ""}`, { stdio: "inherit" });
182
- console.log("Login lingering enabled.");
129
+ else if (platform === "darwin") {
130
+ console.log(" Firewall: macOS will prompt you to allow incoming connections automatically.");
183
131
  }
184
- catch (err) {
185
- console.error(`Warning: failed to enable linger: ${err}`);
132
+ else {
133
+ console.log(" Firewall: You may need to allow incoming connections on this port:");
134
+ console.log(` sudo ufw allow ${directPort}/tcp`);
186
135
  }
187
- console.log("\nHost initialization complete!");
188
136
  }
189
137
  //# sourceMappingURL=init.js.map
@@ -5,6 +5,7 @@ import { loadConfig } from "../config.js";
5
5
  import { connectNats } from "../nats-client.js";
6
6
  import { parseTaskFile, getTaskDir, writeTaskFile, writeTaskStatus, readTaskStatus, appendHistory } from "../task.js";
7
7
  import { getAgent } from "../agents/agent.js";
8
+ import { getPlatform } from "../platform/index.js";
8
9
  import { TASK_SUCCESS_MARKER, TASK_FAILURE_MARKER, TASK_REPORT_PREFIX, TASK_PERMISSION_PREFIX } from "../agents/shared-prompt.js";
9
10
  import { StringCodec } from "nats";
10
11
  /**
@@ -76,7 +77,7 @@ export async function runCommand(taskId) {
76
77
  console.log("Task confirmed by user.");
77
78
  }
78
79
  // Execution loop: retry on permission failure if user grants
79
- const guiEnv = getGuiEnv();
80
+ const guiEnv = getPlatform().getGuiEnv();
80
81
  const agent = getAgent(task.frontmatter.agent);
81
82
  let lastOutput = "";
82
83
  let lastOutcome = "fail";
@@ -300,16 +301,4 @@ function parseTaskOutcome(output) {
300
301
  return "finish";
301
302
  return "finish";
302
303
  }
303
- /**
304
- * Return env vars for the default physical GUI session (:0).
305
- */
306
- function getGuiEnv() {
307
- const uid = process.getuid?.();
308
- const runtimeDir = process.env.XDG_RUNTIME_DIR ||
309
- (uid !== undefined ? `/run/user/${uid}` : "");
310
- return {
311
- DISPLAY: ":0",
312
- ...(runtimeDir ? { XDG_RUNTIME_DIR: runtimeDir } : {}),
313
- };
314
- }
315
304
  //# sourceMappingURL=run.js.map
@@ -7,12 +7,9 @@ export async function sessionsListCommand() {
7
7
  }
8
8
  console.log(`${sessions.length} active session(s):\n`);
9
9
  for (const s of sessions) {
10
- const truncated = s.token.slice(0, 8) + "...";
11
10
  const label = s.label ? ` (${s.label})` : "";
12
- console.log(` ${truncated}${label} created ${s.createdAt}`);
11
+ console.log(` ${s.token}${label} created ${s.createdAt}`);
13
12
  }
14
- console.log("");
15
- console.log("To revoke, use the full token: palmier sessions revoke <token>");
16
13
  }
17
14
  export async function sessionsRevokeCommand(token) {
18
15
  if (revokeSession(token)) {
package/dist/config.js CHANGED
@@ -9,7 +9,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, "host.json");
9
9
  */
10
10
  export function loadConfig() {
11
11
  if (!fs.existsSync(CONFIG_FILE)) {
12
- throw new Error("Host not provisioned. Run `palmier init --token <token>` or `palmier init --lan` first.\n" +
12
+ throw new Error("Host not provisioned. Run `palmier init` first.\n" +
13
13
  `Expected config at: ${CONFIG_FILE}`);
14
14
  }
15
15
  const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
package/dist/index.js CHANGED
@@ -22,12 +22,8 @@ program
22
22
  program
23
23
  .command("init")
24
24
  .description("Provision this host")
25
- .option("--server <url>", "Register with Palmier server at the given URL")
26
- .option("--lan", "Standalone LAN mode (no server needed)")
27
- .option("--host <ip>", "Override detected LAN IP address")
28
- .option("--port <port>", "Direct HTTP port (default: 7400)", parseInt)
29
- .action(async (options) => {
30
- await initCommand(options);
25
+ .action(async () => {
26
+ await initCommand();
31
27
  });
32
28
  program
33
29
  .command("info")
@@ -42,7 +38,7 @@ program
42
38
  await runCommand(taskId);
43
39
  });
44
40
  program
45
- .command("serve", { isDefault: true })
41
+ .command("serve")
46
42
  .description("Start the persistent RPC handler")
47
43
  .action(async () => {
48
44
  await serveCommand();
@@ -86,6 +82,10 @@ sessionsCmd
86
82
  .action(async () => {
87
83
  await sessionsRevokeAllCommand();
88
84
  });
85
+ // No subcommand → default to serve
86
+ if (process.argv.length <= 2) {
87
+ process.argv.push("serve");
88
+ }
89
89
  program.parseAsync(process.argv).catch((err) => {
90
90
  console.error(err);
91
91
  process.exit(1);
@@ -0,0 +1,4 @@
1
+ import type { PlatformService } from "./platform.js";
2
+ export declare function getPlatform(): PlatformService;
3
+ export type { PlatformService } from "./platform.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { LinuxPlatform } from "./linux.js";
2
+ import { WindowsPlatform } from "./windows.js";
3
+ let _instance;
4
+ export function getPlatform() {
5
+ if (!_instance) {
6
+ _instance = process.platform === "win32"
7
+ ? new WindowsPlatform()
8
+ : new LinuxPlatform();
9
+ }
10
+ return _instance;
11
+ }
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ import type { PlatformService } from "./platform.js";
2
+ import type { HostConfig, ParsedTask } from "../types.js";
3
+ export declare class LinuxPlatform implements PlatformService {
4
+ installDaemon(config: HostConfig): void;
5
+ installTaskTimer(config: HostConfig, task: ParsedTask): void;
6
+ removeTaskTimer(taskId: string): void;
7
+ startTask(taskId: string): Promise<void>;
8
+ stopTask(taskId: string): Promise<void>;
9
+ getGuiEnv(): Record<string, string>;
10
+ }
11
+ //# sourceMappingURL=linux.d.ts.map