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.
- package/README.md +32 -9
- package/package.json +13 -4
- package/src/cli/command-registry.ts +66 -62
- package/src/cli/commands/continue-command.ts +75 -1
- package/src/cli/commands/install-skills-command.ts +35 -0
- package/src/cli/commands/new-command.ts +62 -0
- package/src/cli/index.ts +19 -44
- package/src/cli/utils/long-arg-guard.ts +28 -0
- package/src/cli/utils/prompt-file.ts +26 -0
- package/src/infrastructure/cli-parser.ts +77 -0
- package/src/infrastructure/io.ts +9 -0
- package/src/services/skills-cli-driver.ts +58 -0
- package/src/services/skills-service.ts +52 -0
- package/.env +0 -2
- package/.github/workflows/manual-release.yml +0 -34
- package/.github/workflows/release.yml +0 -198
- package/.github/workflows/test.yml +0 -29
- package/.opencode/commands/opsx-apply.md +0 -152
- package/.opencode/commands/opsx-archive.md +0 -157
- package/.opencode/commands/opsx-explore.md +0 -169
- package/.opencode/commands/opsx-propose.md +0 -104
- package/.opencode/commands/opsx-sync.md +0 -140
- package/.opencode/commands/search-intelligently.md +0 -9
- package/.opencode/opencode.json +0 -20
- package/.opencode/skills/intellisearch/.version +0 -1
- package/.opencode/skills/intellisearch/SKILL.md +0 -84
- package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
- package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
- package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
- package/.opencode/skills/intellisearch/references/examples.md +0 -97
- package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
- package/.opencode/skills/intellisearch/references/google-search.md +0 -40
- package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
- package/.opencode/skills/intellisearch/references/workflow.md +0 -115
- package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
- package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
- package/.opencode/skills/openspec-explore/SKILL.md +0 -287
- package/.opencode/skills/openspec-propose/SKILL.md +0 -111
- package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.serena/project.yml +0 -133
- package/AGENTS.md +0 -136
- package/CHANGELOG.md +0 -3
- package/bun.lock +0 -105
- package/docs/INSTALL.md +0 -185
- package/docs/PLAYWRIGHT_CLI_API.md +0 -306
- package/docs/python-release-for-reference.md +0 -175
- package/examples/sample_json_export.json +0 -32
- package/examples/sample_markdown_export.md +0 -221
- package/examples/usage_examples.sh +0 -222
- package/install.ps1 +0 -264
- package/install.sh +0 -275
- package/openspec/MAESTRO_MIGRATION.md +0 -180
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
- package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
- package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
- package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
- package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
- package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
- package/openspec/changes/commander-cli-parser/design.md +0 -107
- package/openspec/changes/commander-cli-parser/proposal.md +0 -31
- package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
- package/openspec/changes/commander-cli-parser/tasks.md +0 -33
- package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
- package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
- package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
- package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
- package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
- package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
- package/openspec/config.yaml +0 -20
- package/openspec/specs/auth/spec.md +0 -394
- package/openspec/specs/cli/spec.md +0 -108
- package/openspec/specs/commands/spec.md +0 -409
- package/openspec/specs/configuration/spec.md +0 -130
- package/openspec/specs/conversations/spec.md +0 -139
- package/openspec/specs/domain-model/spec.md +0 -128
- package/openspec/specs/formatters/spec.md +0 -158
- package/openspec/specs/install-browser/spec.md +0 -146
- package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
- package/openspec/specs/logger/spec.md +0 -71
- package/openspec/specs/mediator/spec.md +0 -72
- package/openspec/specs/multi-profile-conversations/spec.md +0 -145
- package/openspec/specs/path-utils/spec.md +0 -101
- package/openspec/specs/profiles/spec.md +0 -94
- package/openspec/specs/storage/spec.md +0 -198
- package/openspec/specs/testing/spec.md +0 -237
- package/openspec/specs/v2-installer/spec.md +0 -205
- package/openspec/specs/validators/spec.md +0 -65
- package/scripts/build.ts +0 -88
- package/scripts/clean-build.ps1 +0 -4
- package/scripts/clean-build.sh +0 -5
- package/scripts/install-browser.ps1 +0 -5
- package/scripts/install-browser.sh +0 -6
- package/scripts/lint-path-mediation.ps1 +0 -65
- package/scripts/lint-path-mediation.sh +0 -40
- package/tests/cli/auth-command.test.ts +0 -220
- package/tests/cli/command-registry.test.ts +0 -67
- package/tests/cli/continue-command.test.ts +0 -100
- package/tests/cli/delete-command.test.ts +0 -128
- package/tests/cli/export-all-command.test.ts +0 -375
- package/tests/cli/install-browser-command.test.ts +0 -48
- package/tests/cli/list-command.test.ts +0 -179
- package/tests/cli/new-command.test.ts +0 -139
- package/tests/cli/status-command.test.ts +0 -106
- package/tests/core/query-handlers.test.ts +0 -187
- package/tests/fixtures/auth-fixtures.ts +0 -98
- package/tests/fixtures/chat-fixtures.ts +0 -87
- package/tests/infrastructure/cli-table.test.ts +0 -107
- package/tests/infrastructure/formatters.test.ts +0 -202
- package/tests/infrastructure/io.test.ts +0 -197
- package/tests/infrastructure/logger.test.ts +0 -74
- package/tests/infrastructure/storage.test.ts +0 -335
- package/tests/infrastructure/validators.test.ts +0 -67
- package/tests/integration/commands/auth.test.ts +0 -186
- package/tests/integration/commands/continue.test.ts +0 -130
- package/tests/integration/commands/delete.test.ts +0 -140
- package/tests/integration/commands/export.test.ts +0 -266
- package/tests/integration/commands/fetch.test.ts +0 -238
- package/tests/integration/commands/list.test.ts +0 -281
- package/tests/integration/commands/profile.test.ts +0 -258
- package/tests/integration/commands/status.test.ts +0 -223
- package/tests/integration/installer-script-shape.test.ts +0 -67
- package/tests/parity/compare-outputs.ts +0 -359
- package/tests/parity/test-commands-parity.ps1 +0 -82
- package/tests/parity/test-commands-parity.sh +0 -61
- package/tests/services/auth-service.test.ts +0 -315
- package/tests/services/cookie-monitor.test.ts +0 -229
- package/tests/services/cookie-storage-service.test.ts +0 -230
- package/tests/services/gemini-client-wrapper.test.ts +0 -688
- package/tests/services/install-browser-service.test.ts +0 -83
- package/tests/services/playwright-cli-driver.test.ts +0 -468
- package/tests/services/profile-auth-manager.test.ts +0 -334
- package/tests/services/profile-service.test.ts +0 -224
- package/tests/setup.ts +0 -95
- package/tests/smoke/smoke.test.ts +0 -52
- package/tests/unit/config.test.ts +0 -159
- package/tests/unit/errors.test.ts +0 -119
- package/tests/unit/formatters.test.ts +0 -316
- package/tests/unit/mediator.test.ts +0 -152
- package/tests/unit/path-utils.test.ts +0 -289
- package/tsconfig.json +0 -16
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Interactive prompt loop SHALL support continuous chat sessions
|
|
4
|
-
The interactive prompt loop utility SHALL provide a reusable mechanism for chat commands to engage in continuous back-and-forth conversations with the user until explicitly terminated.
|
|
5
|
-
|
|
6
|
-
#### Scenario: User sends multiple messages in a session
|
|
7
|
-
- **WHEN** user starts an interactive session and enters a message
|
|
8
|
-
- **THEN** the system processes the message and displays the response
|
|
9
|
-
- **AND** the system prompts for the next message
|
|
10
|
-
|
|
11
|
-
#### Scenario: User can exit the session
|
|
12
|
-
- **WHEN** user types "/exit" or "/quit"
|
|
13
|
-
- **THEN** the system displays "Goodbye." and terminates the session
|
|
14
|
-
|
|
15
|
-
#### Scenario: Empty input is handled gracefully
|
|
16
|
-
- **WHEN** user presses Enter without typing anything
|
|
17
|
-
- **THEN** the system re-prompts without sending an empty message
|
|
18
|
-
|
|
19
|
-
#### Scenario: Errors during message processing do not terminate the session
|
|
20
|
-
- **WHEN** user enters a message but processing throws an error
|
|
21
|
-
- **THEN** the system displays the error message
|
|
22
|
-
- **AND** the system re-prompts for the next message
|
|
23
|
-
|
|
24
|
-
### Requirement: Interactive prompt loop SHALL support command-specific message handling
|
|
25
|
-
The utility SHALL accept a callback function that handles each message, allowing different commands to process messages differently.
|
|
26
|
-
|
|
27
|
-
#### Scenario: New chat command creates new conversation
|
|
28
|
-
- **WHEN** the new command uses the interactive loop with a message handler
|
|
29
|
-
- **AND** user enters a message
|
|
30
|
-
- **THEN** the system starts a new conversation and displays the response
|
|
31
|
-
|
|
32
|
-
#### Scenario: Continue command sends to existing conversation
|
|
33
|
-
- **WHEN** the continue command uses the interactive loop with a message handler
|
|
34
|
-
- **AND** user enters a message
|
|
35
|
-
- **THEN** the system sends the message to the existing conversation ID
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
## 1. Create shared interactive prompt utility
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 Create `src/cli/utils/interactive-prompt.ts` module
|
|
4
|
-
- [x] 1.2 Define `MessageHandler` callback interface for command-specific message processing
|
|
5
|
-
- [x] 1.3 Implement `runInteractiveLoop` function that handles prompt loop mechanics
|
|
6
|
-
- [x] 1.4 Ensure loop continues until /exit or /quit command
|
|
7
|
-
- [x] 1.5 Handle empty input gracefully without sending
|
|
8
|
-
|
|
9
|
-
## 2. Refactor new-command.ts
|
|
10
|
-
|
|
11
|
-
- [x] 2.1 Import the new `runInteractiveLoop` utility
|
|
12
|
-
- [x] 2.2 Replace `startInteractive` method with call to `runInteractiveLoop`
|
|
13
|
-
- [x] 2.3 Pass message handler callback that creates new chat conversations
|
|
14
|
-
- [x] 2.4 Remove duplicated `startInteractive` method
|
|
15
|
-
- [x] 2.5 Verify interactive mode loops correctly (not just one message)
|
|
16
|
-
|
|
17
|
-
## 3. Refactor continue-command.ts
|
|
18
|
-
|
|
19
|
-
- [x] 3.1 Import the new `runInteractiveLoop` utility
|
|
20
|
-
- [x] 3.2 Replace `startInteractive` method with call to `runInteractiveLoop`
|
|
21
|
-
- [x] 3.3 Pass message handler callback that sends to existing conversation
|
|
22
|
-
- [x] 3.4 Remove duplicated `startInteractive` method
|
|
23
|
-
|
|
24
|
-
## 4. Verify
|
|
25
|
-
|
|
26
|
-
- [x] 4.1 Run `bun run test` to ensure all tests pass
|
|
27
|
-
- [x] 4.2 Manually test `new` command interactive mode loops correctly
|
|
28
|
-
- [x] 4.3 Manually test `continue` command interactive mode works correctly
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
## Context
|
|
2
|
-
|
|
3
|
-
`src/services/gemini-client-wrapper.ts` is a 304-line placeholder that talks
|
|
4
|
-
to `gemini.google.com` over `fetch()` with hand-typed `URLSearchParams` bodies
|
|
5
|
-
and guessed endpoint paths. The class implements two interfaces:
|
|
6
|
-
|
|
7
|
-
- `IGeminiClientService` (`src/core/command-handlers.ts:92`) — `deleteChat`,
|
|
8
|
-
`sendMessage`, `startNewChat`, `profileHasConversation`, `forProfile`.
|
|
9
|
-
- `IGeminiClientQueryService` (`src/core/query-handlers.ts:56`) — `listChats`,
|
|
10
|
-
`fetchChat`, `listModels`.
|
|
11
|
-
|
|
12
|
-
`GeminiClientService` is currently constructed in one place
|
|
13
|
-
(`src/cli/index.ts:98,110`) and consumed in three (`src/cli/index.ts:116-153`,
|
|
14
|
-
`src/services/profile-auth-manager.ts:22-54`, plus all command/query handlers
|
|
15
|
-
that depend on the interfaces — never on the class directly).
|
|
16
|
-
|
|
17
|
-
The proposed replacement is the npm package
|
|
18
|
-
[`gemini-reverse`](https://www.npmjs.com/package/gemini-reverse) by
|
|
19
|
-
[rynn-k](https://github.com/rynn-k/Gemini-Reverse). It exposes:
|
|
20
|
-
|
|
21
|
-
- `class GeminiClient` with constructor
|
|
22
|
-
`({ secure_1psid, secure_1psidts?, proxy?, cookies? })` and async
|
|
23
|
-
`init({ timeout?, autoClose?, closeDelay?, autoRefresh?, refreshInterval?,
|
|
24
|
-
verbose?, watchdogTimeout? })`.
|
|
25
|
-
- `client.listChats(): ChatInfo[]` (fields: `cid`, `title`, `is_pinned`,
|
|
26
|
-
`timestamp`).
|
|
27
|
-
- `client.readChat(cid: string, limit?: number): Promise<ChatHistory | null>` —
|
|
28
|
-
`ChatHistory.turns: { role, text, parts? }[]`.
|
|
29
|
-
- `client.startChat(opts?): ChatSession` — session with
|
|
30
|
-
`sendMessage({ prompt, … }): Promise<ModelOutput>`,
|
|
31
|
-
`readHistory(limit?): Promise<ChatHistory | null>`, `cid`, `rcid`.
|
|
32
|
-
- `client.generateContent({ prompt, … }): Promise<ModelOutput>` for
|
|
33
|
-
single-shot.
|
|
34
|
-
- `client.deleteChat(cid: string): Promise<void>`.
|
|
35
|
-
- `client.listModels(): AvailableModel[]` (fields: `model_id`, `model_name`,
|
|
36
|
-
`display_name`, `description`, `capacity`, `is_available`, …).
|
|
37
|
-
- Error classes: `GeminiError` (base), `AuthError`, `APIError`, `TimeoutError`,
|
|
38
|
-
`UsageLimitExceeded`, `TemporarilyBlocked`, `ModelInvalid`.
|
|
39
|
-
|
|
40
|
-
`src/core/errors.ts` defines `GemitermError` (base), `AuthenticationError`,
|
|
41
|
-
`CookieExpiredError`, `GeminiAPIError`, `ConversationNotFoundError`,
|
|
42
|
-
`ConversationPendingError`. Callers expect these types in `try/catch` blocks
|
|
43
|
-
(e.g. `command-handlers.ts:120`, `query-handlers.ts:138`).
|
|
44
|
-
|
|
45
|
-
## Goals / Non-Goals
|
|
46
|
-
|
|
47
|
-
**Goals:**
|
|
48
|
-
|
|
49
|
-
- Replace the placeholder `fetch()`-based HTTP client with a real Gemini web
|
|
50
|
-
protocol client that talks to the live service.
|
|
51
|
-
- Keep the public class signature of `GeminiClientService` and the two
|
|
52
|
-
interfaces it implements bit-identical, so all 11 CLI commands, the
|
|
53
|
-
`ProfileAuthManager`, and the command/query handlers need zero edits.
|
|
54
|
-
- Translate `gemini-reverse` exceptions into our `GemitermError` subclasses so
|
|
55
|
-
the existing error-handling in command handlers keeps working.
|
|
56
|
-
- Preserve the per-profile session model: each profile gets its own
|
|
57
|
-
`GeminiClient` (and therefore its own auto-refresh watchdog) bound to the
|
|
58
|
-
cookies loaded by `CookieStorageService`.
|
|
59
|
-
- Add a unit test file that mocks `gemini-reverse` and verifies the mapping
|
|
60
|
-
and error translation in isolation, so we don't need a real Google account
|
|
61
|
-
in CI.
|
|
62
|
-
- Stay at the 432/432 test baseline (and add new tests on top of it).
|
|
63
|
-
|
|
64
|
-
**Non-Goals:**
|
|
65
|
-
|
|
66
|
-
- Surfacing `gemini-reverse` features the wrapper doesn't currently expose
|
|
67
|
-
(Gems, file uploads, deep research, streaming, image/video output, candidate
|
|
68
|
-
selection). These are deferred to a follow-up change.
|
|
69
|
-
- Changing the cookie storage file format or the v1.4.1 → v2.0.0 migration
|
|
70
|
-
story.
|
|
71
|
-
- Touching any of the four regression-gate files flagged in `AGENTS.md`
|
|
72
|
-
(`playwright-cli-driver.ts`, `cookie-monitor.ts`, `auth-service.ts`,
|
|
73
|
-
`cookie-storage-service.ts`).
|
|
74
|
-
- Resolving the existing `bun run build` breakage (that's the
|
|
75
|
-
`cross-platform-build-and-ci` change).
|
|
76
|
-
- Re-shaping `IGeminiClientService` or `IGeminiClientQueryService` — those
|
|
77
|
-
are part of the `conversations` / `multi-profile-conversations` /
|
|
78
|
-
`commands` capability contracts and are explicitly preserved.
|
|
79
|
-
|
|
80
|
-
## Decisions
|
|
81
|
-
|
|
82
|
-
### Decision 1: `gemini-reverse` over hand-rolled or other libraries
|
|
83
|
-
|
|
84
|
-
We pick `gemini-reverse@^0.x` (pin the exact range in `tasks.md` based on
|
|
85
|
-
current npm at implementation time) because:
|
|
86
|
-
|
|
87
|
-
- It is the only widely-used library that wraps the live Gemini web protocol
|
|
88
|
-
(same upstream as our `playwright-cli`-driven auth).
|
|
89
|
-
- It handles cookie auto-refresh in the background (the upstream `1PSIDTS`
|
|
90
|
-
cookie rotates on a timer; hand-rolling this is a known footgun).
|
|
91
|
-
- It is pure JavaScript with no native bindings, so Bun resolves it cleanly.
|
|
92
|
-
|
|
93
|
-
**Alternatives considered:**
|
|
94
|
-
|
|
95
|
-
- *Port Python `gemiterm` v1.4.1's `gemini_webapi` usage.* Rejected: that
|
|
96
|
-
project is the Python equivalent, not a Node package.
|
|
97
|
-
- *Re-implement the HTTP client in TypeScript.* Rejected: the upstream
|
|
98
|
-
protocol changes and the auth flow is non-trivial; this is the path that
|
|
99
|
-
produced the placeholder we're replacing.
|
|
100
|
-
- *Use Google's official `generativelanguage` SDK.* Rejected: that's a
|
|
101
|
-
different product (AI Studio API key model) — `gemiterm` is explicitly a
|
|
102
|
-
CLI for the consumer web app, not the AI Studio API.
|
|
103
|
-
|
|
104
|
-
### Decision 2: Compose `GeminiClient` per-instance, not per-CLI
|
|
105
|
-
|
|
106
|
-
The current placeholder holds cookies on a `GeminiClientConfig` and uses
|
|
107
|
-
`fetch()` directly. The new wrapper holds a `GeminiClient` instance from
|
|
108
|
-
`gemini-reverse` as a private field, and calls `listChats()` / `readChat()` /
|
|
109
|
-
`startChat()` / `deleteChat()` / `listModels()` on it.
|
|
110
|
-
|
|
111
|
-
`forProfile(name)` constructs a brand-new `GeminiClientService` with a
|
|
112
|
-
brand-new `GeminiClient` bound to the profile's cookies. This is what
|
|
113
|
-
`gemini-reverse` is designed for (one client = one logged-in account) and
|
|
114
|
-
naturally scopes the upstream auto-refresh watchdog to that profile.
|
|
115
|
-
|
|
116
|
-
**Why not a single shared `GeminiClient` and pass cookies per call?** The
|
|
117
|
-
upstream API bakes cookies into the client at construction time and runs the
|
|
118
|
-
refresh watchdog against that single account. A shared client would mean the
|
|
119
|
-
"current" profile is implicit state, and `forProfile()` could race the
|
|
120
|
-
watchdog. Per-instance is the upstream-idiomatic and race-free model.
|
|
121
|
-
|
|
122
|
-
### Decision 3: Translation layer is a private helper, not a public module
|
|
123
|
-
|
|
124
|
-
Mapping `gemini-reverse` types into our domain types (`ChatInfo`, `Message`,
|
|
125
|
-
model display names) and translating exceptions into `GemitermError`
|
|
126
|
-
subclasses lives as **private methods** on `GeminiClientService`, not as
|
|
127
|
-
exported functions in a new module. Reasons:
|
|
128
|
-
|
|
129
|
-
- Only `GeminiClientService` needs them.
|
|
130
|
-
- Keeping the translation local makes the wrapper's contract surface minimal
|
|
131
|
-
— `core/types.ts` stays the only public domain-types module, and
|
|
132
|
-
`core/errors.ts` stays the only public error module.
|
|
133
|
-
- It avoids leaking the `gemini-reverse` import path into more than one file
|
|
134
|
-
(which simplifies future swaps if we ever need to replace this library
|
|
135
|
-
again).
|
|
136
|
-
|
|
137
|
-
The private helpers we will introduce:
|
|
138
|
-
|
|
139
|
-
- `toDomainChatInfo(raw: geminiReverse.ChatInfo, profileName?: string):
|
|
140
|
-
ChatInfo` — maps `cid → id`, `is_pinned → isPinned`, preserves `timestamp`
|
|
141
|
-
and `title`, adds `profile` when `profileName` is set (matches existing
|
|
142
|
-
placeholder behavior on `gemini-client-wrapper.ts:144`).
|
|
143
|
-
- `toDomainMessages(history: geminiReverse.ChatHistory, conversationId:
|
|
144
|
-
string): Message[]` — flattens `turns[].text ?? turns[].parts[].text` and
|
|
145
|
-
normalizes `role` to `"user" | "model"`.
|
|
146
|
-
- `toDomainModelName(model: geminiReverse.AvailableModel): string` — returns
|
|
147
|
-
`display_name || model_name || model_id`.
|
|
148
|
-
- `translateError(e: unknown): GemitermError` — see Decision 5.
|
|
149
|
-
|
|
150
|
-
### Decision 4: `init()` is exposed on the wrapper, not lazy
|
|
151
|
-
|
|
152
|
-
The placeholder's constructor sets `this.authenticated = !!config.secure1psid`
|
|
153
|
-
and never makes a network call. `gemini-reverse`'s `GeminiClient.init()` is
|
|
154
|
-
async and does perform I/O (probe models, set up refresh watchdog). To keep
|
|
155
|
-
the wrapper's construction synchronous (the factory in `src/cli/index.ts:98,110`
|
|
156
|
-
calls `new GeminiClientService(…)` without `await`), we add an explicit async
|
|
157
|
-
`init(): Promise<void>` on the wrapper that:
|
|
158
|
-
|
|
159
|
-
1. Awaits `this.client.init({ timeout: 300_000, autoClose: false,
|
|
160
|
-
autoRefresh: true, refreshInterval: 540_000 })`.
|
|
161
|
-
2. Is called once at CLI startup (in `src/cli/index.ts` after the factory
|
|
162
|
-
is constructed and before the first user command) — the call site edit
|
|
163
|
-
is a 2-line addition.
|
|
164
|
-
3. Is idempotent (`this.initialized` guard) so re-entry from test setup is
|
|
165
|
-
safe.
|
|
166
|
-
4. `forProfile()` returns a wrapper whose `init()` is also lazily awaited
|
|
167
|
-
before its first call (or eagerly awaited in the same place; decided in
|
|
168
|
-
the implementation based on call-site symmetry).
|
|
169
|
-
|
|
170
|
-
The factory in `src/cli/index.ts` constructs the wrapper with
|
|
171
|
-
`secure1psid: ""` (a placeholder until a profile is selected). For that
|
|
172
|
-
factory instance, `init()` is called with empty cookies and we treat that as
|
|
173
|
-
"deferred" — `init()` is still awaited, but the underlying `GeminiClient` is
|
|
174
|
-
not used for I/O until a profile is selected (the per-profile wrappers
|
|
175
|
-
constructed via `forProfile()` do the real I/O).
|
|
176
|
-
|
|
177
|
-
**Why not lazy `init()` on first call?** Two reasons: (a) the existing
|
|
178
|
-
`ProfileAuthManager` and command handlers can call methods concurrently
|
|
179
|
-
during a multi-profile operation, and lazy init is racy without a mutex; (b)
|
|
180
|
-
explicit init at startup makes the `await` visible in `src/cli/index.ts` and
|
|
181
|
-
keeps the lifecycle linear.
|
|
182
|
-
|
|
183
|
-
### Decision 5: Error translation mapping
|
|
184
|
-
|
|
185
|
-
Add a private `translateError(e: unknown): GemitermError` to the wrapper
|
|
186
|
-
(per Decision 3). Mapping:
|
|
187
|
-
|
|
188
|
-
| `gemini-reverse` class | Translated to | Message |
|
|
189
|
-
|-------------------------|--------------------------------------|------------------------------------------------------|
|
|
190
|
-
| `AuthError` | `AuthenticationError` | `"Session expired or invalid. Please run 'gemiterm login' again."` (matches the existing message at `gemini-client-wrapper.ts:83`) |
|
|
191
|
-
| `TimeoutError` | `GeminiAPIError` | `"Request to Gemini timed out"` |
|
|
192
|
-
| `UsageLimitExceeded` | `GeminiAPIError` | `"Gemini usage limit reached; try again later or switch model"` |
|
|
193
|
-
| `TemporarilyBlocked` | `GeminiAPIError` | `"Temporarily blocked by Gemini; try a proxy or wait"` |
|
|
194
|
-
| `ModelInvalid` | `GeminiAPIError` | `"Model is invalid or unavailable"` |
|
|
195
|
-
| `APIError` | `GeminiAPIError` | `e.message` (with `cause = e`) |
|
|
196
|
-
| any other `GeminiError` | `GeminiAPIError` | `e.message` (with `cause = e`) |
|
|
197
|
-
| anything else | `GeminiAPIError` | `"Unexpected error: " + String(e)` |
|
|
198
|
-
|
|
199
|
-
The `cause` field on `GeminiAPIError` is supported by `Error` since ES2022
|
|
200
|
-
and is the right channel for the underlying error so the existing
|
|
201
|
-
`logger.debug(\`${operation} failed: ${error}\`)` calls in
|
|
202
|
-
command/query handlers still see the original message via
|
|
203
|
-
`error.cause?.message`.
|
|
204
|
-
|
|
205
|
-
We do **not** re-export `gemini-reverse` error classes from `core/errors.ts`.
|
|
206
|
-
That would leak the library's type surface into our public module, which
|
|
207
|
-
violates the "translate, don't re-export" principle and increases the blast
|
|
208
|
-
radius of any future library swap.
|
|
209
|
-
|
|
210
|
-
### Decision 6: `sendMessage` and `startNewChat` return types stay text-only
|
|
211
|
-
|
|
212
|
-
`IGeminiClientService.sendMessage(cid, msg): Promise<string>` and
|
|
213
|
-
`startNewChat(msg): Promise<{ response: string; conversationId: string }>`
|
|
214
|
-
return plain text today. `gemini-reverse` returns a rich `ModelOutput` with
|
|
215
|
-
candidates, images, videos, thoughts, deep-research plans, and an `rcid` we
|
|
216
|
-
can use as the conversation ID.
|
|
217
|
-
|
|
218
|
-
We keep the wrappers text-only for this change and pull
|
|
219
|
-
`output.text.toString()`. Surfacing candidates / media / `rcid` is a
|
|
220
|
-
follow-up change (and a new capability spec) — it requires deciding how
|
|
221
|
-
`gemiterm chat --json` should serialize them, which is out of scope here.
|
|
222
|
-
|
|
223
|
-
For `startNewChat`, the `conversationId` we return comes from the
|
|
224
|
-
`ChatSession.cid` after `client.startChat().sendMessage(...)` returns
|
|
225
|
-
(equivalently `output.rcid` is also a valid candidate — we pick `cid` to
|
|
226
|
-
match the existing `cid` field on `ChatInfo` from `listChats`).
|
|
227
|
-
|
|
228
|
-
### Decision 7: Test strategy — module mock, not real network
|
|
229
|
-
|
|
230
|
-
Add `tests/services/gemini-client-wrapper.test.ts` that:
|
|
231
|
-
|
|
232
|
-
- Uses `bun:test`'s `mock.module("gemini-reverse", …)` to install a fake
|
|
233
|
-
`GeminiClient` class whose constructor records its arguments and whose
|
|
234
|
-
methods return fixtures.
|
|
235
|
-
- Spies on the fake's `listChats` / `readChat` / `startChat` / `deleteChat` /
|
|
236
|
-
`listModels` / `init` methods to assert that the wrapper calls them with
|
|
237
|
-
the right arguments and translates the return values into our domain
|
|
238
|
-
types.
|
|
239
|
-
- Asserts the `AuthError → AuthenticationError` and the other error
|
|
240
|
-
translations by injecting the relevant `gemini-reverse` error class
|
|
241
|
-
instances and catching them.
|
|
242
|
-
- Verifies `forProfile(name)` constructs a *new* `GeminiClient` bound to
|
|
243
|
-
that profile's cookies (asserted via a counter on the mock constructor).
|
|
244
|
-
|
|
245
|
-
This mirrors the test style of
|
|
246
|
-
`tests/services/cookie-storage-service.test.ts` and keeps the test
|
|
247
|
-
self-contained (no network, no Playwright, no `playwright-cli`).
|
|
248
|
-
|
|
249
|
-
The factory instance in `src/cli/index.ts:110` uses an empty
|
|
250
|
-
`secure1psid: ""` and is used only as a `forProfile` factory; it is not
|
|
251
|
-
exercised directly. We add a separate, small test for "empty-cookies client
|
|
252
|
-
constructs but does not throw" to lock in the invariant.
|
|
253
|
-
|
|
254
|
-
### Decision 8: Dependency version
|
|
255
|
-
|
|
256
|
-
Pin `gemini-reverse` to `^0.5.0` (or whatever the latest stable is at
|
|
257
|
-
implementation time — task 1 of `tasks.md` re-validates this against
|
|
258
|
-
`npm view gemini-reverse version` and updates the `package.json` entry
|
|
259
|
-
accordingly). The `^0.5.0` placeholder is to convey "use the latest 0.x" in
|
|
260
|
-
the `tasks.md` pin — implementers should re-check the registry.
|
|
261
|
-
|
|
262
|
-
## Risks / Trade-offs
|
|
263
|
-
|
|
264
|
-
- **[Upstream library is unofficial / can break]** → Mitigation: we wrap it
|
|
265
|
-
in `GeminiClientService` and translate errors. If `gemini-reverse`
|
|
266
|
-
changes its public API, the blast radius is the wrapper file only. We
|
|
267
|
-
also pin to `^0.x` (not `^1.0.0` yet) to acknowledge pre-1.0 churn.
|
|
268
|
-
- **[Cookie auto-refresh race with multi-profile use]** → Mitigation:
|
|
269
|
-
per-instance `GeminiClient` per profile (Decision 2). Each profile's
|
|
270
|
-
watchdog runs against its own session, no shared state.
|
|
271
|
-
- **[Bun resolution of npm package]** → Mitigation: `gemini-reverse` is
|
|
272
|
-
pure JS (no native bindings, no `node-gyp`). Task 1 in `tasks.md` adds
|
|
273
|
-
a `bun test` smoke step that imports the package from a placeholder
|
|
274
|
-
`src/services/__smoke__/import-gemini-reverse.test.ts` to catch any
|
|
275
|
-
resolution issue early. The smoke test is removed after the real tests
|
|
276
|
-
are in place.
|
|
277
|
-
- **[Loss of placeholder-specific `search` / `limit` / `offset` filtering
|
|
278
|
-
in `listChats`]** → Mitigation: `gemini-reverse`'s `listChats()` returns
|
|
279
|
-
a fully populated array. The wrapper applies the same post-filter (case-
|
|
280
|
-
insensitive `search` on `title`, then `offset` slice, then `limit` slice)
|
|
281
|
-
after mapping, preserving the existing behavior at
|
|
282
|
-
`gemini-client-wrapper.ts:148-160`.
|
|
283
|
-
- **[Empty-cookies factory instance crashes `init()`]** → Mitigation:
|
|
284
|
-
`gemini-reverse`'s `init()` tolerates null/empty `secure_1psid` (it
|
|
285
|
-
defers actual I/O until a method is called). We add a smoke test
|
|
286
|
-
asserting the factory instance constructs and `init()` resolves without
|
|
287
|
-
throwing.
|
|
288
|
-
- **[Per-call `GeminiClient` construction is heavier than per-call `fetch`]**
|
|
289
|
-
→ Mitigation: this is a CLI invoked once per user command, not a
|
|
290
|
-
long-running daemon. Construction cost is negligible.
|
|
291
|
-
- **[Test count churn]** → The new test file adds tests but should not
|
|
292
|
-
remove any of the 432 existing tests. `bun run test` after
|
|
293
|
-
implementation must show `≥ 432` tests passing (target: 432 + ~25 new
|
|
294
|
-
cases from the new file).
|
|
295
|
-
|
|
296
|
-
## Migration Plan
|
|
297
|
-
|
|
298
|
-
- **Deploy**: this is a `v2.0.0` change; deploy with the normal release
|
|
299
|
-
process (handled by the `cross-platform-build-and-ci` change once
|
|
300
|
-
that's implemented — out of scope here).
|
|
301
|
-
- **Rollback**: revert the single commit that swaps the wrapper; the
|
|
302
|
-
previous `fetch()`-based implementation lives in `git` history. Cookie
|
|
303
|
-
files in `%APPDATA%\gemiterm\` / `~/.config/gemiterm/` are unchanged.
|
|
304
|
-
- **User action**: none. No re-auth, no config migration, no new flags.
|
|
305
|
-
- **Operational note**: the first call after upgrade will hit
|
|
306
|
-
`gemini-reverse`'s model probe and chat list, which performs a couple
|
|
307
|
-
of HTTPS requests at startup. Latency is comparable to the placeholder.
|
|
308
|
-
|
|
309
|
-
## Open Questions
|
|
310
|
-
|
|
311
|
-
- **Q1**: At implementation time, confirm the latest published
|
|
312
|
-
`gemini-reverse` version on npm and pin accordingly (placeholder:
|
|
313
|
-
`^0.5.0`).
|
|
314
|
-
- **Q2**: Confirm Bun resolves `import { GeminiClient } from "gemini-reverse"`
|
|
315
|
-
cleanly (no `bun install` quirks). This is the first non-Bun-first-party
|
|
316
|
-
runtime dep we're pulling in.
|
|
317
|
-
- **Q3**: Decide whether `init()` failure should be fatal at CLI startup
|
|
318
|
-
or deferred until first use. Default plan (per Decision 4): fatal
|
|
319
|
-
at startup, with the error mapped to `GeminiAPIError` and surfaced as
|
|
320
|
-
the standard CLI error.
|
|
321
|
-
- **Q4**: Should we expose a `--no-auto-refresh` flag to disable
|
|
322
|
-
`gemini-reverse`'s cookie auto-refresh for debugging? Deferred to a
|
|
323
|
-
follow-up; not in this change.
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
## Why
|
|
2
|
-
|
|
3
|
-
`src/services/gemini-client-wrapper.ts` is a hand-rolled placeholder that talks
|
|
4
|
-
to `gemini.google.com` over `fetch()` with hard-coded URL paths
|
|
5
|
-
(`/app/api/chat/history`, `/app/api/chat/<id>/send`, `/app/api/models`, …) and
|
|
6
|
-
URL-encoded form bodies. Those endpoints and shapes are educated guesses — the
|
|
7
|
-
real Gemini web app uses a different RPC and streaming protocol, so today the
|
|
8
|
-
CLI is structurally a stub against a real (changing) Google surface. We need
|
|
9
|
-
to swap the in-tree HTTP client for the community-maintained
|
|
10
|
-
[`gemini-reverse`](https://www.npmjs.com/package/gemini-reverse) library
|
|
11
|
-
(rynn-k/Gemini-Reverse), which already implements the live Gemini web protocol
|
|
12
|
-
(including cookie auto-refresh, streaming, model discovery, and per-account
|
|
13
|
-
sessions). This is a prerequisite for actually shipping v2.0.0 against the
|
|
14
|
-
real Gemini web app.
|
|
15
|
-
|
|
16
|
-
## What Changes
|
|
17
|
-
|
|
18
|
-
- **Add `gemini-reverse` as a runtime dependency** in `package.json` and
|
|
19
|
-
document the minimum supported version pinned in `tasks.md`.
|
|
20
|
-
- **Replace the body of `src/services/gemini-client-wrapper.ts`** so that
|
|
21
|
-
`GeminiClientService` composes a `GeminiClient` from `gemini-reverse`
|
|
22
|
-
instead of issuing `fetch()` calls. The public class signature and the two
|
|
23
|
-
interfaces it implements (`IGeminiClientService` in
|
|
24
|
-
`src/core/command-handlers.ts`, `IGeminiClientQueryService` in
|
|
25
|
-
`src/core/query-handlers.ts`) **do not change** — all call sites in
|
|
26
|
-
`src/cli/index.ts`, `src/services/profile-auth-manager.ts`, and the command
|
|
27
|
-
/ query handlers keep working without edits.
|
|
28
|
-
- **Map `gemini-reverse` types into our domain types** in
|
|
29
|
-
`src/services/gemini-client-wrapper.ts`:
|
|
30
|
-
- `ChatInfo` (ours) ← `ChatInfo` from `gemini-reverse` (`cid` → `id`,
|
|
31
|
-
`is_pinned` → `isPinned`, `timestamp` preserved, optional `profile` added
|
|
32
|
-
for the multi-profile case).
|
|
33
|
-
- `Message[]` (ours) ← `ChatHistory.turns` from `readChat(cid, limit)` (role
|
|
34
|
-
`"user"` vs `"model"`, content flattened from `text` / `parts[].text`,
|
|
35
|
-
`conversationId` injected).
|
|
36
|
-
- `string[]` of model display names (ours) ←
|
|
37
|
-
`AvailableModel[]` from `listModels()` (use `display_name` with `model_id`
|
|
38
|
-
as fallback).
|
|
39
|
-
- **Map `gemini-reverse` errors into our error hierarchy** in
|
|
40
|
-
`src/core/errors.ts` (re-export shim or constructor translation in the
|
|
41
|
-
wrapper):
|
|
42
|
-
- `AuthError` → `AuthenticationError` (with the same "run `gemiterm login`"
|
|
43
|
-
message).
|
|
44
|
-
- `UsageLimitExceeded` / `TemporarilyBlocked` / `ModelInvalid` →
|
|
45
|
-
`GeminiAPIError` with a domain-specific message prefix.
|
|
46
|
-
- `TimeoutError` → `GeminiAPIError` ("Request to Gemini timed out").
|
|
47
|
-
- `APIError` / other `GeminiError` subclasses → `GeminiAPIError` with the
|
|
48
|
-
original `message` and `cause` set to the underlying error.
|
|
49
|
-
- **Preserve multi-profile behavior**: `forProfile(name)` still returns a new
|
|
50
|
-
`GeminiClientService` instance bound to that profile's cookies loaded from
|
|
51
|
-
`CookieStorageService`. The underlying `GeminiClient` is constructed
|
|
52
|
-
per-profile, so the upstream cookie auto-refresh watchdog is scoped to that
|
|
53
|
-
profile's session.
|
|
54
|
-
- **Preserve initialization sequencing**: the wrapper's `init()` is called
|
|
55
|
-
once after the `GeminiClient` is constructed and before any I/O. `init()`
|
|
56
|
-
configures `timeout`, `autoRefresh: true`, `refreshInterval: 540000` (9
|
|
57
|
-
minutes, matching upstream defaults), and `autoClose: false` so the process
|
|
58
|
-
stays warm during a CLI session.
|
|
59
|
-
- **Add a unit test file `tests/services/gemini-client-wrapper.test.ts`** that
|
|
60
|
-
mocks the `gemini-reverse` `GeminiClient` class via a module mock and
|
|
61
|
-
verifies the mapping/translation for `listChats`, `fetchChat`, `sendMessage`,
|
|
62
|
-
`startNewChat`, `deleteChat`, `listModels`, `forProfile`, and the
|
|
63
|
-
`AuthError → AuthenticationError` translation. Reuse the existing
|
|
64
|
-
test-factory style from `tests/services/cookie-storage-service.test.ts`.
|
|
65
|
-
|
|
66
|
-
No public CLI surface, command output, config file format, or persisted cookie
|
|
67
|
-
file format changes. The v1.4.1 → v2.0.0 upgrade path stays intact.
|
|
68
|
-
|
|
69
|
-
## Capabilities
|
|
70
|
-
|
|
71
|
-
### New Capabilities
|
|
72
|
-
|
|
73
|
-
None. This change is a drop-in implementation swap; no new user-visible
|
|
74
|
-
capability is introduced. If we ever need to expose `gemini-reverse` features
|
|
75
|
-
the wrapper doesn't currently surface (Gems, file uploads, deep research,
|
|
76
|
-
streaming `--json`), those would be a separate change with their own
|
|
77
|
-
`specs/<name>/spec.md`.
|
|
78
|
-
|
|
79
|
-
### Modified Capabilities
|
|
80
|
-
|
|
81
|
-
None. The requirements in `openspec/specs/conversations/spec.md`,
|
|
82
|
-
`openspec/specs/multi-profile-conversations/spec.md`, `openspec/specs/commands/`
|
|
83
|
-
(every command that talks to Gemini), and `openspec/specs/auth/spec.md` are
|
|
84
|
-
unaffected — the `IGeminiClientService` / `IGeminiClientQueryService` contract
|
|
85
|
-
and observable behavior of every command are preserved. This is an
|
|
86
|
-
implementation-detail refactor only.
|
|
87
|
-
|
|
88
|
-
## Impact
|
|
89
|
-
|
|
90
|
-
- **Dependencies**: adds `gemini-reverse` to `dependencies` in
|
|
91
|
-
`package.json`. Confirms Bun runtime can resolve and load the package (it's
|
|
92
|
-
pure Node.js, no native bindings).
|
|
93
|
-
- **Source files**:
|
|
94
|
-
- **Modified**: `src/services/gemini-client-wrapper.ts` (full rewrite of
|
|
95
|
-
internal implementation; public class signature unchanged).
|
|
96
|
-
- **Modified**: `src/core/errors.ts` — add `translateGeminiError(e: unknown):
|
|
97
|
-
GemitermError` helper (or extend the wrapper to perform the mapping
|
|
98
|
-
inline; decided in `design.md`).
|
|
99
|
-
- **New**: `tests/services/gemini-client-wrapper.test.ts`.
|
|
100
|
-
- **Call sites (no edits expected, verified during implementation)**:
|
|
101
|
-
- `src/cli/index.ts` (3× `IGeminiClientService` / `IGeminiClientQueryService`
|
|
102
|
-
consumers and the factory `new GeminiClientService({ secure1psid: "" }, …)`).
|
|
103
|
-
- `src/services/profile-auth-manager.ts` (consumes `IGeminiClientService`).
|
|
104
|
-
- `src/core/command-handlers.ts`, `src/core/query-handlers.ts` (consume the
|
|
105
|
-
interfaces, not the class).
|
|
106
|
-
- **Sensitive-area check**: `AGENTS.md` flags four files as regression-gate
|
|
107
|
-
files for auth flow; this change touches `src/services/gemini-client-wrapper.ts`
|
|
108
|
-
only, which is **not** one of those four. The four flagged files
|
|
109
|
-
(`playwright-cli-driver.ts`, `cookie-monitor.ts`, `auth-service.ts`,
|
|
110
|
-
`cookie-storage-service.ts`) are not modified by this change. The wrapper
|
|
111
|
-
consumes `CookieStorageService` via its existing constructor parameter, so
|
|
112
|
-
the auth pipeline keeps working.
|
|
113
|
-
- **Tests**: target stays at the 432/432 baseline. The new test file is
|
|
114
|
-
expected to add coverage (post-implementation count > 432).
|
|
115
|
-
- **Build**: `bun run build` (which currently fails for unrelated reasons
|
|
116
|
-
captured in the `cross-platform-build-and-ci` change) is unaffected by
|
|
117
|
-
this change.
|
|
118
|
-
- **No new environment variables.** `gemini-reverse` honors
|
|
119
|
-
`GEMINI_COOKIE_PATH`; we do not set it, so the library uses its default
|
|
120
|
-
temp directory.
|
|
121
|
-
- **No config-file or cookie-file format change.** v1.4.1 users upgrading
|
|
122
|
-
to v2.0.0 will not need to re-authenticate.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: This change introduces no new requirements
|
|
4
|
-
This change is a pure implementation swap of the in-tree Gemini HTTP client
|
|
5
|
-
(`src/services/gemini-client-wrapper.ts`) for the npm `gemini-reverse`
|
|
6
|
-
library. The public contracts — `IGeminiClientService`,
|
|
7
|
-
`IGeminiClientQueryService`, and the `GeminiClientService` class signature —
|
|
8
|
-
are preserved bit-identical. No new user-visible capability is added, and no
|
|
9
|
-
existing requirement in `openspec/specs/conversations/spec.md`,
|
|
10
|
-
`openspec/specs/multi-profile-conversations/spec.md`, `openspec/specs/commands/`,
|
|
11
|
-
or `openspec/specs/auth/spec.md` is modified. All other capabilities in
|
|
12
|
-
`openspec/specs/` are likewise unaffected.
|
|
13
|
-
|
|
14
|
-
#### Scenario: Public contract preserved
|
|
15
|
-
- **WHEN** a caller imports `GeminiClientService`,
|
|
16
|
-
`IGeminiClientService`, or `IGeminiClientQueryService` and uses the
|
|
17
|
-
existing methods (`listChats`, `fetchChat`, `sendMessage`, `startNewChat`,
|
|
18
|
-
`deleteChat`, `listModels`, `forProfile`, `profileHasConversation`)
|
|
19
|
-
- **THEN** signatures, return types, and thrown error types are identical to
|
|
20
|
-
the pre-change implementation; the 11 CLI commands, the
|
|
21
|
-
`ProfileAuthManager`, and all command/query handlers compile and run
|
|
22
|
-
without source edits
|
|
23
|
-
|
|
24
|
-
#### Scenario: Per-profile sessions preserved
|
|
25
|
-
- **WHEN** a caller invokes `geminiClient.forProfile("work")` and then
|
|
26
|
-
`listChats()` on the returned instance
|
|
27
|
-
- **THEN** the `ChatInfo[]` results are scoped to the "work" profile's
|
|
28
|
-
Google account and carry `profile: "work"` on each entry, matching the
|
|
29
|
-
existing behavior of the placeholder
|