gemiterm 2.0.0 → 2.1.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 (150) 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/install-skills-command.ts +35 -0
  5. package/src/cli/index.ts +19 -44
  6. package/src/infrastructure/cli-parser.ts +77 -0
  7. package/src/services/skills-cli-driver.ts +58 -0
  8. package/src/services/skills-service.ts +52 -0
  9. package/.env +0 -2
  10. package/.github/workflows/manual-release.yml +0 -34
  11. package/.github/workflows/release.yml +0 -198
  12. package/.github/workflows/test.yml +0 -29
  13. package/.opencode/commands/opsx-apply.md +0 -152
  14. package/.opencode/commands/opsx-archive.md +0 -157
  15. package/.opencode/commands/opsx-explore.md +0 -169
  16. package/.opencode/commands/opsx-propose.md +0 -104
  17. package/.opencode/commands/opsx-sync.md +0 -140
  18. package/.opencode/commands/search-intelligently.md +0 -9
  19. package/.opencode/opencode.json +0 -20
  20. package/.opencode/skills/intellisearch/.version +0 -1
  21. package/.opencode/skills/intellisearch/SKILL.md +0 -84
  22. package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
  23. package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
  24. package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
  25. package/.opencode/skills/intellisearch/references/examples.md +0 -97
  26. package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
  27. package/.opencode/skills/intellisearch/references/google-search.md +0 -40
  28. package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
  29. package/.opencode/skills/intellisearch/references/workflow.md +0 -115
  30. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
  31. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
  32. package/.opencode/skills/openspec-explore/SKILL.md +0 -287
  33. package/.opencode/skills/openspec-propose/SKILL.md +0 -111
  34. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
  35. package/.serena/project.yml +0 -133
  36. package/AGENTS.md +0 -136
  37. package/CHANGELOG.md +0 -3
  38. package/bun.lock +0 -105
  39. package/docs/INSTALL.md +0 -185
  40. package/docs/PLAYWRIGHT_CLI_API.md +0 -306
  41. package/docs/python-release-for-reference.md +0 -175
  42. package/examples/sample_json_export.json +0 -32
  43. package/examples/sample_markdown_export.md +0 -221
  44. package/examples/usage_examples.sh +0 -222
  45. package/install.ps1 +0 -264
  46. package/install.sh +0 -275
  47. package/openspec/MAESTRO_MIGRATION.md +0 -180
  48. package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
  49. package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
  50. package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
  51. package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
  52. package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
  53. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
  54. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
  55. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
  56. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
  57. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
  58. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
  59. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
  60. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
  61. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
  62. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
  63. package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
  64. package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
  65. package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
  66. package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
  67. package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
  68. package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
  69. package/openspec/changes/commander-cli-parser/design.md +0 -107
  70. package/openspec/changes/commander-cli-parser/proposal.md +0 -31
  71. package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
  72. package/openspec/changes/commander-cli-parser/tasks.md +0 -33
  73. package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
  74. package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
  75. package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
  76. package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
  77. package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
  78. package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
  79. package/openspec/config.yaml +0 -20
  80. package/openspec/specs/auth/spec.md +0 -394
  81. package/openspec/specs/cli/spec.md +0 -108
  82. package/openspec/specs/commands/spec.md +0 -409
  83. package/openspec/specs/configuration/spec.md +0 -130
  84. package/openspec/specs/conversations/spec.md +0 -139
  85. package/openspec/specs/domain-model/spec.md +0 -128
  86. package/openspec/specs/formatters/spec.md +0 -158
  87. package/openspec/specs/install-browser/spec.md +0 -146
  88. package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
  89. package/openspec/specs/logger/spec.md +0 -71
  90. package/openspec/specs/mediator/spec.md +0 -72
  91. package/openspec/specs/multi-profile-conversations/spec.md +0 -145
  92. package/openspec/specs/path-utils/spec.md +0 -101
  93. package/openspec/specs/profiles/spec.md +0 -94
  94. package/openspec/specs/storage/spec.md +0 -198
  95. package/openspec/specs/testing/spec.md +0 -237
  96. package/openspec/specs/v2-installer/spec.md +0 -205
  97. package/openspec/specs/validators/spec.md +0 -65
  98. package/scripts/build.ts +0 -88
  99. package/scripts/clean-build.ps1 +0 -4
  100. package/scripts/clean-build.sh +0 -5
  101. package/scripts/install-browser.ps1 +0 -5
  102. package/scripts/install-browser.sh +0 -6
  103. package/scripts/lint-path-mediation.ps1 +0 -65
  104. package/scripts/lint-path-mediation.sh +0 -40
  105. package/tests/cli/auth-command.test.ts +0 -220
  106. package/tests/cli/command-registry.test.ts +0 -67
  107. package/tests/cli/continue-command.test.ts +0 -100
  108. package/tests/cli/delete-command.test.ts +0 -128
  109. package/tests/cli/export-all-command.test.ts +0 -375
  110. package/tests/cli/install-browser-command.test.ts +0 -48
  111. package/tests/cli/list-command.test.ts +0 -179
  112. package/tests/cli/new-command.test.ts +0 -139
  113. package/tests/cli/status-command.test.ts +0 -106
  114. package/tests/core/query-handlers.test.ts +0 -187
  115. package/tests/fixtures/auth-fixtures.ts +0 -98
  116. package/tests/fixtures/chat-fixtures.ts +0 -87
  117. package/tests/infrastructure/cli-table.test.ts +0 -107
  118. package/tests/infrastructure/formatters.test.ts +0 -202
  119. package/tests/infrastructure/io.test.ts +0 -197
  120. package/tests/infrastructure/logger.test.ts +0 -74
  121. package/tests/infrastructure/storage.test.ts +0 -335
  122. package/tests/infrastructure/validators.test.ts +0 -67
  123. package/tests/integration/commands/auth.test.ts +0 -186
  124. package/tests/integration/commands/continue.test.ts +0 -130
  125. package/tests/integration/commands/delete.test.ts +0 -140
  126. package/tests/integration/commands/export.test.ts +0 -266
  127. package/tests/integration/commands/fetch.test.ts +0 -238
  128. package/tests/integration/commands/list.test.ts +0 -281
  129. package/tests/integration/commands/profile.test.ts +0 -258
  130. package/tests/integration/commands/status.test.ts +0 -223
  131. package/tests/integration/installer-script-shape.test.ts +0 -67
  132. package/tests/parity/compare-outputs.ts +0 -359
  133. package/tests/parity/test-commands-parity.ps1 +0 -82
  134. package/tests/parity/test-commands-parity.sh +0 -61
  135. package/tests/services/auth-service.test.ts +0 -315
  136. package/tests/services/cookie-monitor.test.ts +0 -229
  137. package/tests/services/cookie-storage-service.test.ts +0 -230
  138. package/tests/services/gemini-client-wrapper.test.ts +0 -688
  139. package/tests/services/install-browser-service.test.ts +0 -83
  140. package/tests/services/playwright-cli-driver.test.ts +0 -468
  141. package/tests/services/profile-auth-manager.test.ts +0 -334
  142. package/tests/services/profile-service.test.ts +0 -224
  143. package/tests/setup.ts +0 -95
  144. package/tests/smoke/smoke.test.ts +0 -52
  145. package/tests/unit/config.test.ts +0 -159
  146. package/tests/unit/errors.test.ts +0 -119
  147. package/tests/unit/formatters.test.ts +0 -316
  148. package/tests/unit/mediator.test.ts +0 -152
  149. package/tests/unit/path-utils.test.ts +0 -289
  150. 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
- }