gopeak 2.1.0 → 2.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 +224 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -0
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -0
- package/build/addon/godot_mcp_editor/plugin.gd +84 -0
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -0
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -0
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -0
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +710 -0
- package/build/cli/check.js +77 -0
- package/build/cli/notify.js +88 -0
- package/build/cli/setup.js +115 -0
- package/build/cli/star.js +51 -0
- package/build/cli/uninstall.js +26 -0
- package/build/cli/utils.js +149 -0
- package/build/cli.js +91 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +556 -0
- package/build/index.js +2761 -2064
- package/build/prompts.js +163 -0
- package/build/visualizer/canvas.js +832 -0
- package/build/visualizer/events.js +814 -0
- package/build/visualizer/layout.js +304 -0
- package/build/visualizer/main.js +245 -0
- package/build/visualizer/modals.js +239 -0
- package/build/visualizer/panel.js +1091 -0
- package/build/visualizer/state.js +210 -0
- package/build/visualizer/syntax.js +106 -0
- package/build/visualizer/usages.js +352 -0
- package/build/visualizer/websocket.js +85 -0
- package/build/visualizer-server.js +375 -0
- package/build/visualizer.html +6395 -0
- package/package.json +15 -6
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gopeak check — Update check logic
|
|
3
|
+
*
|
|
4
|
+
* Modes:
|
|
5
|
+
* gopeak check Interactive: show update status
|
|
6
|
+
* gopeak check --bg Background: refresh cache silently, exit
|
|
7
|
+
* gopeak check --quiet Print one-liner only if update available
|
|
8
|
+
*/
|
|
9
|
+
import { getLocalVersion, fetchLatestVersion, compareSemver, isCacheFresh, updateCacheTimestamp, writeNotifyFile, clearNotifyFile, ensureGopeakDir, } from './utils.js';
|
|
10
|
+
export async function checkForUpdates(args) {
|
|
11
|
+
const isBg = args.includes('--bg');
|
|
12
|
+
const isQuiet = args.includes('--quiet');
|
|
13
|
+
ensureGopeakDir();
|
|
14
|
+
// Background mode: refresh cache and exit silently
|
|
15
|
+
if (isBg) {
|
|
16
|
+
await backgroundCheck();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Interactive or quiet mode: always fetch fresh
|
|
20
|
+
const currentVersion = getLocalVersion();
|
|
21
|
+
const latestVersion = await fetchLatestVersion();
|
|
22
|
+
if (!latestVersion) {
|
|
23
|
+
if (!isQuiet) {
|
|
24
|
+
console.log('⚠️ Could not reach npm registry. Check your network.');
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (compareSemver(latestVersion, currentVersion) > 0) {
|
|
29
|
+
if (isQuiet) {
|
|
30
|
+
console.log(`🚀 GoPeak v${latestVersion} available! Run: npm update -g gopeak`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
printUpdateBox(currentVersion, latestVersion);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (!isQuiet) {
|
|
38
|
+
console.log(`✅ GoPeak v${currentVersion} is up to date.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Background check: called by shell hook (once per day). */
|
|
43
|
+
async function backgroundCheck() {
|
|
44
|
+
// Skip if cache is fresh (< 24h)
|
|
45
|
+
if (isCacheFresh()) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const currentVersion = getLocalVersion();
|
|
49
|
+
const latestVersion = await fetchLatestVersion();
|
|
50
|
+
// Update timestamp regardless of result (avoid hammering on failure)
|
|
51
|
+
updateCacheTimestamp();
|
|
52
|
+
if (!latestVersion)
|
|
53
|
+
return;
|
|
54
|
+
if (compareSemver(latestVersion, currentVersion) > 0) {
|
|
55
|
+
const msg = `🚀 GoPeak v${latestVersion} available! (current: v${currentVersion})\n Run: npm update -g gopeak`;
|
|
56
|
+
writeNotifyFile(msg);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// No update — clear stale notification if any
|
|
60
|
+
clearNotifyFile();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function printUpdateBox(current, latest) {
|
|
64
|
+
const line1 = ` 🚀 GoPeak v${latest} available! (current: v${current})`;
|
|
65
|
+
const line2 = ` npm update -g gopeak`;
|
|
66
|
+
const line3 = ` https://github.com/HaD0Yun/godot-mcp/releases`;
|
|
67
|
+
const maxLen = Math.max(line1.length, line2.length, line3.length) + 2;
|
|
68
|
+
const pad = (s) => s + ' '.repeat(Math.max(0, maxLen - s.length));
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log('╔' + '═'.repeat(maxLen) + '╗');
|
|
71
|
+
console.log('║' + pad(line1) + '║');
|
|
72
|
+
console.log('║' + ' '.repeat(maxLen) + '║');
|
|
73
|
+
console.log('║' + pad(line2) + '║');
|
|
74
|
+
console.log('║' + pad(line3) + '║');
|
|
75
|
+
console.log('╚' + '═'.repeat(maxLen) + '╝');
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gopeak notify — Interactive notification shown before AI CLI tools
|
|
3
|
+
*
|
|
4
|
+
* Called by the shell hook when a notification exists.
|
|
5
|
+
* Shows update prompt (y/n) and star prompt (y/n, one-time).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
8
|
+
import { createInterface } from 'readline';
|
|
9
|
+
import { NOTIFY_FILE, STAR_PROMPTED_FILE, ensureGopeakDir, commandExists, runCommand, } from './utils.js';
|
|
10
|
+
const REPO_URL = 'https://github.com/HaD0Yun/godot-mcp';
|
|
11
|
+
export async function showNotification() {
|
|
12
|
+
ensureGopeakDir();
|
|
13
|
+
const hasUpdate = existsSync(NOTIFY_FILE);
|
|
14
|
+
const hasStarPrompted = existsSync(STAR_PROMPTED_FILE);
|
|
15
|
+
// Nothing to show → exit silently (fast path)
|
|
16
|
+
if (!hasUpdate && hasStarPrompted) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// --- Update notification ---
|
|
20
|
+
if (hasUpdate) {
|
|
21
|
+
const updateInfo = readFileSync(NOTIFY_FILE, 'utf-8').trim();
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(` ${updateInfo}`);
|
|
24
|
+
console.log('');
|
|
25
|
+
const wantsUpdate = await askYesNo(' Update now? (y/n): ');
|
|
26
|
+
if (wantsUpdate) {
|
|
27
|
+
console.log(' Updating...');
|
|
28
|
+
const result = await runCommand('npm update -g gopeak');
|
|
29
|
+
if (result.code === 0) {
|
|
30
|
+
console.log(' ✅ Updated successfully!');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(' ⚠️ Update failed. Run manually: npm update -g gopeak');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Remove notify file (shown once)
|
|
37
|
+
try {
|
|
38
|
+
unlinkSync(NOTIFY_FILE);
|
|
39
|
+
}
|
|
40
|
+
catch { /* ignore */ }
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
// --- Star prompt (one-time) ---
|
|
44
|
+
if (!hasStarPrompted) {
|
|
45
|
+
await askYesNo(' \u2b50 Star GoPeak on GitHub? (y/n): ');
|
|
46
|
+
// Star regardless of answer
|
|
47
|
+
await handleStar();
|
|
48
|
+
writeFileSync(STAR_PROMPTED_FILE, new Date().toISOString());
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function handleStar() {
|
|
53
|
+
const hasGh = await commandExists('gh');
|
|
54
|
+
if (!hasGh) {
|
|
55
|
+
console.log(` ℹ️ gh CLI not installed. Star directly: ${REPO_URL}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const authResult = await runCommand('gh auth status');
|
|
59
|
+
if (authResult.code !== 0) {
|
|
60
|
+
console.log(` ℹ️ gh not authenticated. Star directly: ${REPO_URL}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Check if already starred
|
|
64
|
+
const checkResult = await runCommand('gh api user/starred/HaD0Yun/godot-mcp');
|
|
65
|
+
if (checkResult.code === 0) {
|
|
66
|
+
console.log(' ⭐ Already starred! Thank you!');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const starResult = await runCommand('gh api -X PUT user/starred/HaD0Yun/godot-mcp');
|
|
70
|
+
if (starResult.code === 0) {
|
|
71
|
+
console.log(' ⭐ Starred! Thank you for your support!');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(` ⚠️ Could not star automatically. Star directly: ${REPO_URL}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function askYesNo(prompt) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const rl = createInterface({
|
|
80
|
+
input: process.stdin,
|
|
81
|
+
output: process.stdout,
|
|
82
|
+
});
|
|
83
|
+
rl.question(prompt, (answer) => {
|
|
84
|
+
rl.close();
|
|
85
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gopeak setup — Install shell hooks into ~/.bashrc or ~/.zshrc
|
|
3
|
+
*
|
|
4
|
+
* Wraps AI CLI tools (claude, codex, gemini, opencode, omc, omx)
|
|
5
|
+
* with a precheck function that displays cached GoPeak update notifications.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
|
|
8
|
+
import { getShellRcFile, getShellName, getLocalVersion, ensureGopeakDir, ONBOARDING_SHOWN_FILE, STAR_PROMPTED_FILE, } from './utils.js';
|
|
9
|
+
const MARKER_START = '# >>> GoPeak shell hooks >>>';
|
|
10
|
+
const MARKER_END = '# <<< GoPeak shell hooks <<<';
|
|
11
|
+
/** The shell hook block that gets appended to the RC file. */
|
|
12
|
+
function generateHookBlock() {
|
|
13
|
+
// Target commands (excluding omx — handled separately)
|
|
14
|
+
const targetList = 'claude codex gemini opencode omc';
|
|
15
|
+
const lines = [
|
|
16
|
+
MARKER_START,
|
|
17
|
+
'# GoPeak update notifications for AI CLI tools',
|
|
18
|
+
'# Installed by: gopeak setup | Remove with: gopeak uninstall',
|
|
19
|
+
'',
|
|
20
|
+
'__gopeak_precheck() {',
|
|
21
|
+
' local notify="$HOME/.gopeak/notify"',
|
|
22
|
+
' local star="$HOME/.gopeak/star-prompted"',
|
|
23
|
+
' # If notification exists or star not yet prompted → interactive prompt',
|
|
24
|
+
' if [ -f "$notify" ] || [ ! -f "$star" ]; then',
|
|
25
|
+
' command -v gopeak &>/dev/null && gopeak notify',
|
|
26
|
+
' fi',
|
|
27
|
+
' # Background refresh for next time',
|
|
28
|
+
' local ts="$HOME/.gopeak/last-check"',
|
|
29
|
+
' if [ -f "$ts" ]; then',
|
|
30
|
+
' local age=$(( $(date +%s) - $(cat "$ts") ))',
|
|
31
|
+
' [ "$age" -lt 86400 ] && return',
|
|
32
|
+
' fi',
|
|
33
|
+
' command -v gopeak &>/dev/null && gopeak check --bg &>/dev/null & disown &>/dev/null',
|
|
34
|
+
'}',
|
|
35
|
+
'',
|
|
36
|
+
'# Wrap AI CLI tools: precheck \u2192 original command',
|
|
37
|
+
`for __gopeak_cmd in ${targetList}; do`,
|
|
38
|
+
' if command -v "$__gopeak_cmd" &>/dev/null && ! declare -f "$__gopeak_cmd" &>/dev/null; then',
|
|
39
|
+
' eval "${__gopeak_cmd}() { __gopeak_precheck; command ${__gopeak_cmd} \\"\\$@\\"; }"',
|
|
40
|
+
' fi',
|
|
41
|
+
'done',
|
|
42
|
+
'',
|
|
43
|
+
'# omx: preserve existing function (e.g. --no-alt-screen wrapper)',
|
|
44
|
+
'if declare -f omx &>/dev/null; then',
|
|
45
|
+
' eval "__gopeak_orig_omx() $(declare -f omx | sed \'1d\')"',
|
|
46
|
+
' omx() { __gopeak_precheck; __gopeak_orig_omx "$@"; }',
|
|
47
|
+
'elif command -v omx &>/dev/null; then',
|
|
48
|
+
' omx() { __gopeak_precheck; command omx "$@"; }',
|
|
49
|
+
'fi',
|
|
50
|
+
'',
|
|
51
|
+
'unset __gopeak_cmd',
|
|
52
|
+
MARKER_END,
|
|
53
|
+
];
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
export async function setupShellHooks() {
|
|
57
|
+
const rcFile = getShellRcFile();
|
|
58
|
+
const shellName = getShellName();
|
|
59
|
+
// Check if RC file exists
|
|
60
|
+
if (!existsSync(rcFile)) {
|
|
61
|
+
console.log(`⚠️ ${rcFile} not found. Creating it.`);
|
|
62
|
+
writeFileSync(rcFile, '');
|
|
63
|
+
}
|
|
64
|
+
const content = readFileSync(rcFile, 'utf-8');
|
|
65
|
+
// Check if already installed
|
|
66
|
+
if (content.includes(MARKER_START)) {
|
|
67
|
+
// Replace existing block
|
|
68
|
+
const cleaned = removeHookBlock(content);
|
|
69
|
+
const hookBlock = generateHookBlock();
|
|
70
|
+
writeFileSync(rcFile, cleaned + '\n' + hookBlock + '\n');
|
|
71
|
+
console.log(`🔄 GoPeak shell hooks updated in ${rcFile}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Append new block
|
|
75
|
+
const hookBlock = generateHookBlock();
|
|
76
|
+
appendFileSync(rcFile, '\n' + hookBlock + '\n');
|
|
77
|
+
console.log(`✅ GoPeak shell hooks installed in ${rcFile}`);
|
|
78
|
+
}
|
|
79
|
+
console.log(` Reload with: source ${rcFile}`);
|
|
80
|
+
console.log('');
|
|
81
|
+
// Show onboarding (once)
|
|
82
|
+
ensureGopeakDir();
|
|
83
|
+
if (!existsSync(ONBOARDING_SHOWN_FILE)) {
|
|
84
|
+
printOnboarding();
|
|
85
|
+
writeFileSync(ONBOARDING_SHOWN_FILE, new Date().toISOString());
|
|
86
|
+
}
|
|
87
|
+
// Suggest star (once)
|
|
88
|
+
if (!existsSync(STAR_PROMPTED_FILE)) {
|
|
89
|
+
console.log('⭐ If GoPeak helps your Godot workflow, please star us!');
|
|
90
|
+
console.log(' Run: gopeak star');
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function removeHookBlock(content) {
|
|
95
|
+
const regex = new RegExp(`\\n?${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n?`, 'g');
|
|
96
|
+
return content.replace(regex, '');
|
|
97
|
+
}
|
|
98
|
+
function escapeRegex(s) {
|
|
99
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
+
}
|
|
101
|
+
function printOnboarding() {
|
|
102
|
+
const version = getLocalVersion();
|
|
103
|
+
console.log('╔══════════════════════════════════════════════════════╗');
|
|
104
|
+
console.log(`║ 🎮 GoPeak v${version} — AI-Powered Godot Development`
|
|
105
|
+
+ ' '.repeat(Math.max(0, 39 - version.length)) + '║');
|
|
106
|
+
console.log('║ ║');
|
|
107
|
+
console.log('║ 110+ tools for Godot Engine via MCP ║');
|
|
108
|
+
console.log('║ ║');
|
|
109
|
+
console.log('║ 📖 Docs: https://github.com/HaD0Yun/godot-mcp ║');
|
|
110
|
+
console.log('║ ⭐ Star: gopeak star ║');
|
|
111
|
+
console.log('║ 🔄 Update: npm update -g gopeak ║');
|
|
112
|
+
console.log('╚══════════════════════════════════════════════════════╝');
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
export { MARKER_START, MARKER_END };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gopeak star — GitHub star feature
|
|
3
|
+
*
|
|
4
|
+
* If gh CLI is installed and authenticated: auto-star.
|
|
5
|
+
* If not: print a friendly message with the URL.
|
|
6
|
+
* Never asks the user to install gh.
|
|
7
|
+
*/
|
|
8
|
+
import { commandExists, runCommand, STAR_PROMPTED_FILE, ensureGopeakDir } from './utils.js';
|
|
9
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
10
|
+
const REPO = 'HaD0Yun/godot-mcp';
|
|
11
|
+
const REPO_URL = `https://github.com/${REPO}`;
|
|
12
|
+
export async function starGoPeak() {
|
|
13
|
+
// 1. Check if gh CLI exists
|
|
14
|
+
const hasGh = await commandExists('gh');
|
|
15
|
+
if (!hasGh) {
|
|
16
|
+
console.log(`ℹ️ gh CLI is not installed.`);
|
|
17
|
+
console.log(` Please star GoPeak directly: ${REPO_URL}`);
|
|
18
|
+
markStarPrompted();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// 2. Check gh authentication
|
|
22
|
+
const authResult = await runCommand('gh auth status');
|
|
23
|
+
if (authResult.code !== 0) {
|
|
24
|
+
console.log(`ℹ️ gh CLI is not authenticated.`);
|
|
25
|
+
console.log(` Please star GoPeak directly: ${REPO_URL}`);
|
|
26
|
+
markStarPrompted();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// 3. Check if already starred (REST: 204=starred, 404=not starred)
|
|
30
|
+
const checkResult = await runCommand(`gh api user/starred/${REPO}`);
|
|
31
|
+
if (checkResult.code === 0) {
|
|
32
|
+
console.log(`⭐ You've already starred GoPeak! Thank you!`);
|
|
33
|
+
markStarPrompted();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// 4. Star it (PUT user/starred/:owner/:repo)
|
|
37
|
+
const starResult = await runCommand(`gh api -X PUT user/starred/${REPO}`);
|
|
38
|
+
if (starResult.code === 0) {
|
|
39
|
+
console.log(`⭐ Starred GoPeak! Thank you for your support!`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(`⚠️ Could not star automatically. Please star directly: ${REPO_URL}`);
|
|
43
|
+
}
|
|
44
|
+
markStarPrompted();
|
|
45
|
+
}
|
|
46
|
+
function markStarPrompted() {
|
|
47
|
+
ensureGopeakDir();
|
|
48
|
+
if (!existsSync(STAR_PROMPTED_FILE)) {
|
|
49
|
+
writeFileSync(STAR_PROMPTED_FILE, new Date().toISOString());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gopeak uninstall — Remove shell hooks from RC file
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
+
import { getShellRcFile } from './utils.js';
|
|
6
|
+
import { MARKER_START, MARKER_END } from './setup.js';
|
|
7
|
+
export async function uninstallHooks() {
|
|
8
|
+
const rcFile = getShellRcFile();
|
|
9
|
+
if (!existsSync(rcFile)) {
|
|
10
|
+
console.log(`ℹ️ ${rcFile} not found. Nothing to remove.`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const content = readFileSync(rcFile, 'utf-8');
|
|
14
|
+
if (!content.includes(MARKER_START)) {
|
|
15
|
+
console.log('ℹ️ GoPeak shell hooks are not installed. Nothing to remove.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const regex = new RegExp(`\\n?${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n?`, 'g');
|
|
19
|
+
const cleaned = content.replace(regex, '');
|
|
20
|
+
writeFileSync(rcFile, cleaned);
|
|
21
|
+
console.log(`✅ GoPeak shell hooks removed from ${rcFile}`);
|
|
22
|
+
console.log(` Reload with: source ${rcFile}`);
|
|
23
|
+
}
|
|
24
|
+
function escapeRegex(s) {
|
|
25
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
26
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoPeak CLI Utilities
|
|
3
|
+
* Shared helpers for version checking, caching, and shell detection.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname } from 'path';
|
|
12
|
+
import { get as httpsGet } from 'https';
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
/* Paths */
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
const GOPEAK_DIR = join(homedir(), '.gopeak');
|
|
18
|
+
const LAST_CHECK_FILE = join(GOPEAK_DIR, 'last-check');
|
|
19
|
+
const NOTIFY_FILE = join(GOPEAK_DIR, 'notify');
|
|
20
|
+
const ONBOARDING_SHOWN_FILE = join(GOPEAK_DIR, 'onboarding-shown');
|
|
21
|
+
const STAR_PROMPTED_FILE = join(GOPEAK_DIR, 'star-prompted');
|
|
22
|
+
export { GOPEAK_DIR, LAST_CHECK_FILE, NOTIFY_FILE, ONBOARDING_SHOWN_FILE, STAR_PROMPTED_FILE };
|
|
23
|
+
export function ensureGopeakDir() {
|
|
24
|
+
if (!existsSync(GOPEAK_DIR)) {
|
|
25
|
+
mkdirSync(GOPEAK_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/* ------------------------------------------------------------------ */
|
|
29
|
+
/* Version */
|
|
30
|
+
/* ------------------------------------------------------------------ */
|
|
31
|
+
export function getLocalVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = dirname(__filename);
|
|
35
|
+
// Walk up from build/cli/utils.js → project root
|
|
36
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
37
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
38
|
+
return pkg.version ?? '0.0.0';
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Fetch latest version from npm registry (no dependencies). */
|
|
45
|
+
export function fetchLatestVersion() {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const url = 'https://registry.npmjs.org/gopeak/latest';
|
|
48
|
+
const timeout = setTimeout(() => resolve(null), 5000);
|
|
49
|
+
httpsGet(url, (res) => {
|
|
50
|
+
let data = '';
|
|
51
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
52
|
+
res.on('end', () => {
|
|
53
|
+
clearTimeout(timeout);
|
|
54
|
+
try {
|
|
55
|
+
const json = JSON.parse(data);
|
|
56
|
+
resolve(json.version ?? null);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
resolve(null);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
res.on('error', () => { clearTimeout(timeout); resolve(null); });
|
|
63
|
+
}).on('error', () => { clearTimeout(timeout); resolve(null); });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Compare two semver strings. Returns 1 if a > b, -1 if a < b, 0 if equal. */
|
|
67
|
+
export function compareSemver(a, b) {
|
|
68
|
+
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
69
|
+
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
70
|
+
for (let i = 0; i < 3; i++) {
|
|
71
|
+
const na = pa[i] ?? 0;
|
|
72
|
+
const nb = pb[i] ?? 0;
|
|
73
|
+
if (na > nb)
|
|
74
|
+
return 1;
|
|
75
|
+
if (na < nb)
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
/* ------------------------------------------------------------------ */
|
|
81
|
+
/* Cache */
|
|
82
|
+
/* ------------------------------------------------------------------ */
|
|
83
|
+
/** Check if the last update check was less than `maxAgeSeconds` ago. */
|
|
84
|
+
export function isCacheFresh(maxAgeSeconds = 86400) {
|
|
85
|
+
try {
|
|
86
|
+
if (!existsSync(LAST_CHECK_FILE))
|
|
87
|
+
return false;
|
|
88
|
+
const ts = parseInt(readFileSync(LAST_CHECK_FILE, 'utf-8').trim(), 10);
|
|
89
|
+
return (Date.now() / 1000 - ts) < maxAgeSeconds;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function updateCacheTimestamp() {
|
|
96
|
+
ensureGopeakDir();
|
|
97
|
+
writeFileSync(LAST_CHECK_FILE, String(Math.floor(Date.now() / 1000)));
|
|
98
|
+
}
|
|
99
|
+
export function writeNotifyFile(message) {
|
|
100
|
+
ensureGopeakDir();
|
|
101
|
+
writeFileSync(NOTIFY_FILE, message);
|
|
102
|
+
}
|
|
103
|
+
export function clearNotifyFile() {
|
|
104
|
+
try {
|
|
105
|
+
if (existsSync(NOTIFY_FILE))
|
|
106
|
+
unlinkSync(NOTIFY_FILE);
|
|
107
|
+
}
|
|
108
|
+
catch { /* ignore */ }
|
|
109
|
+
}
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
/* Shell detection */
|
|
112
|
+
/* ------------------------------------------------------------------ */
|
|
113
|
+
export function getShellRcFile() {
|
|
114
|
+
const shell = process.env.SHELL ?? '';
|
|
115
|
+
if (shell.includes('zsh'))
|
|
116
|
+
return join(homedir(), '.zshrc');
|
|
117
|
+
return join(homedir(), '.bashrc');
|
|
118
|
+
}
|
|
119
|
+
export function getShellName() {
|
|
120
|
+
const shell = process.env.SHELL ?? '';
|
|
121
|
+
if (shell.includes('zsh'))
|
|
122
|
+
return 'zsh';
|
|
123
|
+
return 'bash';
|
|
124
|
+
}
|
|
125
|
+
/* ------------------------------------------------------------------ */
|
|
126
|
+
/* Command helpers */
|
|
127
|
+
/* ------------------------------------------------------------------ */
|
|
128
|
+
export async function commandExists(cmd) {
|
|
129
|
+
try {
|
|
130
|
+
await execAsync(`command -v ${cmd}`);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export async function runCommand(cmd) {
|
|
138
|
+
try {
|
|
139
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
140
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), code: 0 };
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
return {
|
|
144
|
+
stdout: (err.stdout ?? '').trim(),
|
|
145
|
+
stderr: (err.stderr ?? '').trim(),
|
|
146
|
+
code: err.code ?? 1,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GoPeak CLI Entrypoint
|
|
4
|
+
*
|
|
5
|
+
* Routes subcommands or falls through to the MCP server.
|
|
6
|
+
*
|
|
7
|
+
* gopeak → Start MCP server (default, backward-compatible)
|
|
8
|
+
* gopeak setup → Install shell hooks
|
|
9
|
+
* gopeak check → Check for updates
|
|
10
|
+
* gopeak star → Star on GitHub
|
|
11
|
+
* gopeak uninstall → Remove shell hooks
|
|
12
|
+
* gopeak version → Print version
|
|
13
|
+
* gopeak help → Show help
|
|
14
|
+
*/
|
|
15
|
+
import { getLocalVersion } from './cli/utils.js';
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const command = args[0];
|
|
18
|
+
const CLI_COMMANDS = ['setup', 'check', 'star', 'notify', 'uninstall', 'version', 'help', '--version', '-v', '--help', '-h'];
|
|
19
|
+
async function main() {
|
|
20
|
+
// If no args or not a CLI command → start MCP server (original behavior)
|
|
21
|
+
if (!command || !CLI_COMMANDS.includes(command)) {
|
|
22
|
+
// Dynamic import to avoid loading MCP SDK for CLI-only commands
|
|
23
|
+
await import('./index.js');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
switch (command) {
|
|
27
|
+
case 'setup': {
|
|
28
|
+
const { setupShellHooks } = await import('./cli/setup.js');
|
|
29
|
+
await setupShellHooks();
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'check': {
|
|
33
|
+
const { checkForUpdates } = await import('./cli/check.js');
|
|
34
|
+
await checkForUpdates(args.slice(1));
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'star': {
|
|
38
|
+
const { starGoPeak } = await import('./cli/star.js');
|
|
39
|
+
await starGoPeak();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'notify': {
|
|
43
|
+
const { showNotification } = await import('./cli/notify.js');
|
|
44
|
+
await showNotification();
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'uninstall': {
|
|
48
|
+
const { uninstallHooks } = await import('./cli/uninstall.js');
|
|
49
|
+
await uninstallHooks();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'version':
|
|
53
|
+
case '--version':
|
|
54
|
+
case '-v': {
|
|
55
|
+
console.log(`gopeak v${getLocalVersion()}`);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'help':
|
|
59
|
+
case '--help':
|
|
60
|
+
case '-h': {
|
|
61
|
+
printHelp();
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function printHelp() {
|
|
67
|
+
const version = getLocalVersion();
|
|
68
|
+
console.log(`
|
|
69
|
+
GoPeak v${version} — AI-Powered Godot Development via MCP
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
gopeak Start MCP server (default)
|
|
73
|
+
gopeak setup Install shell hooks for update notifications
|
|
74
|
+
gopeak check Check for GoPeak updates
|
|
75
|
+
gopeak check --bg Background check (used by shell hooks)
|
|
76
|
+
gopeak check --quiet Print only if update available
|
|
77
|
+
gopeak star Star GoPeak on GitHub
|
|
78
|
+
gopeak uninstall Remove shell hooks
|
|
79
|
+
gopeak version Show current version
|
|
80
|
+
gopeak help Show this help
|
|
81
|
+
|
|
82
|
+
Shell hooks wrap these commands with update notifications:
|
|
83
|
+
claude, codex, gemini, opencode, omc, omx
|
|
84
|
+
|
|
85
|
+
More info: https://github.com/HaD0Yun/godot-mcp
|
|
86
|
+
`.trim());
|
|
87
|
+
}
|
|
88
|
+
main().catch((err) => {
|
|
89
|
+
console.error('gopeak:', err.message ?? err);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|