@nemus-cli/nemus 0.2.1 → 0.2.3

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,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2026-08-25
11
+
12
+ ### Fixed
13
+
14
+ - The one-time `~/.workspace-manager-cache` → `~/.nemus` migration no longer
15
+ copies `last-version-check.json`. If the old (shared) path held another tool's
16
+ update-check cache, inheriting it could make nemus report a wrong "latest"
17
+ version until the entry expired; skipping it forces one fresh lookup instead.
18
+
19
+ ## [0.2.2] - 2026-08-25
20
+
21
+ ### Changed
22
+
23
+ - **Namespaced all cache/config/state under `~/.nemus`** (was
24
+ `~/.workspace-manager-cache`). This avoids collisions with other tools that
25
+ used the old generic path — which could, for example, make the update check
26
+ report a wrong "latest" version.
27
+ - Environment overrides are now **`NEMUS_DIR`** and **`NEMUS_CACHE_DIR`**; the
28
+ legacy `WORKSPACE_MANAGER_DIR` / `WORKSPACE_MANAGER_CACHE_DIR` names still work
29
+ as fallbacks.
30
+ - On first run, existing state is **migrated automatically** from
31
+ `~/.workspace-manager-cache` to `~/.nemus` (a non-destructive copy — the old
32
+ directory is left intact).
33
+ - Published as the scoped package **`@nemus-cli/nemus`** (npm's similarity guard
34
+ blocks the bare name `nemus`). The `nemus` / `nem` CLI commands are unchanged.
35
+
10
36
  ## [0.2.1] - 2026-08-24
11
37
 
12
38
  ### Security
@@ -80,7 +106,9 @@ Initial open-source release of **Nemus**.
80
106
  - Automatic retries with backoff on flaky network operations.
81
107
  - Optional shell integration (auto-cd on create + quick-navigate helper).
82
108
 
83
- [Unreleased]: https://github.com/me-public/nemus/compare/v0.2.1...HEAD
109
+ [Unreleased]: https://github.com/me-public/nemus/compare/v0.2.3...HEAD
110
+ [0.2.3]: https://github.com/me-public/nemus/compare/v0.2.2...v0.2.3
111
+ [0.2.2]: https://github.com/me-public/nemus/compare/v0.2.1...v0.2.2
84
112
  [0.2.1]: https://github.com/me-public/nemus/compare/v0.2.0...v0.2.1
85
113
  [0.2.0]: https://github.com/me-public/nemus/compare/v0.1.0...v0.2.0
