free-coding-models 0.5.73 → 0.5.75
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 +16 -5
- package/bin/free-coding-models.js +13 -1
- package/changelog/v0.5.74.md +17 -0
- package/changelog/v0.5.75.md +7 -0
- package/package.json +3 -3
- package/src/core/cache.js +13 -2
- package/src/core/config.js +63 -5
- package/src/core/security.js +4 -2
- package/src/core/utils.js +13 -0
- package/src/tui/cli-help.js +1 -0
- package/web/dist/assets/index-C98mFTET.js +40 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-DkMffbuv.js +0 -40
package/README.md
CHANGED
|
@@ -673,6 +673,7 @@ Flags combine freely in any order. Run `free-coding-models --help` to print the
|
|
|
673
673
|
| `--show-broken` | Don't auto-hide broken models this run |
|
|
674
674
|
| `--check-drift` | Diff `sources.js` against `models.dev`; exit 1 on mismatch |
|
|
675
675
|
| `--no-telemetry` | Disable anonymous telemetry for this run |
|
|
676
|
+
| `--config-dir <dir>` | Store `config.json` + `backups/` in `<dir>` (e.g. `--config-dir ~/.config/free-coding-models`) |
|
|
676
677
|
|
|
677
678
|
**Tool launchers** — start the TUI pre-configured for a tool, then `Enter` writes the model into that tool's config and launches it:
|
|
678
679
|
|
|
@@ -861,11 +862,11 @@ A: Yes — see [`sources.js`](./sources.js) for the model catalog format.
|
|
|
861
862
|
|
|
862
863
|
## Star History
|
|
863
864
|
|
|
864
|
-
<a href="https://
|
|
865
|
+
<a href="https://star-history.dera.page/#vava-nessa/free-coding-models&type=timeline&logscale&legend=top-left">
|
|
865
866
|
<picture>
|
|
866
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://
|
|
867
|
-
<source media="(prefers-color-scheme: light)" srcset="https://
|
|
868
|
-
<img alt="Star History Chart" src="https://
|
|
867
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=vava-nessa/free-coding-models&type=timeline&theme=dark&logscale&legend=top-left" />
|
|
868
|
+
<source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=vava-nessa/free-coding-models&type=timeline&logscale&legend=top-left" />
|
|
869
|
+
<img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=vava-nessa/free-coding-models&type=timeline&logscale&legend=top-left" />
|
|
869
870
|
</picture>
|
|
870
871
|
</a>
|
|
871
872
|
|
|
@@ -893,6 +894,8 @@ A: Yes — see [`sources.js`](./sources.js) for the model catalog format.
|
|
|
893
894
|
<td align="center" width="120"><a href="https://github.com/stgreenb"><img src="https://avatars.githubusercontent.com/u/18483964?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="stgreenb"></a></td>
|
|
894
895
|
<td align="center" width="120"><a href="https://github.com/MoriDanWork"><img src="https://avatars.githubusercontent.com/u/55363096?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="MoriDanWork"></a></td>
|
|
895
896
|
<td align="center" width="120"><a href="https://github.com/fan92rus"><img src="https://avatars.githubusercontent.com/u/13201333?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="fan92rus"></a></td>
|
|
897
|
+
<td align="center" width="120"><a href="https://github.com/Muhammad95959"><img src="https://avatars.githubusercontent.com/u/75130655?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="Muhammad95959"></a></td>
|
|
898
|
+
<td align="center" width="120"><a href="https://github.com/FaintFlower"><img src="https://avatars.githubusercontent.com/u/310248465?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="FaintFlower"></a></td>
|
|
896
899
|
</tr>
|
|
897
900
|
<tr>
|
|
898
901
|
<td align="center"><a href="https://github.com/vava-nessa"><sub><b>vava-nessa</b></sub></a></td>
|
|
@@ -905,9 +908,17 @@ A: Yes — see [`sources.js`](./sources.js) for the model catalog format.
|
|
|
905
908
|
<td align="center"><a href="https://github.com/stgreenb"><sub><b>stgreenb</b></sub></a></td>
|
|
906
909
|
<td align="center"><a href="https://github.com/MoriDanWork"><sub><b>MoriDanWork</b></sub></a></td>
|
|
907
910
|
<td align="center"><a href="https://github.com/fan92rus"><sub><b>fan92rus</b></sub></a></td>
|
|
911
|
+
<td align="center"><a href="https://github.com/Muhammad95959"><sub><b>Muhammad95959</b></sub></a></td>
|
|
912
|
+
<td align="center"><a href="https://github.com/FaintFlower"><sub><b>FaintFlower</b></sub></a></td>
|
|
908
913
|
</tr>
|
|
909
914
|
<tr>
|
|
910
|
-
<td align="center" colspan="
|
|
915
|
+
<td align="center" colspan="12"><sub>🛡️ <b>fan92rus</b> — Windows path traversal fix (<code>path.sep</code>)</sub></td>
|
|
916
|
+
</tr>
|
|
917
|
+
<tr>
|
|
918
|
+
<td align="center" colspan="12"><sub>📁 <b>Muhammad95959</b> — <code>--config-dir</code> flag & XDG support</sub></td>
|
|
919
|
+
</tr>
|
|
920
|
+
<tr>
|
|
921
|
+
<td align="center" colspan="12"><sub>⭐ <b>FaintFlower</b> — Star History chart mirror fix</sub></td>
|
|
911
922
|
</tr>
|
|
912
923
|
</table>
|
|
913
924
|
|
|
@@ -11,11 +11,23 @@
|
|
|
11
11
|
// 📖 IMPORTANT: these checks MUST run synchronously before any static imports
|
|
12
12
|
// 📖 resolve, because router-daemon.js reads FCM_DEV at module load time.
|
|
13
13
|
import { existsSync, readFileSync } from 'node:fs'
|
|
14
|
-
import { join, dirname } from 'node:path'
|
|
14
|
+
import { join, dirname, resolve } from 'node:path'
|
|
15
15
|
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { homedir } from 'node:os'
|
|
16
17
|
if (process.argv.includes('--dev') || (!process.env.FCM_DEV && existsSync(join(dirname(fileURLToPath(import.meta.url)), '..', '.git')))) {
|
|
17
18
|
process.env.FCM_DEV = '1'
|
|
18
19
|
}
|
|
20
|
+
// 📖 --config-dir <dir>: point FCM's config files (config.json + backups/) at an
|
|
21
|
+
// 📖 arbitrary directory (e.g. the XDG layout ~/.config/free-coding-models).
|
|
22
|
+
// 📖 Stored as FCM_CONFIG_DIR BEFORE any static imports resolve so
|
|
23
|
+
// 📖 src/core/config.js picks the path up at module load time. An externally-set
|
|
24
|
+
// 📖 FCM_CONFIG_DIR (e.g. Docker) is preserved when the flag is absent.
|
|
25
|
+
const _configDirIdx = process.argv.indexOf('--config-dir')
|
|
26
|
+
if (_configDirIdx !== -1 && process.argv[_configDirIdx + 1] && !process.argv[_configDirIdx + 1].startsWith('--')) {
|
|
27
|
+
const _raw = process.argv[_configDirIdx + 1].trim()
|
|
28
|
+
const _expanded = _raw === '~' ? homedir() : _raw.startsWith('~/') ? join(homedir(), _raw.slice(2)) : _raw
|
|
29
|
+
process.env.FCM_CONFIG_DIR = resolve(_expanded)
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
import chalk from 'chalk';
|
|
21
33
|
import { parseArgs, TIER_LETTER_MAP } from '../src/core/utils.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog v0.5.74 - 2026-08-22
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **`--config-dir <dir>` flag for XDG and custom config locations** — you can now point free-coding-models at any directory for its state files via `free-coding-models --config-dir ~/.config/free-coding-models` (or any path) or via `FCM_CONFIG_DIR` (useful for Docker). When set, `config.json`, `backups/` and `cache.json` all live together in the chosen directory instead of scattering `~/.free-coding-models.json` + `~/.free-coding-models.cache.json` + `~/.free-coding-models.backups`. The flag is parsed before any module loads so `src/core/config.js`, `src/core/cache.js` and `src/core/security.js` all respect it, `docker-entrypoint.sh` and `src/tui/cli-help.js` are updated, and 12 new tests cover `getConfigDir()`, `CONFIG_PATH` and cache relocation. With the follow-up fix, `~` and `~/` are now correctly expanded to your homedir (previously `resolve()` would treat `~` as a literal folder) and `saveConfig()` / `saveCache()` auto-create the target directory on first run so an empty XDG folder no longer throws `ENOENT`. Thanks @Muhammad95959 for the feature (PR #158).
|
|
5
|
+
- **Dependabot auto-merge workflow** — new `.github/workflows/dependabot-auto-merge.yml` that squash-merges the `patch-and-minor` group automatically when required checks (`audit`, `lockfile-lint`, `Vercel`, `semgrep`) are green. Major updates stay manual for human review. This keeps weekly dependency churn low while preserving safety for breaking changes.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Star History chart in README was showing a "GitHub restricted access" placeholder** — GitHub restricted stargazer data and `api.star-history.com/chart` now returns an SVG with `x-chart-status: restricted` and a message instead of the real graph. The README now points to the working mirror `star-history.dera.page/svg` and link `star-history.dera.page/#...`, verified to return a real 200 SVG. Fixes the broken image at the bottom of the README. Thanks @FaintFlower (PR #159).
|
|
9
|
+
- **First-run crash when `--config-dir` points to a fresh directory** — previously `saveConfig` wrote to `${CONFIG_PATH}.tmp-...` and then `rename` without ensuring the parent dir exists, which threw `ENOENT` on a non-existent XDG path. Both `saveConfig` (via `dirname(CONFIG_PATH)`) and `saveCache` (via `dirname(cachePath)`) now `mkdir -p` with `0o700` before writing.
|
|
10
|
+
- **Tilde not expanded for `--config-dir` / `FCM_CONFIG_DIR`** — `resolve("~/...")` would create a literal `~/` folder under `cwd`. Added `expandTilde()` that maps `~` and `~/` to `homedir()` before `resolve()`, in both `src/core/config.js` and the pre-import handling in `bin/free-coding-models.js`.
|
|
11
|
+
- **Test `MODELS flat array matches sources count` now accounts for deprecated models** — `sources.js` filters models with `deprecatedAfter < today` (e.g. `cerebras/zai-glm-4.7` deprecated 2026-08-17) when building `MODELS`, but the test counted all models. The test now mirrors the same filter so the suite is green (813/813).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **Dependencies (deps #152, patch-and-minor group, 6 updates)** — `@tabler/icons-react` 3.45.0 -> 3.46.0, `kandown` 0.34.3 -> 0.50.0, `@tanstack/react-virtual` 3.14.8 -> 3.14.9, `@vitejs/plugin-react` 6.0.4 -> 6.0.5, `vite` 8.1.5 -> 8.2.1, `vite-plus` 0.2.6 -> 0.2.9. All checks (audit, lockfile-lint, Vercel, semgrep, notify-discord) passed. Major group (`chalk` 5.6.2 -> 6.0.0 which requires Node 22, plus `@tanstack/react-table` 8 -> 9) is left for manual review and not auto-merged.
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
- 813 tests passing (up from 812 after fixing the deprecated-model count and adding 12 new tests for `--config-dir` / cache relocation).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog v0.5.75 - 2026-08-22
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- **Contributors table now lists the two latest contributors** — `README.md:883` previously showed 10 avatars ending at `fan92rus`. Added `@Muhammad95959` (`--config-dir` flag, PR #158) and `@FaintFlower` (Star History mirror, PR #159) with their avatars and a dedicated footer line each, and updated `colspan` from 10 to 12. `AGENTS.md:104` contributor list also updated so future automation picks them up.
|
|
5
|
+
|
|
6
|
+
### Docs
|
|
7
|
+
- **No code changes** — pure documentation sync so the published npm README and GitHub README correctly credit recent PR authors.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.75",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
],
|
|
59
59
|
"scripts": {
|
|
60
60
|
"start": "node bin/free-coding-models.js",
|
|
61
|
-
"test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js test/probe-cache.test.js test/passive-quota.test.js test/runtime-telemetry.test.js test/extended-benchmarks.test.js test/models-dev.test.js test/model-merger.test.js",
|
|
61
|
+
"test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js test/probe-cache.test.js test/passive-quota.test.js test/runtime-telemetry.test.js test/extended-benchmarks.test.js test/models-dev.test.js test/model-merger.test.js test/config-dir.test.js test/cache-dir.test.js",
|
|
62
62
|
"test:extended-benchmarks": "node --test test/extended-benchmarks.test.js",
|
|
63
63
|
"test:models-dev": "node --test test/models-dev.test.js",
|
|
64
64
|
"update:benchmarks": "node scripts/update-benchmarks.mjs",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"@tabler/icons-react": "^3.44.0",
|
|
77
77
|
"@tanstack/react-table": "^8.21.3",
|
|
78
78
|
"chalk": "^5.6.2",
|
|
79
|
-
"kandown": "^0.
|
|
79
|
+
"kandown": "^0.50.0",
|
|
80
80
|
"socket.io": "^4.8.3",
|
|
81
81
|
"socket.io-client": "^4.8.3",
|
|
82
82
|
"@tanstack/react-virtual": "^3.14.2"
|
package/src/core/cache.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @file cache.js
|
|
3
3
|
* @description Persistent cache for ping results to speed up startup.
|
|
4
4
|
*
|
|
5
|
-
* 📖 Cache file location:
|
|
5
|
+
* 📖 Cache file location: <config-dir>/cache.json when a config dir is chosen
|
|
6
|
+
* 📖 (--config-dir / FCM_CONFIG_DIR), else ~/.free-coding-models.cache.json.
|
|
6
7
|
* 📖 File permissions: 0o600 (user read/write only — contains API timing data)
|
|
7
8
|
*
|
|
8
9
|
* 📖 Why caching matters:
|
|
@@ -50,13 +51,18 @@ import fs from 'node:fs'
|
|
|
50
51
|
import path from 'node:path'
|
|
51
52
|
import os from 'node:os'
|
|
52
53
|
import { fileURLToPath } from 'node:url'
|
|
54
|
+
import { getConfigDir } from './config.js'
|
|
53
55
|
|
|
54
56
|
// 📖 Cache TTL: 5 minutes in milliseconds
|
|
55
57
|
// 📖 Ping results are considered fresh for this duration
|
|
56
58
|
const CACHE_TTL = 5 * 60 * 1000
|
|
57
59
|
|
|
58
|
-
// 📖 Get cache file path —
|
|
60
|
+
// 📖 Get cache file path — follows the chosen config dir (--config-dir /
|
|
61
|
+
// 📖 FCM_CONFIG_DIR) so the cache lands next to config.json + backups/;
|
|
62
|
+
// 📖 falls back to the classic ~/.free-coding-models.cache.json otherwise.
|
|
59
63
|
export function getCachePath() {
|
|
64
|
+
const configDir = getConfigDir()
|
|
65
|
+
if (configDir) return path.join(configDir, 'cache.json')
|
|
60
66
|
const homeDir = os.homedir()
|
|
61
67
|
return path.join(homeDir, '.free-coding-models.cache.json')
|
|
62
68
|
}
|
|
@@ -96,6 +102,11 @@ export function loadCache() {
|
|
|
96
102
|
// 📖 providerTier: Current ping cadence ("fast" | "normal" | "slow")
|
|
97
103
|
export function saveCache(results, providerTier = 'normal') {
|
|
98
104
|
const cachePath = getCachePath()
|
|
105
|
+
// 📖 Ensure parent dir exists (XDG first-run would otherwise ENOENT when cache lives in --config-dir)
|
|
106
|
+
try {
|
|
107
|
+
const parentDir = path.dirname(cachePath)
|
|
108
|
+
if (!fs.existsSync(parentDir)) fs.mkdirSync(parentDir, { mode: 0o700, recursive: true })
|
|
109
|
+
} catch { /* best-effort */ }
|
|
99
110
|
|
|
100
111
|
try {
|
|
101
112
|
const models = {}
|
package/src/core/config.js
CHANGED
|
@@ -103,11 +103,55 @@
|
|
|
103
103
|
|
|
104
104
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs'
|
|
105
105
|
import { homedir } from 'node:os'
|
|
106
|
-
import { join } from 'node:path'
|
|
106
|
+
import { join, resolve, dirname } from 'node:path'
|
|
107
107
|
import { syncShellEnv } from './shell-env.js'
|
|
108
108
|
|
|
109
|
-
// 📖
|
|
110
|
-
|
|
109
|
+
// 📖 Expand leading ~/ or bare ~ to homedir so --config-dir ~/.config/... works.
|
|
110
|
+
// 📖 resolve() alone does NOT expand tilde, it would treat ~ as a literal folder.
|
|
111
|
+
function expandTilde(input) {
|
|
112
|
+
if (input === '~') return homedir()
|
|
113
|
+
if (input.startsWith('~/')) return join(homedir(), input.slice(2))
|
|
114
|
+
return input
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 📖 Resolve a raw config dir string to an absolute path with tilde support.
|
|
118
|
+
function resolveConfigDir(raw) {
|
|
119
|
+
if (typeof raw !== 'string' || !raw.trim()) return null
|
|
120
|
+
const expanded = expandTilde(raw.trim())
|
|
121
|
+
return resolve(expanded)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 📖 getConfigDir: Resolve the user-chosen config directory — set via the
|
|
125
|
+
// 📖 `--config-dir <dir>` CLI flag or via FCM_CONFIG_DIR directly (Docker).
|
|
126
|
+
// 📖 The CLI flag wins over the env var, and the env var wins over nothing.
|
|
127
|
+
// 📖 Returns null so the default dotfile layout in $HOME keeps working unchanged.
|
|
128
|
+
// 📖 NOTE: scanning process.argv here (rather than only trusting the env var)
|
|
129
|
+
// 📖 matters because ESM static imports are hoisted — the bin entry's flag
|
|
130
|
+
// 📖 handling runs after config.js is evaluated, so CONFIG_PATH must be able
|
|
131
|
+
// 📖 to see `--config-dir` on its own.
|
|
132
|
+
export function getConfigDir() {
|
|
133
|
+
const fromArgv = configDirFromArgv()
|
|
134
|
+
if (fromArgv) return fromArgv
|
|
135
|
+
return resolveConfigDir(process.env.FCM_CONFIG_DIR)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 📖 Scan process.argv for `--config-dir <dir>` — the raw value before any
|
|
139
|
+
// 📖 other arg parsing so module-load-time constants like CONFIG_PATH pick it up.
|
|
140
|
+
function configDirFromArgv() {
|
|
141
|
+
const argv = process.argv || []
|
|
142
|
+
const idx = argv.indexOf('--config-dir')
|
|
143
|
+
if (idx === -1) return null
|
|
144
|
+
const raw = argv[idx + 1]
|
|
145
|
+
if (typeof raw !== 'string' || !raw.trim() || raw.startsWith('--')) return null
|
|
146
|
+
return resolveConfigDir(raw)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 📖 New JSON config path — stores all providers' API keys + enabled state.
|
|
150
|
+
// 📖 Overridable via `--config-dir <dir>`: config.json lives inside the chosen
|
|
151
|
+
// 📖 directory (e.g. the XDG layout ~/.config/free-coding-models).
|
|
152
|
+
export const CONFIG_PATH = getConfigDir()
|
|
153
|
+
? join(getConfigDir(), 'config.json')
|
|
154
|
+
: join(homedir(), '.free-coding-models.json')
|
|
111
155
|
export { ENV_VARS }
|
|
112
156
|
|
|
113
157
|
// 📖 Runtime data directory — backups and local snapshots live here.
|
|
@@ -690,6 +734,11 @@ export function loadConfig() {
|
|
|
690
734
|
* @returns {{ success: boolean, error?: string, backupCreated?: boolean }}
|
|
691
735
|
*/
|
|
692
736
|
export function saveConfig(config, options = {}) {
|
|
737
|
+
// 📖 Ensure the config directory exists before first write (XDG first-run would otherwise ENOENT)
|
|
738
|
+
try {
|
|
739
|
+
const parentDir = dirname(CONFIG_PATH)
|
|
740
|
+
if (!existsSync(parentDir)) mkdirSync(parentDir, { mode: 0o700, recursive: true })
|
|
741
|
+
} catch { /* best-effort, write will surface the error */ }
|
|
693
742
|
// 📖 Create backup of existing config before overwriting
|
|
694
743
|
const backupCreated = createBackup()
|
|
695
744
|
const tempPath = `${CONFIG_PATH}.tmp-${process.pid}-${Date.now()}`
|
|
@@ -815,6 +864,15 @@ export function formatPermissionHint(writeError) {
|
|
|
815
864
|
return '\n' + lines.join('\n')
|
|
816
865
|
}
|
|
817
866
|
|
|
867
|
+
// 📖 getBackupsDir: Backup directory for config snapshots. Follows the chosen
|
|
868
|
+
// 📖 config dir (`--config-dir` / FCM_CONFIG_DIR) so backups stay next to the
|
|
869
|
+
// 📖 config file they protect; falls back to ~/.free-coding-models.backups.
|
|
870
|
+
function getBackupsDir() {
|
|
871
|
+
return getConfigDir()
|
|
872
|
+
? join(getConfigDir(), 'backups')
|
|
873
|
+
: join(homedir(), '.free-coding-models.backups')
|
|
874
|
+
}
|
|
875
|
+
|
|
818
876
|
/**
|
|
819
877
|
* 📖 createBackup: Creates a timestamped backup of the current config file.
|
|
820
878
|
* 📖 Keeps only the 5 most recent backups to avoid disk space issues.
|
|
@@ -829,7 +887,7 @@ function createBackup() {
|
|
|
829
887
|
}
|
|
830
888
|
|
|
831
889
|
// 📖 Create backup directory if it doesn't exist
|
|
832
|
-
const backupDir =
|
|
890
|
+
const backupDir = getBackupsDir()
|
|
833
891
|
if (!existsSync(backupDir)) {
|
|
834
892
|
mkdirSync(backupDir, { mode: 0o700, recursive: true })
|
|
835
893
|
}
|
|
@@ -876,7 +934,7 @@ function createBackup() {
|
|
|
876
934
|
* @throws {Error} if no backup exists or restoration fails
|
|
877
935
|
*/
|
|
878
936
|
function restoreFromBackup() {
|
|
879
|
-
const backupDir =
|
|
937
|
+
const backupDir = getBackupsDir()
|
|
880
938
|
|
|
881
939
|
if (!existsSync(backupDir)) {
|
|
882
940
|
throw new Error('No backup directory found')
|
package/src/core/security.js
CHANGED
|
@@ -35,10 +35,12 @@ import fs from 'node:fs'
|
|
|
35
35
|
import path from 'node:path'
|
|
36
36
|
import os from 'node:os'
|
|
37
37
|
import readline from 'node:readline'
|
|
38
|
+
import { CONFIG_PATH } from './config.js'
|
|
38
39
|
|
|
39
|
-
// 📖 Config file path — matches the path used in config.js
|
|
40
|
+
// 📖 Config file path — matches the path used in config.js (honours the
|
|
41
|
+
// 📖 --config-dir / FCM_CONFIG_DIR override when set).
|
|
40
42
|
function getConfigPath() {
|
|
41
|
-
return
|
|
43
|
+
return CONFIG_PATH
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// 📖 Secure file permissions: user read/write only (0o600 = 384 in decimal)
|
package/src/core/utils.js
CHANGED
|
@@ -465,6 +465,8 @@ export function findBestModel(results) {
|
|
|
465
465
|
// --reprobe / --no-cache (boolean) — force-rebuild the probe cache this run
|
|
466
466
|
// --probe-ttl <ms> (value) — override the 24h default TTL
|
|
467
467
|
// --show-broken (boolean) — don't auto-hide broken models (one-shot)
|
|
468
|
+
// - Config location flag:
|
|
469
|
+
// --config-dir <dir> (value) — override where config.json + backups/ live
|
|
468
470
|
//
|
|
469
471
|
// Returns:
|
|
470
472
|
// { apiKey, bestMode, fiableMode, openCodeMode, openCodeDesktopMode, openCodeWebMode, openClawMode,
|
|
@@ -512,6 +514,14 @@ export function parseArgs(argv) {
|
|
|
512
514
|
? syncSetIdx + 1
|
|
513
515
|
: -1
|
|
514
516
|
|
|
517
|
+
// 📖 --config-dir <dir> — override where FCM's config files (config.json + backups/)
|
|
518
|
+
// 📖 live. Any directory works, e.g. --config-dir ~/.config/free-coding-models for the
|
|
519
|
+
// 📖 XDG layout. The bin entry point persists this as FCM_CONFIG_DIR before imports.
|
|
520
|
+
const configDirIdx = args.findIndex(a => a.toLowerCase() === '--config-dir')
|
|
521
|
+
const configDirValueIdx = (configDirIdx !== -1 && args[configDirIdx + 1] && !args[configDirIdx + 1].startsWith('--'))
|
|
522
|
+
? configDirIdx + 1
|
|
523
|
+
: -1
|
|
524
|
+
|
|
515
525
|
// 📖 Set of arg indices that are values for flags (not API keys)
|
|
516
526
|
const skipIndices = new Set()
|
|
517
527
|
if (tierValueIdx !== -1) skipIndices.add(tierValueIdx)
|
|
@@ -520,6 +530,7 @@ export function parseArgs(argv) {
|
|
|
520
530
|
if (pingIntervalValueIdx !== -1) skipIndices.add(pingIntervalValueIdx)
|
|
521
531
|
if (syncSetValueIdx !== -1) skipIndices.add(syncSetValueIdx)
|
|
522
532
|
if (probeTtlValueIdx !== -1) skipIndices.add(probeTtlValueIdx)
|
|
533
|
+
if (configDirValueIdx !== -1) skipIndices.add(configDirValueIdx)
|
|
523
534
|
|
|
524
535
|
for (const [i, arg] of args.entries()) {
|
|
525
536
|
if (arg.startsWith('--') || arg === '-h') {
|
|
@@ -673,6 +684,8 @@ export function parseArgs(argv) {
|
|
|
673
684
|
showBrokenMode,
|
|
674
685
|
// 📖 Runtime telemetry flag (t3) — see src/core/runtime-telemetry.js
|
|
675
686
|
clearRuntimeMode,
|
|
687
|
+
// 📖 Config location flag — see src/core/config.js getConfigDir()
|
|
688
|
+
configDir: configDirValueIdx !== -1 ? args[configDirValueIdx] : null,
|
|
676
689
|
}
|
|
677
690
|
}
|
|
678
691
|
|
package/src/tui/cli-help.js
CHANGED
|
@@ -48,6 +48,7 @@ const CONFIG_FLAGS = [
|
|
|
48
48
|
{ flag: '--daemon-status', description: 'Print FCM Router daemon status JSON' },
|
|
49
49
|
{ flag: '--daemon-stop', description: 'Gracefully stop the FCM Router daemon' },
|
|
50
50
|
{ flag: '--sync-set [name]', description: 'Auto-discover and live-probe models into a router set' },
|
|
51
|
+
{ flag: '--config-dir <dir>', description: 'Store config.json + backups/ in <dir> (e.g. --config-dir ~/.config/free-coding-models for the XDG layout)' },
|
|
51
52
|
{ flag: '--no-telemetry', description: 'Disable anonymous telemetry for this run' },
|
|
52
53
|
{ flag: '--help, -h', description: 'Print this help and exit' },
|
|
53
54
|
]
|