* feat(skills): remove ClawHub catalog integration Drop the ClawHub registry as a skills catalog source across web server, shared UI, VS Code, docs, and locales. The catalog now serves git-based sources only: the curated Anthropic repo and user-defined repositories. Also removes the now-unused adm-zip dependency. * feat(skills): redesign catalog around curated GitHub repositories Replace the single-source dropdown with a card grid of curated GitHub repositories (Anthropic, OpenAI, Cursor pstack/skills, Matt Pocock) plus user-defined sources. Source cards show skill counts, GitHub stars, and last-updated time; a global search covers all loaded sources. Server: curated sources gain GitHub repo metadata (stars, pushed_at) fetched best-effort with a 3-hour in-memory and on-disk cache; scans run through a concurrency-limited, deduplicated cache with 3-hour TTL persisted across restarts. Refresh still bypasses the cache. Shared UI: source cards, global search with clear button, per-skill GitHub links, install/installed states. VS Code curated list updated to match. All new copy translated across 12 locales. * fix(skills): address catalog review findings - GitHub metadata fetch timeout drops to 1.5s (under the catalog client's 3s deadline) and failed lookups cache briefly (5 min) so repeated catalog loads do not re-hit a failing API. - Disk cache files are written with owner-only permissions (0o600); rename preserves the mode. - loadSource deduplicates concurrent in-flight requests per source and the shared isLoadingSource flag now clears only when the last active source load finishes.
53 lines
1.5 KiB
JavaScript
53 lines
1.5 KiB
JavaScript
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
|
|
const resolveDataDir = () => (process.env.OPENCHAMBER_DATA_DIR
|
|
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
|
|
: path.join(os.homedir(), '.config', 'openchamber'));
|
|
|
|
const readJsonFile = (filePath) => {
|
|
try {
|
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
const parsed = JSON.parse(raw);
|
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Read a persisted cache object from the OpenChamber data directory.
|
|
* Returns null when the file is missing, unreadable, or malformed.
|
|
*/
|
|
export const readDiskCache = (fileName) => {
|
|
try {
|
|
return readJsonFile(path.join(resolveDataDir(), fileName));
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Persist a cache object to the OpenChamber data directory with an atomic
|
|
* temp-file rename. Failures are ignored: the in-memory cache stays
|
|
* authoritative and the next successful write retries persistence.
|
|
*/
|
|
export const writeDiskCache = (fileName, data) => {
|
|
const filePath = path.join(resolveDataDir(), fileName);
|
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
try {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(tempPath, JSON.stringify(data), { encoding: 'utf8', mode: 0o600 });
|
|
fs.renameSync(tempPath, filePath);
|
|
return true;
|
|
} catch {
|
|
try {
|
|
fs.unlinkSync(tempPath);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
return false;
|
|
}
|
|
};
|