palmier 0.2.9 → 0.3.1

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.
@@ -7,6 +7,7 @@ import { startNatsTransport } from "../transports/nats-transport.js";
7
7
  import { getTaskDir, readTaskStatus, writeTaskStatus, appendHistory, parseTaskFile } from "../task.js";
8
8
  import { publishHostEvent } from "../events.js";
9
9
  import { getPlatform } from "../platform/index.js";
10
+ import { checkForUpdate } from "../update-checker.js";
10
11
  import { CONFIG_DIR } from "../config.js";
11
12
  const POLL_INTERVAL_MS = 30_000;
12
13
  const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
@@ -80,6 +81,9 @@ export async function serveCommand() {
80
81
  console.error("[monitor] Error checking stale tasks:", err);
81
82
  });
82
83
  }, POLL_INTERVAL_MS);
84
+ // Check for updates on startup and every 24 hours
85
+ checkForUpdate().catch(() => { });
86
+ setInterval(() => { checkForUpdate().catch(() => { }); }, 24 * 60 * 60 * 1000);
83
87
  const handleRpc = createRpcHandler(config, nc);
84
88
  await startNatsTransport(config, handleRpc, nc);
85
89
  }
@@ -3,12 +3,13 @@ import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { parse as parseYaml } from "yaml";
6
- import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, getTaskCreatedAt, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList } from "./task.js";
6
+ import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList } from "./task.js";
7
7
  import { getPlatform } from "./platform/index.js";
8
8
  import { spawnCommand } from "./spawn-command.js";
9
9
  import { getAgent } from "./agents/agent.js";
10
10
  import { validateSession } from "./session-store.js";
11
11
  import { publishHostEvent } from "./events.js";
12
+ import { currentVersion, getLatestVersion, performUpdate } from "./update-checker.js";
12
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
14
  const PLAN_GENERATION_PROMPT = fs.readFileSync(path.join(__dirname, "commands", "plan-generation.md"), "utf-8");
14
15
  /**
@@ -92,7 +93,6 @@ export function createRpcHandler(config, nc) {
92
93
  return {
93
94
  ...task.frontmatter,
94
95
  body: task.body,
95
- created_at: getTaskCreatedAt(taskDir),
96
96
  status: readTaskStatus(taskDir),
97
97
  };
98
98
  }
@@ -107,6 +107,8 @@ export function createRpcHandler(config, nc) {
107
107
  return {
108
108
  tasks: tasks.map((task) => flattenTask(task)),
109
109
  agents: config.agents ?? [],
110
+ version: currentVersion,
111
+ latest_version: getLatestVersion(),
110
112
  };
111
113
  }
112
114
  case "task.create": {
@@ -332,6 +334,12 @@ export function createRpcHandler(config, nc) {
332
334
  }
333
335
  return { ok: true, task_id: params.task_id, result_file: params.result_file };
334
336
  }
337
+ case "host.update": {
338
+ const error = await performUpdate();
339
+ if (error)
340
+ return { error };
341
+ return { ok: true };
342
+ }
335
343
  default:
336
344
  return { error: `Unknown method: ${request.method}` };
337
345
  }
package/dist/task.d.ts CHANGED
@@ -25,10 +25,6 @@ export declare function listTasks(projectRoot: string): ParsedTask[];
25
25
  * Get the directory path for a task by its ID.
26
26
  */
27
27
  export declare function getTaskDir(projectRoot: string, taskId: string): string;
28
- /**
29
- * Get the creation time (birthtime) of a TASK.md file in ms since epoch.
30
- */
31
- export declare function getTaskCreatedAt(taskDir: string): number;
32
28
  /**
33
29
  * Write task status to status.json in the task directory.
34
30
  */
