blun-king-cli 9.1.628-pruefpaket.modules.r7 → 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.
@@ -40,6 +40,7 @@ const {
40
40
  writePrivateFile,
41
41
  } = require('./private-paths');
42
42
 
43
+ const { writeTelegramTemplate } = require('./rename-private-staging.cjs');
43
44
  const PKG = path.resolve(__dirname, '..');
44
45
  const ARGS = process.argv.slice(2);
45
46
 
@@ -253,7 +254,7 @@ function installTelegramForProfile(packageRoot, blunDir) {
253
254
  ensurePrivateDirectory(channelDir);
254
255
  const envFile = path.join(channelDir, '.env');
255
256
  if (!fs.existsSync(envFile)) {
256
- writePrivateFile(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
257
+ writeTelegramTemplate(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
257
258
  } else {
258
259
  securePrivateFile(envFile);
259
260
  }
@@ -1,115 +1,116 @@
1
- 'use strict';
2
- const { createHash, randomUUID } = require('node:crypto');
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
6
- const SCHEMA = 'blun.managed-plugin-selection/v1';
7
-
8
- function snapshotPluginTree(root) {
9
- const entries = [];
10
- const rootStat = fs.lstatSync(root);
11
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
12
- const visit = (directory, relativeDirectory = '') => {
13
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })
14
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
15
- const relativePath = path.join(relativeDirectory, entry.name);
16
- const absolutePath = path.join(directory, entry.name);
17
- const stat = fs.lstatSync(absolutePath);
18
- if (stat.isSymbolicLink()) return false;
19
- if (stat.isDirectory()) {
20
- entries.push({ kind: 'directory', path: relativePath });
21
- if (!visit(absolutePath, relativePath)) return false;
22
- } else if (stat.isFile()) {
23
- entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
24
- } else return false;
25
- }
26
- return true;
27
- };
28
- return visit(root) ? entries : null;
29
- }
30
-
31
- function digestPluginEntries(entries) {
32
- if (entries === null) throw new Error('Invalid managed plugin tree');
33
- const hash = createHash('sha256');
34
- for (const entry of entries) {
35
- hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
36
- if (entry.kind === 'file') {
37
- const length = Buffer.allocUnsafe(8);
38
- length.writeBigUInt64BE(BigInt(entry.content.length));
39
- hash.update(length);
40
- hash.update(entry.content);
41
- }
42
- }
43
- return hash.digest('hex');
44
- }
45
-
46
- function samePath(left, right) {
47
- const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
48
- return normalize(left) === normalize(right);
49
- }
50
-
51
- function scopedPluginRoot(blunDir, pluginRoot) {
52
- const managed = path.resolve(blunDir, 'plugins', 'managed');
53
- const root = path.resolve(pluginRoot);
54
- if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
55
- for (const directory of [blunDir, path.dirname(managed), managed, root]) {
56
- const stat = fs.lstatSync(directory);
57
- if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
58
- }
59
- if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
60
- throw new Error('Selected AgentSpine root resolves outside this profile');
61
- }
62
- return root;
63
- }
64
-
65
- function describeAgentSpineSelection({ blunDir, pluginRoot }) {
66
- const root = scopedPluginRoot(blunDir, pluginRoot);
67
- const digest = digestPluginEntries(snapshotPluginTree(root));
68
- if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
69
- return { schema: SCHEMA, digest };
70
- }
71
-
72
- function selectedAgentSpineRoot({ installed, blunDir }) {
73
- const records = installed.plugins.filter(record => record.id === 'agent-spine');
74
- if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
75
- if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
76
- const [record] = records;
77
- const selection = record.managedSelection;
78
- if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
79
- || Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
80
- throw new Error('Invalid AgentSpine selection');
81
- }
82
- const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
83
- if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
84
- return record.root;
85
- }
86
-
87
- function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
88
- const plugins = path.resolve(blunDir, 'plugins');
89
- const managed = path.join(plugins, 'managed');
90
- ensurePrivateDirectory(plugins);
91
- ensurePrivateDirectory(managed);
92
- const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
93
- const pluginRoot = path.join(managed, `agent-spine-${digest}`);
94
- if (fs.existsSync(pluginRoot)) {
95
- describeAgentSpineSelection({ blunDir, pluginRoot });
96
- return pluginRoot;
97
- }
98
- const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
99
- try {
100
- fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
101
- securePrivateTree(staging);
102
- if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
103
- fs.renameSync(staging, pluginRoot);
104
- describeAgentSpineSelection({ blunDir, pluginRoot });
105
- return pluginRoot;
106
- } finally {
107
- if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
108
- throw new Error('Invalid AgentSpine staging cleanup path');
109
- }
110
- fs.rmSync(staging, { recursive: true, force: true });
111
- }
112
- }
113
-
114
- module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
115
- selectedAgentSpineRoot, copySelectedAgentSpinePlugin };
1
+ 'use strict';
2
+ const { createHash, randomUUID } = require('node:crypto');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { renamePrivateStaging } = require('./rename-private-staging.cjs');
6
+ const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
7
+ const SCHEMA = 'blun.managed-plugin-selection/v1';
8
+
9
+ function snapshotPluginTree(root) {
10
+ const entries = [];
11
+ const rootStat = fs.lstatSync(root);
12
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
13
+ const visit = (directory, relativeDirectory = '') => {
14
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })
15
+ .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
16
+ const relativePath = path.join(relativeDirectory, entry.name);
17
+ const absolutePath = path.join(directory, entry.name);
18
+ const stat = fs.lstatSync(absolutePath);
19
+ if (stat.isSymbolicLink()) return false;
20
+ if (stat.isDirectory()) {
21
+ entries.push({ kind: 'directory', path: relativePath });
22
+ if (!visit(absolutePath, relativePath)) return false;
23
+ } else if (stat.isFile()) {
24
+ entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
25
+ } else return false;
26
+ }
27
+ return true;
28
+ };
29
+ return visit(root) ? entries : null;
30
+ }
31
+
32
+ function digestPluginEntries(entries) {
33
+ if (entries === null) throw new Error('Invalid managed plugin tree');
34
+ const hash = createHash('sha256');
35
+ for (const entry of entries) {
36
+ hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
37
+ if (entry.kind === 'file') {
38
+ const length = Buffer.allocUnsafe(8);
39
+ length.writeBigUInt64BE(BigInt(entry.content.length));
40
+ hash.update(length);
41
+ hash.update(entry.content);
42
+ }
43
+ }
44
+ return hash.digest('hex');
45
+ }
46
+
47
+ function samePath(left, right) {
48
+ const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
49
+ return normalize(left) === normalize(right);
50
+ }
51
+
52
+ function scopedPluginRoot(blunDir, pluginRoot) {
53
+ const managed = path.resolve(blunDir, 'plugins', 'managed');
54
+ const root = path.resolve(pluginRoot);
55
+ if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
56
+ for (const directory of [blunDir, path.dirname(managed), managed, root]) {
57
+ const stat = fs.lstatSync(directory);
58
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
59
+ }
60
+ if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
61
+ throw new Error('Selected AgentSpine root resolves outside this profile');
62
+ }
63
+ return root;
64
+ }
65
+
66
+ function describeAgentSpineSelection({ blunDir, pluginRoot }) {
67
+ const root = scopedPluginRoot(blunDir, pluginRoot);
68
+ const digest = digestPluginEntries(snapshotPluginTree(root));
69
+ if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
70
+ return { schema: SCHEMA, digest };
71
+ }
72
+
73
+ function selectedAgentSpineRoot({ installed, blunDir }) {
74
+ const records = installed.plugins.filter(record => record.id === 'agent-spine');
75
+ if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
76
+ if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
77
+ const [record] = records;
78
+ const selection = record.managedSelection;
79
+ if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
80
+ || Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
81
+ throw new Error('Invalid AgentSpine selection');
82
+ }
83
+ const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
84
+ if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
85
+ return record.root;
86
+ }
87
+
88
+ function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
89
+ const plugins = path.resolve(blunDir, 'plugins');
90
+ const managed = path.join(plugins, 'managed');
91
+ ensurePrivateDirectory(plugins);
92
+ ensurePrivateDirectory(managed);
93
+ const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
94
+ const pluginRoot = path.join(managed, `agent-spine-${digest}`);
95
+ if (fs.existsSync(pluginRoot)) {
96
+ describeAgentSpineSelection({ blunDir, pluginRoot });
97
+ return pluginRoot;
98
+ }
99
+ const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
100
+ try {
101
+ fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
102
+ securePrivateTree(staging);
103
+ if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
104
+ renamePrivateStaging(staging, pluginRoot);
105
+ describeAgentSpineSelection({ blunDir, pluginRoot });
106
+ return pluginRoot;
107
+ } finally {
108
+ if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
109
+ throw new Error('Invalid AgentSpine staging cleanup path');
110
+ }
111
+ fs.rmSync(staging, { recursive: true, force: true });
112
+ }
113
+ }
114
+
115
+ module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
116
+ selectedAgentSpineRoot, copySelectedAgentSpinePlugin };
@@ -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 };
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:6b3e444de8032dc30ce210b87332794c64047374cd1d5148e6777dfb25e9c748
2
+ // BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
3
3
  /*! wasm-webp 0.1.0
4
4
  MIT License
5
5
 
@@ -368248,7 +368248,7 @@ function optionalBuildString(value) {
368248
368248
  var BLUN_BUILD_INFO;
368249
368249
  var init_build_info = __esmMin((() => {
368250
368250
  BLUN_BUILD_INFO = {
368251
- version: optionalBuildString("9.1.628-pruefpaket.modules.r7"),
368251
+ version: optionalBuildString("9.1.628-pruefpaket.modules.r8"),
368252
368252
  channel: typeof __BLUN_CHANNEL__ === "string" ? optionalBuildString(__BLUN_CHANNEL__) : void 0,
368253
368253
  commit: typeof __BLUN_COMMIT__ === "string" ? optionalBuildString(__BLUN_COMMIT__) : void 0,
368254
368254
  buildTarget: typeof __BLUN_BUILD_TARGET__ === "string" ? optionalBuildString(__BLUN_BUILD_TARGET__) : void 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.628-pruefpaket.modules.r7",
3
+ "version": "9.1.628",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {
package/worker-host.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:6b3e444de8032dc30ce210b87332794c64047374cd1d5148e6777dfb25e9c748
2
+ // BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
3
3
  /*! wasm-webp 0.1.0
4
4
  MIT License
5
5
 
@@ -350041,7 +350041,7 @@ function optionalBuildString(value) {
350041
350041
  return typeof value === "string" && value.length > 0 ? value : void 0;
350042
350042
  }
350043
350043
  const BLUN_BUILD_INFO = {
350044
- version: optionalBuildString("9.1.628-pruefpaket.modules.r7"),
350044
+ version: optionalBuildString("9.1.628-pruefpaket.modules.r8"),
350045
350045
  channel: typeof __BLUN_CHANNEL__ === "string" ? optionalBuildString(__BLUN_CHANNEL__) : void 0,
350046
350046
  commit: typeof __BLUN_COMMIT__ === "string" ? optionalBuildString(__BLUN_COMMIT__) : void 0,
350047
350047
  buildTarget: typeof __BLUN_BUILD_TARGET__ === "string" ? optionalBuildString(__BLUN_BUILD_TARGET__) : void 0