upstream: integrar Oasis 1.3.1

Base del merge a tres bandas: git 0.9.5 de epsylon, esta rama y el src de la
APK oficial 1.3.1 (la variante movil: OASIS_MOBILE, regex sin \p{}, audio y
video de Android). Corre en el mismo nodejs-mobile 18.20.4 que ya usabamos.

Se conserva de la rama: Karvan, identidades y multicuenta, topbar y barra
inferior, borrado de EXIF sin sharp, filtro hot del foro, RTC sin STUN y los
modulos apagados en movil (wallet, banking, torrents).

Se toma de upstream lo que ya hace de serie: respuesta citada en chats, ssb-lan
en movil, configuracion en ~/.ssb/oasis (sobrevive a reinstalar el backend).
El asistente de bienvenida deja elegir workflow tambien en movil.
This commit is contained in:
oasis-bot 2026-10-18 02:52:17 +02:00
parent 8d838dd8b1
commit c332afd6ea
265 changed files with 80970 additions and 29167 deletions

View file

@ -1 +0,0 @@
[]

View file

@ -1,3 +0,0 @@
{
"discardedItems": []
}

View file

@ -1 +0,0 @@
[]

View file

@ -1 +0,0 @@
[]

View file

@ -1,7 +1,58 @@
const fs = require('fs');
const path = require('path');
const configFilePath = path.join(__dirname, 'oasis-config.json');
const os = require('os');
const legacyConfigPath = path.join(__dirname, 'oasis-config.json');
const stateDir = process.env.OASIS_STATE_DIR || process.env.ssb_path || path.join(os.homedir(), '.ssb');
const oasisDir = path.join(stateDir, 'oasis');
const configFilePath = path.join(oasisDir, 'oasis-config.json');
const defaultServerConfigPath = path.join(__dirname, 'server-config.json');
const serverConfigFilePath = path.join(oasisDir, 'oasis-server-config.json');
for (const [from, to] of [[path.join(stateDir, 'oasis-config.json'), configFilePath], [path.join(stateDir, 'oasis-server-config.json'), serverConfigFilePath]]) {
try {
if (!fs.existsSync(to) && fs.existsSync(from)) {
fs.mkdirSync(oasisDir, { recursive: true, mode: 0o700 });
fs.renameSync(from, to);
}
} catch (_) {}
}
const writeStateFile = (file, data) => {
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
fs.writeFileSync(tmp, data, { encoding: 'utf8', mode: 0o600 });
fs.renameSync(tmp, file);
try { fs.chmodSync(file, 0o600); } catch (_) {}
};
let configCache = { at: 0, cfg: null };
const CONFIG_TTL_MS = 2000;
const writeConfigFile = (data) => { configCache = { at: 0, cfg: null }; return writeStateFile(configFilePath, data); };
const readJsonFile = (file) => { try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) { return null; } };
const isPlainObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
const mergeDeep = (base, over) => {
if (!isPlainObject(base) || !isPlainObject(over)) return over === undefined ? base : over;
const out = { ...base };
for (const [k, v] of Object.entries(over)) out[k] = isPlainObject(v) && isPlainObject(base[k]) ? mergeDeep(base[k], v) : v;
return out;
};
if (!fs.existsSync(serverConfigFilePath)) {
const shipped = readJsonFile(defaultServerConfigPath);
const hops = shipped && shipped.friends ? shipped.friends.hops : undefined;
try {
if (shipped && shipped.pub === true) writeStateFile(serverConfigFilePath, JSON.stringify(shipped, null, 2));
else if (Number.isFinite(hops) && hops !== 2) writeStateFile(serverConfigFilePath, JSON.stringify({ friends: { hops } }, null, 2));
} catch (_) {}
}
const readServerConfig = () => mergeDeep(readJsonFile(defaultServerConfigPath) || {}, readJsonFile(serverConfigFilePath) || {});
const saveServerConfig = (patch) => writeStateFile(serverConfigFilePath, JSON.stringify(mergeDeep(readJsonFile(serverConfigFilePath) || {}, patch), null, 2));
const inviteCodesPath = path.join(__dirname, 'invite-codes.json');
const readInviteCodes = () => {
const raw = readJsonFile(inviteCodesPath);
return (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((e) => e && typeof e.code === 'string' && e.code.trim());
};
const defaultInvite = () => readInviteCodes()[0] || null;
if (!fs.existsSync(configFilePath) && fs.existsSync(legacyConfigPath)) {
try { writeConfigFile(fs.readFileSync(legacyConfigPath, 'utf8')); } catch (_) {}
}
if (!fs.existsSync(configFilePath)) {
const defaultConfig = {
@ -22,10 +73,10 @@ if (!fs.existsSync(configFilePath)) {
"threadsMod": "on",
"multiverseMod": "on",
"pollsMod": "on",
"fediverseMod": "off",
"fediverseMod": "on",
"invitesMod": "on",
"walletMod": "on",
"legacyMod": "on",
"backupMod": "on",
"devMod": "on",
"cipherMod": "on",
"bookmarksMod": "on",
@ -43,6 +94,14 @@ if (!fs.existsSync(configFilePath)) {
"reportsMod": "on",
"opinionsMod": "on",
"padsMod": "on",
"wikiMod": "on",
"emergenciesMod": "on",
"mailingMod": "on",
"roomsMod": "on",
"phoneMod": "on",
"logisticsMod": "on",
"podcastsMod": "on",
"campaignsMod": "on",
"calendarsMod": "on",
"transfersMod": "on",
"feedMod": "on",
@ -58,6 +117,7 @@ if (!fs.existsSync(configFilePath)) {
"projectsMod": "on",
"industryMod": "on",
"bankingMod": "on",
"schoolMod": "on",
"parliamentMod": "on",
"courtsMod": "on",
"favoritesMod": "on",
@ -65,6 +125,7 @@ if (!fs.existsSync(configFilePath)) {
"mapsMod": "on",
"chatsMod": "on",
"torrentsMod": "on",
"filesMod": "on",
"graphosMod": "on",
"larpMod": "on"
},
@ -74,9 +135,6 @@ if (!fs.existsSync(configFilePath)) {
"pass": "",
"fee": "5"
},
"walletPub": {
"pubId": ""
},
"ai": {
"prompt": "Provide an informative and precise response."
},
@ -87,20 +145,29 @@ if (!fs.existsSync(configFilePath)) {
"language": "en",
"wish": "whole",
"pmVisibility": "whole",
"phone": { "visibility": "mutuals", "dnd": false, "relay": true },
"lanBroadcasting": true
};
fs.writeFileSync(configFilePath, JSON.stringify(defaultConfig, null, 2));
writeConfigFile(JSON.stringify(defaultConfig, null, 2));
}
const getConfig = () => {
const now = Date.now();
if (!configCache.cfg || now - configCache.at > CONFIG_TTL_MS) configCache = { at: now, cfg: readConfig() };
return structuredClone(configCache.cfg);
};
const readConfig = () => {
const configData = fs.readFileSync(configFilePath);
const cfg = JSON.parse(configData);
if (!['whole', 'mutuals', 'only-lan'].includes(cfg.wish)) cfg.wish = 'whole';
if (!['whole', 'mutuals', 'only-lan', 'local'].includes(cfg.wish)) cfg.wish = 'whole';
if (cfg.pmVisibility !== 'whole' && cfg.pmVisibility !== 'mutuals') cfg.pmVisibility = 'whole';
if (!cfg.phone || typeof cfg.phone !== 'object') cfg.phone = {};
cfg.phone = { visibility: cfg.phone.visibility === 'whole' ? 'whole' : 'mutuals', dnd: cfg.phone.dnd === true, relay: cfg.phone.relay !== false, video: cfg.phone.video !== false, ...(typeof cfg.phone.videoDevice === 'string' && cfg.phone.videoDevice.trim() ? { videoDevice: cfg.phone.videoDevice.trim() } : {}) };
if (typeof cfg.ux === 'string') cfg.ux = { current: cfg.ux };
if (!cfg.ux || typeof cfg.ux !== 'object') cfg.ux = { current: 'blocks' };
if (cfg.ux.current === 'menus') cfg.ux.current = 'blocks';
if (cfg.ux.current !== 'blocks' && cfg.ux.current !== 'ainav' && cfg.ux.current !== 'chats') cfg.ux.current = 'blocks';
if (cfg.ux.current !== 'blocks' && cfg.ux.current !== 'ainav' && cfg.ux.current !== 'chats' && cfg.ux.current !== 'feed' && cfg.ux.current !== 'phone') cfg.ux.current = 'blocks';
if (cfg.ux.current === 'ainav' && cfg.modules && cfg.modules.aiNavMod !== 'on') cfg.ux.current = 'blocks';
if (cfg.ux.current === 'chats' && cfg.modules && cfg.modules.chatsMod !== 'on') cfg.ux.current = 'blocks';
@ -113,14 +180,30 @@ const getConfig = () => {
pins = pins.filter((k, i) => pins.indexOf(k) === i).slice(0, 4);
cfg.bottomBarPins = pins;
delete cfg.bottomBarPin;
if (cfg.ux.current === 'phone' && cfg.modules && cfg.modules.phoneMod === 'off') cfg.ux.current = 'blocks';
if (cfg.ux.current === 'feed' && cfg.modules && cfg.modules.feedMod === 'off') cfg.ux.current = 'blocks';
if (!cfg.blobCache || typeof cfg.blobCache !== 'object' || !Number.isFinite(Number(cfg.blobCache.maxMB))) cfg.blobCache = { maxMB: 0 };
if (!Number.isFinite(Number(cfg.blobCache.pubMaxMB))) cfg.blobCache.pubMaxMB = 0;
if (cfg.modules && typeof cfg.modules === 'object') {
if (cfg.modules.backupMod === undefined) cfg.modules.backupMod = cfg.modules.legacyMod === 'off' ? 'off' : 'on';
for (const mod of ['wikiMod', 'emergenciesMod', 'mailingMod', 'logisticsMod', 'podcastsMod', 'campaignsMod', 'filesMod', 'phoneMod', 'roomsMod']) {
if (cfg.modules[mod] === undefined) cfg.modules[mod] = 'on';
}
}
return cfg;
};
const saveConfig = (newConfig) => {
fs.writeFileSync(configFilePath, JSON.stringify(newConfig, null, 2));
writeConfigFile(JSON.stringify(newConfig, null, 2));
};
module.exports = {
configFilePath,
serverConfigFilePath,
getConfig,
saveConfig,
readServerConfig,
saveServerConfig,
readInviteCodes,
defaultInvite,
};

View file

@ -1,27 +0,0 @@
{
"audios": [],
"blogs": [],
"bookmarks": [],
"calendars": [],
"chats": [],
"documents": [],
"events": [],
"forum": [],
"housing": [],
"images": [],
"jobs": [],
"logs": [],
"maps": [],
"market": [],
"pads": [],
"polls": [],
"projects": [],
"reports": [],
"shopProducts": [],
"shops": [],
"tasks": [],
"torrents": [],
"transfers": [],
"videos": [],
"votes": []
}

View file

@ -1 +0,0 @@
{"pending":[],"accepted":[],"lastAcceptMs":0}

View file

@ -0,0 +1,23 @@
[
{
"name": "SNH \"La Plaza\"",
"description": "A shared place to begin an experimental utopia ...",
"url": "https://solarnethub.com",
"code": "pub.solarnethub.com:8008:@0qSCyK3xyL71X4qKkmf84Cb2riP6OeUqxCvbP2Z6HWs=.ed25519~7mC1c1gBJe8pmnWI9eIUYpFtEPUOiuHAxbzfxP/f0Ak=",
"createdAt": "2026-05-24T00:00:00.000Z"
},
{
"name": "t0k10 h4x0rs",
"description": "n0 l0gin. n0 m41l. n0 cl0ud...",
"url": "https://pub.elhack.net",
"code": "pub.elhack.net:8008:@R8WWz72Pz66Jw0SObjxe9Boio5WzskGpTK7wsPzeUEc=.ed25519~HaBhhmtYnYYfBA2j+JoYnckWXhPSzFGn0TCY1I9rP98=",
"createdAt": "2026-08-11T00:00:00.000Z"
},
{
"name": "Acampada 26s",
"description": "Red de la acampada en Madrid del #26s",
"url": "https://acampada26s.net",
"code": "madrid.acampada26s.net:8008:@Wt9tL9XtJCb7sZjX0+FtDlzziFLumWo1DAbou4Q5m3o=.ed25519~tkAoj1gE6dX8AcAOLQXs22VWFWKfOeJdT+7pBPfgbDA=",
"createdAt": "2026-09-26T00:00:00.000Z"
}
]

View file

@ -11,9 +11,7 @@
"fediverseMod": "on",
"invitesMod": "on",
"walletMod": "off",
"legacyMod": "on",
"devMod": "off",
"industryMod": "on",
"cipherMod": "on",
"bookmarksMod": "on",
"videosMod": "on",
@ -34,17 +32,20 @@
"calendarsMod": "on",
"transfersMod": "off",
"feedMod": "on",
"pixeliaMod": "on",
"pixeliaMod": "off",
"melodyMod": "on",
"agendaMod": "on",
"aiMod": "off",
"aiNavMod": "off",
"forumMod": "on",
"gamesMod": "off",
"housingMod": "on",
"jobsMod": "on",
"shopsMod": "on",
"projectsMod": "on",
"industryMod": "on",
"bankingMod": "off",
"schoolMod": "on",
"parliamentMod": "on",
"courtsMod": "off",
"favoritesMod": "on",
@ -52,15 +53,24 @@
"mapsMod": "off",
"chatsMod": "on",
"torrentsMod": "off",
"filesMod": "on",
"phoneMod": "on",
"roomsMod": "on",
"graphosMod": "on",
"larpMod": "on",
"housingMod": "off"
"backupMod": "on",
"campaignsMod": "on",
"emergenciesMod": "on",
"logisticsMod": "on",
"mailingMod": "on",
"podcastsMod": "on",
"wikiMod": "on"
},
"wallet": {
"url": "http://localhost:7474",
"url": "http://127.0.0.1:7474",
"user": "",
"pass": "",
"fee": "5"
"fee": "0.01"
},
"walletPub": {
"pubId": ""
@ -76,6 +86,15 @@
"wish": "whole",
"pmVisibility": "whole",
"lanBroadcasting": true,
"phone": {
"visibility": "whole",
"dnd": false,
"relay": true
},
"blobCache": {
"maxMB": 0,
"pubMaxMB": 0
},
"bottomBarPins": [
"search",
"inbox",
@ -91,4 +110,4 @@
},
"relayOnly": false
}
}
}

View file

@ -11,13 +11,6 @@
"dunbar": 300,
"hops": 2
},
"gossip": {
"connections": 20,
"local": true,
"friends": true,
"seed": true,
"global": true
},
"replicationScheduler": {
"autostart": true,
"partialReplication": null
@ -54,7 +47,11 @@
}
],
"tunnel": [],
"onion": [],
"onion": [
{
"transform": "shs"
}
],
"ws": []
}
}

