@nitsan-ai/ragsuite-test 0.1.3 → 0.1.5

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.
@@ -2,25 +2,23 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { resolveMode } = require('../utils/config');
5
- const { resolveRepoRoot, isImagesInstall } = require('../utils/paths');
5
+ const { resolveRepoRoot, assertNativeDeploy } = require('../utils/paths');
6
6
  const { runScript } = require('../utils/spawn');
7
7
  const { assertApiPortFree } = require('../utils/port');
8
8
  const { error } = require('../utils/log');
9
9
 
10
10
  const name = 'start';
11
- const summary = 'Start stack (git scripts or images pull-only)';
11
+ const summary = 'Start app via npm scripts (native no Docker)';
12
12
 
13
13
  function help() {
14
14
  return `Usage: ragsuite-test start [options]
15
15
 
16
- Git install: scripts/docker-start.sh or native-start.sh
17
- Images install (--from-images): scripts/docker-start-images.sh (pull, no build)
16
+ Runs scripts/native-start.sh (same as \`npm start\` in the install dir).
17
+ Does not use Docker — no containers, no volume risk.
18
18
 
19
19
  Options:
20
- --mode <docker|native> Override (git install; images always docker)
21
- --repo-root <path> Install root
20
+ --repo-root <path> Override active install
22
21
  --dry-run Print script path and exit 0
23
- --detach, -d Detached start
24
22
  `;
25
23
  }
26
24
 
@@ -29,24 +27,14 @@ async function run(ctx) {
29
27
  cwd: ctx.cwd,
30
28
  repoRootFlag: ctx.globals.repoRoot,
31
29
  });
