palmier 0.2.9 → 0.3.0

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 { getUpdateAvailable, 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,7 @@ export function createRpcHandler(config, nc) {
107
107
  return {
108
108
  tasks: tasks.map((task) => flattenTask(task)),
109
109
  agents: config.agents ?? [],
110
+ update_available: getUpdateAvailable(),
110
111
  };
111
112
  }
112
113
  case "task.create": {
@@ -332,6 +333,12 @@ export function createRpcHandler(config, nc) {
332
333
  }
333
334
  return { ok: true, task_id: params.task_id, result_file: params.result_file };
334
335
  }
336
+ case "host.update": {
337
+ const error = await performUpdate();
338
+ if (error)
339
+ return { error };
340
+ return { ok: true };
341
+ }
335
342
  default:
336
343
  return { error: `Unknown method: ${request.method}` };
337
344
  }
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,14 @@
1
+ /**
2
+ * Check the npm registry for a newer version of palmier.
3
+ */
4
+ export declare function checkForUpdate(): Promise<void>;
5
+ /**
6
+ * Get the available update version, or null if up to date.
7
+ */
8
+ export declare function getUpdateAvailable(): string | null;
9
+ /**
10
+ * Run the update and restart the daemon.
11
+ * Returns an error message if the update fails.
12
+ */
13
+ export declare function performUpdate(): Promise<string | null>;
14
+ //# sourceMappingURL=update-checker.d.ts.map
@@ -0,0 +1,91 @@
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
+ const currentVersion = pkg.version;
9
+ let latestVersion = null;
10
+ let lastCheckTime = 0;
11
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
12
+ /**
13
+ * Compare two semver strings (major.minor.patch).
14
+ * Returns true if b is newer than a.
15
+ */
16
+ function isNewer(a, b) {
17
+ const pa = a.split(".").map(Number);
18
+ const pb = b.split(".").map(Number);
19
+ for (let i = 0; i < 3; i++) {
20
+ if ((pb[i] ?? 0) > (pa[i] ?? 0))
21
+ return true;
22
+ if ((pb[i] ?? 0) < (pa[i] ?? 0))
23
+ return false;
24
+ }
25
+ return false;
26
+ }
27
+ /**
28
+ * Check the npm registry for a newer version of palmier.
29
+ */
30
+ export async function checkForUpdate() {
31
+ const now = Date.now();
32
+ if (now - lastCheckTime < CHECK_INTERVAL_MS)
33
+ return;
34
+ lastCheckTime = now;
35
+ try {
36
+ const res = await fetch("https://registry.npmjs.org/palmier/latest", {
37
+ signal: AbortSignal.timeout(10_000),
38
+ });
39
+ if (!res.ok)
40
+ return;
41
+ const data = (await res.json());
42
+ if (data.version && isNewer(currentVersion, data.version)) {
43
+ latestVersion = data.version;
44
+ console.log(`[update] New version available: ${data.version} (current: ${currentVersion})`);
45
+ }
46
+ else {
47
+ latestVersion = null;
48
+ }
49
+ }
50
+ catch {
51
+ // Network errors are expected (offline, etc.)
52
+ }
53
+ }
54
+ /**
55
+ * Get the available update version, or null if up to date.
56
+ */
57
+ export function getUpdateAvailable() {
58
+ return latestVersion;
59
+ }
60
+ /**
61
+ * Run the update and restart the daemon.
62
+ * Returns an error message if the update fails.
63
+ */
64
+ export async function performUpdate() {
65
+ try {
66
+ const { output, exitCode } = await spawnCommand("npm", ["update", "-g", "palmier"], {
67
+ cwd: process.cwd(),
68
+ timeout: 120_000,
69
+ resolveOnFailure: true,
70
+ });
71
+ if (exitCode !== 0) {
72
+ console.error(`[update] npm update failed (exit ${exitCode}):`, output);
73
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
74
+ }
75
+ console.log("[update] Update installed, restarting daemon...");
76
+ latestVersion = null;
77
+ // Small delay to allow the RPC response to be sent
78
+ setTimeout(() => {
79
+ getPlatform().restartDaemon().catch((err) => {
80
+ console.error("[update] Restart failed:", err);
81
+ });
82
+ }, 1000);
83
+ return null;
84
+ }
85
+ catch (err) {
86
+ const msg = err instanceof Error ? err.message : String(err);
87
+ console.error("[update] Update failed:", msg);
88
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
89
+ }
90
+ }
91
+ //# 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.0",
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 { getUpdateAvailable, 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,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
122
122
  return {
123
123
  tasks: tasks.map((task) => flattenTask(task)),
124
124
  agents: config.agents ?? [],
125
+ update_available: getUpdateAvailable(),
125
126
  };
126
127
  }
