group-chat-mcp 0.1.6 → 0.2.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -0
  3. package/STEPS.md +23 -27
  4. package/dist/__tests__/install-metadata-service.test.d.ts +2 -0
  5. package/dist/__tests__/install-metadata-service.test.d.ts.map +1 -0
  6. package/dist/__tests__/install-metadata-service.test.js +80 -0
  7. package/dist/__tests__/install-metadata-service.test.js.map +1 -0
  8. package/dist/__tests__/update-notice.test.d.ts +2 -0
  9. package/dist/__tests__/update-notice.test.d.ts.map +1 -0
  10. package/dist/__tests__/update-notice.test.js +122 -0
  11. package/dist/__tests__/update-notice.test.js.map +1 -0
  12. package/dist/__tests__/update-service.test.d.ts +2 -0
  13. package/dist/__tests__/update-service.test.d.ts.map +1 -0
  14. package/dist/__tests__/update-service.test.js +93 -0
  15. package/dist/__tests__/update-service.test.js.map +1 -0
  16. package/dist/__tests__/version-check-service.test.d.ts +2 -0
  17. package/dist/__tests__/version-check-service.test.d.ts.map +1 -0
  18. package/dist/__tests__/version-check-service.test.js +103 -0
  19. package/dist/__tests__/version-check-service.test.js.map +1 -0
  20. package/dist/constants/storage.d.ts +4 -0
  21. package/dist/constants/storage.d.ts.map +1 -1
  22. package/dist/constants/storage.js +4 -0
  23. package/dist/constants/storage.js.map +1 -1
  24. package/dist/gchat.d.ts +1 -0
  25. package/dist/gchat.d.ts.map +1 -1
  26. package/dist/gchat.js +34 -2
  27. package/dist/gchat.js.map +1 -1
  28. package/dist/services/install-metadata-service.d.ts +15 -0
  29. package/dist/services/install-metadata-service.d.ts.map +1 -0
  30. package/dist/services/install-metadata-service.js +59 -0
  31. package/dist/services/install-metadata-service.js.map +1 -0
  32. package/dist/services/installer-service.d.ts +3 -0
  33. package/dist/services/installer-service.d.ts.map +1 -1
  34. package/dist/services/installer-service.js +55 -46
  35. package/dist/services/installer-service.js.map +1 -1
  36. package/dist/services/update-service.d.ts +13 -0
  37. package/dist/services/update-service.d.ts.map +1 -0
  38. package/dist/services/update-service.js +76 -0
  39. package/dist/services/update-service.js.map +1 -0
  40. package/dist/services/version-check-service.d.ts +12 -0
  41. package/dist/services/version-check-service.d.ts.map +1 -0
  42. package/dist/services/version-check-service.js +109 -0
  43. package/dist/services/version-check-service.js.map +1 -0
  44. package/dist/types/index.d.ts +1 -0
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/types/parsed-command.d.ts +4 -0
  47. package/dist/types/parsed-command.d.ts.map +1 -1
  48. package/dist/types/version-check-result.d.ts +6 -0
  49. package/dist/types/version-check-result.d.ts.map +1 -0
  50. package/dist/types/version-check-result.js +2 -0
  51. package/dist/types/version-check-result.js.map +1 -0
  52. package/dist/utils/update-utils.d.ts +2 -0
  53. package/dist/utils/update-utils.d.ts.map +1 -0
  54. package/dist/utils/update-utils.js +4 -0
  55. package/dist/utils/update-utils.js.map +1 -0
  56. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-03-24
9
+
10
+ ### Added
11
+
12
+ - `gchat update` command: checks npm registry for the latest version, runs `npm install -g group-chat-mcp@latest`, and re-execs the new binary to refresh all previously installed IDE configurations
13
+ - Passive update notice on interactive commands (`install`, `uninstall`) when a newer npm version exists
14
+ - `InstallMetadataService` persists IDE+scope combos during install/uninstall so `gchat update` knows what to refresh
15
+ - `VersionCheckService` with 24h file-cached npm registry queries and semver comparison
16
+ - `formatUpdateNotice` utility for the update available message
17
+ - `update` and `update-post-install` command variants in `ParsedCommand`
18
+ - Unit and integration tests for all new services and update notice behavior
19
+
20
+ ### Changed
21
+
22
+ - `InstallerService` accepts `InstallMetadataService` via constructor parameter (dependency injection)
23
+ - `main()` exported from `gchat.ts` for testability
24
+ - Help text updated with `update` command
25
+
8
26
  ## [0.1.6] - 2026-03-23
