qwen-base 1.0.1 → 1.0.3
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/bin/install.js +118 -141
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
6
8
|
|
|
7
|
-
// Colors
|
|
8
9
|
const green = '\x1b[32m';
|
|
9
10
|
const cyan = '\x1b[36m';
|
|
10
11
|
const yellow = '\x1b[33m';
|
|
@@ -14,12 +15,12 @@ const reset = '\x1b[0m';
|
|
|
14
15
|
const pkg = require('../package.json');
|
|
15
16
|
|
|
16
17
|
const banner = `
|
|
17
|
-
${green}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
${green} \u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
19
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
20
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
|
|
21
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551
|
|
22
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
|
|
23
|
+
\u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u255d${reset}
|
|
23
24
|
|
|
24
25
|
BASE ${dim}v${pkg.version}${reset}
|
|
25
26
|
Builder's Automated State Engine
|
|
@@ -27,8 +28,9 @@ ${green} ██████╗ ███████╗███████╗
|
|
|
27
28
|
`;
|
|
28
29
|
|
|
29
30
|
const args = process.argv.slice(2);
|
|
30
|
-
const
|
|
31
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
31
32
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
33
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
32
34
|
|
|
33
35
|
function parseConfigDirArg() {
|
|
34
36
|
const idx = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
|
|
@@ -41,16 +43,14 @@ function parseConfigDirArg() {
|
|
|
41
43
|
return nextArg;
|
|
42
44
|
}
|
|
43
45
|
const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
|
|
44
|
-
if (configDirArg)
|
|
45
|
-
return configDirArg.split('=')[1];
|
|
46
|
-
}
|
|
46
|
+
if (configDirArg) return configDirArg.split('=')[1];
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const explicitConfigDir = parseConfigDirArg();
|
|
51
|
+
|
|
50
52
|
function expandTilde(filePath) {
|
|
51
|
-
if (filePath && filePath.startsWith('~/'))
|
|
52
|
-
return path.join(os.homedir(), filePath.slice(2));
|
|
53
|
-
}
|
|
53
|
+
if (filePath && filePath.startsWith('~/')) return path.join(os.homedir(), filePath.slice(2));
|
|
54
54
|
return filePath;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -61,167 +61,144 @@ function copyDir(srcDir, destDir, skipDirs = []) {
|
|
|
61
61
|
if (skipDirs.includes(entry.name)) continue;
|
|
62
62
|
const srcPath = path.join(srcDir, entry.name);
|
|
63
63
|
const destPath = path.join(destDir, entry.name);
|
|
64
|
-
if (entry.isDirectory())
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
fs.copyFileSync(srcPath, destPath);
|
|
68
|
-
}
|
|
64
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath, skipDirs);
|
|
65
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
function countFiles(dir
|
|
69
|
+
function countFiles(dir) {
|
|
73
70
|
let count = 0;
|
|
74
71
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
75
72
|
for (const entry of entries) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
count += countFiles(fullPath, ext);
|
|
79
|
-
} else if (!ext || entry.name.endsWith(ext)) {
|
|
80
|
-
count++;
|
|
81
|
-
}
|
|
73
|
+
if (entry.isDirectory()) count += countFiles(path.join(dir, entry.name));
|
|
74
|
+
else count++;
|
|
82
75
|
}
|
|
83
76
|
return count;
|
|
84
77
|
}
|
|
85
78
|
|
|
79
|
+
function wireMcp(workspaceDir, mcpIndexPath) {
|
|
80
|
+
const mcpJsonPath = path.join(workspaceDir, '.mcp.json');
|
|
81
|
+
let mcpConfig = {};
|
|
82
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
83
|
+
try { mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8')); } catch (e) {}
|
|
84
|
+
}
|
|
85
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
86
|
+
const normalizedPath = mcpIndexPath.replace(/\\/g, '/');
|
|
87
|
+
mcpConfig.mcpServers['base-mcp'] = { command: 'node', args: [normalizedPath], type: 'stdio' };
|
|
88
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
console.log(banner);
|
|
87
92
|
|
|
88
93
|
if (hasHelp) {
|
|
89
94
|
console.log(` ${yellow}Usage:${reset} npx qwen-base [options]
|
|
90
95
|
|
|
91
96
|
${yellow}Options:${reset}
|
|
92
|
-
${cyan}-
|
|
93
|
-
${cyan}-
|
|
94
|
-
${cyan}-
|
|
97
|
+
${cyan}-g, --global${reset} Install globally (to Qwen config directory)
|
|
98
|
+
${cyan}-l, --local${reset} Install locally (to ./.qwen/ in current directory)
|
|
99
|
+
${cyan}-c, --config-dir <path>${reset} Specify custom Qwen config directory
|
|
100
|
+
${cyan}-h, --help${reset} Show this help message
|
|
95
101
|
|
|
96
102
|
${yellow}Examples:${reset}
|
|
97
|
-
${dim}# Install globally (
|
|
98
|
-
npx qwen-base
|
|
103
|
+
${dim}# Install globally (recommended)${reset}
|
|
104
|
+
npx qwen-base --global
|
|
99
105
|
|
|
100
106
|
${dim}# Install to current project only${reset}
|
|
101
107
|
npx qwen-base --local
|
|
102
108
|
|
|
103
109
|
${yellow}What gets installed:${reset}
|
|
104
|
-
${cyan}commands/qwen-base/${reset}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
commands/ Command definitions
|
|
108
|
-
hooks/ 7 Python hooks for workspace intelligence
|
|
109
|
-
templates/ operator.json, workspace.json templates
|
|
110
|
-
${cyan}.qwen/hooks/${reset} Python hooks registration
|
|
110
|
+
${cyan}commands/qwen-base/${reset} 11 slash commands
|
|
111
|
+
${cyan}base/ Framework (core, transform, domains, schemas, rules, tools)
|
|
112
|
+
${cyan}.mcp.json base-mcp server registration
|
|
111
113
|
`);
|
|
112
114
|
process.exit(0);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
?
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
fs.
|
|
129
|
-
}
|
|
117
|
+
function install(isGlobal) {
|
|
118
|
+
const src = path.join(__dirname, '..');
|
|
119
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.QWEN_CONFIG_DIR);
|
|
120
|
+
const globalDir = configDir || path.join(os.homedir(), '.qwen');
|
|
121
|
+
const qwenDir = isGlobal ? globalDir : path.join(process.cwd(), '.qwen');
|
|
122
|
+
const baseDest = path.join(qwenDir, 'base');
|
|
123
|
+
const cmdsDest = path.join(qwenDir, 'commands', 'qwen-base');
|
|
124
|
+
const workspaceRoot = isGlobal ? os.homedir() : process.cwd();
|
|
125
|
+
|
|
126
|
+
const locationLabel = isGlobal
|
|
127
|
+
? qwenDir.replace(os.homedir(), '~')
|
|
128
|
+
: qwenDir.replace(process.cwd(), '.');
|
|
129
|
+
|
|
130
|
+
if (fs.existsSync(baseDest) || fs.existsSync(cmdsDest)) {
|
|
131
|
+
console.log(` ${yellow}Existing installation found at ${locationLabel}${reset}`);
|
|
132
|
+
console.log(` Updating...`);
|
|
133
|
+
if (fs.existsSync(baseDest)) fs.rmSync(baseDest, { recursive: true, force: true });
|
|
134
|
+
if (fs.existsSync(cmdsDest)) fs.rmSync(cmdsDest, { recursive: true, force: true });
|
|
135
|
+
}
|
|
130
136
|
|
|
131
|
-
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
fs.copyFileSync(path.join(src, 'src', 'skill', 'base.md'), path.join(baseDest, 'base.md'));
|
|
138
|
-
console.log(` ${green}+${reset} base.md ${dim}(entry point)${reset}`);
|
|
139
|
-
|
|
140
|
-
// Copy framework
|
|
141
|
-
const fwSrc = path.join(src, 'src', 'framework');
|
|
142
|
-
const fwDest = path.join(baseDest, 'framework');
|
|
143
|
-
copyDir(fwSrc, fwDest);
|
|
144
|
-
const fwCount = countFiles(fwSrc);
|
|
145
|
-
console.log(` ${green}+${reset} framework/ ${dim}(${fwCount} files)${reset}`);
|
|
146
|
-
|
|
147
|
-
// Copy commands
|
|
148
|
-
const cmdSrc = path.join(src, 'src', 'commands');
|
|
149
|
-
const cmdDest = path.join(baseDest, 'commands');
|
|
150
|
-
copyDir(cmdSrc, cmdDest);
|
|
151
|
-
const cmdCount = countFiles(cmdSrc);
|
|
152
|
-
console.log(` ${green}+${reset} commands/ ${dim}(${cmdCount} files)${reset}`);
|
|
153
|
-
|
|
154
|
-
// Copy hooks
|
|
155
|
-
const hooksSrc = path.join(src, 'src', 'hooks');
|
|
156
|
-
const hooksDest = path.join(baseDest, 'hooks');
|
|
157
|
-
copyDir(hooksSrc, hooksDest);
|
|
158
|
-
const hookCount = countFiles(hooksSrc);
|
|
159
|
-
console.log(` ${green}+${reset} hooks/ ${dim}(${hookCount} Python hooks)${reset}`);
|
|
160
|
-
|
|
161
|
-
// Copy templates
|
|
162
|
-
const tplSrc = path.join(src, 'src', 'templates');
|
|
163
|
-
const tplDest = path.join(baseDest, 'templates');
|
|
164
|
-
copyDir(tplSrc, tplDest);
|
|
165
|
-
console.log(` ${green}+${reset} templates/ ${dim}(workspace templates)${reset}`);
|
|
166
|
-
|
|
167
|
-
// Copy MCP server
|
|
168
|
-
const mcpSrc = path.join(src, 'src', 'packages', 'base-mcp');
|
|
169
|
-
if (fs.existsSync(mcpSrc)) {
|
|
170
|
-
const mcpDest = path.join(qwenDir, 'base-mcp');
|
|
171
|
-
copyDir(mcpSrc, mcpDest);
|
|
172
|
-
console.log(` ${green}+${reset} base-mcp/ ${dim}(MCP server)${reset}`);
|
|
173
|
-
|
|
174
|
-
// Install MCP deps
|
|
175
|
-
try {
|
|
176
|
-
require('child_process').execSync('npm install --production --silent', {
|
|
177
|
-
cwd: mcpDest, stdio: 'pipe'
|
|
178
|
-
});
|
|
179
|
-
console.log(` ${green}+${reset} MCP dependencies installed${reset}`);
|
|
180
|
-
} catch (e) {
|
|
181
|
-
console.log(` ${yellow}!${reset} MCP deps install failed — run cd ${mcpDest} && npm install${reset}`);
|
|
137
|
+
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
138
|
+
|
|
139
|
+
// Copy framework (src/ → base/)
|
|
140
|
+
if (fs.existsSync(path.join(src, 'src'))) {
|
|
141
|
+
copyDir(path.join(src, 'src'), baseDest);
|
|
142
|
+
console.log(` ${green}+${reset} base/ ${dim}(${countFiles(baseDest)} framework files)${reset}`);
|
|
182
143
|
}
|
|
183
|
-
}
|
|
184
144
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
145
|
+
// Copy commands
|
|
146
|
+
if (fs.existsSync(path.join(src, 'src', 'commands'))) {
|
|
147
|
+
fs.mkdirSync(cmdsDest, { recursive: true });
|
|
148
|
+
copyDir(path.join(src, 'src', 'commands'), cmdsDest);
|
|
149
|
+
console.log(` ${green}+${reset} commands/qwen-base/ ${dim}(${countFiles(cmdsDest)} commands)${reset}`);
|
|
191
150
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
else if (hookFile === 'satellite-detection.py') eventName = 'SessionStart';
|
|
207
|
-
else if (hookFile === 'apex-insights.py') eventName = 'Stop';
|
|
208
|
-
else if (hookFile === 'base-pulse-check.py') eventName = 'SessionStart';
|
|
209
|
-
|
|
210
|
-
if (!settings.hooks[eventName]) settings.hooks[eventName] = [];
|
|
211
|
-
const exists = settings.hooks[eventName].some(h =>
|
|
212
|
-
(h.command && h.command.includes(hookFile)) ||
|
|
213
|
-
(h.hooks && h.hooks.some(i => i.command && i.command.includes(hookFile)))
|
|
214
|
-
);
|
|
215
|
-
if (!exists) {
|
|
216
|
-
settings.hooks[eventName].push({ hooks: [{ type: 'command', command: hookCommand }] });
|
|
151
|
+
|
|
152
|
+
// Wire BASE MCP server into .mcp.json
|
|
153
|
+
const mcpIndexPath = path.join(baseDest, 'packages', 'base-mcp', 'index.js');
|
|
154
|
+
if (fs.existsSync(mcpIndexPath)) {
|
|
155
|
+
wireMcp(workspaceRoot, mcpIndexPath);
|
|
156
|
+
console.log(` ${green}+${reset} Wired base-mcp in .mcp.json`);
|
|
157
|
+
|
|
158
|
+
// Install MCP dependencies
|
|
159
|
+
const mcpDir = path.join(baseDest, 'packages', 'base-mcp');
|
|
160
|
+
try {
|
|
161
|
+
execSync('npm install --production --silent', { cwd: mcpDir, stdio: 'pipe' });
|
|
162
|
+
console.log(` ${green}+${reset} MCP dependencies installed`);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.log(` ${yellow}!${reset} MCP deps install failed — run cd ${mcpDir} && npm install${reset}`);
|
|
217
165
|
}
|
|
218
166
|
}
|
|
219
|
-
|
|
167
|
+
|
|
168
|
+
console.log(`
|
|
169
|
+
${green}Done!${reset} Open Qwen Code and type ${cyan}/base${reset} to start.
|
|
170
|
+
`);
|
|
220
171
|
}
|
|
221
172
|
|
|
222
|
-
|
|
223
|
-
|
|
173
|
+
function promptLocation() {
|
|
174
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
224
175
|
|
|
225
|
-
|
|
226
|
-
|
|
176
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.QWEN_CONFIG_DIR);
|
|
177
|
+
const globalPath = configDir || path.join(os.homedir(), '.qwen');
|
|
178
|
+
const globalLabel = globalPath.replace(os.homedir(), '~');
|
|
179
|
+
|
|
180
|
+
console.log(` ${yellow}Where would you like to install?${reset}
|
|
181
|
+
|
|
182
|
+
${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - available in all projects
|
|
183
|
+
${cyan}2${reset}) Local ${dim}(./.qwen)${reset} - this project only
|
|
227
184
|
`);
|
|
185
|
+
|
|
186
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
187
|
+
rl.close();
|
|
188
|
+
install(answer.trim() !== '2');
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (hasGlobal && hasLocal) {
|
|
193
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
} else if (explicitConfigDir && hasLocal) {
|
|
196
|
+
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
} else if (hasGlobal) {
|
|
199
|
+
install(true);
|
|
200
|
+
} else if (hasLocal) {
|
|
201
|
+
install(false);
|
|
202
|
+
} else {
|
|
203
|
+
promptLocation();
|
|
204
|
+
}
|