127
128
 
@@ -379,6 +380,12 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
379
380
  return { ok: true, task_id: params.task_id, result_file: params.result_file };
380
381
  }
381
382
 
383
+ case "host.update": {
384
+ const error = await performUpdate();
385
+ if (error) return { error };
386
+ return { ok: true };
387
+ }
388
+
382
389
  default:
383
390
  return { error: `Unknown method: ${request.method}` };
384
391
  }
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,90 @@
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
+ 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
+ * Compare two semver strings (major.minor.patch).
17
+ * Returns true if b is newer than a.
18
+ */
19
+ function isNewer(a: string, b: string): boolean {
20
+ const pa = a.split(".").map(Number);
21
+ const pb = b.split(".").map(Number);
22
+ for (let i = 0; i < 3; i++) {
23
+ if ((pb[i] ?? 0) > (pa[i] ?? 0)) return true;
24
+ if ((pb[i] ?? 0) < (pa[i] ?? 0)) return false;
25
+ }
26
+ return false;
27
+ }
28
+
29
+ /**
30
+ * Check the npm registry for a newer version of palmier.
31
+ */
32
+ export async function checkForUpdate(): Promise<void> {
33
+ const now = Date.now();
34
+ if (now - lastCheckTime < CHECK_INTERVAL_MS) return;
35
+ lastCheckTime = now;
36
+
37
+ try {
38
+ const res = await fetch("https://registry.npmjs.org/palmier/latest", {
39
+ signal: AbortSignal.timeout(10_000),
40
+ });
41
+ if (!res.ok) return;
42
+ const data = (await res.json()) as { version?: string };
43
+ if (data.version && isNewer(currentVersion, data.version)) {
44
+ latestVersion = data.version;
45
+ console.log(`[update] New version available: ${data.version} (current: ${currentVersion})`);
46
+ } else {
47
+ latestVersion = null;
48
+ }
49
+ } catch {
50
+ // Network errors are expected (offline, etc.)
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Get the available update version, or null if up to date.
56
+ */
57
+ export function getUpdateAvailable(): string | null {
58
+ return latestVersion;
59
+ }
60
+
61
+ /**
62
+ * Run the update and restart the daemon.
63
+ * Returns an error message if the update fails.
64
+ */
65
+ export async function performUpdate(): Promise<string | null> {
66
+ try {
67
+ const { output, exitCode } = await spawnCommand("npm", ["update", "-g", "palmier"], {
68
+ cwd: process.cwd(),
69
+ timeout: 120_000,
70
+ resolveOnFailure: true,
71
+ });
72
+ if (exitCode !== 0) {
73
+ console.error(`[update] npm update failed (exit ${exitCode}):`, output);
74
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
75
+ }
76
+ console.log("[update] Update installed, restarting daemon...");
77
+ latestVersion = null;
78
+ // Small delay to allow the RPC response to be sent
79
+ setTimeout(() => {
80
+ getPlatform().restartDaemon().catch((err) => {
81
+ console.error("[update] Restart failed:", err);
82
+ });
83
+ }, 1000);
84
+ return null;
85
+ } catch (err) {
86
+ const msg = err instanceof Error ? err.message : String(err);
87
+ console.error("[update] Update failed:", msg);
88
+ return `Update failed. Please run manually:\nnpm update -g palmier`;
89
+ }
90
+ }