@nemus-cli/nemus 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-08-25
11
+
12
+ ### Changed
13
+
14
+ - **Namespaced all cache/config/state under `~/.nemus`** (was
15
+ `~/.workspace-manager-cache`). This avoids collisions with other tools that
16
+ used the old generic path — which could, for example, make the update check
17
+ report a wrong "latest" version.
18
+ - Environment overrides are now **`NEMUS_DIR`** and **`NEMUS_CACHE_DIR`**; the
19
+ legacy `WORKSPACE_MANAGER_DIR` / `WORKSPACE_MANAGER_CACHE_DIR` names still work
20
+ as fallbacks.
21
+ - On first run, existing state is **migrated automatically** from
22
+ `~/.workspace-manager-cache` to `~/.nemus` (a non-destructive copy — the old
23
+ directory is left intact).
24
+ - Published as the scoped package **`@nemus-cli/nemus`** (npm's similarity guard
25
+ blocks the bare name `nemus`). The `nemus` / `nem` CLI commands are unchanged.
26
+
10
27
  ## [0.2.1] - 2026-08-24
11
28
 
12
29
  ### Security
@@ -80,7 +97,8 @@ Initial open-source release of **Nemus**.
80
97
  - Automatic retries with backoff on flaky network operations.
81
98
  - Optional shell integration (auto-cd on create + quick-navigate helper).
82
99
 
83
- [Unreleased]: https://github.com/me-public/nemus/compare/v0.2.1...HEAD
100
+ [Unreleased]: https://github.com/me-public/nemus/compare/v0.2.2...HEAD
101
+ [0.2.2]: https://github.com/me-public/nemus/compare/v0.2.1...v0.2.2
84
102
  [0.2.1]: https://github.com/me-public/nemus/compare/v0.2.0...v0.2.1
85
103
  [0.2.0]: https://github.com/me-public/nemus/compare/v0.1.0...v0.2.0
86
104
  [0.1.0]: https://github.com/me-public/nemus/releases/tag/v0.1.0
package/README.md CHANGED
@@ -225,14 +225,18 @@ Run `nemus --help` for the full command reference.
225
225
 
226
226
  ## Configuration
227
227
 
228
- `nemus configure` writes `~/.workspace-manager-cache/config.json`. Environment
228
+ `nemus configure` writes `~/.nemus/config.json`. Environment
229
229
  variables override it:
230
230
 
231
231
  ```bash
232
- export WORKSPACE_MANAGER_DIR="$HOME/my-workspaces" # default: ~/workspaces
233
- export WORKSPACE_MANAGER_CACHE_DIR="$HOME/.cache/nemus" # default: ~/.workspace-manager-cache
232
+ export NEMUS_DIR="$HOME/my-workspaces" # default: ~/workspaces
233
+ export NEMUS_CACHE_DIR="$HOME/.cache/nemus" # default: ~/.nemus
234
234
  ```
235
235
 
236
+ (The legacy `WORKSPACE_MANAGER_DIR` / `WORKSPACE_MANAGER_CACHE_DIR` names still
237
+ work as fallbacks. On first run, state from the old `~/.workspace-manager-cache`
238
+ location is migrated to `~/.nemus` automatically.)
239
+
236
240
  Key settings: `githubOrg` (which org's repos to list — leave empty to list your
237
241
  own), `cloneProtocol` (`ssh`|`https`), `aiAgent`, `primaryAgent`, `installMcp`.
238
242
 
@@ -57,7 +57,7 @@ async function cacheInfo() {
57
57
  console.log(` Repositories: ${(0, colors_1.colorize)(String(stats.repoCount), 'cyan')}`);
58
58
  console.log(` Last updated: ${stats.age}`);
59
59
  console.log(` Cache size: ${(0, colors_1.colorize)(String(Math.round((stats.size || 0) / 1024)) + ' KB', 'yellow')}`);
60
- console.log(` Location: ${(0, colors_1.colorize)('~/.workspace-manager-cache/repos-cache.json', 'gray')}`);
60
+ console.log(` Location: ${(0, colors_1.colorize)('~/.nemus/repos-cache.json', 'gray')}`);
61
61
  }
