gemiterm 2.0.0 → 2.1.1

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 (155) hide show
  1. package/README.md +32 -9
  2. package/package.json +13 -4
  3. package/src/cli/command-registry.ts +66 -62
  4. package/src/cli/commands/continue-command.ts +75 -1
  5. package/src/cli/commands/install-skills-command.ts +35 -0
  6. package/src/cli/commands/new-command.ts +62 -0
  7. package/src/cli/index.ts +19 -44
  8. package/src/cli/utils/long-arg-guard.ts +28 -0
  9. package/src/cli/utils/prompt-file.ts +26 -0
  10. package/src/infrastructure/cli-parser.ts +77 -0
  11. package/src/infrastructure/io.ts +9 -0
  12. package/src/services/skills-cli-driver.ts +58 -0
  13. package/src/services/skills-service.ts +52 -0
  14. package/.env +0 -2
  15. package/.github/workflows/manual-release.yml +0 -34
  16. package/.github/workflows/release.yml +0 -198
  17. package/.github/workflows/test.yml +0 -29
  18. package/.opencode/commands/opsx-apply.md +0 -152
  19. package/.opencode/commands/opsx-archive.md +0 -157
  20. package/.opencode/commands/opsx-explore.md +0 -169
  21. package/.opencode/commands/opsx-propose.md +0 -104
  22. package/.opencode/commands/opsx-sync.md +0 -140
  23. package/.opencode/commands/search-intelligently.md +0 -9
  24. package/.opencode/opencode.json +0 -20
  25. package/.opencode/skills/intellisearch/.version +0 -1
  26. package/.opencode/skills/intellisearch/SKILL.md +0 -84
  27. package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
  28. package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
  29. package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
  30. package/.opencode/skills/intellisearch/references/examples.md +0 -97
  31. package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
  32. package/.opencode/skills/intellisearch/references/google-search.md +0 -40
  33. package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
  34. package/.opencode/skills/intellisearch/references/workflow.md +0 -115
  35. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
  36. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
  37. package/.opencode/skills/openspec-explore/SKILL.md +0 -287
  38. package/.opencode/skills/openspec-propose/SKILL.md +0 -111
  39. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
  40. package/.serena/project.yml +0 -133
  41. package/AGENTS.md +0 -136
  42. package/CHANGELOG.md +0 -3
  43. package/bun.lock +0 -105
  44. package/docs/INSTALL.md +0 -185
  45. package/docs/PLAYWRIGHT_CLI_API.md +0 -306
  46. package/docs/python-release-for-reference.md +0 -175
  47. package/examples/sample_json_export.json +0 -32
  48. package/examples/sample_markdown_export.md +0 -221
  49. package/examples/usage_examples.sh +0 -222
  50. package/install.ps1 +0 -264
  51. package/install.sh +0 -275
  52. package/openspec/MAESTRO_MIGRATION.md +0 -180
  53. package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
  54. package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
  55. package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
  56. package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
  57. package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
  58. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
  59. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
  60. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
  61. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
  62. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
  63. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
  64. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
  65. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
  66. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
  67. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
  68. package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
  69. package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
  70. package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
  71. package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
  72. package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
  73. package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
  74. package/openspec/changes/commander-cli-parser/design.md +0 -107
  75. package/openspec/changes/commander-cli-parser/proposal.md +0 -31
  76. package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
  77. package/openspec/changes/commander-cli-parser/tasks.md +0 -33
  78. package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
  79. package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
  80. package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
  81. package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
  82. package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
  83. package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
  84. package/openspec/config.yaml +0 -20
  85. package/openspec/specs/auth/spec.md +0 -394
  86. package/openspec/specs/cli/spec.md +0 -108
  87. package/openspec/specs/commands/spec.md +0 -409
  88. package/openspec/specs/configuration/spec.md +0 -130
  89. package/openspec/specs/conversations/spec.md +0 -139
  90. package/openspec/specs/domain-model/spec.md +0 -128
  91. package/openspec/specs/formatters/spec.md +0 -158
  92. package/openspec/specs/install-browser/spec.md +0 -146
  93. package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
  94. package/openspec/specs/logger/spec.md +0 -71
  95. package/openspec/specs/mediator/spec.md +0 -72
  96. package/openspec/specs/multi-profile-conversations/spec.md +0 -145
  97. package/openspec/specs/path-utils/spec.md +0 -101
  98. package/openspec/specs/profiles/spec.md +0 -94
  99. package/openspec/specs/storage/spec.md +0 -198
  100. package/openspec/specs/testing/spec.md +0 -237
  101. package/openspec/specs/v2-installer/spec.md +0 -205
  102. package/openspec/specs/validators/spec.md +0 -65
  103. package/scripts/build.ts +0 -88
  104. package/scripts/clean-build.ps1 +0 -4
  105. package/scripts/clean-build.sh +0 -5
  106. package/scripts/install-browser.ps1 +0 -5
  107. package/scripts/install-browser.sh +0 -6
  108. package/scripts/lint-path-mediation.ps1 +0 -65
  109. package/scripts/lint-path-mediation.sh +0 -40
  110. package/tests/cli/auth-command.test.ts +0 -220
  111. package/tests/cli/command-registry.test.ts +0 -67
  112. package/tests/cli/continue-command.test.ts +0 -100
  113. package/tests/cli/delete-command.test.ts +0 -128
  114. package/tests/cli/export-all-command.test.ts +0 -375
  115. package/tests/cli/install-browser-command.test.ts +0 -48
  116. package/tests/cli/list-command.test.ts +0 -179
  117. package/tests/cli/new-command.test.ts +0 -139
  118. package/tests/cli/status-command.test.ts +0 -106
  119. package/tests/core/query-handlers.test.ts +0 -187
  120. package/tests/fixtures/auth-fixtures.ts +0 -98
  121. package/tests/fixtures/chat-fixtures.ts +0 -87
  122. package/tests/infrastructure/cli-table.test.ts +0 -107
  123. package/tests/infrastructure/formatters.test.ts +0 -202
  124. package/tests/infrastructure/io.test.ts +0 -197
  125. package/tests/infrastructure/logger.test.ts +0 -74
  126. package/tests/infrastructure/storage.test.ts +0 -335
  127. package/tests/infrastructure/validators.test.ts +0 -67
  128. package/tests/integration/commands/auth.test.ts +0 -186
  129. package/tests/integration/commands/continue.test.ts +0 -130
  130. package/tests/integration/commands/delete.test.ts +0 -140
  131. package/tests/integration/commands/export.test.ts +0 -266
  132. package/tests/integration/commands/fetch.test.ts +0 -238
  133. package/tests/integration/commands/list.test.ts +0 -281
  134. package/tests/integration/commands/profile.test.ts +0 -258
  135. package/tests/integration/commands/status.test.ts +0 -223
  136. package/tests/integration/installer-script-shape.test.ts +0 -67
  137. package/tests/parity/compare-outputs.ts +0 -359
  138. package/tests/parity/test-commands-parity.ps1 +0 -82
  139. package/tests/parity/test-commands-parity.sh +0 -61
  140. package/tests/services/auth-service.test.ts +0 -315
  141. package/tests/services/cookie-monitor.test.ts +0 -229
  142. package/tests/services/cookie-storage-service.test.ts +0 -230
  143. package/tests/services/gemini-client-wrapper.test.ts +0 -688
  144. package/tests/services/install-browser-service.test.ts +0 -83
  145. package/tests/services/playwright-cli-driver.test.ts +0 -468
  146. package/tests/services/profile-auth-manager.test.ts +0 -334
  147. package/tests/services/profile-service.test.ts +0 -224
  148. package/tests/setup.ts +0 -95
  149. package/tests/smoke/smoke.test.ts +0 -52
  150. package/tests/unit/config.test.ts +0 -159
  151. package/tests/unit/errors.test.ts +0 -119
  152. package/tests/unit/formatters.test.ts +0 -316
  153. package/tests/unit/mediator.test.ts +0 -152
  154. package/tests/unit/path-utils.test.ts +0 -289
  155. package/tsconfig.json +0 -16