9
27
 
10
28
  ### Added
package/README.md CHANGED
@@ -15,6 +15,8 @@ Multi-agent communication server using the Model Context Protocol. Enables AI ag
15
15
  - Pull-based inbox for clients without push notification support (`read_notifications`)
16
16
  - File-based shared state with atomic locking
17
17
  - Zero-config setup via `gchat install`
18
+ - Built-in update command (`gchat update`) with automatic IDE config refresh
19
+ - Passive update notices on interactive commands when a newer version is available
18
20
 
19
21
  ## Installation
20
22
 
@@ -48,6 +50,14 @@ The installer prompts for:
48
50
 
49
51
  For Claude Code, the installer registers the MCP server via `claude mcp add` (requires the Claude Code CLI on PATH). For Cursor, it writes `mcp.json` with the server entry and `hooks.json` with session lifecycle hooks that register and unregister agents per chat session.
50
52
 
53
+ To update to the latest version:
54
+
55
+ ```bash
56
+ gchat update
57
+ ```
58
+
59
+ This checks the npm registry, installs the latest version, and refreshes all previously configured IDE setups. Interactive commands (`install`, `uninstall`) also display an update notice when a newer version is available.
60
+
51
61
  To remove the configuration:
52
62
 
53
63
  ```bash
package/STEPS.md CHANGED
@@ -1,43 +1,39 @@
1
1
  # End Goal
2
2
 
3
- Add a per-conversation message-sending lock that serializes sends within a conversation. When an agent attempts to send while another agent is already sending, the server waits internally until the competing message lands, then returns that message with strict instructions to reconsider and resend never discarding the blocked agent's intent.
3
+ Add manual and automatic update capabilities to the `gchat` CLI, allowing users to update the globally installed `group-chat-mcp` npm package on demand and be notified (or auto-updated) when a newer version is available.
4
4
 
5
5
  ## Steps
6
6
 
7
- - [ ] Add a per-conversation send lock to the state layer
8
- - [ ] File-based lock per conversation (reusing the existing lock primitive pattern) that tracks the sending agent's ID
9
- - [ ] 10-second stale threshold with automatic lock breaking and process liveness check
10
- - [ ] Integrate the lock into the send_message tool handler
11
- - [ ] On lock contention: wait internally (poll) until the competing message lands
12
- - [ ] On lock release: read the latest message(s) from the conversation file that arrived while waiting, return them with strict instructions to read, reconsider the original intent, and resend (do NOT echo the blocked agent's original message)
13
- - [ ] On timeout: break the stale lock and proceed with the blocked agent's send normally
14
- - [ ] Handle lock cleanup on agent disconnect/crash to prevent deadlocks
7
+ - [ ] Persist install metadata during `gchat install` so `gchat update` knows which IDE+scope combos to refresh
8
+ - [ ] Add a version check mechanism that queries the npm registry for the latest published version, compares it to the locally installed version, and caches the result for 24 hours
9
+ - [ ] Integrate the version check into interactive CLI commands (install, uninstall, update) to display an update notice when a newer version is available
10
+ - [ ] Add a `gchat update` command that updates the globally installed package, then re-execs the new binary to refresh configs for all persisted IDE+scope combos
15
11
 
16
12
  ## Questions Answered
17
13
 
18
- 1. Q: Should the send lock be scoped per-conversation or global?
19
- A: Per-conversation. Agents can send to different conversations simultaneously, but only one at a time per conversation.
14
+ 1. Q: What should "automatic update" mean for gchat?
15
+ A: Notify only. On CLI invocation, check the npm registry in the background. If a newer version exists, print an update notice. No mutation without explicit user action via `gchat update`.
20
16
 
21
- 2. Q: Should the server reject immediately or wait internally?
22
- A: Wait internally until the competing message is sent, then return that message in the rejection response. This way the blocked agent immediately sees the message that beat it, can adjust its reply, and retry without waiting for a notification cycle.
17
+ 2. Q: Should the update notice appear on all commands or only interactive ones?
18
+ A: Interactive commands only (install, uninstall, update). Hook commands (cursor-join, cursor-leave) output JSON and must stay clean.
23
19
 
24
- 3. Q: Should the blocked agent still receive the competing message via normal notifications?
25
- A: Yes. The notification system stays unchanged. Duplicate exposure is acceptable and keeps the system simple.
20
+ 3. Q: Should the version check result be cached?
21
+ A: Yes. Cache for 24 hours in a local file. Skip the registry call if checked within that window.
26
22
 
27
- 4. Q: How long should the blocked agent wait before timing out?
28
- A: 10 seconds. Matches existing stale lock threshold. Normal sends complete in milliseconds.
23
+ 4. Q: Should `gchat update` support targeting a specific version?
24
+ A: No. Always install the latest published version.
29
25
 
30
- 5. Q: What happens on timeout?
31
- A: Break the stale lock and let the blocked agent send its message. Automatic recovery, no manual intervention.
26
+ 5. Q: Should `gchat update` re-run the installer after updating?
27
+ A: No full reinstall. After updating the npm package, detect which IDE configurations exist and apply incremental config patches (new files, updated files) without requiring the user to re-run `gchat install`.
32
28
 
33
- 6. Q: Should the rejection response echo the blocked agent's original message?
34
- A: No. Only return the competing message. The agent must reconsider its message in light of what the other agent said — that's the entire point of this feature. Echoing the original would tempt it to just resend without reconsidering.
29
+ 6. Q: How should config patching work?
30
+ A: Re-apply the current version's installer logic for all managed IDE+scope combos. The installer's merge logic is already idempotent it updates existing entries without clobbering unrelated config.
35
31
 
36
- 7. Q: When multiple agents are blocked on the same conversation, queue or race?
37
- A: Race. All waiters re-contend when the lock releases. Losers wait again and see each subsequent competing message. Natural fit for file-based locks.
32
+ 7. Q: How should `gchat update` know which IDE+scope combos to refresh?
33
+ A: Persist install choices in a metadata file (e.g. `~/.group-chat-mcp/install-meta.json`) during `gchat install`. `gchat update` reads this file to determine what to refresh.
38
34
 
39
- 8. Q: How should the blocked agent retrieve the competing message?
40
- A: Read from the conversation's message file after the lock releases. Simple, uses existing data. In a multi-waiter race, each agent sees all messages sent since it started waiting.
35
+ 8. Q: Should the version check run in parallel with the main command or sequentially before it?
36
+ A: Parallel. Fire the registry check at command start, run the main command, print the update notice (if any) after the command completes. No added latency.
41
37
 
42
- 9. Q: Should the send_message tool description be updated to document the lock behavior?
43
- A: No. Discovery only. Agents learn about it when they hit contention via the rejection response.
38
+ 9. Q: After npm install replaces the binary, should the running (old) process refresh configs or re-exec the new binary?
39
+ A: Re-exec the new binary. After `npm install -g` completes, spawn `gchat update --post-install` using the newly installed binary. The new process runs the new version's installer logic, guaranteeing configs match the new version's expectations.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=install-metadata-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-metadata-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/install-metadata-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { InstallMetadataService } from '../services/install-metadata-service.js';
6
+ import { IDE } from '../enums/ide.js';
7
+ import { Scope } from '../enums/scope.js';
8
+ let tmpDir;
9
+ let service;
10
+ beforeEach(async () => {
11
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gchat-meta-test-'));
12
+ service = new InstallMetadataService(tmpDir);
13
+ });
14
+ afterEach(async () => {
15
+ await fs.rm(tmpDir, { recursive: true, force: true });
16
+ });
17
+ describe('InstallMetadataService', () => {
18
+ describe('addInstall', () => {
19
+ it('Given no metadata file exists When addInstall is called with (ClaudeCode, Global) Then the file is created with one entry', async () => {
20
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
21
+ const raw = await fs.readFile(path.join(tmpDir, 'install-meta.json'), 'utf-8');
22
+ const entries = JSON.parse(raw);
23
+ expect(entries).toEqual([{ ide: 'claudeCode', scope: 'global' }]);
24
+ });
25
+ it('Given a metadata file with (ClaudeCode, Global) When addInstall is called with (Cursor, Local) Then the file contains both entries', async () => {
26
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
27
+ await service.addInstall(IDE.Cursor, Scope.Local);
28
+ const raw = await fs.readFile(path.join(tmpDir, 'install-meta.json'), 'utf-8');
29
+ const entries = JSON.parse(raw);
30
+ expect(entries).toEqual([
31
+ { ide: 'claudeCode', scope: 'global' },
32
+ { ide: 'cursor', scope: 'local' },
33
+ ]);
34
+ });
35
+ it('Given a metadata file with (ClaudeCode, Global) When addInstall is called with (ClaudeCode, Global) again Then the file still contains one entry', async () => {
36
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
37
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
38
+ const raw = await fs.readFile(path.join(tmpDir, 'install-meta.json'), 'utf-8');
39
+ const entries = JSON.parse(raw);
40
+ expect(entries).toEqual([{ ide: 'claudeCode', scope: 'global' }]);
41
+ });
42
+ });
43
+ describe('removeInstall', () => {
44
+ it('Given a metadata file with one entry When removeInstall is called for that entry Then the file is deleted', async () => {
45
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
46
+ await service.removeInstall(IDE.ClaudeCode, Scope.Global);
47
+ const exists = await fs.access(path.join(tmpDir, 'install-meta.json')).then(() => true).catch(() => false);
48
+ expect(exists).toBe(false);
49
+ });
50
+ it('Given a metadata file with two entries When removeInstall is called for one Then the file contains the other entry', async () => {
51
+ await service.addInstall(IDE.ClaudeCode, Scope.Global);
52
+ await service.addInstall(IDE.Cursor, Scope.Local);
53
+ await service.removeInstall(IDE.ClaudeCode, Scope.Global);
54
+ const raw = await fs.readFile(path.join(tmpDir, 'install-meta.json'), 'utf-8');
55
+ const entries = JSON.parse(raw);
56
+ expect(entries).toEqual([{ ide: 'cursor', scope: 'local' }]);
57
+ });
58
+ it('Given no metadata file exists When removeInstall is called Then no error is thrown', async () => {
59
+ await expect(service.removeInstall(IDE.ClaudeCode, Scope.Global)).resolves.toBeUndefined();
60
+ });
61
+ });
62
+ describe('getInstalls', () => {
63
+ it('Given no metadata file exists When getInstalls is called Then an empty array is returned', async () => {
64
+ const installs = await service.getInstalls();
65
+ expect(installs).toEqual([]);
66
+ });
67
+ it('Given a metadata file with malformed entries When getInstalls is called Then only valid entries are returned', async () => {
68
+ const metadataPath = path.join(tmpDir, 'install-meta.json');
69
+ await fs.writeFile(metadataPath, JSON.stringify([42, null, { ide: 123 }, { ide: 'claudeCode', scope: 'global' }]), 'utf-8');
70
+ const result = await service.getInstalls();
71
+ expect(result).toEqual([{ ide: 'claudeCode', scope: 'global' }]);
72
+ });
73
+ it('Given a corrupt metadata file When getInstalls is called Then an empty array is returned', async () => {
74
+ await fs.writeFile(path.join(tmpDir, 'install-meta.json'), '{not valid json!!!', 'utf-8');
75
+ const installs = await service.getInstalls();
76
+ expect(installs).toEqual([]);
77
+ });
78
+ });
79
+ });
80
+ //# sourceMappingURL=install-metadata-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-metadata-service.test.js","sourceRoot":"","sources":["../../src/__tests__/install-metadata-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=update-notice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notice.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/update-notice.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { formatUpdateNotice } from '../utils/update-utils.js';
3
+ import { IDE } from '../enums/ide.js';
4
+ import { Scope } from '../enums/scope.js';
5
+ const { mockCheckForUpdate, mockSelectIDE, mockSelectScope, mockClose, mockInstall, mockClaudeCodeScope, mockInit, mockReapStaleAgents, mockRegisterAgent, mockGetOrCreateProjectConversation, mockJoinConversation, mockGetConversation, mockWriteSessionAgent, } = vi.hoisted(() => ({
6
+ mockCheckForUpdate: vi.fn(),
7
+ mockSelectIDE: vi.fn(),
8
+ mockSelectScope: vi.fn(),
9
+ mockClose: vi.fn(),
10
+ mockInstall: vi.fn(),
11
+ mockClaudeCodeScope: vi.fn().mockReturnValue('global'),
12
+ mockInit: vi.fn(),
13
+ mockReapStaleAgents: vi.fn(),
14
+ mockRegisterAgent: vi.fn().mockResolvedValue({ id: 'agent-1', profile: { name: 'test' }, conversations: [] }),
15
+ mockGetOrCreateProjectConversation: vi.fn().mockResolvedValue({ id: 'conv-1', participants: [] }),
16
+ mockJoinConversation: vi.fn(),
17
+ mockGetConversation: vi.fn().mockResolvedValue({ id: 'conv-1', participants: ['agent-1'] }),
18
+ mockWriteSessionAgent: vi.fn(),
19
+ }));
20
+ vi.mock('../services/version-check-service.js', () => {
21
+ return {
22
+ VersionCheckService: vi.fn(function () {
23
+ this.checkForUpdate = mockCheckForUpdate;
24
+ }),
25
+ };
26
+ });
27
+ vi.mock('../utils/prompt-utils.js', () => {
28
+ return {
29
+ PromptUtils: vi.fn(function () {
30
+ this.selectIDE = mockSelectIDE;
31
+ this.selectScope = mockSelectScope;
32
+ this.close = mockClose;
33
+ }),
34
+ };
35
+ });
36
+ vi.mock('../services/installer-service.js', () => {
37
+ return {
38
+ InstallerService: vi.fn(function () {
39
+ this.install = mockInstall;
40
+ this.uninstall = vi.fn();
41
+ this.claudeCodeScope = mockClaudeCodeScope;
42
+ this.resolveSettingsPath = vi.fn().mockReturnValue('/tmp/settings.json');
43
+ }),
44
+ };
45
+ });
46
+ vi.mock('../services/state-service.js', () => {
47
+ return {
48
+ StateService: vi.fn(function () {
49
+ this.init = mockInit;
50
+ this.reapStaleAgents = mockReapStaleAgents;
51
+ this.registerAgent = mockRegisterAgent;
52
+ this.getOrCreateProjectConversation = mockGetOrCreateProjectConversation;
53
+ this.joinConversation = mockJoinConversation;
54
+ this.getConversation = mockGetConversation;
55
+ }),
56
+ };
57
+ });
58
+ vi.mock('../services/session-state-service.js', () => {
59
+ return {
60
+ SessionStateService: vi.fn(function () {
61
+ this.writeSessionAgent = mockWriteSessionAgent;
62
+ }),
63
+ };
64
+ });
65
+ vi.mock('../utils/notification-utils.js', () => {
66
+ return {
67
+ writeNotificationToParticipants: vi.fn(),
68
+ writeProfileSetupNotification: vi.fn(),
69
+ };
70
+ });
71
+ describe('formatUpdateNotice', () => {
72
+ it('Given ("0.1.6", "0.2.0") When called Then it returns the formatted update notice string', () => {
73
+ const result = formatUpdateNotice('0.1.6', '0.2.0');
74
+ expect(result).toBe('\nUpdate available: 0.1.6 → 0.2.0. Run `gchat update` to install.\n');
75
+ });
76
+ });
77
+ describe('update notice behavior', () => {
78
+ let errorSpy;
79
+ let logSpy;
80
+ let originalArgv;
81
+ beforeEach(() => {
82
+ originalArgv = process.argv;
83
+ process.argv = ['node', 'gchat.js', 'install'];
84
+ mockSelectIDE.mockResolvedValue([IDE.ClaudeCode]);
85
+ mockSelectScope.mockResolvedValue(Scope.Global);
86
+ mockInstall.mockResolvedValue(undefined);
87
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
88
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
89
+ });
90
+ afterEach(() => {
91
+ process.argv = originalArgv;
92
+ errorSpy.mockRestore();
93
+ logSpy.mockRestore();
94
+ vi.clearAllMocks();
95
+ });
96
+ it('Given VersionCheckService returns null When an interactive command completes Then no notice is printed', async () => {
97
+ mockCheckForUpdate.mockResolvedValue(null);
98
+ const { main } = await import('../gchat.js');
99
+ await main();
100
+ expect(errorSpy).not.toHaveBeenCalled();
101
+ });
102
+ it('Given VersionCheckService returns updateAvailable: true When an interactive command completes Then the update notice is printed', async () => {
103
+ mockCheckForUpdate.mockResolvedValue({ current: '0.1.6', latest: '0.2.0', updateAvailable: true });
104
+ const { main } = await import('../gchat.js');
105
+ await main();
106
+ expect(errorSpy).toHaveBeenCalledWith(formatUpdateNotice('0.1.6', '0.2.0'));
107
+ });
108
+ it('Given VersionCheckService returns updateAvailable: false When an interactive command completes Then no notice is printed', async () => {
109
+ mockCheckForUpdate.mockResolvedValue({ current: '0.2.0', latest: '0.2.0', updateAvailable: false });
110
+ const { main } = await import('../gchat.js');
111
+ await main();
112
+ expect(errorSpy).not.toHaveBeenCalled();
113
+ });
114
+ it('Given a hook command (cursor-join) is executed Then the version check is not fired', async () => {
115
+ vi.clearAllMocks();
116
+ process.argv = ['node', 'gchat.js', 'cursor-join', '--project', '/tmp/test', '--server-pid', '1234'];
117
+ const { main } = await import('../gchat.js');
118
+ await main();
119
+ expect(mockCheckForUpdate).not.toHaveBeenCalled();
120
+ });
121
+ });
122
+ //# sourceMappingURL=update-notice.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notice.test.js","sourceRoot":"","sources":["../../src/__tests__/update-notice.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=update-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/update-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { parseCommand } from '../gchat.js';
4
+ import { UpdateService } from '../services/update-service.js';
5
+ import { IDE } from '../enums/ide.js';
6
+ import { Scope } from '../enums/scope.js';
7
+ vi.mock('node:child_process', () => ({
8
+ execFileSync: vi.fn(),
9
+ }));
10
+ describe('parseCommand', () => {
11
+ it('Given args ["update"] When parseCommand is called Then it returns { command: "update" }', () => {
12
+ const result = parseCommand(['update']);
13
+ expect(result).toEqual({ command: 'update' });
14
+ });
15
+ it('Given args ["update", "--post-install"] When parseCommand is called Then it returns { command: "update-post-install" }', () => {
16
+ const result = parseCommand(['update', '--post-install']);
17
+ expect(result).toEqual({ command: 'update-post-install' });
18
+ });
19
+ });
20
+ describe('UpdateService', () => {
21
+ let mockVersionCheck;
22
+ let mockInstallMetadata;
23
+ let mockInstaller;
24
+ let updateService;
25
+ beforeEach(() => {
26
+ mockVersionCheck = {
27
+ checkForUpdate: vi.fn(),
28
+ getLocalVersion: vi.fn().mockReturnValue('0.2.0'),
29
+ };
30
+ mockInstallMetadata = {
31
+ getInstalls: vi.fn(),
32
+ };
33
+ mockInstaller = {
34
+ install: vi.fn(),
35
+ };
36
+ updateService = new UpdateService(mockVersionCheck, mockInstallMetadata, mockInstaller);
37
+ });
38
+ describe('performUpdate', () => {
39
+ it('Given checkForUpdate returns updateAvailable: false When performUpdate is called Then it prints "Already up to date" and does not run npm install', async () => {
40
+ mockVersionCheck.checkForUpdate.mockResolvedValue({
41
+ current: '0.2.0', latest: '0.2.0', updateAvailable: false,
42
+ });
43
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
44
+ await updateService.performUpdate();
45
+ expect(logSpy).toHaveBeenCalledWith('Already up to date (0.2.0)');
46
+ logSpy.mockRestore();
47
+ });
48
+ it('Given checkForUpdate returns updateAvailable: true When performUpdate is called Then it runs npm install and re-execs gchat', async () => {
49
+ mockVersionCheck.checkForUpdate.mockResolvedValue({
50
+ current: '0.1.6', latest: '0.2.0', updateAvailable: true,
51
+ });
52
+ const mockedExecFileSync = vi.mocked(execFileSync);
53
+ mockedExecFileSync.mockImplementation((file, args) => {
54
+ if (file === 'which')
55
+ return '/usr/local/bin/gchat';
56
+ return '';
57
+ });
58
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
59
+ await updateService.performUpdate();
60
+ expect(logSpy).toHaveBeenCalledWith('Updating group-chat-mcp 0.1.6 → 0.2.0...');
61
+ expect(mockedExecFileSync).toHaveBeenCalledWith('npm', ['install', '-g', 'group-chat-mcp@latest'], { stdio: 'inherit' });
62
+ expect(mockedExecFileSync).toHaveBeenCalledWith('/usr/local/bin/gchat', ['update', '--post-install'], { stdio: 'inherit' });
63
+ logSpy.mockRestore();
64
+ });
65
+ it('Given checkForUpdate returns null When performUpdate is called Then it throws an error', async () => {
66
+ mockVersionCheck.checkForUpdate.mockResolvedValue(null);
67
+ await expect(updateService.performUpdate()).rejects.toThrow('Failed to check for updates. Please try again later.');
68
+ });
69
+ });
70
+ describe('performPostInstall', () => {
71
+ it('Given install metadata has entries When performPostInstall is called Then it calls InstallerService.install for each entry', async () => {
72
+ mockInstallMetadata.getInstalls.mockResolvedValue([
73
+ { ide: IDE.ClaudeCode, scope: Scope.Global },
74
+ { ide: IDE.Cursor, scope: Scope.Local },
75
+ ]);
76
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
77
+ await updateService.performPostInstall();
78
+ expect(mockInstaller.install).toHaveBeenCalledTimes(2);
79
+ expect(mockInstaller.install).toHaveBeenCalledWith({ ide: IDE.ClaudeCode, scope: Scope.Global });
80
+ expect(mockInstaller.install).toHaveBeenCalledWith({ ide: IDE.Cursor, scope: Scope.Local });
81
+ logSpy.mockRestore();
82
+ });
83
+ it('Given install metadata is empty When performPostInstall is called Then it prints "No install metadata found" and does not call InstallerService.install', async () => {
84
+ mockInstallMetadata.getInstalls.mockResolvedValue([]);
85
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
86
+ await updateService.performPostInstall();
87
+ expect(logSpy).toHaveBeenCalledWith('No install metadata found. Run `gchat install` to configure your IDE.');
88
+ expect(mockInstaller.install).not.toHaveBeenCalled();
89
+ logSpy.mockRestore();
90
+ });
91
+ });
92
+ });
93
+ //# sourceMappingURL=update-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-service.test.js","sourceRoot":"","sources":["../../src/__tests__/update-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=version-check-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/version-check-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { VersionCheckService } from '../services/version-check-service.js';
6
+ import { VERSION_CHECK_FILE } from '../constants/storage.js';
7
+ let tmpDir;
8
+ let service;
9
+ beforeEach(async () => {
10
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gchat-version-check-test-'));
11
+ service = new VersionCheckService(tmpDir);
12
+ vi.spyOn(VersionCheckService.prototype, 'getLocalVersion').mockReturnValue('0.1.6');
13
+ });
14
+ afterEach(async () => {
15
+ vi.restoreAllMocks();
16
+ await fs.rm(tmpDir, { recursive: true, force: true });
17
+ });
18
+ describe('VersionCheckService', () => {
19
+ describe('getLocalVersion', () => {
20
+ it('Given the package.json exists When getLocalVersion is called Then it returns a valid semver string', () => {
21
+ vi.restoreAllMocks();
22
+ const svc = new VersionCheckService(tmpDir);
23
+ const version = svc.getLocalVersion();
24
+ expect(version).toMatch(/^\d+\.\d+\.\d+/);
25
+ });
26
+ });
27
+ describe('checkForUpdate', () => {
28
+ it('Given a fresh cache with latest "0.2.0" and local version "0.1.6" When checkForUpdate is called Then it returns updateAvailable true without hitting the registry', async () => {
29
+ const cachePath = path.join(tmpDir, VERSION_CHECK_FILE);
30
+ await fs.writeFile(cachePath, JSON.stringify({ latest: '0.2.0', checkedAt: Date.now() }), 'utf-8');
31
+ const fetchSpy = vi.spyOn(service, 'fetchLatestVersion');
32
+ const result = await service.checkForUpdate();
33
+ expect(result).toEqual({
34
+ current: '0.1.6',
35
+ latest: '0.2.0',
36
+ updateAvailable: true,
37
+ });
38
+ expect(fetchSpy).not.toHaveBeenCalled();
39
+ });
40
+ it('Given a fresh cache with latest "0.1.6" and local version "0.1.6" When checkForUpdate is called Then it returns updateAvailable false', async () => {
41
+ const cachePath = path.join(tmpDir, VERSION_CHECK_FILE);
42
+ await fs.writeFile(cachePath, JSON.stringify({ latest: '0.1.6', checkedAt: Date.now() }), 'utf-8');
43
+ const result = await service.checkForUpdate();
44
+ expect(result).toEqual({
45
+ current: '0.1.6',
46
+ latest: '0.1.6',
47
+ updateAvailable: false,
48
+ });
49
+ });
50
+ it('Given an expired cache When checkForUpdate is called Then it queries the registry and writes a new cache', async () => {
51
+ const cachePath = path.join(tmpDir, VERSION_CHECK_FILE);
52
+ const expiredTime = Date.now() - 86_400_000 - 1;
53
+ await fs.writeFile(cachePath, JSON.stringify({ latest: '0.1.5', checkedAt: expiredTime }), 'utf-8');
54
+ vi.spyOn(service, 'fetchLatestVersion').mockResolvedValue('0.2.0');
55
+ const result = await service.checkForUpdate();
56
+ expect(result).toEqual({
57
+ current: '0.1.6',
58
+ latest: '0.2.0',
59
+ updateAvailable: true,
60
+ });
61
+ const raw = await fs.readFile(cachePath, 'utf-8');
62
+ const cached = JSON.parse(raw);
63
+ expect(cached.latest).toBe('0.2.0');
64
+ });
65
+ it('Given no cache file When checkForUpdate is called Then it queries the registry and writes a new cache', async () => {
66
+ vi.spyOn(service, 'fetchLatestVersion').mockResolvedValue('0.2.0');
67
+ const result = await service.checkForUpdate();
68
+ expect(result).toEqual({
69
+ current: '0.1.6',
70
+ latest: '0.2.0',
71
+ updateAvailable: true,
72
+ });
73
+ const cachePath = path.join(tmpDir, VERSION_CHECK_FILE);
74
+ const raw = await fs.readFile(cachePath, 'utf-8');
75
+ const cached = JSON.parse(raw);
76
+ expect(cached.latest).toBe('0.2.0');
77
+ });
78
+ it('Given the npm registry is unreachable and no cache exists When checkForUpdate is called Then it returns null', async () => {
79
+ vi.spyOn(service, 'fetchLatestVersion').mockResolvedValue(null);
80
+ const result = await service.checkForUpdate();
81
+ expect(result).toBeNull();
82
+ });
83
+ it('Given a corrupt cache file When checkForUpdate is called Then it queries the registry', async () => {
84
+ const cachePath = path.join(tmpDir, VERSION_CHECK_FILE);
85
+ await fs.writeFile(cachePath, 'not-json!!!', 'utf-8');
86
+ vi.spyOn(service, 'fetchLatestVersion').mockResolvedValue('0.2.0');
87
+ const result = await service.checkForUpdate();
88
+ expect(result).toEqual({
89
+ current: '0.1.6',
90
+ latest: '0.2.0',
91
+ updateAvailable: true,
92
+ });
93
+ });
94
+ it('Given getLocalVersion throws When checkForUpdate is called Then it returns null', async () => {
95
+ vi.spyOn(VersionCheckService.prototype, 'getLocalVersion').mockImplementation(() => {
96
+ throw new Error('cannot read package.json');
97
+ });
98
+ const result = await service.checkForUpdate();
99
+ expect(result).toBeNull();
100
+ });
101
+ });
102
+ });
103
+ //# sourceMappingURL=version-check-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check-service.test.js","sourceRoot":"","sources":["../../src/__tests__/version-check-service.test.ts"],"names":[],"mappings":"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"}
@@ -4,4 +4,8 @@ export declare const CONVERSATIONS_FILE = "conversations.json";
4
4
  export declare const MESSAGES_DIR = "messages";
5
5
  export declare const INBOXES_DIR = "inboxes";
6
6
  export declare const SESSIONS_DIR = "sessions";
7
+ export declare const INSTALL_META_FILE = "install-meta.json";
8
+ export declare const VERSION_CHECK_FILE = "version-check.json";
9
+ export declare const VERSION_CHECK_TTL_MS = 86400000;
10
+ export declare const NPM_REGISTRY_TIMEOUT_MS = 3000;
7
11
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/constants/storage.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,QAA6C,CAAC;AACnE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,YAAY,aAAa,CAAC;AACvC,eAAO,MAAM,WAAW,YAAY,CAAC;AACrC,eAAO,MAAM,YAAY,aAAa,CAAC"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/constants/storage.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,QAA6C,CAAC;AACnE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,YAAY,aAAa,CAAC;AACvC,eAAO,MAAM,WAAW,YAAY,CAAC;AACrC,eAAO,MAAM,YAAY,aAAa,CAAC;AACvC,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,oBAAoB,WAAa,CAAC;AAC/C,eAAO,MAAM,uBAAuB,OAAQ,CAAC"}