32
- const images = isImagesInstall(repoRoot);
33
- const mode = images
34
- ? 'docker'
35
- : resolveMode({
36
- flagMode: ctx.globals.mode,
37
- repoRoot,
38
- env: ctx.env,
39
- });
40
-
41
- let scriptName;
42
- if (images) {
43
- scriptName = 'docker-start-images.sh';
44
- } else {
45
- scriptName = mode === 'native' ? 'native-start.sh' : 'docker-start.sh';
30
+ try {
31
+ assertNativeDeploy(repoRoot);
32
+ } catch (err) {
33
+ error(err.message);
34
+ return 1;
46
35
  }
47
36
 
48
- const forward = normalizeStartArgs(ctx.commandArgs);
49
-
37
+ const mode = resolveMode();
50
38
  if (!ctx.globals.dryRun) {
51
39
  try {
52
40
  await assertApiPortFree(ctx.env);
@@ -56,23 +44,10 @@ async function run(ctx) {
56
44
  }
57
45
  }
58
46
 
59
- return runScript(repoRoot, path.join('scripts', scriptName), forward, {
47
+ return runScript(repoRoot, path.join('scripts', 'native-start.sh'), [], {
60
48
  dryRun: ctx.globals.dryRun,
61
49
  env: { ...ctx.env, RAGSUITE_MODE: mode },
62
50
  });
63
51
  }
64
52
 
65
- function normalizeStartArgs(args) {
66
- const out = [];
67
- for (const a of args) {
68
- if (a === '--detach' || a === '-d') {
69
- out.push('--detach');
70
- continue;
71
- }
72
- if (a === '--') continue;
73
- out.push(a);
74
- }
75
- return out;
76
- }
77
-
78
53
  module.exports = { name, summary, help, run };
@@ -2,22 +2,21 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { resolveMode } = require('../utils/config');
5
- const { resolveRepoRoot, isImagesInstall } = require('../utils/paths');
5
+ const { resolveRepoRoot, assertNativeDeploy } = require('../utils/paths');
6
6
  const { runScript } = require('../utils/spawn');
7
+ const { error } = require('../utils/log');
7
8
 
8
9
  const name = 'stop';
9
- const summary = 'Stop stack (git scripts or images pull-only)';
10
+ const summary = 'Stop native app processes (does not touch Docker volumes)';
10
11
 
11
12
  function help() {
12
13
  return `Usage: ragsuite-test stop [options]
13
14
 
14
- Git install: docker-stop.sh / native-stop.sh
15
- Images install: docker-stop-images.sh
16
- Never wipes Docker volumes.
15
+ Runs scripts/native-stop.sh (same as \`npm run stop\` in the install dir).
16
+ Stops host processes only — never runs docker compose down.
17
17
 
18
18
  Options:
19
- --mode <docker|native> Override (git install)
20
- --repo-root <path> Install root
19
+ --repo-root <path> Override active install
21
20
  --dry-run Print script path and exit 0
22
21
  `;
23
22
  }
@@ -27,24 +26,16 @@ function run(ctx) {
27
26
  cwd: ctx.cwd,
28
27
  repoRootFlag: ctx.globals.repoRoot,
29
28
  });
30
- const images = isImagesInstall(repoRoot);
31
- const mode = images
32
- ? 'docker'
33
- : resolveMode({
34
- flagMode: ctx.globals.mode,
35
- repoRoot,
36
- env: ctx.env,
37
- });
38
-
39
- const scriptName = images
40
- ? 'docker-stop-images.sh'
41
- : mode === 'native'
42
- ? 'native-stop.sh'
43
- : 'docker-stop.sh';
44
-
45
- return runScript(repoRoot, path.join('scripts', scriptName), [], {
29
+ try {
30
+ assertNativeDeploy(repoRoot);
31
+ } catch (err) {
32
+ error(err.message);
33
+ return 1;
34
+ }
35
+
36
+ return runScript(repoRoot, path.join('scripts', 'native-stop.sh'), [], {
46
37
  dryRun: ctx.globals.dryRun,
47
- env: { ...ctx.env, RAGSUITE_MODE: mode },
38
+ env: { ...ctx.env, RAGSUITE_MODE: resolveMode() },
48
39
  });
49
40
  }
50
41
 
@@ -2,86 +2,89 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { resolveRepoRoot } = require('../utils/paths');
5
+ const { resolveRepoRoot, assertNativeDeploy, looksLikeGitCheckout } = require('../utils/paths');
6
6
  const { readConfig } = require('../utils/config');
7
+ const { setActiveInstall } = require('../utils/global-config');
7
8
  const { captureCommand } = require('../utils/spawn');
8
- const { info, warn } = require('../utils/log');
9
+ const { info, warn, error } = require('../utils/log');
9
10
 
10
11
  const name = 'update';
11
- const summary = 'Verify install path and print safe update steps';
12
+ const summary = 'git pull in place — keeps .env and data (no Docker)';
12
13
 
13
14
  function help() {
14
- return `Usage: ragsuite-test update [options]
15
+ return `Usage: ragsuite-test update
15
16
 
16
- Verifies the install/repo path and prints safe update steps.
17
- Git installs: git fetch + pull guidance (no destructive reset).
17
+ Upgrades the active git install without wiping data:
18
+ git pull --ff-only in the install dir
19
+ • Keeps .env and any host Postgres/Redis/Chroma data
18
20
 
19
- Never runs docker compose down -v.
21
+ Never runs docker compose (no volume risk).
22
+
23
+ Typical flow after a new release:
24
+ npm install -g @nitsan-ai/ragsuite-test@latest
25
+ ragsuite-test update
26
+ ragsuite-test start
20
27
 
21
28
  Options:
22
- --repo-root <path> Monorepo / install root
29
+ --repo-root <path> Override active install
30
+ --dry-run Print actions only
23
31
  `;
24
32
  }
25
33
 
26
34
  function run(ctx) {
27
- const repoRoot = resolveRepoRoot({
28
- cwd: ctx.cwd,
29
- repoRootFlag: ctx.globals.repoRoot,
30
- env: ctx.env,
31
- });
35
+ let repoRoot;
36
+ try {
37
+ repoRoot = resolveRepoRoot({
38
+ cwd: ctx.cwd,
39
+ repoRootFlag: ctx.globals.repoRoot,
40
+ env: ctx.env,
41
+ });
42
+ assertNativeDeploy(repoRoot);
43
+ } catch (err) {
44
+ error(err.message);
45
+ return 1;
46
+ }
32
47
 
48
+ setActiveInstall(repoRoot);
33
49
  const cfg = readConfig(repoRoot);
34
- const source = (cfg && cfg.source) || 'checkout';
35
-
36
- info(`Install path OK: ${repoRoot}`);
37
- info(` source=${source}`);
38
- info('');
50
+ const source = (cfg && cfg.source) || 'git';
39
51
 
40
- info('Safe update steps:');
41
- info(' 1. Stop the stack (ragsuite-test stop --repo-root …)');
42
- info(' 2. Update sources (git pull)');
43
- info(' 3. Docker: npm run rebuild or docker compose build');
44
- info(' 4. Native: re-run backend/scripts/setup.sh if Python deps changed');
45
- info(' 5. Start again (ragsuite-test start --repo-root …)');
46
- info(' 6. Run doctor after upgrades');
47
- info('');
48
- info('Never: docker compose down -v (destroys Postgres/Chroma data).');
52
+ info(`Updating install: ${repoRoot}`);
53
+ info(` source=${source} mode=native`);
54
+ info(' (.env and database data are kept)');
49
55
  info('');
50
56
 
51
- const gitDir = path.join(repoRoot, '.git');
52
- if (!fs.existsSync(gitDir)) {
53
- warn('No .git directory — skip fetch/status. Re-init with --from-git if needed.');
57
+ if (ctx.globals.dryRun) {
58
+ info('[dry-run] would update via git pull --ff-only');
54
59
  return 0;
55
60
  }
56
61
 
57
- const remote = captureCommand('git', ['remote', '-v'], { cwd: repoRoot, env: ctx.env });
58
- if (remote.status !== 0 || !String(remote.stdout || '').trim()) {
59
- warn('No git remote configured — skip fetch.');
60
- return 0;
62
+ if (!looksLikeGitCheckout(repoRoot) && !fs.existsSync(path.join(repoRoot, '.git'))) {
63
+ error('This install has no .git directory. Re-run: ragsuite-test init --force');
64
+ return 1;
61
65
  }
62
66
 
63
- info('Git remotes:');
64
- info(String(remote.stdout).trimEnd());
65
- info('');
66
- info('Running git fetch (non-destructive)…');
67
- const fetch = captureCommand('git', ['fetch', '--all', '--prune'], {
67
+ info('Fetching and pulling latest from git (non-destructive)…');
68
+ const pull = captureCommand('git', ['pull', '--ff-only'], {
68
69
  cwd: repoRoot,
69
70
  env: ctx.env,
70
71
  });
71
- if (fetch.status !== 0) {
72
- warn(
73
- `git fetch failed (exit ${fetch.status}). Private remotes need credentials. Continuing with status only.`,
74
- );
75
- if (fetch.stderr) warn(String(fetch.stderr).trimEnd());
76
- } else {
77
- info('git fetch OK');
78
- }
79
-
80
- const status = captureCommand('git', ['status', '-sb'], { cwd: repoRoot, env: ctx.env });
81
- if (status.stdout) {
82
- info('');
83
- info(String(status.stdout).trimEnd());
72
+ if (pull.status !== 0) {
73
+ warn(`git pull --ff-only failed (exit ${pull.status}). Trying fetch + status…`);
74
+ if (pull.stderr) warn(String(pull.stderr).trimEnd());
75
+ captureCommand('git', ['fetch', '--all', '--prune'], { cwd: repoRoot, env: ctx.env });
76
+ const st = captureCommand('git', ['status', '-sb'], { cwd: repoRoot, env: ctx.env });
77
+ if (st.stdout) info(String(st.stdout).trimEnd());
78
+ error('Could not fast-forward. Fix local commits or stash, then: cd install && git pull');
79
+ return 1;
84
80
  }
81
+ if (pull.stdout) info(String(pull.stdout).trimEnd());
82
+ info('git pull OK.');
83
+ info('');
84
+ info('Restart to apply:');
85
+ info(' ragsuite-test stop');
86
+ info(' ragsuite-test start');
87
+ info(' # or: cd install && npm run stop && npm start');
85
88
  return 0;
86
89
  }
87
90
 
package/src/index.js CHANGED
@@ -19,7 +19,8 @@ function globalHelp() {
19
19
  'RAGSuite test CLI (experimental)',
20
20
  '',
21
21
  'Usage: ragsuite-test <command> [options]',
22
- ' (from cli/: node src/index.js <command> [options])',
22
+ '',
23
+ 'Deploy path: git clone + native npm scripts (no Docker).',
23
24
  '',
24
25
  'Commands:',
25
26
  ];
@@ -30,9 +31,8 @@ function globalHelp() {
30
31
  '',
31
32
  'Global options:',
32
33
  ' --help, -h Show help',
33
- ' --repo-root <path> Monorepo / install root',
34
+ ' --repo-root <path> Override active install',
34
35
  ' --from-git [url] init: clone source (default public repo)',
35
- ' --from-images init: Docker images only (no app source on disk)',
36
36
  ' --install-dir <path> init: install target (default ~/ragsuite-test)',
37
37
  ' --llm-api-key <key> init: optional override for CUSTOM_LLM_INTERNAL_API_KEY',
38
38
  ' --smtp-host <host> init: optional SMTP override',
@@ -40,10 +40,10 @@ function globalHelp() {
40
40
  ' --smtp-password <pass> init: optional SMTP override',
41
41
  ' --email-from <email> init: optional SMTP override',
42
42
  ' --force init: overwrite non-empty install dir / .env',
43
- ' --yes, -y Non-interactive (same defaults as plain init)',
44
- ' --mode <docker|native> Override config (Docker optional)',
45
- ' --dry-run Print actions without running (start/stop/doctor/logs)',
43
+ ' --yes, -y Non-interactive',
44
+ ' --dry-run Print actions without running (start/stop/doctor/logs/update)',
46
45
  '',
46
+ 'Active install: ~/.ragsuite-test/config.json (set by init)',
47
47
  'Env: RAGSUITE_REPO_ROOT or RAGSUITE_INSTALL_DIR',
48
48
  ' RAGSUITE_TEST_LLM_API_KEY, RAGSUITE_TEST_SMTP_* , RAGSUITE_TEST_EMAIL_FROM',
49
49
  '',
@@ -93,7 +93,7 @@ async function main(argv = process.argv) {
93
93
  const code = typeof result?.then === 'function' ? await result : result;
94
94
  return typeof code === 'number' ? code : 0;
95
95
  } catch (err) {
96
- if (err && (err.code === 'NOT_REPO_ROOT' || err.code === 'USAGE' || err.code === 'INSTALL_DIR_NONEMPTY' || err.code === 'ENV_EXISTS' || err.code === 'ENV_INCOMPLETE' || err.code === 'PORT_IN_USE' || err.code === 'LLM_KEY_INVALID' || err.code === 'SMTP_REQUIRED' || err.code === 'GIT_CLONE_FAILED' || err.code === 'MISSING_GIT')) {
96
+ if (err && (err.code === 'NOT_REPO_ROOT' || err.code === 'USAGE' || err.code === 'INSTALL_DIR_NONEMPTY' || err.code === 'ENV_EXISTS' || err.code === 'ENV_INCOMPLETE' || err.code === 'PORT_IN_USE' || err.code === 'LLM_KEY_INVALID' || err.code === 'SMTP_REQUIRED' || err.code === 'GIT_CLONE_FAILED' || err.code === 'MISSING_GIT' || err.code === 'IMAGES_REMOVED' || err.code === 'NATIVE_SCRIPT_MISSING')) {
97
97
  error(err.message);
98
98
  return 1;
99
99
  }
package/src/utils/args.js CHANGED
@@ -72,7 +72,7 @@ function parseArgv(argv) {
72
72
  const { value, nextI } = takeValue('--mode', t, i);
73
73
  i = nextI;
74
74
  if (!value || String(value).startsWith('-')) {
75
- const err = new Error('--mode requires docker or native');
75
+ const err = new Error('--mode requires native (docker was removed from deployment)');
76
76
  err.code = 'USAGE';
77
77
  throw err;
78
78
  }
@@ -208,8 +208,15 @@ function parseArgv(argv) {
208
208
 
209
209
  function normalizeMode(value) {
210
210
  const m = String(value).trim().toLowerCase();
211
- if (m !== 'docker' && m !== 'native') {
212
- const err = new Error(`Invalid mode "${value}" (use docker or native)`);
211
+ if (m === 'docker') {
212
+ const err = new Error(
213
+ 'Docker mode was removed from deployment. Use native (omit --mode or pass --mode native).',
214
+ );
215
+ err.code = 'USAGE';
216
+ throw err;
217
+ }
218
+ if (m !== 'native') {
219
+ const err = new Error(`Invalid mode "${value}" (use native)`);
213
220
  err.code = 'USAGE';
214
221
  throw err;
215
222
  }
@@ -10,11 +10,11 @@ function configPath(repoRoot) {
10
10
  return path.join(repoRoot, CONFIG_DIR, 'config.json');
11
11
  }
12
12
 
13
- function defaultConfig(repoRoot, mode = 'docker') {
13
+ function defaultConfig(repoRoot) {
14
14
  const root = path.resolve(repoRoot);
15
15
  return {
16
16
  version: CONFIG_VERSION,
17
- mode: mode === 'native' ? 'native' : 'docker',
17
+ mode: 'native',
18
18
  source: 'checkout',
19
19
  repoRoot: root,
20
20
  installDir: root,
@@ -23,7 +23,9 @@ function defaultConfig(repoRoot, mode = 'docker') {
23
23
 
24
24
  function normalizeSource(raw) {
25
25
  const s = String(raw || '').trim();
26
- if (s === 'images' || s === 'git' || s === 'zip' || s === 'checkout') return s;
26
+ if (s === 'git' || s === 'checkout' || s === 'zip') return s === 'zip' ? 'checkout' : s;
27
+ // Legacy images installs are no longer supported for start/stop
28
+ if (s === 'images') return 'images';
27
29
  return 'checkout';
28
30
  }
29
31
 
@@ -39,7 +41,7 @@ function readConfig(repoRoot) {
39
41
  }
40
42
  return {
41
43
  version: Number(raw.version) || CONFIG_VERSION,
42
- mode: raw.mode === 'native' ? 'native' : 'docker',
44
+ mode: 'native',
43
45
  source: normalizeSource(raw.source),
44
46
  repoRoot: path.resolve(String(raw.repoRoot || repoRoot)),
45
47
  installDir: path.resolve(String(raw.installDir || raw.repoRoot || repoRoot)),
@@ -62,37 +64,20 @@ function writeConfig(repoRoot, partial = {}) {
62
64
  ...existing,
63
65
  ...partial,
64
66
  version: CONFIG_VERSION,
65
- source,
67
+ source: source === 'images' ? 'git' : source,
66
68
  repoRoot: path.resolve(partial.repoRoot || existing.repoRoot || repoRoot),
67
69
  installDir: path.resolve(
68
70
  partial.installDir || existing.installDir || partial.repoRoot || existing.repoRoot || repoRoot,
69
71
  ),
70
- mode: (partial.mode || existing.mode) === 'native' ? 'native' : 'docker',
72
+ mode: 'native',
71
73
  };
72
74
  fs.writeFileSync(configPath(repoRoot), `${JSON.stringify(next, null, 2)}\n`, 'utf8');
73
75
  return next;
74
76
  }
75
77
 
76
- /**
77
- * Resolve runtime mode: CLI --mode > config > RAGSUITE_MODE > docker.
78
- */
79
- function resolveMode({ flagMode, repoRoot, env = process.env } = {}) {
80
- if (flagMode === 'docker' || flagMode === 'native') {
81
- return flagMode;
82
- }
83
- if (repoRoot) {
84
- const cfg = readConfig(repoRoot);
85
- if (cfg && (cfg.mode === 'docker' || cfg.mode === 'native')) {
86
- return cfg.mode;
87
- }
88
- }
89
- const fromEnv = String(env.RAGSUITE_MODE || '')
90
- .trim()
91
- .toLowerCase();
92
- if (fromEnv === 'native') {
93
- return 'native';
94
- }
95
- return 'docker';
78
+ /** Deployment always uses native npm scripts. */
79
+ function resolveMode() {
80
+ return 'native';
96
81
  }
97
82
 
98
83
  module.exports = {
@@ -1,42 +1,36 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Distribution modes for the testing CLI.
5
- *
6
- * - git-clone: `ragsuite-test init` (source on disk)
7
- * - images: `ragsuite-test init --from-images` (pull GHCR, no app source)
8
- * - local-checkout: --repo-root
4
+ * Distribution for the testing CLI.
5
+ * Deploy path is git clone + native npm scripts only (no Docker).
9
6
  */
10
7
 
11
8
  const MODE_LOCAL = 'local-checkout';
12
9
  const MODE_GIT = 'git-clone';
13
- const MODE_IMAGES = 'images';
14
- const MODE_REGISTRY = 'registry-compose';
15
10
 
16
11
  function assertSupportedInstall() {}
17
12
 
18
13
  function resolveComposeBundle() {
19
- const err = new Error(
20
- 'Use: ragsuite-test init --from-images (pull-only) OR ragsuite-test init (git source)',
21
- );
14
+ const err = new Error('Docker compose bundles were removed from deployment. Use: ragsuite-test init');
22
15
  err.code = 'DIST_NOT_READY';
23
16
  throw err;
24
17
  }
25
18
 
26
19
  function installHint() {
27
20
  return [
28
- 'Choose an install style:',
29
- ' Source on disk: ragsuite-test init',
30
- ' No source: ragsuite-test init --from-images',
31
- 'Then: ragsuite-test start --repo-root ~/ragsuite-test --detach',
21
+ 'Day-to-day (native / npm scripts — no Docker):',
22
+ ' ragsuite-test start',
23
+ ' ragsuite-test update',
24
+ ' ragsuite-test stop',
25
+ 'Or in the install dir:',
26
+ ' npm start',
27
+ ' npm run stop',
32
28
  ].join('\n');
33
29
  }
34
30
 
35
31
  module.exports = {
36
32
  MODE_LOCAL,
37
33
  MODE_GIT,
38
- MODE_IMAGES,
39
- MODE_REGISTRY,
40
34
  mode: MODE_GIT,
41
35
  assertSupportedInstall,
42
36
  resolveComposeBundle,
@@ -1,18 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
- const os = require('os');
5
4
  const path = require('path');
6
5
  const { spawnSync } = require('child_process');
7
- const { looksLikeFullBundleRoot, looksLikeRepoRoot } = require('./paths');
6
+ const {
7
+ defaultInstallDir,
8
+ looksLikeFullBundleRoot,
9
+ looksLikeRepoRoot,
10
+ } = require('./paths');
8
11
 
9
12
  /** Default public monorepo (when the GitHub repo is public). */
10
13
  const DEFAULT_GIT_URL = 'https://github.com/nitsan-ai/RAGSUITE.git';
11
14
 
12
- function defaultInstallDir() {
13
- return path.join(os.homedir(), 'ragsuite-test');
14
- }
15
-
16
15
  function requireGit() {
17
16
  const which = spawnSync('which', ['git'], { encoding: 'utf8' });
18
17
  if (which.status !== 0) {
@@ -49,7 +48,13 @@ function cloneGitToInstallDir(gitUrl, options = {}) {
49
48
  if (!isDirEmptyOrMissing(target)) {
50
49
  if (!force) {
51
50
  const err = new Error(
52
- `Install directory is not empty: ${target}. Pass --force to remove and re-clone, or choose another --install-dir.`,
51
+ [
52
+ `Install directory is not empty: ${target}`,
53
+ ' If already installed: ragsuite-test start',
54
+ ' To upgrade in place: ragsuite-test update',
55
+ ' To wipe & reinstall: ragsuite-test init --force',
56
+ ' Or use another path: ragsuite-test init --install-dir <path>',
57
+ ].join('\n'),
53
58
  );
54
59
  err.code = 'INSTALL_DIR_NONEMPTY';
55
60
  throw err;
@@ -76,7 +81,7 @@ function cloneGitToInstallDir(gitUrl, options = {}) {
76
81
  const repoRoot = target;
77
82
  if (!looksLikeRepoRoot(repoRoot) && !looksLikeFullBundleRoot(repoRoot)) {
78
83
  const err = new Error(
79
- `Cloned repo at ${repoRoot} does not look like a RAGSuite monorepo (missing scripts/docker-start.sh).`,
84
+ `Cloned repo at ${repoRoot} does not look like a RAGSuite monorepo (missing scripts/native-start.sh or docker-start.sh).`,
80
85
  );
81
86
  err.code = 'GIT_INVALID_REPO';
82
87
  throw err;
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ /** User-level CLI state (not inside the app install). Like other npm CLIs. */
8
+ function globalConfigDir() {
9
+ return path.join(os.homedir(), '.ragsuite-test');
10
+ }
11
+
12
+ function globalConfigPath() {
13
+ return path.join(globalConfigDir(), 'config.json');
14
+ }
15
+
16
+ function readGlobalConfig() {
17
+ const file = globalConfigPath();
18
+ if (!fs.existsSync(file)) return null;
19
+ try {
20
+ const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
21
+ if (!raw || typeof raw !== 'object') return null;
22
+ return {
23
+ installDir: raw.installDir ? path.resolve(String(raw.installDir)) : null,
24
+ updatedAt: raw.updatedAt || null,
25
+ };
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function writeGlobalConfig(partial = {}) {
32
+ const dir = globalConfigDir();
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ const existing = readGlobalConfig() || {};
35
+ const next = {
36
+ ...existing,
37
+ ...partial,
38
+ updatedAt: new Date().toISOString(),
39
+ };
40
+ if (next.installDir) next.installDir = path.resolve(String(next.installDir));
41
+ fs.writeFileSync(globalConfigPath(), `${JSON.stringify(next, null, 2)}\n`, 'utf8');
42
+ return next;
43
+ }
44
+
45
+ function setActiveInstall(installDir) {
46
+ return writeGlobalConfig({ installDir: path.resolve(installDir) });
47
+ }
48
+
49
+ function getActiveInstall() {
50
+ const g = readGlobalConfig();
51
+ return g && g.installDir ? g.installDir : null;
52
+ }
53
+
54
+ module.exports = {
55
+ globalConfigDir,
56
+ globalConfigPath,
57
+ readGlobalConfig,
58
+ writeGlobalConfig,
59
+ setActiveInstall,
60
+ getActiveInstall,
61
+ };