package/dist/task.js CHANGED
@@ -109,18 +109,6 @@ export function listTasks(projectRoot) {
109
109
  export function getTaskDir(projectRoot, taskId) {
110
110
  return path.join(projectRoot, "tasks", taskId);
111
111
  }
112
- /**
113
- * Get the creation time (birthtime) of a TASK.md file in ms since epoch.
114
- */
115
- export function getTaskCreatedAt(taskDir) {
116
- const filePath = path.join(taskDir, "TASK.md");
117
- try {
118
- return fs.statSync(filePath).birthtimeMs;
119
- }
120
- catch {
121
- return 0;
122
- }
123
- }
124
112
  /**
125
113
  * Write task status to status.json in the task directory.
126
114
  */
@@ -0,0 +1,15 @@
1
+ export declare const currentVersion: string;
2
+ /**
3
+ * Check the npm registry for the latest version of palmier.
4
+ */
5
+ export declare function checkForUpdate(): Promise<void>;
6
+ /**
7
+ * Get the latest version from npm, or null if not yet checked.
8
+ */
9
+ export declare function getLatestVersion(): string | null;
10
+ /**
11
+ * Run the update and restart the daemon.
12
+ * Returns an error message if the update fails.
13
+ */
14
+ export declare function performUpdate(): Promise<string | null>;
15
+ //# sourceMappingURL=update-checker.d.ts.map
@@ -0,0 +1,73 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { spawnCommand } from "./spawn-command.js";
5
+ import { getPlatform } from "./platform/index.js";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf-8"));
8
+ export const currentVersion = pkg.version;
9
+ let latestVersion = null;
10
+ let lastCheckTime = 0;
11
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
12
+ /**
13
+ * Check the npm registry for the latest version of palmier.
14
+ */
15
+ export async function checkForUpdate() {
16
+ const now = Date.now();
17
+ if (now - lastCheckTime < CHECK_INTERVAL_MS)
18
+ return;
19
+ lastCheckTime = now;
20
+ try {
21
+ const res = await fetch("https://registry.npmjs.org/palmier/latest", {
22
+ signal: AbortSignal.timeout(10_000),
23
+ });
24
+ if (!res.ok)
25
+ return;
26
+ const data = (await res.json());
27
+ if (data.version) {
28
+ latestVersion = data.version;
29
+ console.log(`[update] Latest version: ${data.version} (current: ${currentVersion})`);
30
+ }
31
+ }
32
+ catch {
33
+ // Network errors are expected (offline, etc.)
34
+ }
35
+ }
36
+ /**
37
+ * Get the latest version from npm, or null if not yet checked.
38
+ */
39
+ export function getLatestVersion() {
40
+ return latestVersion;
41
+ }
42
+ /**
43
+ * Run the update and restart the daemon.
44
+ * Returns an error message if the update fails.
45
+ */
46
+ export async function performUpdate() {
47
+ try {
48
+ const { output, exitCode } = await spawnCommand("npm", ["update", "-g", "palmier"], {
49
+ cwd: process.cwd(),
50
+ timeout: 120_000,
51
+ resolveOnFailure: true,
52
+ });
53
+ if (exitCode !== 0) {
54
+ console.error(`[update] npm update failed (exit ${exitCode}):`, output);
55
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
56
+ }
57
+ console.log("[update] Update installed, restarting daemon...");
58
+ latestVersion = null;
59
+ // Small delay to allow the RPC response to be sent
60
+ setTimeout(() => {
61
+ getPlatform().restartDaemon().catch((err) => {
62
+ console.error("[update] Restart failed:", err);
63
+ });
64
+ }, 1000);
65
+ return null;
66
+ }
67
+ catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ console.error("[update] Update failed:", msg);
70
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
71
+ }
72
+ }
73
+ //# sourceMappingURL=update-checker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -7,6 +7,7 @@ import { startNatsTransport } from "../transports/nats-transport.js";
7
7
  import { getTaskDir, readTaskStatus, writeTaskStatus, appendHistory, parseTaskFile } from "../task.js";
8
8
  import { publishHostEvent } from "../events.js";
9
9
  import { getPlatform } from "../platform/index.js";
10
+ import { checkForUpdate } from "../update-checker.js";
10
11
  import type { HostConfig } from "../types.js";
11
12
  import { CONFIG_DIR } from "../config.js";
12
13
  import type { NatsConnection } from "nats";
@@ -100,6 +101,10 @@ export async function serveCommand(): Promise<void> {
100
101
  });
101
102
  }, POLL_INTERVAL_MS);
102
103
 
104
+ // Check for updates on startup and every 24 hours
105
+ checkForUpdate().catch(() => {});
106
+ setInterval(() => { checkForUpdate().catch(() => {}); }, 24 * 60 * 60 * 1000);
107
+
103
108
  const handleRpc = createRpcHandler(config, nc);
104
109
  await startNatsTransport(config, handleRpc, nc);
105
110
  }
@@ -4,12 +4,13 @@ import * as path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { parse as parseYaml } from "yaml";
6
6
  import { type NatsConnection } from "nats";
7
- import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, getTaskCreatedAt, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList } from "./task.js";
7
+ import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList } from "./task.js";
8
8
  import { getPlatform } from "./platform/index.js";
9
9
  import { spawnCommand } from "./spawn-command.js";
10
10
  import { getAgent } from "./agents/agent.js";
