@andespindola/brainlink 0.1.0-beta.141 → 0.1.0-beta.142
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/README.md
CHANGED
|
@@ -694,6 +694,7 @@ blink config set-vault "s3://my-memory-bucket/brainlink" --global
|
|
|
694
694
|
|
|
695
695
|
`config set-vault` writes configuration through CLI (no manual file edits required).
|
|
696
696
|
By default it writes local config (`./brainlink.config.json`), appends the vault to `allowedVaults`, and migrates Markdown memory from the current configured vault when the target is empty.
|
|
697
|
+
When the configured default vault is changed manually in config files, Brainlink also performs automatic migration on the next command that uses the configured vault (without explicit `--vault`).
|
|
697
698
|
Use `--global` to write to `$BRAINLINK_HOME/brainlink.config.json`, `--no-migrate` to skip migration, and `--no-index` to skip post-migration indexing.
|
|
698
699
|
`config doctor` is dry-run by default; use `--fix` to apply safe config normalization and allowlist fixes.
|
|
699
700
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { indexVault } from './index-vault.js';
|
|
2
|
+
import { migrateVaultContent } from './migrate-vault.js';
|
|
3
|
+
import { getLastConfiguredVaultForKey, setLastConfiguredVaultForKey } from '../infrastructure/vault-migration-state.js';
|
|
4
|
+
export const autoMigrateConfiguredVaultIfChanged = async (input) => {
|
|
5
|
+
const configKey = input.configKey.trim();
|
|
6
|
+
const configuredVault = input.configuredVault.trim();
|
|
7
|
+
if (configKey.length === 0 || configuredVault.length === 0) {
|
|
8
|
+
return {
|
|
9
|
+
changed: false,
|
|
10
|
+
migrated: false
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const previousVault = await getLastConfiguredVaultForKey(configKey);
|
|
14
|
+
if (!previousVault) {
|
|
15
|
+
await setLastConfiguredVaultForKey(configKey, configuredVault);
|
|
16
|
+
return {
|
|
17
|
+
changed: false,
|
|
18
|
+
migrated: false
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (previousVault === configuredVault) {
|
|
22
|
+
return {
|
|
23
|
+
changed: false,
|
|
24
|
+
migrated: false
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const migration = await migrateVaultContent(previousVault, configuredVault);
|
|
28
|
+
const shouldIndex = migration.copied + migration.conflicted > 0;
|
|
29
|
+
if (shouldIndex) {
|
|
30
|
+
await indexVault(configuredVault);
|
|
31
|
+
}
|
|
32
|
+
await setLastConfiguredVaultForKey(configKey, configuredVault);
|
|
33
|
+
return {
|
|
34
|
+
changed: true,
|
|
35
|
+
migrated: shouldIndex
|
|
36
|
+
};
|
|
37
|
+
};
|
package/dist/cli/runtime.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { autoMigrateConfiguredVaultIfChanged } from '../application/auto-migrate-configured-vault.js';
|
|
2
|
+
import { loadBrainlinkConfigWithSource, resolveAgentRuntimeDefaults } from '../infrastructure/config.js';
|
|
2
3
|
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
3
4
|
export const parsePositiveInteger = (value, fallback) => {
|
|
4
5
|
const parsed = Number.parseInt(value, 10);
|
|
5
6
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
6
7
|
};
|
|
7
8
|
export const resolveOptions = async (options) => {
|
|
8
|
-
const config = await
|
|
9
|
+
const { config, vaultSource } = await loadBrainlinkConfigWithSource();
|
|
10
|
+
if (options.vault === undefined) {
|
|
11
|
+
const sourceKey = vaultSource.sourcePath ? `${vaultSource.source}:${vaultSource.sourcePath}` : vaultSource.source;
|
|
12
|
+
await autoMigrateConfiguredVaultIfChanged({
|
|
13
|
+
configKey: sourceKey,
|
|
14
|
+
configuredVault: config.vault
|
|
15
|
+
});
|
|
16
|
+
}
|
|
9
17
|
const vault = options.vault ?? config.vault;
|
|
10
18
|
const allowedVault = assertVaultAllowed(vault, config.allowedVaults);
|
|
11
19
|
const agent = options.agent ?? config.defaultAgent;
|
|
@@ -181,12 +181,87 @@ export const resolveAgentRuntimeDefaults = (config, agent) => {
|
|
|
181
181
|
defaultSearchMode: profile?.defaultSearchMode ?? config.defaultSearchMode
|
|
182
182
|
};
|
|
183
183
|
};
|
|
184
|
+
const mergeConfigLayers = (layers) => layers.reduce((state, config) => ({
|
|
185
|
+
...state,
|
|
186
|
+
...config
|
|
187
|
+
}), {});
|
|
188
|
+
export const getVaultConfigSourceDetails = async (cwd = safeCwd()) => {
|
|
189
|
+
const [globalConfig, localConfig, legacyLocalConfig] = await Promise.all([
|
|
190
|
+
loadRawConfig('global', cwd),
|
|
191
|
+
loadRawConfig('local', cwd),
|
|
192
|
+
loadLegacyLocalRawConfig(cwd)
|
|
193
|
+
]);
|
|
194
|
+
if (typeof legacyLocalConfig.vault === 'string' && legacyLocalConfig.vault.trim().length > 0) {
|
|
195
|
+
return {
|
|
196
|
+
source: 'local-legacy',
|
|
197
|
+
sourcePath: resolve(cwd, '.brainlink.json')
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (typeof localConfig.vault === 'string' && localConfig.vault.trim().length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
source: 'local',
|
|
203
|
+
sourcePath: getLocalConfigPath(cwd)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (typeof globalConfig.vault === 'string' && globalConfig.vault.trim().length > 0) {
|
|
207
|
+
return {
|
|
208
|
+
source: 'global',
|
|
209
|
+
sourcePath: getGlobalConfigPath()
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
source: 'default',
|
|
214
|
+
sourcePath: null
|
|
215
|
+
};
|
|
216
|
+
};
|
|
184
217
|
export const loadBrainlinkConfig = async (cwd = safeCwd()) => {
|
|
185
218
|
const globalConfig = await readJsonConfig(getGlobalConfigPath());
|
|
186
219
|
const localConfigs = await Promise.all(configFilenames.map((filename) => readJsonConfig(resolve(cwd, filename))));
|
|
187
|
-
const merged = [globalConfig, ...localConfigs]
|
|
188
|
-
...state,
|
|
189
|
-
...config
|
|
190
|
-
}), {});
|
|
220
|
+
const merged = mergeConfigLayers([globalConfig, ...localConfigs]);
|
|
191
221
|
return sanitizeConfig(merged);
|
|
192
222
|
};
|
|
223
|
+
export const loadBrainlinkConfigWithSource = async (cwd = safeCwd()) => {
|
|
224
|
+
const globalConfigPath = getGlobalConfigPath();
|
|
225
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
226
|
+
const legacyLocalConfigPath = resolve(cwd, '.brainlink.json');
|
|
227
|
+
const [globalConfig, localConfig, legacyLocalConfig] = await Promise.all([
|
|
228
|
+
readJsonConfig(globalConfigPath),
|
|
229
|
+
readJsonConfig(localConfigPath),
|
|
230
|
+
readJsonConfig(legacyLocalConfigPath)
|
|
231
|
+
]);
|
|
232
|
+
const config = sanitizeConfig(mergeConfigLayers([globalConfig, localConfig, legacyLocalConfig]));
|
|
233
|
+
if (typeof legacyLocalConfig.vault === 'string' && legacyLocalConfig.vault.trim().length > 0) {
|
|
234
|
+
return {
|
|
235
|
+
config,
|
|
236
|
+
vaultSource: {
|
|
237
|
+
source: 'local-legacy',
|
|
238
|
+
sourcePath: legacyLocalConfigPath
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (typeof localConfig.vault === 'string' && localConfig.vault.trim().length > 0) {
|
|
243
|
+
return {
|
|
244
|
+
config,
|
|
245
|
+
vaultSource: {
|
|
246
|
+
source: 'local',
|
|
247
|
+
sourcePath: localConfigPath
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (typeof globalConfig.vault === 'string' && globalConfig.vault.trim().length > 0) {
|
|
252
|
+
return {
|
|
253
|
+
config,
|
|
254
|
+
vaultSource: {
|
|
255
|
+
source: 'global',
|
|
256
|
+
sourcePath: globalConfigPath
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
config,
|
|
262
|
+
vaultSource: {
|
|
263
|
+
source: 'default',
|
|
264
|
+
sourcePath: null
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { getBrainlinkHomePath } from './paths.js';
|
|
4
|
+
const defaultState = {
|
|
5
|
+
byConfigKey: {}
|
|
6
|
+
};
|
|
7
|
+
const statePath = () => join(getBrainlinkHomePath(), 'vault-migration-state.json');
|
|
8
|
+
const sanitizeState = (value) => {
|
|
9
|
+
if (typeof value !== 'object' || value === null) {
|
|
10
|
+
return defaultState;
|
|
11
|
+
}
|
|
12
|
+
const record = value;
|
|
13
|
+
const byConfigKeyRecord = typeof record.byConfigKey === 'object' && record.byConfigKey !== null ? record.byConfigKey : {};
|
|
14
|
+
const byConfigKey = Object.entries(byConfigKeyRecord).reduce((state, [key, vault]) => {
|
|
15
|
+
if (typeof key !== 'string' || key.trim().length === 0) {
|
|
16
|
+
return state;
|
|
17
|
+
}
|
|
18
|
+
if (typeof vault !== 'string' || vault.trim().length === 0) {
|
|
19
|
+
return state;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
[key]: vault.trim()
|
|
24
|
+
};
|
|
25
|
+
}, {});
|
|
26
|
+
return {
|
|
27
|
+
byConfigKey
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const readState = async () => {
|
|
31
|
+
try {
|
|
32
|
+
const raw = await readFile(statePath(), 'utf8');
|
|
33
|
+
return sanitizeState(JSON.parse(raw));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
37
|
+
return defaultState;
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const writeState = async (state) => {
|
|
43
|
+
const path = statePath();
|
|
44
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
45
|
+
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
46
|
+
};
|
|
47
|
+
export const getVaultMigrationStatePath = () => statePath();
|
|
48
|
+
export const getLastConfiguredVaultForKey = async (configKey) => {
|
|
49
|
+
const state = await readState();
|
|
50
|
+
const value = state.byConfigKey[configKey];
|
|
51
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
52
|
+
};
|
|
53
|
+
export const setLastConfiguredVaultForKey = async (configKey, vault) => {
|
|
54
|
+
const key = configKey.trim();
|
|
55
|
+
const value = vault.trim();
|
|
56
|
+
if (key.length === 0 || value.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const state = await readState();
|
|
60
|
+
if (state.byConfigKey[key] === value) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await writeState({
|
|
64
|
+
byConfigKey: {
|
|
65
|
+
...state.byConfigKey,
|
|
66
|
+
[key]: value
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
package/package.json
CHANGED