View file

@ -1,28 +1,65 @@
let _inboxCount = 0;
let _phoneCount = 0;
let _phoneCall = null;
let _phoneRoom = null;
let _pubIds = new Set();
let _inboxPmCount = 0;
let _inboxNotifCount = 0;
let _carbonHcT = 0;
let _walletReady = false;
let _donatableAuthors = new Set();
let _carbonHcH = 0;
let _lastRefresh = 0;
let _onlinePeers = null;
let _syncedPeers = null;
let _inboxUnread = null;
let _lastSyncTs = null;
let _ecoValue = null;
let _lastActivity = null;
let _maxBlockBytes = 0;
let _inhabitantCount = 0;
let _tribesCount = 0;
let _mentionsCount = 0;
let _mentionsTotal = 0;
let _bestMatch = null;
let _dismissedSuggestion = null;
let _matchPool = [];
let _matchIdx = -1;
let _sectionMatches = new Map();
const _dismissedSuggestions = new Set();
let _suggestionsQuiet = false;
let _featuredEmergency = null;
let _dismissedEmergency = null;
module.exports = {
getInboxCount: () => _inboxCount,
setInboxCount: (n) => { _inboxCount = n; },
getPhoneCount: () => _phoneCount,
setPhoneCount: (n) => { _phoneCount = Number(n) || 0; },
getPhoneCall: () => _phoneCall,
setPhoneCall: (st) => { _phoneCall = st || null; },
getPhoneRoom: () => _phoneRoom,
setPhoneRoom: (st) => { _phoneRoom = st || null; },
getPubIds: () => _pubIds,
setPubIds: (set) => { _pubIds = set instanceof Set ? set : new Set(); },
isPubId: (id) => _pubIds.has(String(id || '')),
getInboxPmCount: () => _inboxPmCount,
setInboxPmCount: (n) => { _inboxPmCount = n; },
getInboxNotifCount: () => _inboxNotifCount,
setInboxNotifCount: (n) => { _inboxNotifCount = n; },
getCarbonHcT: () => _carbonHcT,
setCarbonHcT: (n) => { _carbonHcT = n; },
getDonatableAuthors: () => _donatableAuthors,
setDonatableAuthors: (set) => { _donatableAuthors = set instanceof Set ? set : new Set(); },
getWalletReady: () => _walletReady,
setWalletReady: (v) => { _walletReady = !!v; },
getCarbonHcH: () => _carbonHcH,
setCarbonHcH: (n) => { _carbonHcH = n; },
getLastRefresh: () => _lastRefresh,
setLastRefresh: (t) => { _lastRefresh = t; },
getOnlinePeerCount: () => _onlinePeers,
setOnlinePeerCount: (n) => { _onlinePeers = n; },
getSyncedPeerCount: () => _syncedPeers,
setSyncedPeerCount: (n) => { _syncedPeers = Math.max(0, Number(n) || 0); },
getInboxUnreadCount: () => _inboxUnread,
setInboxUnreadCount: (n) => { _inboxUnread = n; },
getLastSyncTs: () => _lastSyncTs,
@ -33,12 +70,36 @@ module.exports = {
setLastActivity: (a) => { _lastActivity = a; },
getMaxBlockBytes: () => _maxBlockBytes,
setMaxBlockBytes: (n) => { if (Number(n) > _maxBlockBytes) _maxBlockBytes = Number(n); },
getMentionsTotal: () => _mentionsTotal,
setMentionsTotal: (n) => { _mentionsTotal = Math.max(0, Number(n) || 0); },
getMentionsCount: () => _mentionsCount,
setMentionsCount: (n) => { _mentionsCount = Math.max(0, Number(n) || 0); },
getInhabitantCount: () => _inhabitantCount,
setInhabitantCount: (n) => { _inhabitantCount = Math.max(0, Number(n) || 0); },
getTribesCount: () => _tribesCount,
setTribesCount: (n) => { _tribesCount = Math.max(0, Number(n) || 0); },
getFeaturedEmergency: () => _featuredEmergency,
setFeaturedEmergency: (a) => { _featuredEmergency = a || null; },
getDismissedEmergency: () => _dismissedEmergency,
setDismissedEmergency: (id) => { _dismissedEmergency = id || null; },
getBestMatch: () => _bestMatch,
setBestMatch: (m) => { _bestMatch = m || null; },
getDismissedSuggestion: () => _dismissedSuggestion,
setDismissedSuggestion: (href) => { _dismissedSuggestion = href || null; }
setMatchPool: (pool) => {
const before = new Set(_matchPool.map(m => m.href));
_matchPool = Array.isArray(pool) ? pool.filter(m => m && m.href) : [];
if (_matchPool.some(m => !before.has(m.href) && !_dismissedSuggestions.has(m.href))) _suggestionsQuiet = false;
},
nextBestMatch: () => {
const avail = _matchPool.filter(m => !_dismissedSuggestions.has(m.href));
if (!avail.length) { _bestMatch = null; return null; }
_matchIdx = (_matchIdx + 1) % avail.length;
_bestMatch = avail[_matchIdx];
return _bestMatch;
},
setSectionMatches: (map) => { _sectionMatches = map instanceof Map ? map : new Map(); },
getSectionMatch: (section) => (_sectionMatches.get(String(section || '')) || []).find(m => !_dismissedSuggestions.has(m.href)) || null,
isKnownSuggestion: (href) => !!href && (_matchPool.some(m => m.href === href) || [..._sectionMatches.values()].some(list => list.some(m => m.href === href))),
dismissSuggestion: (href) => { if (href) { _dismissedSuggestions.add(href); _suggestionsQuiet = true; } },
areSuggestionsQuiet: () => _suggestionsQuiet,
isSuggestionDismissed: (href) => _dismissedSuggestions.has(href)
};

View file

@ -1,7 +0,0 @@
{
"name": "SNH \"La Plaza\"",
"description": "A shared place to begin a utopia ...",
"url": "https://pub.solarnethub.com",
"code": "pub.solarnethub.com:8008:@0qSCyK3xyL71X4qKkmf84Cb2riP6OeUqxCvbP2Z6HWs=.ed25519~7mC1c1gBJe8pmnWI9eIUYpFtEPUOiuHAxbzfxP/f0Ak=",
"createdAt": "2026-05-24T00:00:00.000Z"
}

View file

@ -0,0 +1,112 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const LEGACY_CONFIG_DIR = __dirname;
const ROOT = 'oasis';
const FOLDERS = {
'wallet-addresses.json': 'banking',
'banking-address-book.json': 'banking',
'banking-allocations.json': 'banking',
'banking-eco-history.json': 'banking',
'banking-epochs.json': 'banking',
'banking-ubi-notice.json': 'banking',
'banking-confirm-notice.json': 'banking',
'banking-ubi-paid.json': 'banking',
'banking-funds-history.json': 'banking',
'content_favorites.json': 'content',
'blob-access.json': 'content',
'follow_state.json': 'content',
'invites-used.json': 'content',
'agenda-config.json': 'content',
'AI-history.json': 'ai',
'AI-vectors.json': 'ai',
'AI-search-vectors.json': 'ai',
'fediverse-accounts.json': 'multiverse',
'nextcloud-account.json': 'multiverse',
'oasis-backup.json': 'backup',
'oasis-first-contact': 'flags',
'oasis-mentions-seen': 'flags',
'oasis-inbox-read': 'flags',
'oasis-inbox-archived': 'flags',
'oasis-political-seen': 'flags',
'gossip_unfollowed.json': 'peers',
'lan-peers.json': 'peers',
'peer-health.json': 'peers',
'phone-history.json': 'phone',
'phone-seen.json': 'phone',
'snapshot.oasissn': 'content',
'snapshot-recent.oasissn': 'content'
};
const ALIASES = {
'oasis-backup.json': ['backup.json']
};
const ssbDir = () => {
try {
const resolved = require('../server/ssb_config').statePath('.');
if (resolved) return path.resolve(resolved);
} catch (_) {}
if (process.env.OASIS_STATE_DIR) return process.env.OASIS_STATE_DIR;
return path.join(os.homedir(), '.ssb');
};
const stateDir = (name) => path.join(ssbDir(), ROOT, FOLDERS[name] || '');
const statePath = (name) => {
const dir = stateDir(name);
const target = path.join(dir, name);
try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
if (!fs.existsSync(target)) {
const candidates = [name, ...(ALIASES[name] || [])]
.flatMap(n => [path.join(ssbDir(), n), path.join(LEGACY_CONFIG_DIR, n)]);
const legacy = candidates.find(p => fs.existsSync(p));
if (legacy) {
try { fs.renameSync(legacy, target); }
catch (_) { fs.copyFileSync(legacy, target); try { fs.unlinkSync(legacy); } catch (_) {} }
}
}
} catch (_) {}
return target;
};
const keysDir = (base = null) => {
const root = ssbDir();
if (base && path.resolve(base) !== root) return path.join(base, 'keys');
const target = path.join(root, ROOT, 'keys');
try {
if (!fs.existsSync(target)) {
fs.mkdirSync(path.dirname(target), { recursive: true });
const legacy = path.join(root, 'keys');
if (fs.existsSync(legacy)) fs.renameSync(legacy, target);
else fs.mkdirSync(target, { recursive: true, mode: 0o700 });
}
} catch (_) {}
return target;
};
const migrateAll = () => {
for (const name of Object.keys(FOLDERS)) {
try {
const legacy = [name, ...(ALIASES[name] || [])]
.flatMap(n => [path.join(ssbDir(), n), path.join(LEGACY_CONFIG_DIR, n)])
.find(p => fs.existsSync(p));
if (legacy) statePath(name);
} catch (_) {}
}
for (const name of Object.keys(FOLDERS)) {
if (FOLDERS[name] !== 'flags' || name.endsWith('.json')) continue;
const legacy = path.join(ssbDir(), name);
const target = path.join(stateDir(name), name);
try { if (fs.existsSync(legacy) && fs.existsSync(target)) fs.unlinkSync(legacy); } catch (_) {}
}
for (const leftover of ['keys', 'node_modules']) {
const dir = path.join(ssbDir(), leftover);
try { if (fs.statSync(dir).isDirectory() && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir); } catch (_) {}
}
};
module.exports = { statePath, stateDir, ssbDir, keysDir, migrateAll };

View file

@ -1 +0,0 @@
{}