@@ -1,139 +0,0 @@
1
- import { describe, test, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
2
- import { NewCommand } from "../../src/cli/commands/new-command.ts";
3
- import { Mediator } from "../../src/core/mediator.ts";
4
- import type { CliCommandContext } from "../../src/cli/command-registry.ts";
5
- import { COMMAND_TYPES } from "../../src/core/command-handlers.ts";
6
-
7
- describe("NewCommand", () => {
8
- let command: NewCommand;
9
- let mediator: Mediator;
10
- let context: CliCommandContext;
11
- let logSpy: ReturnType<typeof spyOn>;
12
-
13
- beforeEach(() => {
14
- command = new NewCommand();
15
- mediator = new Mediator();
16
- context = {
17
- verbose: false,
18
- mediator,
19
- profileAuthManager: {
20
- getActiveProfiles: mock(() => ["default"]),
21
- findProfileForConversation: mock(() => null),
22
- ensureAuthenticated: mock(() => ({ secure_1psid: "", secure_1psidts: null })),
23
- } as unknown as CliCommandContext["profileAuthManager"],
24
- };
25
- logSpy = spyOn(console, "log").mockImplementation(() => {});
26
- });
27
-
28
- afterEach(() => {
29
- mock.restore();
30
- logSpy.mockRestore();
31
- });
32
-
33
- test("has correct name and description", () => {
34
- expect(command.name).toBe("new");
35
- expect(command.description).toBe("Start a new conversation");
36
- });
37
-
38
- test("shows help with --help flag", async () => {
39
- await command.execute(["--help"], context);
40
- const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
41
- expect(output).toContain("Usage: gemiterm new");
42
- expect(output).toContain("--profile");
43
- expect(output).toContain("message");
44
- });
45
-
46
- test("shows help with -h flag", async () => {
47
- await command.execute(["-h"], context);
48
- const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
49
- expect(output).toContain("Usage: gemiterm new");
50
- });
51
-
52
- test("sends start-new-chat command with message", async () => {
53
- const mockHandler = {
54
- commandType: COMMAND_TYPES.START_NEW_CHAT,
55
- handle: mock(async () => ({
56
- response: "Hello from Gemini!",
57
- conversationId: "conv-123",
58
- })),
59
- };
60
- mediator.registerCommandHandler(mockHandler as any);
61
-
62
- await command.execute(["Hello there"], context);
63
-
64
- expect(mockHandler.handle).toHaveBeenCalledWith(
65
- expect.objectContaining({
66
- type: COMMAND_TYPES.START_NEW_CHAT,
67
- payload: expect.objectContaining({
68
- message: "Hello there",
69
- }),
70
- }),
71
- );
72
-
73
- const output = logSpy.mock.calls.map((c) => c[0]).join("\n");
74
- expect(output).toContain("conv-123");
75
- expect(output).toContain("Hello from Gemini!");
76
- });
77
-
78
- test("sends start-new-chat command with profile specified via -p", async () => {
79
- const mockHandler = {
80
- commandType: COMMAND_TYPES.START_NEW_CHAT,
81
- handle: mock(async () => ({
82
- response: "Hello from Gemini!",
83
- conversationId: "conv-456",
84
- })),
85
- };
86
- mediator.registerCommandHandler(mockHandler as any);
87
-
88
- await command.execute(["-p", "dhb-worker", "a new test message"], context);
89
-
90
- expect(mockHandler.handle).toHaveBeenCalledWith(
91
- expect.objectContaining({
92
- type: COMMAND_TYPES.START_NEW_CHAT,
93
- payload: expect.objectContaining({
94
- message: "a new test message",
95
- profileName: "dhb-worker",
96
- }),
97
- }),
98
- );
99
- });
100
-
101
- test("sends start-new-chat command with profile specified via --profile", async () => {
102
- const mockHandler = {
103
- commandType: COMMAND_TYPES.START_NEW_CHAT,
104
- handle: mock(async () => ({
105
- response: "Hello from Gemini!",
106
- conversationId: "conv-789",
107
- })),
108
- };
109
- mediator.registerCommandHandler(mockHandler as any);
110
-
111
- await command.execute(["--profile", "my-profile", "test message"], context);
112
-
113
- expect(mockHandler.handle).toHaveBeenCalledWith(
114
- expect.objectContaining({
115
- type: COMMAND_TYPES.START_NEW_CHAT,
116
- payload: expect.objectContaining({
117
- message: "test message",
118
- profileName: "my-profile",
119
- }),
120
- }),
121
- );
122
- });
123
-
124
- test("does not include profileName in payload when no profile specified", async () => {
125
- const mockHandler = {
126
- commandType: COMMAND_TYPES.START_NEW_CHAT,
127
- handle: mock(async () => ({
128
- response: "Hello",
129
- conversationId: "conv-abc",
130
- })),
131
- };
132
- mediator.registerCommandHandler(mockHandler as any);
133
-
134
- await command.execute(["hello"], context);
135
-
136
- const call = mockHandler.handle.mock.calls[0][0];
137
- expect(call.payload.profileName).toBeUndefined();
138
- });
139
- });
@@ -1,106 +0,0 @@
1
- import { describe, test, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
2
- import { StatusCommand } from "../../src/cli/commands/status-command.ts";
3
- import { Mediator } from "../../src/core/mediator.ts";
4
- import type { CliCommandContext } from "../../src/cli/command-registry.ts";
5
- import * as configModule from "../../src/infrastructure/config.ts";
6
-
7
- describe("StatusCommand", () => {
8
- let command: StatusCommand;
9
- let context: CliCommandContext;
10
- let exitSpy: ReturnType<typeof spyOn>;
11
-
12
- beforeEach(() => {
13
- command = new StatusCommand();
14
- context = { verbose: false, mediator: new Mediator() };
15
- exitSpy = spyOn(process, "exit").mockImplementation((code?: number) => {
16
- throw new Error(`process.exit(${code})`);
17
- });
18
- });
19
-
20
- afterEach(() => {
21
- mock.restore();
22
- exitSpy.mockRestore();
23
- });
24
-
25
- test("has correct name and description", () => {
26
- expect(command.name).toBe("status");
27
- expect(command.description).toBe("Show configuration and profile status");
28
- });
29
-
30
- describe("execute", () => {
31
- test("exits with code 2 when no profiles exist", async () => {
32
- spyOn(configModule, "listProfiles").mockReturnValue([]);
33
- spyOn(configModule, "getConfigDir").mockReturnValue("/tmp/gemiterm");
34
- spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
35
- spyOn(configModule, "getDefaultProfileName").mockReturnValue("default");
36
- spyOn(console, "log").mockImplementation(() => {});
37
-
38
- await expect(command.execute([], context)).rejects.toThrow("process.exit(2)");
39
- });
40
-
41
- test("displays config directory and profile table", async () => {
42
- spyOn(configModule, "listProfiles").mockReturnValue(["default"]);
43
- spyOn(configModule, "getConfigDir").mockReturnValue("/home/user/.config/gemiterm");
44
- spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
45
- spyOn(configModule, "getDefaultProfileName").mockReturnValue("default");
46
-
47
- const logOutput: string[] = [];
48
- spyOn(console, "log").mockImplementation((...args) => {
49
- logOutput.push(args.join(" "));
50
- });
51
-
52
- const { ProfileManager } = await import("../../src/infrastructure/storage.ts");
53
- const mockGetStatus = mock(() => ({
54
- name: "default",
55
- exists: true,
56
- isActive: true,
57
- expiresAt: "2026-07-01T00:00:00.000Z",
58
- isDefault: true,
59
- }));
60
- const origProto = ProfileManager.prototype.getStatus;
61
- ProfileManager.prototype.getStatus = mockGetStatus;
62
-
63
- try {
64
- await command.execute([], context);
65
-
66
- const combined = logOutput.join("\n");
67
- expect(combined).toContain("Configuration");
68
- expect(combined).toContain("/home/user/.config/gemiterm");
69
- expect(combined).toContain("Profiles");
70
- expect(combined).toContain("default");
71
- } finally {
72
- ProfileManager.prototype.getStatus = origProto;
73
- }
74
- });
75
-
76
- test("displays multiple profiles with correct default marker", async () => {
77
- spyOn(configModule, "listProfiles").mockReturnValue(["work", "personal"]);
78
- spyOn(configModule, "getConfigDir").mockReturnValue("/home/user/.config/gemiterm");
79
- spyOn(configModule, "ensureConfigDir").mockImplementation(() => {});
80
- spyOn(configModule, "getDefaultProfileName").mockReturnValue("work");
81
-
82
- const logOutput: string[] = [];
83
- spyOn(console, "log").mockImplementation((...args) => {
84
- logOutput.push(args.join(" "));
85
- });
86
-
87
- const { ProfileManager } = await import("../../src/infrastructure/storage.ts");
88
- const statuses = {
89
- work: { name: "work", exists: true, isActive: true, expiresAt: "2026-08-01T00:00:00.000Z", isDefault: true },
90
- personal: { name: "personal", exists: true, isActive: false, expiresAt: null, isDefault: false },
91
- };
92
- const origProto = ProfileManager.prototype.getStatus;
93
- ProfileManager.prototype.getStatus = mock((name: string) => statuses[name as keyof typeof statuses]);
94
-
95
- try {
96
- await command.execute([], context);
97
-
98
- const combined = logOutput.join("\n");
99
- expect(combined).toContain("work");
100
- expect(combined).toContain("personal");
101
- } finally {
102
- ProfileManager.prototype.getStatus = origProto;
103
- }
104
- });
105
- });
106
- });
@@ -1,187 +0,0 @@
1
- import { describe, test, expect, mock, beforeEach } from "bun:test";
2
- import {
3
- ListChatsQueryHandler,
4
- FetchChatQueryHandler,
5
- GetProfileStatusesQueryHandler,
6
- GetAuthStatusQueryHandler,
7
- ListModelsQueryHandler,
8
- QUERY_TYPES,
9
- } from "../../src/core/query-handlers.ts";
10
- import type { IGeminiClientQueryService, IProfileQueryService } from "../../src/core/query-handlers.ts";
11
- import type { Query } from "../../src/core/mediator.ts";
12
-
13
- function makeQuery<T>(type: string, payload: T): Query<T> {
14
- return { type, payload: payload as unknown as Record<string, unknown> };
15
- }
16
-
17
- describe("ListChatsQueryHandler", () => {
18
- let mockClient: IGeminiClientQueryService & { forProfile: (name: string) => IGeminiClientQueryService };
19
-
20
- beforeEach(() => {
21
- mockClient = {
22
- listChats: mock(() => Promise.resolve([])),
23
- fetchChat: mock(() => Promise.resolve([])),
24
- listModels: mock(() => Promise.resolve([])),
25
- forProfile: (name: string) => mockClient,
26
- };
27
- });
28
-
29
- test("has correct queryType", () => {
30
- const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
31
- expect(handler.queryType).toBe(QUERY_TYPES.LIST_CHATS);
32
- });
33
-
34
- test("returns chats from gemini client", async () => {
35
- const chats = [
36
- { id: "1", title: "Chat 1", isPinned: false, timestamp: 1000 },
37
- { id: "2", title: "Chat 2", isPinned: true, timestamp: 2000 },
38
- ];
39
- mockClient.listChats = mock(() => Promise.resolve(chats));
40
-
41
- const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
42
- const result = await handler.handle(makeQuery(QUERY_TYPES.LIST_CHATS, {}));
43
- expect(result.chats).toEqual(chats);
44
- });
45
-
46
- test("passes limit, offset, search options", async () => {
47
- mockClient.listChats = mock((opts) => {
48
- expect(opts).toEqual({ limit: 10, offset: 5, search: "hello" });
49
- return Promise.resolve([]);
50
- });
51
-
52
- const handler = new ListChatsQueryHandler(() => mockClient as any, () => []);
53
- await handler.handle(
54
- makeQuery(QUERY_TYPES.LIST_CHATS, { limit: 10, offset: 5, search: "hello" }),
55
- );
56
- expect(mockClient.listChats).toHaveBeenCalledTimes(1);
57
- });
58
- });
59
-
60
- describe("FetchChatQueryHandler", () => {
61
- let mockClient: IGeminiClientQueryService;
62
-
63
- beforeEach(() => {
64
- mockClient = {
65
- listChats: mock(() => Promise.resolve([])),
66
- fetchChat: mock(() => Promise.resolve([])),
67
- listModels: mock(() => Promise.resolve([])),
68
- };
69
- });
70
-
71
- test("has correct queryType", () => {
72
- const handler = new FetchChatQueryHandler(mockClient);
73
- expect(handler.queryType).toBe(QUERY_TYPES.FETCH_CHAT);
74
- });
75
-
76
- test("returns messages for conversation", async () => {
77
- const messages = [
78
- { role: "user" as const, content: "Hello" },
79
- { role: "model" as const, content: "Hi there!" },
80
- ];
81
- mockClient.fetchChat = mock(() => Promise.resolve(messages));
82
-
83
- const handler = new FetchChatQueryHandler(mockClient);
84
- const result = await handler.handle(
85
- makeQuery(QUERY_TYPES.FETCH_CHAT, { conversationId: "conv-123" }),
86
- );
87
- expect(result.messages).toEqual(messages);
88
- expect(mockClient.fetchChat).toHaveBeenCalledWith("conv-123");
89
- });
90
- });
91
-
92
- describe("GetProfileStatusesQueryHandler", () => {
93
- let mockProfileService: IProfileQueryService;
94
-
95
- beforeEach(() => {
96
- mockProfileService = {
97
- getProfileStatuses: mock(() => Promise.resolve([])),
98
- getAuthStatus: mock(() => Promise.resolve({ authenticated: false, profileName: null })),
99
- };
100
- });
101
-
102
- test("has correct queryType", () => {
103
- const handler = new GetProfileStatusesQueryHandler(mockProfileService);
104
- expect(handler.queryType).toBe(QUERY_TYPES.GET_PROFILE_STATUSES);
105
- });
106
-
107
- test("returns profile statuses", async () => {
108
- const statuses = [
109
- {
110
- name: "default",
111
- exists: true,
112
- isActive: true,
113
- expiresAt: "2099-01-01T00:00:00Z",
114
- isDefault: true,
115
- },
116
- ];
117
- mockProfileService.getProfileStatuses = mock(() => Promise.resolve(statuses));
118
-
119
- const handler = new GetProfileStatusesQueryHandler(mockProfileService);
120
- const result = await handler.handle(makeQuery(QUERY_TYPES.GET_PROFILE_STATUSES, {}));
121
- expect(result.statuses).toEqual(statuses);
122
- });
123
- });
124
-
125
- describe("GetAuthStatusQueryHandler", () => {
126
- let mockProfileService: IProfileQueryService;
127
-
128
- beforeEach(() => {
129
- mockProfileService = {
130
- getProfileStatuses: mock(() => Promise.resolve([])),
131
- getAuthStatus: mock(() => Promise.resolve({ authenticated: false, profileName: null })),
132
- };
133
- });
134
-
135
- test("has correct queryType", () => {
136
- const handler = new GetAuthStatusQueryHandler(mockProfileService);
137
- expect(handler.queryType).toBe(QUERY_TYPES.GET_AUTH_STATUS);
138
- });
139
-
140
- test("returns authenticated true with profile name", async () => {
141
- mockProfileService.getAuthStatus = mock(() =>
142
- Promise.resolve({ authenticated: true, profileName: "default" }),
143
- );
144
-
145
- const handler = new GetAuthStatusQueryHandler(mockProfileService);
146
- const result = await handler.handle(makeQuery(QUERY_TYPES.GET_AUTH_STATUS, {}));
147
- expect(result.authenticated).toBe(true);
148
- expect(result.profileName).toBe("default");
149
- });
150
-
151
- test("returns authenticated false with null profile name", async () => {
152
- mockProfileService.getAuthStatus = mock(() =>
153
- Promise.resolve({ authenticated: false, profileName: null }),
154
- );
155
-
156
- const handler = new GetAuthStatusQueryHandler(mockProfileService);
157
- const result = await handler.handle(makeQuery(QUERY_TYPES.GET_AUTH_STATUS, {}));
158
- expect(result.authenticated).toBe(false);
159
- expect(result.profileName).toBeNull();
160
- });
161
- });
162
-
163
- describe("ListModelsQueryHandler", () => {
164
- let mockClient: IGeminiClientQueryService;
165
-
166
- beforeEach(() => {
167
- mockClient = {
168
- listChats: mock(() => Promise.resolve([])),
169
- fetchChat: mock(() => Promise.resolve([])),
170
- listModels: mock(() => Promise.resolve([])),
171
- };
172
- });
173
-
174
- test("has correct queryType", () => {
175
- const handler = new ListModelsQueryHandler(mockClient);
176
- expect(handler.queryType).toBe(QUERY_TYPES.LIST_MODELS);
177
- });
178
-
179
- test("returns models from gemini client", async () => {
180
- const models = ["gemini-pro", "gemini-1.5-pro", "gemini-1.5-flash"];
181
- mockClient.listModels = mock(() => Promise.resolve(models));
182
-
183
- const handler = new ListModelsQueryHandler(mockClient);
184
- const result = await handler.handle(makeQuery(QUERY_TYPES.LIST_MODELS, {}));
185
- expect(result.models).toEqual(models);
186
- });
187
- });
@@ -1,98 +0,0 @@
1
- import type { Cookie } from "../../src/core/types.ts";
2
- import { mkdirSync, writeFileSync } from "node:fs";
3
- import { join, resolve } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { getTestConfigDir, createTestConfigDir } from "../setup.ts";
6
-
7
- const MOCK_COOKIE_DEFAULTS: Omit<Cookie, "name" | "value" | "expires"> = {
8
- domain: ".google.com",
9
- path: "/",
10
- httpOnly: true,
11
- secure: true,
12
- sameSite: "None",
13
- };
14
-
15
- const FUTURE_EXPIRY = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
16
- const PAST_EXPIRY = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60;
17
-
18
- export interface MockCookiesOptions {
19
- count?: number;
20
- names?: string[];
21
- expiry?: number;
22
- values?: Record<string, string>;
23
- }
24
-
25
- export function createMockCookies(options: MockCookiesOptions = {}): Cookie[] {
26
- const {
27
- count = 4,
28
- names,
29
- expiry = FUTURE_EXPIRY,
30
- values = {},
31
- } = options;
32
-
33
- const defaultNames = [
34
- "__Secure-1PSID",
35
- "__Secure-1PSIDTS",
36
- "__Secure-1PSIDCC",
37
- "NID",
38
- ];
39
-
40
- const cookieNames = names ?? defaultNames.slice(0, count);
41
-
42
- return cookieNames.map((name) => ({
43
- name,
44
- value: values[name] ?? `mock-value-${name}`,
45
- ...MOCK_COOKIE_DEFAULTS,
46
- expires: expiry,
47
- }));
48
- }
49
-
50
- export interface MockStorageStateOptions {
51
- cookies?: Cookie[];
52
- expiry?: number;
53
- }
54
-
55
- export function createMockStorageState(options: MockStorageStateOptions = {}): object {
56
- const cookies = options.cookies ?? createMockCookies({ expiry: options.expiry ?? FUTURE_EXPIRY });
57
- return { cookies };
58
- }
59
-
60
- export function createExpiredStorageState(): object {
61
- const cookies = createMockCookies({ expiry: PAST_EXPIRY });
62
- return { cookies };
63
- }
64
-
65
- export interface MockProfileDirOptions {
66
- profileName?: string;
67
- configDir?: string;
68
- cookies?: Cookie[];
69
- expired?: boolean;
70
- }
71
-
72
- export function mockProfileDir(options: MockProfileDirOptions = {}): string {
73
- const {
74
- profileName = "test-profile",
75
- configDir,
76
- expired = false,
77
- } = options;
78
-
79
- let dir: string;
80
- try {
81
- dir = configDir ?? getTestConfigDir();
82
- } catch {
83
- dir = createTestConfigDir();
84
- }
85
-
86
- const resolvedDir = resolve(dir);
87
- const profileDir = join(resolvedDir, "profiles", profileName);
88
- mkdirSync(profileDir, { recursive: true });
89
-
90
- const state = expired
91
- ? createExpiredStorageState()
92
- : createMockStorageState({ cookies: options.cookies });
93
-
94
- const filePath = join(profileDir, "storage_state.json");
95
- writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
96
-
97
- return profileDir;
98
- }
@@ -1,87 +0,0 @@
1
- import type { ChatInfo, Message, Conversation } from "../../src/core/types.ts";
2
-
3
- export interface MockChatInfoOptions {
4
- count?: number;
5
- ids?: string[];
6
- titles?: string[];
7
- pinnedIndices?: number[];
8
- baseTimestamp?: number;
9
- }
10
-
11
- export function createMockChatList(options: MockChatInfoOptions = {}): ChatInfo[] {
12
- const {
13
- count = 3,
14
- ids,
15
- titles,
16
- pinnedIndices = [],
17
- baseTimestamp = Date.now(),
18
- } = options;
19
-
20
- const defaultIds = ["conv-abc123", "conv-def456", "conv-ghi789", "conv-jkl012", "conv-mno345"];
21
- const defaultTitles = [
22
- "Chat about TypeScript",
23
- "Pinned conversation",
24
- "Bun runtime overview",
25
- "Testing strategies",
26
- "Deployment options",
27
- ];
28
-
29
- return Array.from({ length: count }, (_, i) => ({
30
- id: ids?.[i] ?? defaultIds[i] ?? `conv-${i}`,
31
- title: titles?.[i] ?? defaultTitles[i] ?? `Untitled chat ${i + 1}`,
32
- isPinned: pinnedIndices.includes(i),
33
- timestamp: baseTimestamp - i * 86400000,
34
- }));
35
- }
36
-
37
- export interface MockMessageOptions {
38
- count?: number;
39
- conversationId?: string;
40
- roles?: Array<"user" | "model">;
41
- contents?: string[];
42
- }
43
-
44
- export function createMockMessageHistory(options: MockMessageOptions = {}): Message[] {
45
- const {
46
- count = 4,
47
- conversationId,
48
- roles,
49
- contents,
50
- } = options;
51
-
52
- const defaultRoles: Array<"user" | "model"> = ["user", "model", "user", "model"];
53
- const defaultContents = [
54
- "Hello, Gemini!",
55
- "Hi there! How can I help you today?",
56
- "Tell me about TypeScript.",
57
- "TypeScript is a typed superset of JavaScript.",
58
- ];
59
-
60
- return Array.from({ length: count }, (_, i) => ({
61
- role: roles?.[i] ?? defaultRoles[i % defaultRoles.length],
62
- content: contents?.[i] ?? defaultContents[i % defaultContents.length],
63
- ...(conversationId ? { conversationId } : {}),
64
- }));
65
- }
66
-
67
- export interface MockConversationOptions {
68
- id?: string;
69
- title?: string;
70
- messages?: Message[];
71
- messageCount?: number;
72
- }
73
-
74
- export function createMockConversation(options: MockConversationOptions = {}): Conversation {
75
- const {
76
- id = "conv-abc123",
77
- title = "Chat about TypeScript",
78
- messages,
79
- messageCount = 4,
80
- } = options;
81
-
82
- return {
83
- id,
84
- title,
85
- messages: messages ?? createMockMessageHistory({ count: messageCount, conversationId: id }),
86
- };
87
- }