86
114
  [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,41 @@ 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
+ * `last-version-check.json` is deliberately NOT migrated: the old path could be
58
+ * shared by another tool whose "latest version" is unrelated to nemus, and
59
+ * copying it would make the update check report a wrong version until the entry
60
+ * expires. Skipping it just forces one fresh lookup.
61
+ */
62
+ const MIGRATION_SKIP = new Set(['last-version-check.json']);
63
+ function migrateLegacyCacheDir(targetDir) {
64
+ try {
65
+ if (targetDir === DEFAULT_CACHE_DIR && !fs.existsSync(targetDir) && fs.existsSync(LEGACY_CACHE_DIR)) {
66
+ fs.cpSync(LEGACY_CACHE_DIR, targetDir, {
67
+ recursive: true,
68
+ filter: (src) => !MIGRATION_SKIP.has(path.basename(src)),
69
+ });
70
+ }
71
+ }
72
+ catch {
73
+ // best-effort — ignore and let the dir be created lazily
74
+ }
75
+ }
76
+ const CACHE_DIR_RESOLVED = process.env.NEMUS_CACHE_DIR || process.env.WORKSPACE_MANAGER_CACHE_DIR || DEFAULT_CACHE_DIR;
77
+ migrateLegacyCacheDir(CACHE_DIR_RESOLVED);
78
+ // Config file lives inside the cache dir.
79
+ const CONFIG_FILE = path.join(CACHE_DIR_RESOLVED, 'config.json');
47
80
  const DEFAULTS = {
48
81
  workspacesDir: path.join(HOME_DIR, 'workspaces'),
49
82
  githubOrg: '',
@@ -99,8 +132,8 @@ function getUserConfig() {
99
132
  }
100
133
  // Bootstrap constants from initial config read (env vars take precedence)
101
134
  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');
135
+ exports.WORKSPACES_DIR = process.env.NEMUS_DIR || process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
136
+ exports.CACHE_DIR = CACHE_DIR_RESOLVED;
104
137
  exports.HISTORY_FILE = path.join(exports.CACHE_DIR, 'history.jsonl');
105
138
  exports.SUITES_FILE = path.join(exports.CACHE_DIR, 'suites.json');
106
139
  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.3",
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,63 @@ 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 last-version-check.json (avoids inheriting a foreign latest)', async () => {
240
+ delete process.env.NEMUS_CACHE_DIR;
241
+ delete process.env.WORKSPACE_MANAGER_CACHE_DIR;
242
+ const legacy = path.join(tempDir, '.workspace-manager-cache');
243
+ fs.writeFileSync(path.join(legacy, 'suites.json'), '{"x":1}');
244
+ fs.writeFileSync(path.join(legacy, 'last-version-check.json'), '{"latestVersion":"99.0.0"}');
245
+ vi.resetModules();
246
+ await loadModule();
247
+ // other state migrates, but the version-check cache is left behind
248
+ expect(fs.existsSync(path.join(tempDir, '.nemus', 'suites.json'))).toBe(true);
249
+ expect(fs.existsSync(path.join(tempDir, '.nemus', 'last-version-check.json'))).toBe(false);
250
+ });
251
+
252
+ it('does not migrate when the new dir already exists', async () => {
253
+ delete process.env.NEMUS_CACHE_DIR;
254
+ delete process.env.WORKSPACE_MANAGER_CACHE_DIR;
255
+ fs.mkdirSync(path.join(tempDir, '.nemus'), { recursive: true });
256
+ fs.writeFileSync(path.join(tempDir, '.workspace-manager-cache', 'suites.json'), '{"x":1}');
257
+ vi.resetModules();
258
+ await loadModule();
259
+ expect(fs.existsSync(path.join(tempDir, '.nemus', 'suites.json'))).toBe(false);
260
+ });
261
+ });
203
262
  });
@@ -4,8 +4,44 @@ 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
+ * `last-version-check.json` is deliberately NOT migrated: the old path could be
21
+ * shared by another tool whose "latest version" is unrelated to nemus, and
22
+ * copying it would make the update check report a wrong version until the entry
23
+ * expires. Skipping it just forces one fresh lookup.
24
+ */
25
+ const MIGRATION_SKIP = new Set(['last-version-check.json']);
26
+ function migrateLegacyCacheDir(targetDir: string): void {
27
+ try {
28
+ if (targetDir === DEFAULT_CACHE_DIR && !fs.existsSync(targetDir) && fs.existsSync(LEGACY_CACHE_DIR)) {
29
+ fs.cpSync(LEGACY_CACHE_DIR, targetDir, {
30
+ recursive: true,
31
+ filter: (src) => !MIGRATION_SKIP.has(path.basename(src)),
32
+ });
33
+ }
34
+ } catch {
35
+ // best-effort — ignore and let the dir be created lazily
36
+ }
37
+ }
38
+
39
+ const CACHE_DIR_RESOLVED =
40
+ process.env.NEMUS_CACHE_DIR || process.env.WORKSPACE_MANAGER_CACHE_DIR || DEFAULT_CACHE_DIR;
41
+ migrateLegacyCacheDir(CACHE_DIR_RESOLVED);
42
+
43
+ // Config file lives inside the cache dir.
44
+ const CONFIG_FILE = path.join(CACHE_DIR_RESOLVED, 'config.json');
9
45
 
10
46
  /** Coding-agent CLIs Nemus can integrate with. */
11
47
  export type ConcreteAgentType = 'claude' | 'pi' | 'opencode' | 'codex' | 'gemini';
@@ -78,8 +114,9 @@ export function getUserConfig(): UserConfig {
78
114
  // Bootstrap constants from initial config read (env vars take precedence)
79
115
  const initialConfig = getUserConfig();
80
116
 
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');
117
+ export const WORKSPACES_DIR =
118
+ process.env.NEMUS_DIR || process.env.WORKSPACE_MANAGER_DIR || initialConfig.workspacesDir;
119
+ export const CACHE_DIR = CACHE_DIR_RESOLVED;
83
120
  export const HISTORY_FILE = path.join(CACHE_DIR, 'history.jsonl');
84
121
  export const SUITES_FILE = path.join(CACHE_DIR, 'suites.json');
85
122
  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