gm-qwen 2.0.380

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/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ node_modules/
2
+ *.log
3
+ *.swp
4
+ *.swo
5
+ .DS_Store
6
+ dist/
7
+ build/
8
+ *.tmp
9
+ .env
10
+ .env.local
11
+ .vscode/
12
+ .idea/
13
+ *.iml
14
+ bin/
package/CLAUDE.md ADDED
@@ -0,0 +1,11 @@
1
+ # CLAUDE
2
+
3
+ ## Technical Notes
4
+
5
+ Hook response format: `{"decision":"allow|block","reason":"text"}` with exit code 0.
6
+
7
+ Tool names for this platform:
8
+
9
+ When filtering transcript history by sessionId, use: `if (sessionId && entry.sessionId === sessionId)`
10
+
11
+ Verification file `.gm-stop-verified` is auto-added to .gitignore and tracks session completion state.
@@ -0,0 +1,26 @@
1
+ # Contributing
2
+
3
+ Please ensure all code follows the conventions established in this project.
4
+
5
+ ## Before Committing
6
+
7
+ Run the build to verify everything is working:
8
+
9
+ ```bash
10
+ npm run build plugforge-starter [output-dir]
11
+ ```
12
+
13
+ ## Platform Conventions
14
+
15
+ - Each platform adapter in `platforms/` extends PlatformAdapter or CLIAdapter
16
+ - File generation logic goes in `createFileStructure()`
17
+ - Use TemplateBuilder methods for shared generation logic
18
+ - Skills are auto-discovered from plugforge-starter/skills/
19
+
20
+ ## Testing
21
+
22
+ Build all 9 platform outputs:
23
+
24
+ ```bash
25
+ node cli.js plugforge-starter /tmp/test-build
26
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # gm-qwen for Qwen Code
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install -g gm-qwen
7
+ ```
8
+
9
+ Or install directly:
10
+
11
+ ```bash
12
+ qwen extensions install https://github.com/AnEntrypoint/gm-qwen
13
+ ```
14
+
15
+ Restart Qwen Code to activate.
16
+
17
+ ## What it does
18
+
19
+ Installs the gm state machine agent (PLAN→EXECUTE→EMIT→VERIFY→COMPLETE) into Qwen Code with full hook support, skills, and automated git enforcement.
package/agents/gm.md ADDED
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: gm
3
+ description: Agent (not skill) - immutable programming state machine. Always invoke for all work coordination.
4
+ agent: true
5
+ enforce: critical
6
+ ---
7
+
8
+ # GM — Skill-First Orchestrator
9
+
10
+ **Invoke the `gm` skill immediately.** Use the Skill tool with `skill: "gm"`.
11
+
12
+ **CRITICAL: Skills are invoked via the Skill tool ONLY. Do NOT use the Agent tool to load skills. Skills are not agents. Use: `Skill tool` with `skill: "gm"` (or `"planning"`, `"gm-execute"`, `"gm-emit"`, `"gm-complete"`, `"update-docs"`). Using the Agent tool for skills is a violation.**
13
+
14
+ All work coordination, planning, execution, and verification happens through the skill tree:
15
+ - `gm` skill → `planning` skill → `gm-execute` skill → `gm-emit` skill → `gm-complete` skill → `update-docs` skill
16
+
17
+ All code execution uses `exec:<lang>` via the Bash tool — never direct `Bash(node ...)` or `Bash(npm ...)`.
18
+
19
+ To send stdin to a running background task: `exec:type` with task_id on line 1 and input on line 2.
20
+
21
+ Do not use `EnterPlanMode`. Do not run code directly via Bash. Invoke `gm` skill first.
22
+
23
+ Responses to the user must be two sentences maximum, only when the user needs to know something, and in plain conversational language — no file paths, filenames, symbols, or technical identifiers.
package/bin/plugkit ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ DIR="$(cd "$(dirname "$0")" && pwd)"
3
+ case "$(uname -s)" in
4
+ Darwin) OS=darwin ;;
5
+ *) OS=linux ;;
6
+ esac
7
+ case "$(uname -m)" in
8
+ arm64|aarch64) ARCH=arm64 ;;
9
+ *) ARCH=x64 ;;
10
+ esac
11
+ exec "$DIR/plugkit-$OS-$ARCH" "$@"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/bin/plugkit.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { spawnSync } = require('child_process');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const dir = __dirname;
8
+ const platform = os.platform();
9
+ const arch = os.arch();
10
+
11
+ let bin;
12
+ if (platform === 'win32') {
13
+ bin = path.join(dir, arch === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe');
14
+ if (!require('fs').existsSync(bin)) bin = path.join(dir, 'plugkit.exe');
15
+ } else if (platform === 'darwin') {
16
+ bin = path.join(dir, arch === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64');
17
+ } else {
18
+ bin = path.join(dir, arch === 'arm64' || arch === 'aarch64' ? 'plugkit-linux-arm64' : 'plugkit-linux-x64');
19
+ }
20
+
21
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
22
+ process.exit(result.status ?? 1);
package/cli.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
7
+ const destDir = path.join(homeDir, '.qwen', 'extensions', 'gm-qwen');
8
+
9
+ const srcDir = __dirname;
10
+ const isUpgrade = fs.existsSync(destDir);
11
+
12
+ console.log(isUpgrade ? 'Upgrading gm-qwen...' : 'Installing gm-qwen...');
13
+
14
+ try {
15
+ fs.mkdirSync(destDir, { recursive: true });
16
+
17
+ const filesToCopy = [["agents","agents"],["hooks","hooks"],["scripts","scripts"],["skills","skills"],["bin","bin"],["gm.json","gm.json"],["README.md","README.md"],["CLAUDE.md","CLAUDE.md"]];
18
+
19
+ function copyRecursive(src, dst) {
20
+ if (!fs.existsSync(src)) return;
21
+ if (fs.statSync(src).isDirectory()) {
22
+ fs.mkdirSync(dst, { recursive: true });
23
+ fs.readdirSync(src).forEach(f => copyRecursive(path.join(src, f), path.join(dst, f)));
24
+ } else {
25
+ fs.copyFileSync(src, dst);
26
+ }
27
+ }
28
+
29
+ filesToCopy.forEach(([src, dst]) => copyRecursive(path.join(srcDir, src), path.join(destDir, dst)));
30
+
31
+ const qwenExtJson = path.join(destDir, 'qwen-extension.json');
32
+ if (!fs.existsSync(qwenExtJson)) {
33
+ const pkg = JSON.parse(fs.readFileSync(path.join(srcDir, 'package.json'), 'utf-8'));
34
+ fs.writeFileSync(qwenExtJson, JSON.stringify({
35
+ name: 'gm',
36
+ version: pkg.version,
37
+ description: pkg.description,
38
+ hooks: './hooks/hooks.json',
39
+ skills: './skills'
40
+ }, null, 2) + '\n');
41
+ }
42
+
43
+ const destPath = process.platform === 'win32' ? destDir.replace(/\\/g, '/') : destDir;
44
+ console.log(`✓ gm-qwen ${isUpgrade ? 'upgraded' : 'installed'} to ${destPath}`);
45
+ console.log('Restart Qwen Code to activate.');
46
+ } catch (e) {
47
+ console.error('Installation failed:', e.message);
48
+ process.exit(1);
49
+ }
package/gm.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "gm",
3
+ "version": "2.0.380",
4
+ "description": "State machine agent with hooks, skills, and automated git enforcement",
5
+ "author": "AnEntrypoint",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "claude-code",
9
+ "claude-plugin",
10
+ "wfgy",
11
+ "automation"
12
+ ],
13
+ "homepage": "https://github.com/AnEntrypoint/gm",
14
+ "agents": [
15
+ {
16
+ "name": "gm",
17
+ "description": "Agent (not skill) - immutable programming state machine. Always invoke for all work coordination."
18
+ }
19
+ ],
20
+ "engines": {
21
+ "node": ">=16.0.0"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "plugkitVersion": "0.1.93"
27
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "description": "Hooks for gm Qwen Code extension",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "*",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook pre-tool-use",
11
+ "timeout": 3600
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "SessionStart": [
17
+ {
18
+ "matcher": "*",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook session-start",
23
+ "timeout": 180000
24
+ }
25
+ ]
26
+ }
27
+ ],
28
+ "UserPromptSubmit": [
29
+ {
30
+ "matcher": "*",
31
+ "hooks": [
32
+ {
33
+ "type": "command",
34
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook prompt-submit",
35
+ "timeout": 60000
36
+ }
37
+ ]
38
+ }
39
+ ],
40
+ "Stop": [
41
+ {
42
+ "matcher": "*",
43
+ "hooks": [
44
+ {
45
+ "type": "command",
46
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook stop",
47
+ "timeout": 300000
48
+ }
49
+ ]
50
+ },
51
+ {
52
+ "matcher": "*",
53
+ "hooks": [
54
+ {
55
+ "type": "command",
56
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook stop-git",
57
+ "timeout": 60000
58
+ }
59
+ ]
60
+ }
61
+ ]
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "gm-qwen",
3
+ "version": "2.0.380",
4
+ "description": "State machine agent with hooks, skills, and automated git enforcement",
5
+ "author": "AnEntrypoint",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "qwen-code",
9
+ "agent",
10
+ "state-machine",
11
+ "automation",
12
+ "gm"
13
+ ],
14
+ "homepage": "https://github.com/AnEntrypoint/gm-qwen#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/AnEntrypoint/gm-qwen/issues"
17
+ },
18
+ "engines": {
19
+ "node": ">=16.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "files": [
25
+ "agents/",
26
+ "bin/",
27
+ "hooks/",
28
+ "scripts/",
29
+ "skills/",
30
+ "gm.json",
31
+ "cli.js",
32
+ "install.js",
33
+ "README.md",
34
+ "CLAUDE.md",
35
+ ".gitignore",
36
+ "CONTRIBUTING.md"
37
+ ],
38
+ "peerDependencies": {
39
+ "@qwen-code/qwen-code": "*"
40
+ }
41
+ }
@@ -0,0 +1,14 @@
1
+ @echo off
2
+ setlocal
3
+ set "PLUGKIT=%CLAUDE_PLUGIN_ROOT%\bin\plugkit.exe"
4
+ set "BINDIR=%CLAUDE_PLUGIN_ROOT%\bin"
5
+ set "VERFILE=%BINDIR%\.plugkit-version"
6
+ if not exist "%BINDIR%" mkdir "%BINDIR%"
7
+ if not exist "%PLUGKIT%" (
8
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "$g=ConvertFrom-Json(Get-Content '%CLAUDE_PLUGIN_ROOT%\gm.json' -Raw);$v=$g.plugkitVersion;[Net.ServicePointManager]::SecurityProtocol='Tls12';$r=[Net.HttpWebRequest]::Create(\"https://github.com/AnEntrypoint/rs-plugkit/releases/download/v$v/plugkit-win32-x64.exe\");$r.Timeout=1000;$r.AllowAutoRedirect=$true;try{$rs=$r.GetResponse();$s=$rs.GetResponseStream();$f=[IO.File]::Create('%PLUGKIT%');$s.CopyTo($f);$f.Dispose();$rs.Dispose();Set-Content -NoNewline '%VERFILE%' $v}catch{}" 2>nul
9
+ )
10
+ if exist "%PLUGKIT%" (
11
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "try{$p=Split-Path '%CLAUDE_PLUGIN_ROOT%';Get-ChildItem $p -Directory|Where-Object{$_.FullName -ne '%CLAUDE_PLUGIN_ROOT%'}|ForEach-Object{Remove-Item $_.FullName -Recurse -Force -ErrorAction SilentlyContinue}}catch{}" 2>nul
12
+ "%PLUGKIT%" bootstrap
13
+ )
14
+ endlocal
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+ const { execFileSync } = require('child_process');
7
+
8
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || process.env.CODEX_PLUGIN_ROOT;
9
+ if (!pluginRoot) process.exit(0);
10
+
11
+ const IS_WIN = process.platform === 'win32';
12
+ const binDir = path.join(pluginRoot, 'bin');
13
+ const binPath = path.join(binDir, IS_WIN ? 'plugkit.exe' : 'plugkit');
14
+ const pendingPath = binPath + '.pending';
15
+ const versionFile = path.join(binDir, '.plugkit-version');
16
+ const pendingVersionFile = pendingPath + '.version';
17
+
18
+ function getAssetName() {
19
+ const os = process.platform === 'win32' ? 'win32' : process.platform === 'darwin' ? 'darwin' : 'linux';
20
+ const cpu = process.arch === 'arm64' ? 'arm64' : 'x64';
21
+ const ext = process.platform === 'win32' ? '.exe' : '';
22
+ return `plugkit-${os}-${cpu}${ext}`;
23
+ }
24
+
25
+ function killDaemon() {
26
+ try {
27
+ execFileSync(binPath, ['runner', 'stop'], { timeout: 5000, stdio: 'ignore' });
28
+ } catch {}
29
+ if (IS_WIN) {
30
+ try { execFileSync('taskkill', ['/F', '/IM', 'plugkit.exe'], { timeout: 3000, stdio: 'ignore' }); } catch {}
31
+ }
32
+ }
33
+
34
+ function applyPending() {
35
+ if (!fs.existsSync(pendingPath)) return;
36
+ killDaemon();
37
+ const oldPath = binPath + '.old';
38
+ try { fs.unlinkSync(oldPath); } catch {}
39
+ try { fs.renameSync(binPath, oldPath); } catch {}
40
+ try {
41
+ fs.renameSync(pendingPath, binPath);
42
+ if (fs.existsSync(pendingVersionFile)) {
43
+ try { fs.renameSync(pendingVersionFile, versionFile); } catch {}
44
+ }
45
+ } catch {
46
+ try { if (!fs.existsSync(binPath)) fs.renameSync(oldPath, binPath); } catch {}
47
+ }
48
+ }
49
+
50
+ applyPending();
51
+
52
+ function getRequiredVersion() {
53
+ try {
54
+ return JSON.parse(fs.readFileSync(path.join(pluginRoot, 'gm.json'), 'utf8')).plugkitVersion || null;
55
+ } catch { return null; }
56
+ }
57
+
58
+ function getCurrentVersion() {
59
+ try { return fs.readFileSync(versionFile, 'utf8').trim() || null; } catch { return null; }
60
+ }
61
+
62
+ const required = getRequiredVersion();
63
+ const current = getCurrentVersion();
64
+ if (current && current === required) process.exit(0);
65
+
66
+ function download(version, dest, cb) {
67
+ const asset = getAssetName();
68
+ const urlPath = `/AnEntrypoint/rs-plugkit/releases/download/v${version}/${asset}`;
69
+ if (!fs.existsSync(path.dirname(dest))) fs.mkdirSync(path.dirname(dest), { recursive: true });
70
+ const follow = (url) => {
71
+ const mod = url.startsWith('https') ? https : require('http');
72
+ const opts = { ...require('url').parse(url), headers: { 'User-Agent': 'gm-bootstrap' } };
73
+ mod.get(opts, res => {
74
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return follow(res.headers.location);
75
+ if (res.statusCode !== 200) return cb(new Error(`HTTP ${res.statusCode}`));
76
+ const chunks = [];
77
+ res.on('data', c => chunks.push(c));
78
+ res.on('end', () => {
79
+ try {
80
+ fs.writeFileSync(dest, Buffer.concat(chunks));
81
+ try { fs.chmodSync(dest, 0o755); } catch {}
82
+ cb(null);
83
+ } catch (e) { cb(e); }
84
+ });
85
+ }).on('error', cb);
86
+ };
87
+ follow(`https://github.com${urlPath}`);
88
+ }
89
+
90
+ killDaemon();
91
+
92
+ download(required, binPath, (err) => {
93
+ if (err && err.code === 'EBUSY') {
94
+ download(required, pendingPath, (err2) => {
95
+ if (err2) {
96
+ process.stderr.write(`bootstrap: pending failed: ${err2.message}\n`);
97
+ process.exit(fs.existsSync(binPath) ? 0 : 1);
98
+ }
99
+ try { fs.writeFileSync(pendingVersionFile, required); } catch {}
100
+ process.exit(0);
101
+ });
102
+ return;
103
+ }
104
+ if (err) {
105
+ process.stderr.write(`bootstrap: ${err.message}\n`);
106
+ process.exit(fs.existsSync(binPath) ? 0 : 1);
107
+ }
108
+ try { fs.writeFileSync(versionFile, required); } catch {}
109
+ process.exit(0);
110
+ });
@@ -0,0 +1,28 @@
1
+ #!/bin/sh
2
+ set -e
3
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT}}"
4
+ [ -z "$PLUGIN_ROOT" ] && exit 0
5
+ BINDIR="$PLUGIN_ROOT/bin"
6
+ VERFILE="$BINDIR/.plugkit-version"
7
+ IS_WIN=0
8
+ case "$(uname -s 2>/dev/null)" in MINGW*|CYGWIN*|MSYS*) IS_WIN=1;; esac
9
+ [ -f /proc/version ] && grep -qi microsoft /proc/version 2>/dev/null && IS_WIN=1
10
+ if [ $IS_WIN -eq 1 ]; then EXT=".exe"; OS="win32"; else EXT=""; OS="$(uname -s | tr '[:upper:]' '[:lower:]')"; fi
11
+ case "$(uname -m 2>/dev/null)" in arm64|aarch64) ARCH="arm64";; *) ARCH="x64";; esac
12
+ ASSET="plugkit-${OS}-${ARCH}${EXT}"
13
+ PLUGKIT="$BINDIR/plugkit${EXT}"
14
+ mkdir -p "$BINDIR"
15
+ if [ ! -f "$PLUGKIT" ]; then
16
+ VER="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('$PLUGIN_ROOT/gm.json','utf8')).plugkitVersion)" 2>/dev/null || python3 -c "import json,sys;d=json.load(open('$PLUGIN_ROOT/gm.json'));sys.stdout.write(d['plugkitVersion'])" 2>/dev/null || python -c "import json,sys;d=json.load(open('$PLUGIN_ROOT/gm.json'));sys.stdout.write(d['plugkitVersion'])" 2>/dev/null || sed -n 's/.*"plugkitVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$PLUGIN_ROOT/gm.json" | head -1)"
17
+ [ -z "$VER" ] && exit 0
18
+ URL="https://github.com/AnEntrypoint/rs-plugkit/releases/download/v${VER}/${ASSET}"
19
+ curl -fsSL --location --max-time 30 "$URL" -o "$PLUGKIT" 2>/dev/null || exit 0
20
+ chmod +x "$PLUGKIT" 2>/dev/null || true
21
+ printf '%s' "$VER" > "$VERFILE"
22
+ fi
23
+ PARENT="$(dirname "$PLUGIN_ROOT")"
24
+ for d in "$PARENT"/*/; do
25
+ [ "$d" = "$PLUGIN_ROOT/" ] && continue
26
+ rm -rf "$d" 2>/dev/null || true
27
+ done
28
+ "$PLUGKIT" bootstrap
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+
7
+ function isInsideNodeModules() {
8
+ return __dirname.includes(path.sep + 'node_modules' + path.sep);
9
+ }
10
+
11
+ function getProjectRoot() {
12
+ if (!isInsideNodeModules()) return null;
13
+ let current = __dirname;
14
+ while (current !== path.dirname(current)) {
15
+ current = path.dirname(current);
16
+ if (path.basename(current) === 'node_modules') return path.dirname(current);
17
+ }
18
+ return null;
19
+ }
20
+
21
+ function safeCopyFile(src, dst) {
22
+ try {
23
+ const dstDir = path.dirname(dst);
24
+ if (!fs.existsSync(dstDir)) fs.mkdirSync(dstDir, { recursive: true });
25
+ fs.writeFileSync(dst, fs.readFileSync(src));
26
+ return true;
27
+ } catch { return false; }
28
+ }
29
+
30
+ function safeCopyDirectory(src, dst) {
31
+ try {
32
+ if (!fs.existsSync(src)) return false;
33
+ fs.mkdirSync(dst, { recursive: true });
34
+ fs.readdirSync(src, { withFileTypes: true }).forEach(entry => {
35
+ const srcPath = path.join(src, entry.name);
36
+ const dstPath = path.join(dst, entry.name);
37
+ if (entry.isDirectory()) safeCopyDirectory(srcPath, dstPath);
38
+ else if (entry.isFile()) safeCopyFile(srcPath, dstPath);
39
+ });
40
+ return true;
41
+ } catch { return false; }
42
+ }
43
+
44
+ function updateGitignore(projectRoot) {
45
+ try {
46
+ const gitignorePath = path.join(projectRoot, '.gitignore');
47
+ const entry = '.gm-stop-verified';
48
+ let content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
49
+ if (content.includes(entry)) return;
50
+ if (content && !content.endsWith('\n')) content += '\n';
51
+ fs.writeFileSync(gitignorePath, content + entry + '\n', 'utf-8');
52
+ } catch {}
53
+ }
54
+
55
+ function getRequiredVersion(sourceDir) {
56
+ try {
57
+ const gm = JSON.parse(fs.readFileSync(path.join(sourceDir, 'gm.json'), 'utf-8'));
58
+ return gm.plugkitVersion || null;
59
+ } catch { return null; }
60
+ }
61
+
62
+ function getInstalledVersion(binPath) {
63
+ try {
64
+ const { spawnSync } = require('child_process');
65
+ const r = spawnSync(binPath, ['--version'], { encoding: 'utf8', timeout: 5000 });
66
+ const m = (r.stdout || '').trim().match(/(\d+\.\d+\.\d+)/);
67
+ return m ? m[1] : null;
68
+ } catch { return null; }
69
+ }
70
+
71
+ function downloadBin(version, dest, callback) {
72
+ const IS_WIN = process.platform === 'win32';
73
+ const asset = IS_WIN ? 'plugkit.exe' : 'plugkit';
74
+ const urlPath = version
75
+ ? `/AnEntrypoint/rs-plugkit/releases/download/v${version}/${asset}`
76
+ : `/AnEntrypoint/rs-plugkit/releases/latest/download/${asset}`;
77
+ const destDir = path.dirname(dest);
78
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
79
+ const follow = (url) => {
80
+ const mod = url.startsWith('https') ? https : require('http');
81
+ const opts = { ...require('url').parse(url), headers: { 'User-Agent': 'gm-postinstall' } };
82
+ mod.get(opts, res => {
83
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return follow(res.headers.location);
84
+ if (res.statusCode !== 200) return callback(new Error(`HTTP ${res.statusCode}`));
85
+ const chunks = [];
86
+ res.on('data', c => chunks.push(c));
87
+ res.on('end', () => { try { fs.writeFileSync(dest, Buffer.concat(chunks)); try { fs.chmodSync(dest, 0o755); } catch {} callback(null); } catch (e) { callback(e); } });
88
+ }).on('error', callback);
89
+ };
90
+ follow(`https://github.com${urlPath}`);
91
+ }
92
+
93
+ function install() {
94
+ if (!isInsideNodeModules()) return;
95
+ const projectRoot = getProjectRoot();
96
+ if (!projectRoot) return;
97
+ const kiloDir = path.join(projectRoot, '.config', 'kilo');
98
+ const sourceDir = path.dirname(__dirname);
99
+
100
+ safeCopyDirectory(path.join(sourceDir, 'agents'), path.join(kiloDir, 'agents'));
101
+ safeCopyDirectory(path.join(sourceDir, 'hooks'), path.join(kiloDir, 'hooks'));
102
+ safeCopyDirectory(path.join(sourceDir, 'skills'), path.join(kiloDir, 'skills'));
103
+ safeCopyFile(path.join(sourceDir, 'kilocode.json'), path.join(kiloDir, 'kilocode.json'));
104
+ safeCopyFile(path.join(sourceDir, '.mcp.json'), path.join(kiloDir, '.mcp.json'));
105
+ safeCopyFile(path.join(sourceDir, 'gm.mjs'), path.join(kiloDir, 'gm.mjs'));
106
+ safeCopyFile(path.join(sourceDir, 'index.mjs'), path.join(kiloDir, 'index.mjs'));
107
+ safeCopyFile(path.join(sourceDir, 'README.md'), path.join(kiloDir, 'README.md'));
108
+ safeCopyFile(path.join(sourceDir, 'LICENSE'), path.join(kiloDir, 'LICENSE'));
109
+ safeCopyFile(path.join(sourceDir, 'CONTRIBUTING.md'), path.join(kiloDir, 'CONTRIBUTING.md'));
110
+ safeCopyFile(path.join(sourceDir, '.gitignore'), path.join(kiloDir, '.gitignore'));
111
+ safeCopyFile(path.join(sourceDir, '.editorconfig'), path.join(kiloDir, '.editorconfig'));
112
+
113
+ const pluginDir = path.join(kiloDir, 'plugin');
114
+ if (!fs.existsSync(pluginDir)) fs.mkdirSync(pluginDir, { recursive: true });
115
+ const gmMjsSrc = path.join(sourceDir, 'gm.mjs');
116
+ if (fs.existsSync(gmMjsSrc)) safeCopyFile(gmMjsSrc, path.join(pluginDir, 'gm.mjs'));
117
+ fs.writeFileSync(path.join(pluginDir, 'index.js'), "export { default } from './gm.mjs';\n", 'utf-8');
118
+
119
+ updateGitignore(projectRoot);
120
+
121
+ const IS_WIN = process.platform === 'win32';
122
+ const binDest = path.join(kiloDir, 'hooks', 'bin', IS_WIN ? 'plugkit.exe' : 'plugkit');
123
+ const requiredVersion = getRequiredVersion(sourceDir);
124
+ const installedVersion = fs.existsSync(binDest) ? getInstalledVersion(binDest) : null;
125
+ if (!installedVersion || (requiredVersion && installedVersion !== requiredVersion)) {
126
+ downloadBin(requiredVersion, binDest, (err) => {
127
+ if (err) process.stderr.write('plugkit download failed: ' + err.message + '\n');
128
+ });
129
+ }
130
+ }
131
+
132
+ install();