blun-king-cli 9.1.626 → 9.1.628

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.
@@ -1,162 +1,163 @@
1
- 'use strict';
2
-
3
- const { randomUUID } = require('node:crypto');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
- const { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
7
-
8
- const {
9
- ensurePrivateDirectory,
10
- securePrivateFile,
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 readInstalledPlugins(filePath) {
19
- if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
20
-
21
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
- if (!isObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.plugins)
23
- || !parsed.plugins.every(isObject)) {
24
- throw new Error(`Ungültige Plugin-Konfiguration: ${filePath}`);
25
- }
26
- return parsed;
27
- }
28
-
29
- function updatedInstalledPlugins(installed, pluginRoot, now, pluginId = 'telegram') {
30
- const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
31
- const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
32
- const previousMcpServers = isObject(previousCapabilities.mcpServers)
33
- ? previousCapabilities.mcpServers : {};
34
- const previousTelegramMcp = isObject(previousMcpServers[pluginId])
35
- ? previousMcpServers[pluginId] : {};
36
- const telegram = {
37
- ...previous,
38
- id: pluginId,
39
- root: pluginRoot,
40
- source: 'local-path',
41
- enabled: true,
42
- installedAt: typeof previous?.installedAt === 'string' && previous.installedAt.length > 0
43
- ? previous.installedAt : now,
44
- updatedAt: now,
45
- capabilities: {
46
- ...previousCapabilities,
47
- mcpServers: {
48
- ...previousMcpServers,
49
- [pluginId]: { ...previousTelegramMcp, enabled: previousTelegramMcp.enabled === false && pluginId === 'agent-spine' ? false : true },
50
- },
51
- },
52
- };
53
-
54
- const plugins = [];
55
- let replaced = false;
56
- for (const plugin of installed.plugins) {
57
- if (plugin.id !== pluginId) {
58
- plugins.push(plugin);
59
- } else if (!replaced) {
60
- plugins.push(telegram);
61
- replaced = true;
62
- }
63
- }
64
- if (!replaced) plugins.push(telegram);
65
- return { ...installed, version: 1, plugins };
66
- }
67
-
68
- function writeInstalledPlugins(filePath, installed) {
69
- const temporaryPath = path.join(
70
- path.dirname(filePath),
71
- `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
72
- );
73
- try {
74
- writePrivateFile(temporaryPath, `${JSON.stringify(installed, null, 2)}\n`);
75
- fs.renameSync(temporaryPath, filePath);
76
- } catch (error) {
77
- fs.rmSync(temporaryPath, { force: true });
78
- throw error;
79
- }
80
- }
81
-
82
- function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
83
- const sourceRoot = path.join(packageRoot, 'telegram-plugin');
84
- const pluginsDir = path.join(blunDir, 'plugins');
85
- const managedDir = path.join(pluginsDir, 'managed');
86
- const pluginRoot = path.join(managedDir, 'telegram');
87
- const installedFile = path.join(pluginsDir, 'installed.json');
88
-
89
- ensurePrivateDirectory(pluginsDir);
90
- if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
91
- const installed = readInstalledPlugins(installedFile);
92
- const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
93
- ensurePrivateDirectory(managedDir);
94
- const nonce = `${process.pid}-${randomUUID()}`;
95
- const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
96
- const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
97
- let hadPreviousPlugin = false;
98
- let installedNewPlugin = false;
99
- try {
100
- if (fs.existsSync(pluginRoot)) {
101
- ensurePrivateDirectory(pluginRoot);
102
- hadPreviousPlugin = true;
103
- }
104
- fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
105
- if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
106
- try {
107
- fs.renameSync(stagingRoot, pluginRoot);
108
- installedNewPlugin = true;
109
- writeInstalledPlugins(installedFile, updatedInstalled);
110
- } catch (error) {
111
- if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
112
- if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
113
- fs.renameSync(backupRoot, pluginRoot);
114
- }
115
- throw error;
116
- }
117
- if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
118
- } finally {
119
- fs.rmSync(stagingRoot, { recursive: true, force: true });
120
- }
121
-
122
- return { installedFile, pluginRoot };
123
- }
124
-
125
- function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
126
- const installedFile = path.join(blunDir, 'plugins', 'installed.json');
127
- ensurePrivateDirectory(path.dirname(installedFile));
128
- if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
129
- const installed = readInstalledPlugins(installedFile);
130
- const records = installed.plugins.filter(plugin => plugin.id === 'agent-spine');
131
- if (records.length > 1) throw new Error('Ambiguous AgentSpine registration');
132
- const selected = selectedAgentSpineRoot({ installed, blunDir });
133
- if (selected) return { installedFile, pluginRoot: selected, reused: true, selected: true };
134
- if (records[0]?.enabled === false) {
135
- return { installedFile, pluginRoot: records[0].root, reused: true, disabled: true };
136
- }
137
- if (records[0]) {
138
- const currentRoot = records[0].root;
139
- if (typeof currentRoot !== 'string' || !path.isAbsolute(currentRoot)) {
140
- throw new Error('Invalid AgentSpine registration root');
141
- }
142
- const managed = path.resolve(blunDir, 'plugins', 'managed');
143
- const sameDirectory = (left, right) => process.platform === 'win32'
144
- ? left.toLowerCase() === right.toLowerCase() : left === right;
145
- if (!sameDirectory(path.dirname(path.resolve(currentRoot)), managed)
146
- || !/^agent-spine-[a-f0-9]{64}$/.test(path.basename(currentRoot))) {
147
- return { installedFile, pluginRoot: currentRoot, reused: true, custom: true };
148
- }
149
- // An upgrade must not conceal changes to the previous immutable default.
150
- describeAgentSpineSelection({ blunDir, pluginRoot: currentRoot });
151
- }
152
- const pluginRoot = copySelectedAgentSpinePlugin({ blunDir, sourceRoot: path.join(packageRoot, 'agent-spine-plugin') });
153
- if (records[0]?.root === pluginRoot) return { installedFile, pluginRoot, reused: true };
154
- writeInstalledPlugins(installedFile, updatedInstalledPlugins(installed, pluginRoot, now, 'agent-spine'));
155
- return { installedFile, pluginRoot, reused: false };
156
- }
157
-
158
- module.exports = {
159
- installManagedAgentSpinePlugin,
160
- installManagedTelegramPlugin,
161
- readInstalledPlugins,
162
- };
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { renamePrivateStaging, copyTelegramFiles } = require('./rename-private-staging.cjs');
7
+ const { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
8
+
9
+ const {
10
+ ensurePrivateDirectory,
11
+ securePrivateFile,
12
+ writePrivateFile,
13
+ } = require('./private-paths');
14
+
15
+ function isObject(value) {
16
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
17
+ }
18
+
19
+ function readInstalledPlugins(filePath) {
20
+ if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
21
+
22
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
23
+ if (!isObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.plugins)
24
+ || !parsed.plugins.every(isObject)) {
25
+ throw new Error(`Ungültige Plugin-Konfiguration: ${filePath}`);
26
+ }
27
+ return parsed;
28
+ }
29
+
30
+ function updatedInstalledPlugins(installed, pluginRoot, now, pluginId = 'telegram') {
31
+ const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
32
+ const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
33
+ const previousMcpServers = isObject(previousCapabilities.mcpServers)
34
+ ? previousCapabilities.mcpServers : {};
35
+ const previousTelegramMcp = isObject(previousMcpServers[pluginId])
36
+ ? previousMcpServers[pluginId] : {};
37
+ const telegram = {
38
+ ...previous,
39
+ id: pluginId,
40
+ root: pluginRoot,
41
+ source: 'local-path',
42
+ enabled: true,
43
+ installedAt: typeof previous?.installedAt === 'string' && previous.installedAt.length > 0
44
+ ? previous.installedAt : now,
45
+ updatedAt: now,
46
+ capabilities: {
47
+ ...previousCapabilities,
48
+ mcpServers: {
49
+ ...previousMcpServers,
50
+ [pluginId]: { ...previousTelegramMcp, enabled: previousTelegramMcp.enabled === false && pluginId === 'agent-spine' ? false : true },
51
+ },
52
+ },
53
+ };
54
+
55
+ const plugins = [];
56
+ let replaced = false;
57
+ for (const plugin of installed.plugins) {
58
+ if (plugin.id !== pluginId) {
59
+ plugins.push(plugin);
60
+ } else if (!replaced) {
61
+ plugins.push(telegram);
62
+ replaced = true;
63
+ }
64
+ }
65
+ if (!replaced) plugins.push(telegram);
66
+ return { ...installed, version: 1, plugins };
67
+ }
68
+
69
+ function writeInstalledPlugins(filePath, installed) {
70
+ const temporaryPath = path.join(
71
+ path.dirname(filePath),
72
+ `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
73
+ );
74
+ try {
75
+ writePrivateFile(temporaryPath, `${JSON.stringify(installed, null, 2)}\n`);
76
+ fs.renameSync(temporaryPath, filePath);
77
+ } catch (error) {
78
+ fs.rmSync(temporaryPath, { force: true });
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
84
+ const sourceRoot = path.join(packageRoot, 'telegram-plugin');
85
+ const pluginsDir = path.join(blunDir, 'plugins');
86
+ const managedDir = path.join(pluginsDir, 'managed');
87
+ const pluginRoot = path.join(managedDir, 'telegram');
88
+ const installedFile = path.join(pluginsDir, 'installed.json');
89
+
90
+ ensurePrivateDirectory(pluginsDir);
91
+ if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
92
+ const installed = readInstalledPlugins(installedFile);
93
+ const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
94
+ ensurePrivateDirectory(managedDir);
95
+ const nonce = `${process.pid}-${randomUUID()}`;
96
+ const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
97
+ const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
98
+ let hadPreviousPlugin = false;
99
+ let installedNewPlugin = false;
100
+ try {
101
+ if (fs.existsSync(pluginRoot)) {
102
+ ensurePrivateDirectory(pluginRoot);
103
+ hadPreviousPlugin = true;
104
+ }
105
+ copyTelegramFiles(sourceRoot, stagingRoot);
106
+ if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
107
+ try {
108
+ renamePrivateStaging(stagingRoot, pluginRoot);
109
+ installedNewPlugin = true;
110
+ writeInstalledPlugins(installedFile, updatedInstalled);
111
+ } catch (error) {
112
+ if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
113
+ if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
114
+ fs.renameSync(backupRoot, pluginRoot);
115
+ }
116
+ throw error;
117
+ }
118
+ if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
119
+ } finally {
120
+ fs.rmSync(stagingRoot, { recursive: true, force: true });
121
+ }
122
+
123
+ return { installedFile, pluginRoot };
124
+ }
125
+
126
+ function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
127
+ const installedFile = path.join(blunDir, 'plugins', 'installed.json');
128
+ ensurePrivateDirectory(path.dirname(installedFile));
129
+ if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
130
+ const installed = readInstalledPlugins(installedFile);
131
+ const records = installed.plugins.filter(plugin => plugin.id === 'agent-spine');
132
+ if (records.length > 1) throw new Error('Ambiguous AgentSpine registration');
133
+ const selected = selectedAgentSpineRoot({ installed, blunDir });
134
+ if (selected) return { installedFile, pluginRoot: selected, reused: true, selected: true };
135
+ if (records[0]?.enabled === false) {
136
+ return { installedFile, pluginRoot: records[0].root, reused: true, disabled: true };
137
+ }
138
+ if (records[0]) {
139
+ const currentRoot = records[0].root;
140
+ if (typeof currentRoot !== 'string' || !path.isAbsolute(currentRoot)) {
141
+ throw new Error('Invalid AgentSpine registration root');
142
+ }
143
+ const managed = path.resolve(blunDir, 'plugins', 'managed');
144
+ const sameDirectory = (left, right) => process.platform === 'win32'
145
+ ? left.toLowerCase() === right.toLowerCase() : left === right;
146
+ if (!sameDirectory(path.dirname(path.resolve(currentRoot)), managed)
147
+ || !/^agent-spine-[a-f0-9]{64}$/.test(path.basename(currentRoot))) {
148
+ return { installedFile, pluginRoot: currentRoot, reused: true, custom: true };
149
+ }
150
+ // An upgrade must not conceal changes to the previous immutable default.
151
+ describeAgentSpineSelection({ blunDir, pluginRoot: currentRoot });
152
+ }
153
+ const pluginRoot = copySelectedAgentSpinePlugin({ blunDir, sourceRoot: path.join(packageRoot, 'agent-spine-plugin') });
154
+ if (records[0]?.root === pluginRoot) return { installedFile, pluginRoot, reused: true };
155
+ writeInstalledPlugins(installedFile, updatedInstalledPlugins(installed, pluginRoot, now, 'agent-spine'));
156
+ return { installedFile, pluginRoot, reused: false };
157
+ }
158
+
159
+ module.exports = {
160
+ installManagedAgentSpinePlugin,
161
+ installManagedTelegramPlugin,
162
+ readInstalledPlugins,
163
+ };
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const pause = new Int32Array(new SharedArrayBuffer(4));
5
+ const RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
6
+ function withWindowsFsRetry(operation, options = {}) {
7
+ if ((options.platform || process.platform) !== 'win32') return operation();
8
+ const now = options.now || (() => Number(process.hrtime.bigint()) / 1e6);
9
+ const sleep = options.sleep || (ms => Atomics.wait(pause, 0, 0, ms));
10
+ const started = now();
11
+ let firstError;
12
+ for (let attempt = 1; ; attempt++) {
13
+ if (options.validate) options.validate(firstError);
14
+ try { return operation(); } catch (error) {
15
+ if (!RETRY_CODES.has(error.code)) throw error;
16
+ firstError ||= error;
17
+ const elapsedMs = now() - started;
18
+ if (attempt >= 11 || elapsedMs >= 500) {
19
+ firstError.blunFsRetry = { attempts: attempt, elapsedMs, operation: options.label || 'Dateisystemoperation' };
20
+ firstError.message += ' [BLUN: ' + (options.label || 'Dateisystemoperation')
21
+ + ' nach ' + attempt + ' Versuchen endgueltig fehlgeschlagen; Originalfehler bleibt erhalten]';
22
+ throw firstError;
23
+ }
24
+ sleep(Math.min(50, 500 - elapsedMs));
25
+ }
26
+ }
27
+ }
28
+ function canonical(value) { return path.resolve(value).toLowerCase(); }
29
+ function stagingState(staging, impl, expected) {
30
+ const parent = path.dirname(path.resolve(staging));
31
+ if (path.basename(parent) !== 'managed'
32
+ || !/^\.(?:agent-spine-selected|telegram)-[0-9]+-[a-f0-9-]+\.tmp$/.test(path.basename(staging))
33
+ || canonical(impl.realpathSync(parent)) !== canonical(parent)) throw new Error('Invalid private staging scope');
34
+ let stat;
35
+ try { stat = impl.lstatSync(staging); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
36
+ if (!stat.isDirectory() || stat.isSymbolicLink()
37
+ || canonical(impl.realpathSync(staging)) !== canonical(staging)
38
+ || (expected && (stat.dev !== expected.dev || stat.ino !== expected.ino))) throw new Error('Private staging identity changed');
39
+ return stat;
40
+ }
41
+ function renamePrivateStaging(source, target, options = {}) {
42
+ const impl = options.fsImpl || fs;
43
+ if ((options.platform || process.platform) !== 'win32') return impl.renameSync(source, target);
44
+ if (canonical(path.dirname(target)) !== canonical(path.dirname(source))
45
+ || !/^(?:telegram|agent-spine-[a-f0-9]{64})$/.test(path.basename(target))) throw new Error('Invalid private staging target');
46
+ const identity = stagingState(source, impl);
47
+ if (!identity) throw new Error('Private staging is missing');
48
+ return withWindowsFsRetry(() => impl.renameSync(source, target), {
49
+ ...options, label: 'Privates Plugin umbenennen', validate(original) {
50
+ if (!stagingState(source, impl, identity)) throw original || new Error('Private staging disappeared');
51
+ try { impl.lstatSync(target); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
52
+ throw original || new Error('Private staging target already exists');
53
+ },
54
+ });
55
+ }
56
+ function copyTelegramFiles(source, staging, options = {}) {
57
+ const impl = options.fsImpl || fs;
58
+ if ((options.platform || process.platform) !== 'win32') return impl.cpSync(source, staging, { recursive: true, force: true });
59
+ if (!path.basename(staging).startsWith('.telegram-')) throw new Error('Invalid Telegram staging');
60
+ if (stagingState(staging, impl)) throw new Error('Telegram staging already exists');
61
+ let identity;
62
+ return withWindowsFsRetry(() => {
63
+ try { return impl.cpSync(source, staging, { recursive: true, force: true }); }
64
+ catch (error) { identity ||= stagingState(staging, impl); throw error; }
65
+ }, { ...options, label: 'Telegram-Dateien kopieren', validate() { stagingState(staging, impl, identity); } });
66
+ }
67
+ function writeTelegramTemplate(file, content, options = {}) {
68
+ const impl = options.fsImpl || fs;
69
+ const write = options.writeFile || require('./private-paths').writePrivateFile;
70
+ if ((options.platform || process.platform) !== 'win32') return write(file, content);
71
+ const parent = path.dirname(path.resolve(file));
72
+ if (path.basename(file) !== '.env' || path.basename(parent) !== 'telegram'
73
+ || path.basename(path.dirname(parent)) !== 'channels'
74
+ || canonical(impl.realpathSync(parent)) !== canonical(parent)) throw new Error('Invalid Telegram template scope');
75
+ return withWindowsFsRetry(() => write(file, content), {
76
+ ...options, label: 'Telegram-Vorlagendatei schreiben', validate(original) {
77
+ try { impl.lstatSync(file); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
78
+ throw original || new Error('Telegram template already exists; refusing overwrite');
79
+ },
80
+ });
81
+ }
82
+ module.exports = { withWindowsFsRetry, renamePrivateStaging, copyTelegramFiles, writeTelegramTemplate };