11
11
  import { validateSession } from "./session-store.js";
12
12
  import { publishHostEvent } from "./events.js";
13
+ import { currentVersion, getLatestVersion, performUpdate } from "./update-checker.js";
13
14
  import type { HostConfig, ParsedTask, RpcMessage } from "./types.js";
14
15
 
15
16
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -105,7 +106,6 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
105
106
  return {
106
107
  ...task.frontmatter,
107
108
  body: task.body,
108
- created_at: getTaskCreatedAt(taskDir),
109
109
  status: readTaskStatus(taskDir),
110
110
  };
111
111
  }
@@ -122,6 +122,8 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
122
122
  return {
123
123
  tasks: tasks.map((task) => flattenTask(task)),
124
124
  agents: config.agents ?? [],
125
+ version: currentVersion,
126
+ latest_version: getLatestVersion(),
125
127
  };
126
128
  }
127
129
 
@@ -379,6 +381,12 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
379
381
  return { ok: true, task_id: params.task_id, result_file: params.result_file };
380
382
  }
381
383
 
384
+ case "host.update": {
385
+ const error = await performUpdate();
386
+ if (error) return { error };
387
+ return { ok: true };
388
+ }
389
+
382
390
  default:
383
391
  return { error: `Unknown method: ${request.method}` };
384
392
  }
package/src/task.ts CHANGED
@@ -126,18 +126,6 @@ export function getTaskDir(projectRoot: string, taskId: string): string {
126
126
  return path.join(projectRoot, "tasks", taskId);
127
127
  }
128
128
 
129
- /**
130
- * Get the creation time (birthtime) of a TASK.md file in ms since epoch.
131
- */
132
- export function getTaskCreatedAt(taskDir: string): number {
133
- const filePath = path.join(taskDir, "TASK.md");
134
- try {
135
- return fs.statSync(filePath).birthtimeMs;
136
- } catch {
137
- return 0;
138
- }
139
- }
140
-
141
129
  /**
142
130
  * Write task status to status.json in the task directory.
143
131
  */
@@ -0,0 +1,74 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { spawnCommand } from "./spawn-command.js";
5
+ import { getPlatform } from "./platform/index.js";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")) as { version: string };
9
+ export const currentVersion = pkg.version;
10
+
11
+ let latestVersion: string | null = null;
12
+ let lastCheckTime = 0;
13
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
14
+
15
+ /**
16
+ * Check the npm registry for the latest version of palmier.
17
+ */
18
+ export async function checkForUpdate(): Promise<void> {
19
+ const now = Date.now();
20
+ if (now - lastCheckTime < CHECK_INTERVAL_MS) return;
21
+ lastCheckTime = now;
22
+
23
+ try {
24
+ const res = await fetch("https://registry.npmjs.org/palmier/latest", {
25
+ signal: AbortSignal.timeout(10_000),
26
+ });
27
+ if (!res.ok) return;
28
+ const data = (await res.json()) as { version?: string };
29
+ if (data.version) {
30
+ latestVersion = data.version;
31
+ console.log(`[update] Latest version: ${data.version} (current: ${currentVersion})`);
32
+ }
33
+ } catch {
34
+ // Network errors are expected (offline, etc.)
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the latest version from npm, or null if not yet checked.
40
+ */
41
+ export function getLatestVersion(): string | null {
42
+ return latestVersion;
43
+ }
44
+
45
+ /**
46
+ * Run the update and restart the daemon.
47
+ * Returns an error message if the update fails.
48
+ */
49
+ export async function performUpdate(): Promise<string | null> {
50
+ try {
51
+ const { output, exitCode } = await spawnCommand("npm", ["update", "-g", "palmier"], {
52
+ cwd: process.cwd(),
53
+ timeout: 120_000,
54
+ resolveOnFailure: true,
55
+ });
56
+ if (exitCode !== 0) {
57
+ console.error(`[update] npm update failed (exit ${exitCode}):`, output);
58
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
59
+ }
60
+ console.log("[update] Update installed, restarting daemon...");
61
+ latestVersion = null;
62
+ // Small delay to allow the RPC response to be sent
63
+ setTimeout(() => {
64
+ getPlatform().restartDaemon().catch((err) => {
65
+ console.error("[update] Restart failed:", err);
66
+ });
67
+ }, 1000);
68
+ return null;
69
+ } catch (err) {
70
+ const msg = err instanceof Error ? err.message : String(err);
71
+ console.error("[update] Update failed:", msg);
72
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
73
+ }
74
+ }