qwen-base 1.0.1 → 1.0.2
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 +91 -143
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
6
7
|
|
|
7
|
-
// Colors
|
|
8
8
|
const green = '\x1b[32m';
|
|
9
9
|
const cyan = '\x1b[36m';
|
|
10
10
|
const yellow = '\x1b[33m';
|
|
@@ -14,12 +14,12 @@ const reset = '\x1b[0m';
|
|
|
14
14
|
const pkg = require('../package.json');
|
|
15
15
|
|
|
16
16
|
const banner = `
|
|
17
|
-
${green}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
${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
|
|
18
|
+
\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
|
|
19
|
+
\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
|
|
20
|
+
\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
|
|
21
|
+
\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
|
|
22
|
+
\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
23
|
|
|
24
24
|
BASE ${dim}v${pkg.version}${reset}
|
|
25
25
|
Builder's Automated State Engine
|
|
@@ -27,8 +27,10 @@ ${green} ██████╗ ███████╗███████╗
|
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
29
|
const args = process.argv.slice(2);
|
|
30
|
-
const
|
|
30
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
31
31
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
32
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
33
|
+
const skipTools = args.includes('--skip-tools');
|
|
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,24 +61,17 @@ 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
|
}
|
|
@@ -89,139 +82,94 @@ if (hasHelp) {
|
|
|
89
82
|
console.log(` ${yellow}Usage:${reset} npx qwen-base [options]
|
|
90
83
|
|
|
91
84
|
${yellow}Options:${reset}
|
|
92
|
-
${cyan}-
|
|
93
|
-
${cyan}-
|
|
94
|
-
${cyan}-
|
|
85
|
+
${cyan}-g, --global${reset} Install globally (to Qwen config directory)
|
|
86
|
+
${cyan}-l, --local${reset} Install locally (to ./.qwen/ in current directory)
|
|
87
|
+
${cyan}-c, --config-dir <path>${reset} Specify custom Qwen config directory
|
|
88
|
+
${cyan}--skip-tools${reset} Skip OSS analysis tool installation
|
|
89
|
+
${cyan}-h, --help${reset} Show this help message
|
|
95
90
|
|
|
96
91
|
${yellow}Examples:${reset}
|
|
97
|
-
${dim}# Install globally (
|
|
98
|
-
npx qwen-base
|
|
92
|
+
${dim}# Install globally (recommended)${reset}
|
|
93
|
+
npx qwen-base --global
|
|
99
94
|
|
|
100
95
|
${dim}# Install to current project only${reset}
|
|
101
96
|
npx qwen-base --local
|
|
102
97
|
|
|
103
98
|
${yellow}What gets installed:${reset}
|
|
104
|
-
${cyan}commands/qwen-base/${reset}
|
|
105
|
-
|
|
106
|
-
framework/ Tasks, templates, frameworks (11 commands)
|
|
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
|
|
99
|
+
${cyan}commands/qwen-base/${reset} 11 slash commands
|
|
100
|
+
${cyan}base/ Framework (core, transform, domains, schemas, rules, tools)
|
|
111
101
|
`);
|
|
112
102
|
process.exit(0);
|
|
113
103
|
}
|
|
114
104
|
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const src = path.join(__dirname, '..');
|
|
134
|
-
|
|
135
|
-
// Copy entry point
|
|
136
|
-
fs.mkdirSync(baseDest, { recursive: true });
|
|
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}`);
|
|
105
|
+
function install(isGlobal) {
|
|
106
|
+
const src = path.join(__dirname, '..');
|
|
107
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.QWEN_CONFIG_DIR);
|
|
108
|
+
const globalDir = configDir || path.join(os.homedir(), '.qwen');
|
|
109
|
+
const qwenDir = isGlobal ? globalDir : path.join(process.cwd(), '.qwen');
|
|
110
|
+
const aegisDest = path.join(qwenDir, 'aegis');
|
|
111
|
+
const cmdsDest = path.join(qwenDir, 'commands', 'qwen-base');
|
|
112
|
+
|
|
113
|
+
const locationLabel = isGlobal
|
|
114
|
+
? qwenDir.replace(os.homedir(), '~')
|
|
115
|
+
: qwenDir.replace(process.cwd(), '.');
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(aegisDest) || fs.existsSync(cmdsDest)) {
|
|
118
|
+
console.log(` ${yellow}Existing installation found at ${locationLabel}${reset}`);
|
|
119
|
+
console.log(` Updating...`);
|
|
120
|
+
if (fs.existsSync(aegisDest)) fs.rmSync(aegisDest, { recursive: true, force: true });
|
|
121
|
+
if (fs.existsSync(cmdsDest)) fs.rmSync(cmdsDest, { recursive: true, force: true });
|
|
182
122
|
}
|
|
183
|
-
}
|
|
184
123
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
124
|
+
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
125
|
+
|
|
126
|
+
// Copy framework (note: BASE uses 'src/' structure)
|
|
127
|
+
if (fs.existsSync(path.join(src, 'src'))) {
|
|
128
|
+
copyDir(path.join(src, 'src'), path.join(qwenDir, 'base'));
|
|
129
|
+
console.log(` ${green}+${reset} base/ ${dim}(${countFiles(path.join(qwenDir, 'base'))} framework files)${reset}`);
|
|
191
130
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const hookCommand = `python3 ${hookPath}`;
|
|
199
|
-
|
|
200
|
-
// Determine which event
|
|
201
|
-
let eventName = 'SessionStart';
|
|
202
|
-
if (hookFile === 'active-hook.py') eventName = 'SessionStart';
|
|
203
|
-
else if (hookFile === 'psmm-injector.py') eventName = 'SessionStart';
|
|
204
|
-
else if (hookFile === 'backlog-hook.py') eventName = 'SessionStart';
|
|
205
|
-
else if (hookFile === 'operator.py') eventName = 'SessionStart';
|
|
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 }] });
|
|
217
|
-
}
|
|
131
|
+
|
|
132
|
+
// Copy commands
|
|
133
|
+
if (fs.existsSync(path.join(src, 'src', 'commands'))) {
|
|
134
|
+
fs.mkdirSync(cmdsDest, { recursive: true });
|
|
135
|
+
copyDir(path.join(src, 'src', 'commands'), cmdsDest);
|
|
136
|
+
console.log(` ${green}+${reset} commands/qwen-base/ ${dim}(${countFiles(cmdsDest)} commands)${reset}`);
|
|
218
137
|
}
|
|
219
|
-
|
|
138
|
+
|
|
139
|
+
console.log(`
|
|
140
|
+
${green}Done!${reset} Open Qwen Code and type ${cyan}/base${reset} to start.
|
|
141
|
+
`);
|
|
220
142
|
}
|
|
221
143
|
|
|
222
|
-
|
|
223
|
-
|
|
144
|
+
function promptLocation() {
|
|
145
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
224
146
|
|
|
225
|
-
|
|
226
|
-
|
|
147
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.QWEN_CONFIG_DIR);
|
|
148
|
+
const globalPath = configDir || path.join(os.homedir(), '.qwen');
|
|
149
|
+
const globalLabel = globalPath.replace(os.homedir(), '~');
|
|
150
|
+
|
|
151
|
+
console.log(` ${yellow}Where would you like to install?${reset}
|
|
152
|
+
|
|
153
|
+
${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - available in all projects
|
|
154
|
+
${cyan}2${reset}) Local ${dim}(./.qwen)${reset} - this project only
|
|
227
155
|
`);
|
|
156
|
+
|
|
157
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
158
|
+
rl.close();
|
|
159
|
+
install(answer.trim() !== '2');
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (hasGlobal && hasLocal) {
|
|
164
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
} else if (explicitConfigDir && hasLocal) {
|
|
167
|
+
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
} else if (hasGlobal) {
|
|
170
|
+
install(true);
|
|
171
|
+
} else if (hasLocal) {
|
|
172
|
+
install(false);
|
|
173
|
+
} else {
|
|
174
|
+
promptLocation();
|
|
175
|
+
}
|