blun-king-cli 9.1.5 → 9.1.8
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/launcher-mode.js +1 -0
- package/bin/launcher-runtime.js +60 -29
- package/bin/standard-tools-bootstrap.js +218 -0
- package/bin/update-notice.js +37 -15
- package/blun.mjs +4 -2
- package/package.json +50 -47
- package/standard-skills/blun-app-design-system/SKILL.md +30 -0
- package/standard-skills/blun-app-design-system/provenance.json +17 -0
- package/standard-skills/blun-app-design-system/references/tokens.md +48 -0
- package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -0
- package/standard-tools/language-guard/blun_language_guard.py +686 -0
- package/standard-tools/language-guard/check_diacritics.py +353 -0
- package/standard-tools/language-guard/guard_service_client.py +82 -0
- package/standard-tools/language-guard/language_quality.py +172 -0
- package/standard-tools/language-guard/translation_guard.py +916 -0
- package/standard-tools/manifest.json +76 -0
- package/skills/design-taste-frontend/SKILL.md +0 -1206
- package/skills/full-output-enforcement/SKILL.md +0 -49
- package/skills/high-end-visual-design/SKILL.md +0 -98
- package/skills/image-to-code/SKILL.md +0 -1228
- package/skills/industrial-brutalist-ui/SKILL.md +0 -92
- package/skills/minimalist-ui/SKILL.md +0 -85
- package/skills/motion-design-taste/SKILL.md +0 -74
- package/skills/premortem/SKILL.md +0 -148
- package/skills/redesign-existing-projects/SKILL.md +0 -178
- package/skills/screenshot-lesen/SKILL.md +0 -52
- package/skills/stitch-design-taste/DESIGN.md +0 -121
- package/skills/stitch-design-taste/SKILL.md +0 -184
- package/skills/web-lesen/SKILL.md +0 -58
package/bin/launcher-mode.js
CHANGED
|
@@ -11,6 +11,7 @@ const LAUNCHER_HELP_TEXT = [
|
|
|
11
11
|
' blun Starts the CLI without automatically attaching Telegram.',
|
|
12
12
|
' king Starts the same CLI and automatically attaches the configured Telegram channel.',
|
|
13
13
|
' Both use the same version, account, model, and commands.',
|
|
14
|
+
' blun tools list shows optional tools; enable writes only explicit choices.',
|
|
14
15
|
'',
|
|
15
16
|
].join('\n');
|
|
16
17
|
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -16,13 +16,17 @@ const {
|
|
|
16
16
|
launcherModeFromArgv,
|
|
17
17
|
} = require('./launcher-mode');
|
|
18
18
|
const { installManagedTelegramPlugin } = require('./plugin-bootstrap');
|
|
19
|
+
const {
|
|
20
|
+
availableStandardToolNames,
|
|
21
|
+
seedStandardDesignSkill,
|
|
22
|
+
seedStandardTools,
|
|
23
|
+
} = require('./standard-tools-bootstrap');
|
|
19
24
|
const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
|
|
20
25
|
const { tryAcquireUpdateLease } = require('./update-lease');
|
|
21
26
|
const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
|
|
22
27
|
const {
|
|
23
28
|
ensurePrivateDirectory,
|
|
24
29
|
securePrivateFile,
|
|
25
|
-
securePrivateTree,
|
|
26
30
|
writePrivateFile,
|
|
27
31
|
} = require('./private-paths');
|
|
28
32
|
|
|
@@ -164,10 +168,25 @@ function exitCodeForChild(result, loaded) {
|
|
|
164
168
|
return 1;
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
function installTelegramForProfile(packageRoot, blunDir) {
|
|
172
|
+
installManagedTelegramPlugin({ packageRoot, blunDir });
|
|
173
|
+
const channelsDir = path.join(blunDir, 'channels');
|
|
174
|
+
const channelDir = path.join(channelsDir, 'telegram');
|
|
175
|
+
ensurePrivateDirectory(channelsDir);
|
|
176
|
+
ensurePrivateDirectory(channelDir);
|
|
177
|
+
const envFile = path.join(channelDir, '.env');
|
|
178
|
+
if (!fs.existsSync(envFile)) {
|
|
179
|
+
writePrivateFile(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
|
|
180
|
+
} else {
|
|
181
|
+
securePrivateFile(envFile);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
async function runLauncher(options = {}) {
|
|
168
186
|
const callerCwd = process.cwd();
|
|
169
187
|
const mode = options.mode || launcherModeFromArgv(process.argv);
|
|
170
188
|
const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
|
|
189
|
+
const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
|
|
171
190
|
const noticeResult = options.noticeLease
|
|
172
191
|
? { acquired: true, lease: options.noticeLease }
|
|
173
192
|
: await tryAcquireUpdateLease({ packageRoot: PKG, role: 'notice' });
|
|
@@ -197,6 +216,40 @@ async function runLauncher(options = {}) {
|
|
|
197
216
|
}
|
|
198
217
|
await installProbe.lease.release();
|
|
199
218
|
|
|
219
|
+
if (explicitToolsRequest) {
|
|
220
|
+
const action = String(ARGS[1] || '').toLowerCase();
|
|
221
|
+
const available = availableStandardToolNames({ packageRoot: PKG });
|
|
222
|
+
if (action === 'list' && ARGS.length === 2) {
|
|
223
|
+
process.stdout.write(`${available.join('\n')}\n`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (action === 'enable' && ARGS.length > 2) {
|
|
227
|
+
const privatePaths = resolveLauncherPrivatePaths();
|
|
228
|
+
const selected = [...new Set(ARGS.slice(2))];
|
|
229
|
+
const unknown = selected.find((name) => !available.includes(name));
|
|
230
|
+
if (unknown) {
|
|
231
|
+
process.stderr.write(`Unbekanntes Standard-Tool: ${unknown}\n`);
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const mcpNames = selected.filter((name) => name !== 'telegram');
|
|
236
|
+
if (mcpNames.length > 0) {
|
|
237
|
+
seedStandardTools({
|
|
238
|
+
packageRoot: PKG,
|
|
239
|
+
blunDir: privatePaths.blunHome,
|
|
240
|
+
names: mcpNames,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (selected.includes('telegram')) {
|
|
244
|
+
installTelegramForProfile(PKG, privatePaths.blunHome);
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`${selected.join('\n')}\n`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
process.stderr.write('Usage: blun tools list | blun tools enable <name...>\n');
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
200
253
|
if (ARGS.some((arg) => arg === '--version' || arg === '-V')) {
|
|
201
254
|
process.stdout.write(`${readPackageVersion()}\n`);
|
|
202
255
|
return;
|
|
@@ -253,37 +306,15 @@ async function runLauncher(options = {}) {
|
|
|
253
306
|
const cfgFile = path.join(blunDir, 'config.toml');
|
|
254
307
|
if (fs.existsSync(cfgFile)) securePrivateFile(cfgFile);
|
|
255
308
|
|
|
256
|
-
// --- 3.
|
|
257
|
-
|
|
258
|
-
installManagedTelegramPlugin({ packageRoot: PKG, blunDir });
|
|
259
|
-
const channelsDir = path.join(blunDir, 'channels');
|
|
260
|
-
const channelDir = path.join(channelsDir, 'telegram');
|
|
261
|
-
ensurePrivateDirectory(channelsDir);
|
|
262
|
-
ensurePrivateDirectory(channelDir);
|
|
263
|
-
const envFile = path.join(channelDir, '.env');
|
|
264
|
-
if (!fs.existsSync(envFile)) {
|
|
265
|
-
writePrivateFile(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
|
|
266
|
-
} else {
|
|
267
|
-
securePrivateFile(envFile);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
309
|
+
// --- 3. Eigenes BLUN-Grunddesign als einzigen Standard-Skill installieren
|
|
310
|
+
seedStandardDesignSkill({ packageRoot: PKG, blunDir });
|
|
270
311
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const skillsDst = path.join(blunDir, 'skills');
|
|
275
|
-
if (fs.existsSync(skillsSrc)) {
|
|
276
|
-
if (fs.existsSync(skillsDst)) securePrivateTree(skillsDst);
|
|
277
|
-
else ensurePrivateDirectory(skillsDst);
|
|
278
|
-
for (const entry of fs.readdirSync(skillsSrc)) {
|
|
279
|
-
const dst = path.join(skillsDst, entry);
|
|
280
|
-
if (!fs.existsSync(dst)) {
|
|
281
|
-
fs.cpSync(path.join(skillsSrc, entry), dst, { recursive: true });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
312
|
+
// --- 4. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
|
|
313
|
+
if (mode === LAUNCHER_MODES.KING) {
|
|
314
|
+
installTelegramForProfile(PKG, blunDir);
|
|
284
315
|
}
|
|
285
316
|
|
|
286
|
-
// ---
|
|
317
|
+
// --- 5. Start -----------------------------------------------------------
|
|
287
318
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
288
319
|
env.BLUN_HOME = blunDir;
|
|
289
320
|
env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
ensurePrivateDirectory,
|
|
9
|
+
securePrivateFile,
|
|
10
|
+
securePrivateTree,
|
|
11
|
+
writePrivateFile,
|
|
12
|
+
} = require('./private-paths');
|
|
13
|
+
|
|
14
|
+
function isObject(value) {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readCatalogue(packageRoot) {
|
|
19
|
+
const manifestPath = path.join(packageRoot, 'standard-tools', 'manifest.json');
|
|
20
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
21
|
+
if (!isObject(parsed) || parsed.version !== 1 || !isObject(parsed.tools)) {
|
|
22
|
+
throw new Error(`Ungueltiger Standard-Tools-Katalog: ${manifestPath}`);
|
|
23
|
+
}
|
|
24
|
+
for (const [name, tool] of Object.entries(parsed.tools)) {
|
|
25
|
+
if (!isObject(tool) || !Array.isArray(tool.detectTokens)
|
|
26
|
+
|| !tool.detectTokens.every((token) => typeof token === 'string' && token.length > 0)) {
|
|
27
|
+
throw new Error(`Ungueltiger Standard-Tool-Eintrag: ${name}`);
|
|
28
|
+
}
|
|
29
|
+
if (tool.platforms !== undefined
|
|
30
|
+
&& (!Array.isArray(tool.platforms)
|
|
31
|
+
|| !tool.platforms.every((platform) => platform === 'darwin' || platform === 'win32'))) {
|
|
32
|
+
throw new Error(`Ungueltige Standard-Tool-Plattform: ${name}`);
|
|
33
|
+
}
|
|
34
|
+
if (tool.owner === 'user-mcp'
|
|
35
|
+
&& (!isObject(tool.config) || typeof tool.config.enabled !== 'boolean')) {
|
|
36
|
+
throw new Error(`Standard-Tool braucht einen Aktivierungszustand: ${name}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readMcpDocument(configPath) {
|
|
43
|
+
if (!fs.existsSync(configPath)) return { mcpServers: {} };
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`Ungueltige MCP-Konfiguration (JSON): ${configPath}`, { cause: error });
|
|
49
|
+
}
|
|
50
|
+
if (!isObject(parsed) || (parsed.mcpServers !== undefined && !isObject(parsed.mcpServers))) {
|
|
51
|
+
throw new Error(`Ungueltige MCP-Konfiguration: ${configPath}`);
|
|
52
|
+
}
|
|
53
|
+
return parsed.mcpServers === undefined ? { ...parsed, mcpServers: {} } : parsed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function serverSearchText(name, config) {
|
|
57
|
+
const values = [name];
|
|
58
|
+
if (isObject(config)) {
|
|
59
|
+
if (typeof config.command === 'string') values.push(config.command);
|
|
60
|
+
if (Array.isArray(config.args)) {
|
|
61
|
+
values.push(...config.args.filter((value) => typeof value === 'string'));
|
|
62
|
+
}
|
|
63
|
+
if (typeof config.url === 'string') values.push(config.url);
|
|
64
|
+
}
|
|
65
|
+
return values.join('\n').toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hasEquivalentServer(servers, name, tool) {
|
|
69
|
+
if (Object.hasOwn(servers, name)) return true;
|
|
70
|
+
const tokens = tool.detectTokens.map((token) => token.toLowerCase());
|
|
71
|
+
return Object.entries(servers).some(([serverName, config]) => {
|
|
72
|
+
const searchText = serverSearchText(serverName, config);
|
|
73
|
+
return tokens.some((token) => searchText.includes(token));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function writeMcpDocument(configPath, document) {
|
|
78
|
+
const temporaryPath = path.join(
|
|
79
|
+
path.dirname(configPath),
|
|
80
|
+
`.${path.basename(configPath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
81
|
+
);
|
|
82
|
+
try {
|
|
83
|
+
writePrivateFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
84
|
+
fs.renameSync(temporaryPath, configPath);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function bundledToolConfig(name, tool, { packageRoot, blunDir, platform, env }) {
|
|
92
|
+
const config = structuredClone(tool.config);
|
|
93
|
+
config.enabled = true;
|
|
94
|
+
if (name === 'blun-language-guard') {
|
|
95
|
+
config.command = platform === 'win32' ? 'python' : 'python3';
|
|
96
|
+
config.args = [
|
|
97
|
+
path.join(
|
|
98
|
+
packageRoot,
|
|
99
|
+
'standard-tools',
|
|
100
|
+
'language-guard',
|
|
101
|
+
'blun_language_guard.py',
|
|
102
|
+
),
|
|
103
|
+
'serve',
|
|
104
|
+
];
|
|
105
|
+
config.env = {
|
|
106
|
+
...(isObject(config.env) ? config.env : {}),
|
|
107
|
+
PYTHONDONTWRITEBYTECODE: '1',
|
|
108
|
+
PYTHONUTF8: '1',
|
|
109
|
+
BLUN_LANGUAGE_GUARD_KEY_FILE: path.join(
|
|
110
|
+
blunDir,
|
|
111
|
+
'secrets',
|
|
112
|
+
'blun-language-guard-signing.key',
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (name === 'windows-mcp' && platform === 'win32') {
|
|
117
|
+
const localUvx = typeof env.USERPROFILE === 'string'
|
|
118
|
+
? path.join(env.USERPROFILE, '.local', 'bin', 'uvx.exe')
|
|
119
|
+
: undefined;
|
|
120
|
+
config.command = localUvx !== undefined && fs.existsSync(localUvx) ? localUvx : 'uvx';
|
|
121
|
+
}
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function availableStandardToolNames({
|
|
126
|
+
packageRoot,
|
|
127
|
+
platform = process.platform,
|
|
128
|
+
}) {
|
|
129
|
+
const catalogue = readCatalogue(packageRoot);
|
|
130
|
+
return Object.entries(catalogue.tools)
|
|
131
|
+
.filter(([, tool]) => tool.owner === 'user-mcp' || tool.owner === 'managed-plugin')
|
|
132
|
+
.filter(([, tool]) => !Array.isArray(tool.platforms) || tool.platforms.includes(platform))
|
|
133
|
+
.map(([name]) => name);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function seedStandardTools({
|
|
137
|
+
packageRoot,
|
|
138
|
+
blunDir,
|
|
139
|
+
names,
|
|
140
|
+
platform = process.platform,
|
|
141
|
+
env = process.env,
|
|
142
|
+
}) {
|
|
143
|
+
const catalogue = readCatalogue(packageRoot);
|
|
144
|
+
if (!Array.isArray(names) || names.length === 0) {
|
|
145
|
+
throw new Error('Mindestens ein Standard-Tool muss ausdruecklich ausgewaehlt werden.');
|
|
146
|
+
}
|
|
147
|
+
const selectedNames = [...new Set(names)];
|
|
148
|
+
for (const name of selectedNames) {
|
|
149
|
+
const tool = catalogue.tools[name];
|
|
150
|
+
if (!isObject(tool) || tool.owner !== 'user-mcp') {
|
|
151
|
+
throw new Error(`Unbekanntes Standard-Tool: ${name}`);
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(tool.platforms) && !tool.platforms.includes(platform)) {
|
|
154
|
+
throw new Error(`Standard-Tool ist auf dieser Plattform nicht verfuegbar: ${name}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const configPath = path.join(blunDir, 'mcp.json');
|
|
158
|
+
ensurePrivateDirectory(blunDir);
|
|
159
|
+
if (fs.existsSync(configPath)) securePrivateFile(configPath);
|
|
160
|
+
const document = readMcpDocument(configPath);
|
|
161
|
+
const servers = { ...document.mcpServers };
|
|
162
|
+
const added = [];
|
|
163
|
+
|
|
164
|
+
for (const name of selectedNames) {
|
|
165
|
+
const tool = catalogue.tools[name];
|
|
166
|
+
if (hasEquivalentServer(servers, name, tool)) continue;
|
|
167
|
+
servers[name] = bundledToolConfig(name, tool, {
|
|
168
|
+
packageRoot,
|
|
169
|
+
blunDir,
|
|
170
|
+
platform,
|
|
171
|
+
env,
|
|
172
|
+
});
|
|
173
|
+
added.push(name);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (added.length > 0) writeMcpDocument(configPath, { ...document, mcpServers: servers });
|
|
177
|
+
return { added, configPath };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function seedStandardDesignSkill({ packageRoot, blunDir }) {
|
|
181
|
+
const sourcePath = path.join(
|
|
182
|
+
packageRoot,
|
|
183
|
+
'standard-skills',
|
|
184
|
+
'blun-app-design-system',
|
|
185
|
+
);
|
|
186
|
+
const sourceStat = fs.lstatSync(sourcePath);
|
|
187
|
+
if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) {
|
|
188
|
+
throw new Error(`Ungueltiger BLUN-Standard-Skill: ${sourcePath}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const skillsDir = path.join(blunDir, 'skills');
|
|
192
|
+
ensurePrivateDirectory(skillsDir);
|
|
193
|
+
const destinationPath = path.join(skillsDir, 'blun-app-design-system');
|
|
194
|
+
const installed = !fs.existsSync(destinationPath);
|
|
195
|
+
if (!installed) {
|
|
196
|
+
const destinationStat = fs.lstatSync(destinationPath);
|
|
197
|
+
if (!destinationStat.isDirectory() || destinationStat.isSymbolicLink()) {
|
|
198
|
+
throw new Error(`Ungueltiges Ziel fuer BLUN-Standard-Skill: ${destinationPath}`);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
ensurePrivateDirectory(destinationPath);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fs.cpSync(sourcePath, destinationPath, {
|
|
205
|
+
recursive: true,
|
|
206
|
+
force: true,
|
|
207
|
+
errorOnExist: false,
|
|
208
|
+
});
|
|
209
|
+
securePrivateTree(destinationPath);
|
|
210
|
+
return { installed, updated: !installed, destinationPath };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
availableStandardToolNames,
|
|
215
|
+
readCatalogue,
|
|
216
|
+
seedStandardDesignSkill,
|
|
217
|
+
seedStandardTools,
|
|
218
|
+
};
|
package/bin/update-notice.js
CHANGED
|
@@ -620,10 +620,19 @@ function createInstallInvocation(version, options = {}) {
|
|
|
620
620
|
if (platform !== 'win32' && path.basename(installPrefix) === 'lib') {
|
|
621
621
|
installPrefix = path.dirname(installPrefix);
|
|
622
622
|
}
|
|
623
|
-
let
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (!
|
|
623
|
+
let npmUserConfigPath;
|
|
624
|
+
let npmGlobalConfigPath;
|
|
625
|
+
if (options.npmUserConfigPath || options.npmGlobalConfigPath) {
|
|
626
|
+
if (!options.npmUserConfigPath || !options.npmGlobalConfigPath) {
|
|
627
|
+
throw new Error('INVALID_NPM_CONFIG_PATHS');
|
|
628
|
+
}
|
|
629
|
+
npmUserConfigPath = fs.realpathSync(path.resolve(options.npmUserConfigPath));
|
|
630
|
+
npmGlobalConfigPath = fs.realpathSync(path.resolve(options.npmGlobalConfigPath));
|
|
631
|
+
if (!fs.statSync(npmUserConfigPath).isFile()
|
|
632
|
+
|| !fs.statSync(npmGlobalConfigPath).isFile()
|
|
633
|
+
|| npmUserConfigPath === npmGlobalConfigPath) {
|
|
634
|
+
throw new Error('INVALID_NPM_CONFIG_PATHS');
|
|
635
|
+
}
|
|
627
636
|
}
|
|
628
637
|
return {
|
|
629
638
|
command: execPath,
|
|
@@ -638,16 +647,21 @@ function createInstallInvocation(version, options = {}) {
|
|
|
638
647
|
'--ignore-scripts=false',
|
|
639
648
|
'--package-lock-only=false',
|
|
640
649
|
`--prefix=${installPrefix}`,
|
|
641
|
-
...(
|
|
642
|
-
`--userconfig=${
|
|
643
|
-
`--globalconfig=${
|
|
650
|
+
...(npmUserConfigPath ? [
|
|
651
|
+
`--userconfig=${npmUserConfigPath}`,
|
|
652
|
+
`--globalconfig=${npmGlobalConfigPath}`,
|
|
644
653
|
] : []),
|
|
645
654
|
],
|
|
646
655
|
windowsVerbatimArguments: false,
|
|
647
656
|
};
|
|
648
657
|
}
|
|
649
658
|
|
|
650
|
-
function createInstallEnvironment(
|
|
659
|
+
function createInstallEnvironment(
|
|
660
|
+
sourceEnv,
|
|
661
|
+
installDirectory,
|
|
662
|
+
npmUserConfigPath,
|
|
663
|
+
npmGlobalConfigPath,
|
|
664
|
+
) {
|
|
651
665
|
const allowedKeys = new Set([
|
|
652
666
|
'appdata',
|
|
653
667
|
'comspec',
|
|
@@ -685,8 +699,8 @@ function createInstallEnvironment(sourceEnv, installDirectory, npmConfigPath) {
|
|
|
685
699
|
result.npm_config_dry_run = 'false';
|
|
686
700
|
result.npm_config_ignore_scripts = 'false';
|
|
687
701
|
result.npm_config_package_lock_only = 'false';
|
|
688
|
-
result.npm_config_userconfig =
|
|
689
|
-
result.npm_config_globalconfig =
|
|
702
|
+
result.npm_config_userconfig = npmUserConfigPath;
|
|
703
|
+
result.npm_config_globalconfig = npmGlobalConfigPath;
|
|
690
704
|
return result;
|
|
691
705
|
}
|
|
692
706
|
|
|
@@ -763,7 +777,8 @@ function preparePinnedInstaller(version, options = {}) {
|
|
|
763
777
|
let closeSettled = false;
|
|
764
778
|
let selectedAction;
|
|
765
779
|
let installDirectory;
|
|
766
|
-
let
|
|
780
|
+
let npmUserConfigPath;
|
|
781
|
+
let npmGlobalConfigPath;
|
|
767
782
|
let disconnectTimer;
|
|
768
783
|
const close = Promise.withResolvers();
|
|
769
784
|
const closePromise = close.promise;
|
|
@@ -848,9 +863,15 @@ function preparePinnedInstaller(version, options = {}) {
|
|
|
848
863
|
resolveInstallTempRoot(options),
|
|
849
864
|
options.blunDir,
|
|
850
865
|
);
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
866
|
+
npmUserConfigPath = path.join(installDirectory, 'user-npmrc');
|
|
867
|
+
npmGlobalConfigPath = path.join(installDirectory, 'global-npmrc');
|
|
868
|
+
writePrivateFile(npmUserConfigPath, '');
|
|
869
|
+
writePrivateFile(npmGlobalConfigPath, '');
|
|
870
|
+
invocation = createInstallInvocation(version, {
|
|
871
|
+
...options,
|
|
872
|
+
npmUserConfigPath,
|
|
873
|
+
npmGlobalConfigPath,
|
|
874
|
+
});
|
|
854
875
|
child = forkImpl(path.join(__dirname, 'update-lease.js'), ['--installer-worker'], {
|
|
855
876
|
detached: true,
|
|
856
877
|
execPath: invocation.command,
|
|
@@ -858,7 +879,8 @@ function preparePinnedInstaller(version, options = {}) {
|
|
|
858
879
|
env: createInstallEnvironment(
|
|
859
880
|
options.env || process.env,
|
|
860
881
|
installDirectory,
|
|
861
|
-
|
|
882
|
+
npmUserConfigPath,
|
|
883
|
+
npmGlobalConfigPath,
|
|
862
884
|
),
|
|
863
885
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
864
886
|
windowsHide: true,
|
package/blun.mjs
CHANGED
|
@@ -492529,8 +492529,10 @@ var AuthFlowController = class {
|
|
|
492529
492529
|
availableModels,
|
|
492530
492530
|
availableProviders
|
|
492531
492531
|
});
|
|
492532
|
-
|
|
492533
|
-
|
|
492532
|
+
const accountContext = await this.refreshManagedAccountContextResult();
|
|
492533
|
+
if (accountContext.kind !== "ready") {
|
|
492534
|
+
if (accountContext.kind === "unauthenticated") this.enterLoginRequiredStartupState();
|
|
492535
|
+
else this.enterManagedAccountUnavailableStartupState(accountContext.kind);
|
|
492534
492536
|
return;
|
|
492535
492537
|
}
|
|
492536
492538
|
if (selected === void 0) return;
|
package/package.json
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "blun-king-cli",
|
|
3
|
+
"version": "9.1.8",
|
|
4
|
+
"description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"blun": "bin/blun.js",
|
|
8
|
+
"king": "bin/king.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/*.test.js",
|
|
12
|
+
"postinstall": "node scripts/fix-node-pty-perms.js"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "\u003e=24.15.0"
|
|
16
|
+
},
|
|
17
|
+
"os": [
|
|
18
|
+
"darwin",
|
|
19
|
+
"win32"
|
|
20
|
+
],
|
|
21
|
+
"cpu": [
|
|
22
|
+
"x64",
|
|
23
|
+
"arm64"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
|
|
27
|
+
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@mariozechner/clipboard": "^0.3.9",
|
|
30
|
+
"node-pty": "^1.1.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin/",
|
|
34
|
+
"blun.mjs",
|
|
35
|
+
"dist-web/",
|
|
36
|
+
"native/",
|
|
37
|
+
"scripts/fix-node-pty-perms.js",
|
|
38
|
+
"telegram-plugin/",
|
|
39
|
+
"LIESMICH.txt",
|
|
40
|
+
"standard-tools/",
|
|
41
|
+
"standard-skills/"
|
|
42
|
+
],
|
|
43
|
+
"keywords": [
|
|
44
|
+
"blun",
|
|
45
|
+
"ai",
|
|
46
|
+
"agent",
|
|
47
|
+
"cli",
|
|
48
|
+
"assistant"
|
|
49
|
+
],
|
|
50
|
+
"author": "BLUN / Mayk Biletti"
|
|
48
51
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blun-app-design-system
|
|
3
|
+
description: Apply the canonical BLUN application design language to desktop and web product interfaces while preserving the current product structure, tokens, accessibility, and runtime behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BLUN App Design System
|
|
7
|
+
|
|
8
|
+
Use this skill for BLUN application screens, components, dialogs, navigation, chat, workspaces, settings, and related product UI.
|
|
9
|
+
|
|
10
|
+
## Source order
|
|
11
|
+
|
|
12
|
+
1. Read the target project's current design tokens and nearby components.
|
|
13
|
+
2. Treat the implemented product as authoritative. Read [references/tokens.md](references/tokens.md) for the verified baseline.
|
|
14
|
+
3. Use the archived visual reference only as inspiration. Never copy its old palette, typography, product names, or CSS into a runtime.
|
|
15
|
+
4. Preserve the existing information architecture and interaction model unless the task explicitly changes them.
|
|
16
|
+
|
|
17
|
+
## Build rules
|
|
18
|
+
|
|
19
|
+
- Use neutral dark and light surfaces with BLUN blue as the single primary accent.
|
|
20
|
+
- Use Geist for UI text and JetBrains Mono for code, values, and terminal-like content.
|
|
21
|
+
- Keep operational surfaces compact, predictable, and easy to scan. Use cards only for repeated items, dialogs, and genuinely framed tools.
|
|
22
|
+
- Prefer semantic tokens over literal component colors. Extend both themes when a new semantic color is required.
|
|
23
|
+
- Use familiar icons for actions, visible focus states, stable control dimensions, and reduced-motion fallbacks.
|
|
24
|
+
- Keep text within its container at desktop and mobile widths. Do not size type directly from viewport width.
|
|
25
|
+
- Preserve accessibility contrast, keyboard behavior, and native platform expectations.
|
|
26
|
+
- Do not overwrite a product stylesheet with a bundled reference. Make focused edits in the target's existing source.
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
Run `node scripts/check-blun-design.mjs <css-file>` from this skill directory for a token drift check. Then verify the actual interface in a real browser at representative desktop and mobile sizes. The token check is not a visual test.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blun-app-design-system",
|
|
3
|
+
"authority": "implemented-product",
|
|
4
|
+
"sources": {
|
|
5
|
+
"visualReference": {
|
|
6
|
+
"role": "historical-input-only",
|
|
7
|
+
"sha256": "F821EFEC89EB32628C3028F1FE34E83BF284B96E0FCFB6C647137DC93774DA64",
|
|
8
|
+
"normalizedCssSha256": "925260FBCC490B5C4EAC955ACD983BCCBF8FF35AFF8B1E374D1810C4D31FC83A"
|
|
9
|
+
},
|
|
10
|
+
"implementedProduct": {
|
|
11
|
+
"role": "authoritative",
|
|
12
|
+
"version": "1.0.110",
|
|
13
|
+
"file": "style.css",
|
|
14
|
+
"normalizedCssSha256": "956F156F9F944ED5AAB639C71C85A3989ABB6FC7505F17B3F7076E520781369F"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Canonical BLUN Tokens
|
|
2
|
+
|
|
3
|
+
These values were verified against the current BLUN Code 1.0.110 product source. The target project's own newer implementation wins if it has intentionally advanced.
|
|
4
|
+
|
|
5
|
+
## Core palette
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
:root {
|
|
9
|
+
--bg-sidebar: #0a0a0b;
|
|
10
|
+
--bg-primary: #17181a;
|
|
11
|
+
--bg-secondary: #1f2022;
|
|
12
|
+
--bg-card: #1c1d1f;
|
|
13
|
+
--border: #2a2c2f;
|
|
14
|
+
--text-primary: #f4f4f5;
|
|
15
|
+
--text-secondary: #a6a7ab;
|
|
16
|
+
--text-muted: #76777b;
|
|
17
|
+
--brand: #0261ff;
|
|
18
|
+
--brand-text-dark: #4d8bff;
|
|
19
|
+
--brand-text-light: #0051d6;
|
|
20
|
+
--font: "Geist", "Inter Tight", "Segoe UI Variable Text", "Segoe UI", sans-serif;
|
|
21
|
+
--mono: "JetBrains Mono Variable", "JetBrains Mono", "Cascadia Code", Consolas, monospace;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:root[data-theme="light"] {
|
|
25
|
+
--bg-sidebar: #ececee;
|
|
26
|
+
--bg-primary: #ffffff;
|
|
27
|
+
--bg-secondary: #f6f6f7;
|
|
28
|
+
--bg-card: #fbfbfc;
|
|
29
|
+
--border: #e2e2e5;
|
|
30
|
+
--text-primary: #17181a;
|
|
31
|
+
--text-secondary: #57585c;
|
|
32
|
+
--text-muted: #8a8b90;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Shape and motion
|
|
37
|
+
|
|
38
|
+
- Compact controls normally use 5-8 px radii; larger overlays may use 8-12 px.
|
|
39
|
+
- Pills are reserved for statuses, compact toggles, and genuinely pill-shaped controls.
|
|
40
|
+
- Use restrained 120-200 ms transitions and disable nonessential movement under reduced motion.
|
|
41
|
+
- Use subtle borders and controlled shadows to establish hierarchy; avoid decorative gradients and floating decoration.
|
|
42
|
+
|
|
43
|
+
## Layout
|
|
44
|
+
|
|
45
|
+
- Keep navigation, chat, and work output as stable application regions.
|
|
46
|
+
- Use `minmax(0, 1fr)` in flexible grid tracks so long content cannot force overflow.
|
|
47
|
+
- Give chat and composer a shared readable measure and stable gutters.
|
|
48
|
+
- Keep toolbars and icon controls dimensionally stable across hover, loading, and dynamic labels.
|