auq-mcp-server 2.4.0 → 2.6.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.
- package/README.md +40 -0
- package/dist/bin/auq.js +40 -0
- package/dist/bin/tui-app.js +115 -1
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/sessions.js +144 -2
- package/dist/src/cli/commands/update.js +124 -0
- package/dist/src/config/__tests__/updateCheck.test.js +34 -0
- package/dist/src/config/defaults.js +2 -0
- package/dist/src/config/types.js +2 -0
- package/dist/src/tui/components/Footer.js +4 -1
- package/dist/src/tui/components/Header.js +3 -1
- package/dist/src/tui/components/UpdateBadge.js +29 -0
- package/dist/src/tui/components/UpdateOverlay.js +199 -0
- package/dist/src/tui/constants/keybindings.js +3 -0
- package/dist/src/update/__tests__/cache.test.js +136 -0
- package/dist/src/update/__tests__/changelog.test.js +86 -0
- package/dist/src/update/__tests__/checker.test.js +148 -0
- package/dist/src/update/__tests__/index.test.js +37 -0
- package/dist/src/update/__tests__/installer.test.js +117 -0
- package/dist/src/update/__tests__/package-manager.test.js +73 -0
- package/dist/src/update/__tests__/version.test.js +74 -0
- package/dist/src/update/cache.js +74 -0
- package/dist/src/update/changelog.js +63 -0
- package/dist/src/update/checker.js +121 -0
- package/dist/src/update/index.js +15 -0
- package/dist/src/update/installer.js +51 -0
- package/dist/src/update/package-manager.js +49 -0
- package/dist/src/update/types.js +7 -0
- package/dist/src/update/version.js +114 -0
- package/package.json +1 -1
- package/dist/src/tui/components/Spinner.js +0 -19
- package/dist/src/tui/utils/__tests__/detectTheme.test.js +0 -78
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package manager detection for the auto-update system.
|
|
3
|
+
*
|
|
4
|
+
* Detects which package manager was used to install AUQ by inspecting
|
|
5
|
+
* environment variables and process paths, following a priority-based
|
|
6
|
+
* detection strategy.
|
|
7
|
+
*/
|
|
8
|
+
/** The npm package name used for global install/update commands. */
|
|
9
|
+
export const PACKAGE_NAME = "auq-mcp-server";
|
|
10
|
+
/**
|
|
11
|
+
* Detects which package manager was used to install AUQ.
|
|
12
|
+
*
|
|
13
|
+
* Detection priority:
|
|
14
|
+
* 1. `process.env.npm_config_user_agent` — most reliable for npm/yarn/pnpm
|
|
15
|
+
* 2. `process.env.npm_execpath` — fallback for npm-based managers
|
|
16
|
+
* 3. `process.execPath` — check if executable path contains package manager names
|
|
17
|
+
* 4. Default to npm as universal fallback
|
|
18
|
+
*
|
|
19
|
+
* @returns Package manager info with name and global install command prefix.
|
|
20
|
+
* The install command does NOT include the package name — the caller appends it.
|
|
21
|
+
*/
|
|
22
|
+
export function detectPackageManager() {
|
|
23
|
+
// Priority 1: npm_config_user_agent (most reliable)
|
|
24
|
+
// Format is typically: "npm/10.2.0 node/v20.9.0 darwin arm64"
|
|
25
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
26
|
+
if (userAgent.includes("bun"))
|
|
27
|
+
return { name: "bun", installCommand: "bun add -g" };
|
|
28
|
+
if (userAgent.includes("yarn"))
|
|
29
|
+
return { name: "yarn", installCommand: "yarn global add" };
|
|
30
|
+
if (userAgent.includes("pnpm"))
|
|
31
|
+
return { name: "pnpm", installCommand: "pnpm add -g" };
|
|
32
|
+
if (userAgent.includes("npm"))
|
|
33
|
+
return { name: "npm", installCommand: "npm install -g" };
|
|
34
|
+
// Priority 2: npm_execpath (path to the package manager executable)
|
|
35
|
+
const execpath = (process.env.npm_execpath || "").toLowerCase();
|
|
36
|
+
if (execpath.includes("bun"))
|
|
37
|
+
return { name: "bun", installCommand: "bun add -g" };
|
|
38
|
+
if (execpath.includes("yarn"))
|
|
39
|
+
return { name: "yarn", installCommand: "yarn global add" };
|
|
40
|
+
if (execpath.includes("pnpm"))
|
|
41
|
+
return { name: "pnpm", installCommand: "pnpm add -g" };
|
|
42
|
+
// Priority 3: process.execPath (runtime executable path)
|
|
43
|
+
// Useful for detecting bun when run outside a package manager context
|
|
44
|
+
const execPath = process.execPath.toLowerCase();
|
|
45
|
+
if (execPath.includes("bun"))
|
|
46
|
+
return { name: "bun", installCommand: "bun add -g" };
|
|
47
|
+
// Priority 4: Default fallback — npm is universally available
|
|
48
|
+
return { name: "npm", installCommand: "npm install -g" };
|
|
49
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semver comparison utilities for the auto-update system.
|
|
3
|
+
*
|
|
4
|
+
* Provides version parsing, comparison, and update type detection
|
|
5
|
+
* without requiring external dependencies like the `semver` package.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
/**
|
|
11
|
+
* Parse a version string into its numeric components.
|
|
12
|
+
*
|
|
13
|
+
* Strips a leading `v` prefix if present. Splits prerelease
|
|
14
|
+
* identifiers on `-`. Handles two-part versions like "2.5"
|
|
15
|
+
* by treating the missing patch as 0.
|
|
16
|
+
*
|
|
17
|
+
* @param version - Semver string, e.g., "2.5.0", "v1.2.3-beta.1"
|
|
18
|
+
* @returns Parsed version object
|
|
19
|
+
* @throws Error if any numeric component is NaN
|
|
20
|
+
*/
|
|
21
|
+
export function parseVersion(version) {
|
|
22
|
+
// Strip leading 'v' prefix
|
|
23
|
+
const cleaned = version.startsWith("v") ? version.slice(1) : version;
|
|
24
|
+
// Separate prerelease from numeric parts
|
|
25
|
+
const [numericPart, ...prereleaseParts] = cleaned.split("-");
|
|
26
|
+
const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join("-") : undefined;
|
|
27
|
+
const parts = numericPart.split(".");
|
|
28
|
+
const major = Number(parts[0]);
|
|
29
|
+
const minor = Number(parts[1] ?? "0");
|
|
30
|
+
const patch = Number(parts[2] ?? "0");
|
|
31
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
|
|
32
|
+
throw new Error(`Invalid version string: "${version}"`);
|
|
33
|
+
}
|
|
34
|
+
return { major, minor, patch, prerelease };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Determine if `latest` is newer than `current`.
|
|
38
|
+
*
|
|
39
|
+
* Compares major, minor, and patch components numerically.
|
|
40
|
+
* For prerelease handling: a version WITH a prerelease tag is
|
|
41
|
+
* considered OLDER than the same version WITHOUT one
|
|
42
|
+
* (e.g., `2.5.0-beta.1 < 2.5.0`).
|
|
43
|
+
*
|
|
44
|
+
* @param current - Currently installed version
|
|
45
|
+
* @param latest - Latest available version
|
|
46
|
+
* @returns true if `latest` is strictly newer than `current`
|
|
47
|
+
*/
|
|
48
|
+
export function isNewer(current, latest) {
|
|
49
|
+
const c = parseVersion(current);
|
|
50
|
+
const l = parseVersion(latest);
|
|
51
|
+
// Compare major.minor.patch numerically
|
|
52
|
+
if (l.major !== c.major)
|
|
53
|
+
return l.major > c.major;
|
|
54
|
+
if (l.minor !== c.minor)
|
|
55
|
+
return l.minor > c.minor;
|
|
56
|
+
if (l.patch !== c.patch)
|
|
57
|
+
return l.patch > c.patch;
|
|
58
|
+
// Same numeric version — check prerelease
|
|
59
|
+
// A version with prerelease is older than the same version without
|
|
60
|
+
if (c.prerelease && !l.prerelease)
|
|
61
|
+
return true;
|
|
62
|
+
if (!c.prerelease && l.prerelease)
|
|
63
|
+
return false;
|
|
64
|
+
// Both have prerelease or both don't — considered equal
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Determine the type of update between two versions.
|
|
69
|
+
*
|
|
70
|
+
* @param current - Currently installed version
|
|
71
|
+
* @param latest - Latest available version
|
|
72
|
+
* @returns "major" | "minor" | "patch"
|
|
73
|
+
*/
|
|
74
|
+
export function getUpdateType(current, latest) {
|
|
75
|
+
const c = parseVersion(current);
|
|
76
|
+
const l = parseVersion(latest);
|
|
77
|
+
if (l.major > c.major)
|
|
78
|
+
return "major";
|
|
79
|
+
if (l.minor > c.minor)
|
|
80
|
+
return "minor";
|
|
81
|
+
return "patch";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read the current installed version from the package's package.json.
|
|
85
|
+
*
|
|
86
|
+
* Resolves the path relative to this module's location, trying
|
|
87
|
+
* multiple paths to handle both local dev and global install layouts.
|
|
88
|
+
*
|
|
89
|
+
* @returns The current version string
|
|
90
|
+
* @throws Error if package.json cannot be found or parsed
|
|
91
|
+
*/
|
|
92
|
+
export function getCurrentVersion() {
|
|
93
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
94
|
+
const __dirname = dirname(__filename);
|
|
95
|
+
// Try different possible paths for package.json
|
|
96
|
+
// - ../../package.json: from src/update/ in local dev (src/update -> src -> root)
|
|
97
|
+
// - ../../../package.json: from dist/update/ in global install (dist/update -> dist -> root)
|
|
98
|
+
const possiblePaths = [
|
|
99
|
+
join(__dirname, "..", "..", "package.json"),
|
|
100
|
+
join(__dirname, "..", "..", "..", "package.json"),
|
|
101
|
+
];
|
|
102
|
+
for (const path of possiblePaths) {
|
|
103
|
+
try {
|
|
104
|
+
const packageJson = JSON.parse(readFileSync(path, "utf-8"));
|
|
105
|
+
if (packageJson.version) {
|
|
106
|
+
return packageJson.version;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Try next path
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
throw new Error("Could not determine current version from package.json");
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Text } from "ink";
|
|
2
|
-
import React, { useEffect, useState } from "react";
|
|
3
|
-
import { useTheme } from "../ThemeContext.js";
|
|
4
|
-
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
|
-
/**
|
|
6
|
-
* Spinner displays an animated loading indicator
|
|
7
|
-
* Uses braille pattern characters for smooth animation
|
|
8
|
-
*/
|
|
9
|
-
export const Spinner = ({ color }) => {
|
|
10
|
-
const { theme } = useTheme();
|
|
11
|
-
const [frame, setFrame] = useState(0);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const timer = setInterval(() => {
|
|
14
|
-
setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
15
|
-
}, 80);
|
|
16
|
-
return () => clearInterval(timer);
|
|
17
|
-
}, []);
|
|
18
|
-
return (React.createElement(Text, { color: color ?? theme.colors.primary }, SPINNER_FRAMES[frame]));
|
|
19
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { detectSystemTheme, clearThemeCache } from "../detectTheme.js";
|
|
3
|
-
describe("detectSystemTheme", () => {
|
|
4
|
-
const originalEnv = process.env;
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
// Reset environment and clear cache before each test
|
|
7
|
-
process.env = { ...originalEnv };
|
|
8
|
-
clearThemeCache();
|
|
9
|
-
});
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
process.env = originalEnv;
|
|
12
|
-
clearThemeCache();
|
|
13
|
-
});
|
|
14
|
-
describe("COLORFGBG detection", () => {
|
|
15
|
-
it("should detect dark theme when background is 0 (black)", () => {
|
|
16
|
-
process.env.COLORFGBG = "15;0";
|
|
17
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
18
|
-
});
|
|
19
|
-
it("should detect dark theme when background is 6", () => {
|
|
20
|
-
process.env.COLORFGBG = "7;6";
|
|
21
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
22
|
-
});
|
|
23
|
-
it("should detect light theme when background is 7 (white/light gray)", () => {
|
|
24
|
-
process.env.COLORFGBG = "0;7";
|
|
25
|
-
expect(detectSystemTheme()).toBe("light");
|
|
26
|
-
});
|
|
27
|
-
it("should detect light theme when background is 15 (bright white)", () => {
|
|
28
|
-
process.env.COLORFGBG = "0;15";
|
|
29
|
-
expect(detectSystemTheme()).toBe("light");
|
|
30
|
-
});
|
|
31
|
-
it("should handle three-part COLORFGBG format", () => {
|
|
32
|
-
// Some terminals use foreground;middle;background format
|
|
33
|
-
process.env.COLORFGBG = "15;default;0";
|
|
34
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
35
|
-
});
|
|
36
|
-
it("should handle three-part COLORFGBG with light background", () => {
|
|
37
|
-
process.env.COLORFGBG = "0;default;15";
|
|
38
|
-
expect(detectSystemTheme()).toBe("light");
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
describe("fallback behavior", () => {
|
|
42
|
-
it("should fallback to dark when COLORFGBG is not set", () => {
|
|
43
|
-
delete process.env.COLORFGBG;
|
|
44
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
45
|
-
});
|
|
46
|
-
it("should fallback to dark when COLORFGBG is invalid", () => {
|
|
47
|
-
process.env.COLORFGBG = "invalid";
|
|
48
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
49
|
-
});
|
|
50
|
-
it("should fallback to dark when COLORFGBG has invalid number", () => {
|
|
51
|
-
process.env.COLORFGBG = "15;abc";
|
|
52
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
53
|
-
});
|
|
54
|
-
it("should fallback to dark when COLORFGBG is empty", () => {
|
|
55
|
-
process.env.COLORFGBG = "";
|
|
56
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe("caching", () => {
|
|
60
|
-
it("should cache the result", () => {
|
|
61
|
-
process.env.COLORFGBG = "15;0";
|
|
62
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
63
|
-
// Change the environment variable
|
|
64
|
-
process.env.COLORFGBG = "0;15";
|
|
65
|
-
// Should still return cached result
|
|
66
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
67
|
-
});
|
|
68
|
-
it("should return fresh result after cache is cleared", () => {
|
|
69
|
-
process.env.COLORFGBG = "15;0";
|
|
70
|
-
expect(detectSystemTheme()).toBe("dark");
|
|
71
|
-
// Clear cache and change environment
|
|
72
|
-
clearThemeCache();
|
|
73
|
-
process.env.COLORFGBG = "0;15";
|
|
74
|
-
// Should return new result
|
|
75
|
-
expect(detectSystemTheme()).toBe("light");
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
});
|