62
62
  else {
63
63
  console.log('\n' + (0, colors_1.colorize)('Cache Status:', 'yellow'));
@@ -10,7 +10,7 @@ import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
11
  import * as os from "node:os";
12
12
 
13
- const CACHE_DIR = path.join(os.homedir(), ".workspace-manager-cache");
13
+ const CACHE_DIR = path.join(os.homedir(), ".nemus");
14
14
  const REVIEW_FILE = path.join(CACHE_DIR, "last-permission-review");
15
15
  const REMINDER_FILE = path.join(CACHE_DIR, "last-permission-reminder");
16
16
  const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
@@ -27,7 +27,7 @@ from concurrent.futures import ThreadPoolExecutor
27
27
  from pathlib import Path
28
28
 
29
29
  # ─── Cache ───────────────────────────────────────────────────────────────────
30
- CACHE_FILE = Path.home() / ".workspace-manager-cache" / "pr-status.json"
30
+ CACHE_FILE = Path.home() / ".nemus" / "pr-status.json"
31
31
  CACHE_TTL = 300 # seconds
32
32
 
33
33
  # ─── ANSI helpers ─────────────────────────────────────────────────────────────
@@ -42,8 +42,32 @@ const os = __importStar(require("os"));
42
42
  const path = __importStar(require("path"));
43
43
  const fs = __importStar(require("fs"));
44
44
  const HOME_DIR = os.homedir();
45
- // Config file lives outside CACHE_DIR so we can read it to determine CACHE_DIR
46
- const CONFIG_FILE = path.join(HOME_DIR, '.workspace-manager-cache', 'config.json');
45
+ // Cache/config/state directory. Prefer the branded NEMUS_* env vars; fall back
46
+ // to the legacy WORKSPACE_MANAGER_* names for backward compatibility; else the
47
+ // default ~/.nemus home.
48
+ const DEFAULT_CACHE_DIR = path.join(HOME_DIR, '.nemus');
49
+ const LEGACY_CACHE_DIR = path.join(HOME_DIR, '.workspace-manager-cache');
50
+ /**
51
+ * One-time, best-effort migration of state from the pre-0.2.2 cache location
52
+ * (~/.workspace-manager-cache) to ~/.nemus. Runs only when using the default
53
+ * location (no env override) and the new dir doesn't exist yet. Copies rather
54
+ * than moves, so any other tool that happened to share the old path is left
55
+ * untouched; a failure is harmless because a fresh dir is created on first write.
56
+ */
57
+ function migrateLegacyCacheDir(targetDir) {
58
+ try {
59
+ if (targetDir === DEFAULT_CACHE_DIR && !fs.existsSync(targetDir) && fs.existsSync(LEGACY_CACHE_DIR)) {
60
+ fs.cpSync(LEGACY_CACHE_DIR, targetDir, { recursive: true });
61
+ }
62
+ }
63
+ catch {
64
+ // best-effort — ignore and let the dir be created lazily
65
+ }
66
+ }
67
+ const CACHE_DIR_RESOLVED = process.env.NEMUS_CACHE_DIR || process.env.WORKSPACE_MANAGER_CACHE_DIR || DEFAULT_CACHE_DIR;
68
+ migrateLegacyCacheDir(CACHE_DIR_RESOLVED);
69
+ // Config file lives inside the cache dir.
70
+ const CONFIG_FILE = path.join(CACHE_DIR_RESOLVED, 'config.json');
47
71
  const DEFAULTS = {
48
72
  workspacesDir: path.join(HOME_DIR, 'workspaces'),
49
73
  githubOrg: '',
@@ -99,8 +123,8 @@ function getUserConfig() {
99
123
  }
100
124
  // Bootstrap constants from initial config read (env vars take precedence)
101
125
  const initialConfig = getUserConfig();
102
- exports.WORKSPACES_DIR = process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
103
- exports.CACHE_DIR = process.env.WORKSPACE_MANAGER_CACHE_DIR || path.join(HOME_DIR, '.workspace-manager-cache');
126
+ exports.WORKSPACES_DIR = process.env.NEMUS_DIR || process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
127
+ exports.CACHE_DIR = CACHE_DIR_RESOLVED;
104
128
  exports.HISTORY_FILE = path.join(exports.CACHE_DIR, 'history.jsonl');
105
129
  exports.SUITES_FILE = path.join(exports.CACHE_DIR, 'suites.json');
106
130
  exports.META_FILENAME = '.workspace-meta.json';
@@ -36,12 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.OperationTimer = exports.clearHistory = exports.filterHistory = exports.getOperationStats = exports.readHistory = exports.logOperation = exports.ensureCacheDir = void 0;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- const CACHE_DIR = path.join(os.homedir(), '.workspace-manager-cache');
41
- const HISTORY_FILE = path.join(CACHE_DIR, 'history.jsonl');
39
+ const config_1 = require("./config");
40
+ const HISTORY_FILE = path.join(config_1.CACHE_DIR, 'history.jsonl');
42
41
  const ensureCacheDir = async () => {
43
42
  try {
44
- await fs.mkdir(CACHE_DIR, { recursive: true });
43
+ await fs.mkdir(config_1.CACHE_DIR, { recursive: true });
45
44
  }
46
45
  catch {
47
46
  // Ignore if already exists
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nemus-cli/nemus",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Nemus — a CLI for managing multi-repository workspaces. Create, sync, and operate across dozens of repos with a single command, and wire them up to your favorite coding agent.",
5
5
  "keywords": [
6
6
  "workspace",
@@ -32,4 +32,4 @@ w cfg
32
32
 
33
33
  ## Success Criteria
34
34
 
35
- - User's settings are saved to `~/.workspace-manager-cache/config.json`.
35
+ - User's settings are saved to `~/.nemus/config.json`.
@@ -16,7 +16,7 @@ export async function cacheInfo() {
16
16
  console.log(` Repositories: ${colorize(String(stats.repoCount), 'cyan')}`);
17
17
  console.log(` Last updated: ${stats.age}`);
18
18
  console.log(` Cache size: ${colorize(String(Math.round((stats.size || 0) / 1024)) + ' KB', 'yellow')}`);
19
- console.log(` Location: ${colorize('~/.workspace-manager-cache/repos-cache.json', 'gray')}`);
19
+ console.log(` Location: ${colorize('~/.nemus/repos-cache.json', 'gray')}`);
20
20
  } else {
21
21
  console.log('\n' + colorize('Cache Status:', 'yellow'));
22
22
  console.log(' No cache found. Cache will be created on next repository fetch.\n');
@@ -10,7 +10,7 @@ import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
11
  import * as os from "node:os";
12
12
 
13
- const CACHE_DIR = path.join(os.homedir(), ".workspace-manager-cache");
13
+ const CACHE_DIR = path.join(os.homedir(), ".nemus");
14
14
  const REVIEW_FILE = path.join(CACHE_DIR, "last-permission-review");
15
15
  const REMINDER_FILE = path.join(CACHE_DIR, "last-permission-reminder");
16
16
  const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
@@ -27,7 +27,7 @@ from concurrent.futures import ThreadPoolExecutor
27
27
  from pathlib import Path
28
28
 
29
29
  # ─── Cache ───────────────────────────────────────────────────────────────────
30
- CACHE_FILE = Path.home() / ".workspace-manager-cache" / "pr-status.json"
30
+ CACHE_FILE = Path.home() / ".nemus" / "pr-status.json"
31
31
  CACHE_TTL = 300 # seconds
32
32
 
33
33
  # ─── ANSI helpers ─────────────────────────────────────────────────────────────
@@ -200,4 +200,50 @@ describe('config', () => {
200
200
  expect(getUserConfig().aiAgent).toBe('auto');
201
201
  });
202
202
  });
203
+
204
+ describe('cache dir location + legacy migration', () => {
205
+ it('defaults CACHE_DIR to ~/.nemus when no env override is set', async () => {
206
+ delete process.env.NEMUS_CACHE_DIR;
207
+ delete process.env.WORKSPACE_MANAGER_CACHE_DIR;
208
+ vi.resetModules();
209
+ const { CACHE_DIR } = await loadModule();
210
+ expect(CACHE_DIR).toBe(path.join(tempDir, '.nemus'));
211
+ });
212
+
213
+ it('prefers NEMUS_CACHE_DIR, then legacy WORKSPACE_MANAGER_CACHE_DIR', async () => {
214
+ const nemusCache = path.join(tempDir, 'custom-nemus-cache');
215
+ process.env.NEMUS_CACHE_DIR = nemusCache;
216
+ vi.resetModules();
217
+ expect((await loadModule()).CACHE_DIR).toBe(nemusCache);
218
+
219
+ delete process.env.NEMUS_CACHE_DIR;
220
+ process.env.WORKSPACE_MANAGER_CACHE_DIR = path.join(tempDir, '.workspace-manager-cache');
221
+ vi.resetModules();
222
+ expect((await loadModule()).CACHE_DIR).toBe(path.join(tempDir, '.workspace-manager-cache'));
223
+ });
224
+
225
+ it('migrates state from ~/.workspace-manager-cache to ~/.nemus on first run', async () => {
226
+ delete process.env.NEMUS_CACHE_DIR;
227
+ delete process.env.WORKSPACE_MANAGER_CACHE_DIR;
228
+ // legacy dir (created by beforeEach) holds prior state; new dir absent
229
+ fs.writeFileSync(path.join(tempDir, '.workspace-manager-cache', 'suites.json'), '{"x":1}');
230
+ expect(fs.existsSync(path.join(tempDir, '.nemus'))).toBe(false);
231
+ vi.resetModules();
232
+ const { CACHE_DIR } = await loadModule();
233
+ expect(CACHE_DIR).toBe(path.join(tempDir, '.nemus'));
234
+ // copied, not moved: file exists in the new dir AND the legacy dir survives
235
+ expect(fs.readFileSync(path.join(tempDir, '.nemus', 'suites.json'), 'utf-8')).toBe('{"x":1}');
236
+ expect(fs.existsSync(path.join(tempDir, '.workspace-manager-cache', 'suites.json'))).toBe(true);
237
+ });
238
+
239
+ it('does not migrate when the new dir already exists', async () => {
240
+ delete process.env.NEMUS_CACHE_DIR;
241
+ delete process.env.WORKSPACE_MANAGER_CACHE_DIR;
242
+ fs.mkdirSync(path.join(tempDir, '.nemus'), { recursive: true });
243
+ fs.writeFileSync(path.join(tempDir, '.workspace-manager-cache', 'suites.json'), '{"x":1}');
244
+ vi.resetModules();
245
+ await loadModule();
246
+ expect(fs.existsSync(path.join(tempDir, '.nemus', 'suites.json'))).toBe(false);
247
+ });
248
+ });
203
249
  });
@@ -4,8 +4,35 @@ import * as fs from 'fs';
4
4
 
5
5
  const HOME_DIR = os.homedir();
6
6
 
7
- // Config file lives outside CACHE_DIR so we can read it to determine CACHE_DIR
8
- const CONFIG_FILE = path.join(HOME_DIR, '.workspace-manager-cache', 'config.json');
7
+ // Cache/config/state directory. Prefer the branded NEMUS_* env vars; fall back
8
+ // to the legacy WORKSPACE_MANAGER_* names for backward compatibility; else the
9
+ // default ~/.nemus home.
10
+ const DEFAULT_CACHE_DIR = path.join(HOME_DIR, '.nemus');
11
+ const LEGACY_CACHE_DIR = path.join(HOME_DIR, '.workspace-manager-cache');
12
+
13
+ /**
14
+ * One-time, best-effort migration of state from the pre-0.2.2 cache location
15
+ * (~/.workspace-manager-cache) to ~/.nemus. Runs only when using the default
16
+ * location (no env override) and the new dir doesn't exist yet. Copies rather
17
+ * than moves, so any other tool that happened to share the old path is left
18
+ * untouched; a failure is harmless because a fresh dir is created on first write.
19
+ */
20
+ function migrateLegacyCacheDir(targetDir: string): void {
21
+ try {
22
+ if (targetDir === DEFAULT_CACHE_DIR && !fs.existsSync(targetDir) && fs.existsSync(LEGACY_CACHE_DIR)) {
23
+ fs.cpSync(LEGACY_CACHE_DIR, targetDir, { recursive: true });
24
+ }
25
+ } catch {
26
+ // best-effort — ignore and let the dir be created lazily
27
+ }
28
+ }
29
+
30
+ const CACHE_DIR_RESOLVED =
31
+ process.env.NEMUS_CACHE_DIR || process.env.WORKSPACE_MANAGER_CACHE_DIR || DEFAULT_CACHE_DIR;
32
+ migrateLegacyCacheDir(CACHE_DIR_RESOLVED);
33
+
34
+ // Config file lives inside the cache dir.
35
+ const CONFIG_FILE = path.join(CACHE_DIR_RESOLVED, 'config.json');
9
36
 
10
37
  /** Coding-agent CLIs Nemus can integrate with. */
11
38
  export type ConcreteAgentType = 'claude' | 'pi' | 'opencode' | 'codex' | 'gemini';
@@ -78,8 +105,9 @@ export function getUserConfig(): UserConfig {
78
105
  // Bootstrap constants from initial config read (env vars take precedence)
79
106
  const initialConfig = getUserConfig();
80
107
 
81
- export const WORKSPACES_DIR = process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
82
- export const CACHE_DIR = process.env.WORKSPACE_MANAGER_CACHE_DIR || path.join(HOME_DIR, '.workspace-manager-cache');
108
+ export const WORKSPACES_DIR =
109
+ process.env.NEMUS_DIR || process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
110
+ export const CACHE_DIR = CACHE_DIR_RESOLVED;
83
111
  export const HISTORY_FILE = path.join(CACHE_DIR, 'history.jsonl');
84
112
  export const SUITES_FILE = path.join(CACHE_DIR, 'suites.json');
85
113
  export const META_FILENAME = '.workspace-meta.json';
@@ -1,8 +1,7 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
- import * as os from 'os';
3
+ import { CACHE_DIR } from './config';
4
4
 
5
- const CACHE_DIR = path.join(os.homedir(), '.workspace-manager-cache');
6
5
  const HISTORY_FILE = path.join(CACHE_DIR, 'history.jsonl');
7
6
 
8
7
  export interface OperationRecord {
@@ -62,7 +62,7 @@ describe('Pi extensions', () => {
62
62
  path.join(extensionsDir, 'ws-review-reminder.ts'),
63
63
  'utf-8'
64
64
  );
65
- expect(content).toContain('.workspace-manager-cache');
65
+ expect(content).toContain('.nemus');
66
66
  expect(content).toContain('last-permission-review');
67
67
  });
68
68