useathena 0.2.0 → 0.2.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.
- package/README.md +3 -0
- package/dist/cli/service.js +7 -0
- package/dist/cli/update.js +81 -0
- package/dist/cli.js +15 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,6 +165,9 @@ installs the browser-sensor API as a login service, and prints the extension set
|
|
|
165
165
|
with its token. `--yes` takes the first detected provider and says yes to all of it;
|
|
166
166
|
opt out per-piece with `--no-hook`, `--no-service`, `--skip-test`, or `--model <spec>`.
|
|
167
167
|
|
|
168
|
+
Already installed? `athena update` pulls the latest published version and
|
|
169
|
+
restarts the background serve service on the new code.
|
|
170
|
+
|
|
168
171
|
Model providers are spec strings, set once by `setup` (or per-run via `ATHENA_MODEL`):
|
|
169
172
|
|
|
170
173
|
```text
|
package/dist/cli/service.js
CHANGED
|
@@ -43,6 +43,13 @@ Restart=always
|
|
|
43
43
|
WantedBy=default.target
|
|
44
44
|
`;
|
|
45
45
|
}
|
|
46
|
+
export function serviceInstalled(platform = process.platform) {
|
|
47
|
+
if (platform === "darwin")
|
|
48
|
+
return existsSync(join(homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`));
|
|
49
|
+
if (platform === "linux")
|
|
50
|
+
return existsSync(join(homedir(), ".config", "systemd", "user", SYSTEMD_UNIT));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
46
53
|
export function installService(athenaBin, platform = process.platform) {
|
|
47
54
|
const logPath = join(dirname(dbPath()), "serve.log");
|
|
48
55
|
if (platform === "darwin") {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { installService, serviceInstalled } from "./service.js";
|
|
5
|
+
import { bold, dim, green, red, yellow } from "./format.js";
|
|
6
|
+
export function compareVersions(a, b) {
|
|
7
|
+
const left = a.split(".").map(Number);
|
|
8
|
+
const right = b.split(".").map(Number);
|
|
9
|
+
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
10
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
11
|
+
if (diff !== 0)
|
|
12
|
+
return Math.sign(diff);
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
export function planUpdate(opts) {
|
|
17
|
+
if (opts.devCheckout)
|
|
18
|
+
return "dev-checkout";
|
|
19
|
+
return compareVersions(opts.current, opts.latest) < 0 ? "install" : "up-to-date";
|
|
20
|
+
}
|
|
21
|
+
export function readPackageInfo(packageRoot) {
|
|
22
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
23
|
+
return { name: pkg.name, version: pkg.version };
|
|
24
|
+
}
|
|
25
|
+
export async function fetchLatestVersion(name, timeoutMs = 10_000) {
|
|
26
|
+
const response = await fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
throw new Error(`npm registry lookup failed: HTTP ${response.status}`);
|
|
29
|
+
const body = (await response.json());
|
|
30
|
+
if (!body.version)
|
|
31
|
+
throw new Error("npm registry reply had no version");
|
|
32
|
+
return body.version;
|
|
33
|
+
}
|
|
34
|
+
export function nudgeLine(current, latest) {
|
|
35
|
+
if (compareVersions(current, latest) >= 0)
|
|
36
|
+
return undefined;
|
|
37
|
+
return `\n${yellow("↑")} ${latest} is out (you run ${current}) — update with ${bold("athena update")}`;
|
|
38
|
+
}
|
|
39
|
+
/** One quiet line at the end of `athena status` when the registry is ahead; silent offline. */
|
|
40
|
+
export async function updateNudge(packageRoot) {
|
|
41
|
+
try {
|
|
42
|
+
const { name, version } = readPackageInfo(packageRoot);
|
|
43
|
+
return nudgeLine(version, await fetchLatestVersion(name, 1_500));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function globalAthenaBin() {
|
|
50
|
+
const prefix = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8" }).stdout?.trim();
|
|
51
|
+
if (!prefix)
|
|
52
|
+
return undefined;
|
|
53
|
+
const bin = join(prefix, "bin", "athena");
|
|
54
|
+
return existsSync(bin) ? bin : undefined;
|
|
55
|
+
}
|
|
56
|
+
export async function runUpdate(packageRoot, athenaBin) {
|
|
57
|
+
const { name, version } = readPackageInfo(packageRoot);
|
|
58
|
+
const latest = await fetchLatestVersion(name);
|
|
59
|
+
const plan = planUpdate({ current: version, latest, devCheckout: existsSync(join(packageRoot, ".git")) });
|
|
60
|
+
if (plan === "dev-checkout") {
|
|
61
|
+
console.log(`${yellow("dev checkout")} at ${packageRoot} — update with ${bold("git pull")} (running ${version}, registry has ${latest})`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (plan === "up-to-date") {
|
|
65
|
+
console.log(`${green("✓")} athena ${version} is up to date`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log(dim(`updating ${name} ${version} → ${latest} (npm install -g)…`));
|
|
69
|
+
const install = spawnSync("npm", ["install", "-g", `${name}@latest`], { encoding: "utf8" });
|
|
70
|
+
if (install.status !== 0) {
|
|
71
|
+
console.log(red(`✗ update failed: ${(install.stderr ?? "").trim().slice(-300)}`));
|
|
72
|
+
console.log(yellow(` retry manually: npm install -g ${name}@latest`));
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(`${green("✓")} athena ${latest} installed`);
|
|
77
|
+
if (serviceInstalled()) {
|
|
78
|
+
const result = installService(globalAthenaBin() ?? athenaBin);
|
|
79
|
+
console.log(result.installed ? `${green("✓")} serve service restarted on ${latest}` : yellow(result.message));
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { maybeAutoLearn } from "./serve/auto-learn.js";
|
|
|
15
15
|
import { recordOutcome } from "./serve/outcome.js";
|
|
16
16
|
import { loadConfig } from "./config.js";
|
|
17
17
|
import { installClaudeCodeHook, runSetup } from "./cli/setup.js";
|
|
18
|
+
import { readPackageInfo, runUpdate, updateNudge } from "./cli/update.js";
|
|
18
19
|
import { bold, dim, green, red, yellow } from "./cli/format.js";
|
|
19
20
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
20
21
|
const athenaBin = join(packageRoot, "bin", "athena");
|
|
@@ -43,6 +44,7 @@ usage: athena <command> [args]
|
|
|
43
44
|
${bold("hook")} install [--print] install the Claude Code sensor hook into ~/.claude/settings.json
|
|
44
45
|
${bold("serve")} [--port n] local API for browser sensors (default 127.0.0.1:4517)
|
|
45
46
|
${bold("serve")} install|uninstall run the API at login (launchd/systemd user service)
|
|
47
|
+
${bold("update")} update to the latest published version (restarts serve)
|
|
46
48
|
${bold("mcp")} run the MCP stdio server (claude mcp add athena -- athena mcp)
|
|
47
49
|
|
|
48
50
|
store: ${dbPath()} ${dim("(override with ATHENA_DB)")}
|
|
@@ -57,6 +59,10 @@ async function main() {
|
|
|
57
59
|
console.log(HELP);
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
62
|
+
if (command === "version" || command === "--version") {
|
|
63
|
+
console.log(readPackageInfo(packageRoot).version);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
60
66
|
if (command === "hook") {
|
|
61
67
|
await runHook(args);
|
|
62
68
|
return;
|
|
@@ -76,9 +82,13 @@ async function main() {
|
|
|
76
82
|
console.log(dim(`\nfor the guided version (model provider, sensors): athena setup`));
|
|
77
83
|
break;
|
|
78
84
|
}
|
|
79
|
-
case "status":
|
|
85
|
+
case "status": {
|
|
80
86
|
console.log(cmdStatus(store));
|
|
87
|
+
const nudge = await updateNudge(packageRoot);
|
|
88
|
+
if (nudge)
|
|
89
|
+
console.log(nudge);
|
|
81
90
|
break;
|
|
91
|
+
}
|
|
82
92
|
case "rules": {
|
|
83
93
|
const domain = flag(args, "domain");
|
|
84
94
|
console.log(cmdRules(store, {
|
|
@@ -206,6 +216,10 @@ async function main() {
|
|
|
206
216
|
});
|
|
207
217
|
break;
|
|
208
218
|
}
|
|
219
|
+
case "update": {
|
|
220
|
+
await runUpdate(packageRoot, athenaBin);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
209
223
|
case "mcp": {
|
|
210
224
|
// stdio transport: stdout belongs to the protocol, so print nothing.
|
|
211
225
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|