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,221 +0,0 @@
1
- <!-- conversation_id: sample_conversation_123 -->
2
- ---
3
- title: "Python Code Review Session"
4
- export_date: "2026-03-21 14:30:00"
5
- message_count: 6
6
- ---
7
-
8
- # Python Code Review Session
9
-
10
- **User:**
11
-
12
- Can you review this Python function for best practices?
13
-
14
- ```python
15
- def get_data(url, params={}):
16
- response = requests.get(url, params)
17
- return response.json()
18
- ```
19
-
20
- **Gemini:**
21
-
22
- <small>2026-03-21 14:30:15</small>
23
-
24
- I'll review your function for best practices. Here are some suggestions:
25
-
26
- ## 1. Add Type Hints
27
-
28
- ```python
29
- import requests
30
- from typing import Any
31
-
32
- def get_data(url: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
33
- if params is None:
34
- params = {}
35
- response = requests.get(url, params=params)
36
- response.raise_for_status()
37
- return response.json()
38
- ```
39
-
40
- ## 2. Handle Errors Gracefully
41
-
42
- Consider catching potential exceptions:
43
-
44
- ```python
45
- def get_data(url: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
46
- try:
47
- response = requests.get(url, params=params or {})
48
- response.raise_for_status()
49
- return response.json()
50
- except requests.RequestException as e:
51
- raise RuntimeError(f"Failed to fetch data: {e}") from e
52
- ```
53
-
54
- ## 3. Add Docstring
55
-
56
- ```python
57
- def get_data(url: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
58
- """Fetch JSON data from a URL.
59
-
60
- Args:
61
- url: The endpoint URL to request.
62
- params: Optional query parameters.
63
-
64
- Returns:
65
- The JSON response as a dictionary.
66
-
67
- Raises:
68
- RuntimeError: If the request fails.
69
- """
70
- ...
71
- ```
72
-
73
- ## Key Improvements Summary
74
-
75
- | Issue | Original | Improved |
76
- |-------|----------|----------|
77
- | Mutable default | `params={}` | `params=None` |
78
- | Type hints | Missing | Full type annotations |
79
- | Error handling | None | raise_for_status() + try/except |
80
- | Docstring | None | Google-style docstring |
81
-
82
- **User:**
83
-
84
- Thanks! What about adding a timeout parameter?
85
-
86
- **Gemini:**
87
-
88
- <small>2026-03-21 14:31:00</small>
89
-
90
- Good idea! Here's the updated version with timeout:
91
-
92
- ```python
93
- import requests
94
- from typing import Any
95
-
96
- def get_data(
97
- url: str,
98
- params: dict[str, Any] | None = None,
99
- timeout: float | tuple[float, float] = 5.0
100
- ) -> dict[str, Any]:
101
- """Fetch JSON data from a URL with timeout support.
102
-
103
- Args:
104
- url: The endpoint URL to request.
105
- params: Optional query parameters.
106
- timeout: Request timeout in seconds. Can be a single value
107
- or tuple (connect_timeout, read_timeout).
108
-
109
- Returns:
110
- The JSON response as a dictionary.
111
-
112
- Raises:
113
- RuntimeError: If the request fails or times out.
114
- """
115
- try:
116
- response = requests.get(url, params=params or {}, timeout=timeout)
117
- response.raise_for_status()
118
- return response.json()
119
- except requests.Timeout:
120
- raise RuntimeError(f"Request timed out after {timeout}s") from None
121
- except requests.RequestException as e:
122
- raise RuntimeError(f"Failed to fetch data: {e}") from e
123
- ```
124
-
125
- **Usage Examples:**
126
-
127
- ```python
128
- # Basic usage
129
- data = get_data("https://api.example.com/users")
130
-
131
- # With query params
132
- data = get_data("https://api.example.com/search", {"q": "python"})
133
-
134
- # With custom timeout (10 seconds)
135
- data = get_data("https://api.example.com/slow", timeout=10.0)
136
-
137
- # With separate connect/read timeouts
138
- data = get_data("https://api.example.com/", timeout=(2.0, 10.0))
139
- ```
140
-
141
- The timeout parameter prevents your application from hanging indefinitely on slow or unresponsive endpoints.
142
-
143
- **User:**
144
-
145
- Perfect! One more question - how would you add retry logic with exponential backoff?
146
-
147
- **Gemini:**
148
-
149
- <small>2026-03-21 14:32:30</small>
150
-
151
- Here's an implementation with retry logic using exponential backoff:
152
-
153
- ```python
154
- import requests
155
- import time
156
- from typing import Any
157
-
158
- def get_data_with_retry(
159
- url: str,
160
- params: dict[str, Any] | None = None,
161
- max_retries: int = 3,
162
- base_delay: float = 1.0,
163
- max_delay: float = 30.0
164
- ) -> dict[str, Any]:
165
- """Fetch JSON data with automatic retry on failure.
166
-
167
- Args:
168
- url: The endpoint URL to request.
169
- params: Optional query parameters.
170
- max_retries: Maximum number of retry attempts.
171
- base_delay: Initial delay between retries in seconds.
172
- max_delay: Maximum delay cap between retries.
173
-
174
- Returns:
175
- The JSON response as a dictionary.
176
-
177
- Raises:
178
- RuntimeError: If all retry attempts fail.
179
- """
180
- last_exception: Exception | None = None
181
-
182
- for attempt in range(max_retries + 1):
183
- try:
184
- response = requests.get(url, params=params or {}, timeout=5.0)
185
- response.raise_for_status()
186
- return response.json()
187
- except (requests.Timeout, requests.ConnectionError) as e:
188
- last_exception = e
189
- if attempt < max_retries:
190
- delay = min(base_delay * (2 ** attempt), max_delay)
191
- time.sleep(delay)
192
- except requests.RequestException as e:
193
- raise RuntimeError(f"Request failed: {e}") from e
194
-
195
- raise RuntimeError(f"All {max_retries} retries exhausted") from last_exception
196
- ```
197
-
198
- ## Retry Strategy Visualization
199
-
200
- ```
201
- Attempt 1: Immediate
202
- Attempt 2: Wait 1s
203
- Attempt 3: Wait 2s
204
- Attempt 4: Wait 4s
205
- ... (capped at max_delay)
206
- ```
207
-
208
- ## When to Use Retries
209
-
210
- | Scenario | Retry? | Reason |
211
- |----------|--------|--------|
212
- | Connection error | Yes | Temporary network issue |
213
- | Timeout | Yes | Server may be temporarily busy |
214
- | 404 Not Found | No | Resource doesn't exist |
215
- | 500 Server Error | Yes | Temporary server issue |
216
- | 401 Unauthorized | No | Credentials are invalid |
217
-
218
- This pattern is especially useful for:
219
- - Production APIs with reliability requirements
220
- - Batch operations processing many requests
221
- - Microservices communicating over networks
@@ -1,222 +0,0 @@
1
- #!/bin/bash
2
- # Usage Examples for gemiterm
3
- # This script demonstrates common workflows and commands
4
-
5
- set -e
6
-
7
- echo "========================================"
8
- echo "gemiterm Usage Examples"
9
- echo "========================================"
10
-
11
- #------------------------------------------
12
- # SECTION 1: First-Time Setup
13
- #------------------------------------------
14
- section1() {
15
- echo ""
16
- echo "=== Section 1: First-Time Setup ==="
17
- echo ""
18
-
19
- # Install the CLI
20
- echo "1. Install gemiterm:"
21
- echo " pip install -e ."
22
- echo ""
23
-
24
- # Install browser dependencies
25
- echo "2. Install Chromium for Playwright:"
26
- echo " playwright install chromium"
27
- echo ""
28
-
29
- # Authenticate
30
- echo "3. Authenticate with Google:"
31
- echo " gemiterm auth"
32
- echo " # Opens browser window for login"
33
- echo ""
34
-
35
- # Verify authentication
36
- echo "4. Verify authentication status:"
37
- echo " gemiterm status"
38
- echo " # Shows: Authenticated: True"
39
- echo ""
40
- }
41
-
42
- #------------------------------------------
43
- # SECTION 2: Daily Usage Workflows
44
- #------------------------------------------
45
- section2() {
46
- echo ""
47
- echo "=== Section 2: Daily Usage ==="
48
- echo ""
49
-
50
- # Check status
51
- echo "1. Check authentication status:"
52
- echo " gemiterm status"
53
- echo ""
54
-
55
- # List recent chats
56
- echo "2. List your recent chats:"
57
- echo " gemiterm list"
58
- echo " # Shows table of conversations"
59
- echo ""
60
-
61
- # List with limit
62
- echo "3. List specific number of chats:"
63
- echo " gemiterm list -n 5"
64
- echo " # Shows 5 most recent chats"
65
- echo ""
66
-
67
- # Fetch a specific chat
68
- echo "4. View a conversation's history:"
69
- echo " gemiterm fetch <conversation_id>"
70
- echo " # Example:"
71
- echo " gemiterm fetch abc123xyz"
72
- echo ""
73
-
74
- # Continue a conversation
75
- echo "5. Send a message to existing chat:"
76
- echo " gemiterm continue <conversation_id> <message>"
77
- echo " # Example:"
78
- echo " gemiterm continue abc123xyz \"Continue my previous thought\""
79
- echo ""
80
- }
81
-
82
- #------------------------------------------
83
- # SECTION 3: Export for Backup
84
- #------------------------------------------
85
- section3() {
86
- echo ""
87
- echo "=== Section 3: Export for Backup ==="
88
- echo ""
89
-
90
- # Export single chat
91
- echo "1. Export a single conversation to Markdown:"
92
- echo " gemiterm export <conversation_id>"
93
- echo " # Creates: gemini-chat-{id}-{date}.md"
94
- echo ""
95
-
96
- # Export with custom output
97
- echo "2. Export with custom output path:"
98
- echo " gemiterm export <conversation_id> -o my-chat.md"
99
- echo ""
100
-
101
- # Export as JSON
102
- echo "3. Export as JSON format:"
103
- echo " gemiterm export <conversation_id> -f json"
104
- echo ""
105
-
106
- # Export with metadata
107
- echo "4. Export with full metadata:"
108
- echo " gemiterm export <conversation_id> --include-metadata"
109
- echo ""
110
-
111
- # Batch export all
112
- echo "5. Export all conversations:"
113
- echo " gemiterm export-all"
114
- echo " # Creates ./exports/ with all chats"
115
- echo ""
116
-
117
- # Export to specific directory
118
- echo "6. Export to custom directory:"
119
- echo " gemiterm export-all -o ./my-backups/"
120
- echo ""
121
-
122
- # Export recent only
123
- echo "7. Export only recent conversations:"
124
- echo " gemiterm export-all --since 2026-03-01"
125
- echo ""
126
- }
127
-
128
- #------------------------------------------
129
- # SECTION 4: Error Scenarios
130
- #------------------------------------------
131
- section4() {
132
- echo ""
133
- echo "=== Section 4: Error Handling ==="
134
- echo ""
135
-
136
- # Not authenticated
137
- echo "1. If not authenticated:"
138
- echo " $ gemiterm list"
139
- echo " Error: Not authenticated. Run 'gemiterm auth' first."
140
- echo " $ gemiterm auth"
141
- echo ""
142
-
143
- # Session expired
144
- echo "2. If session expired:"
145
- echo " $ gemiterm status"
146
- echo " Error: Session expired. Please re-authenticate."
147
- echo " $ gemiterm auth"
148
- echo ""
149
-
150
- # Conversation not found
151
- echo "3. If conversation doesn't exist:"
152
- echo " $ gemiterm fetch nonexistent-id"
153
- echo " Error: Conversation 'nonexistent-id' not found."
154
- echo " Run 'gemiterm list' to see available conversations."
155
- echo ""
156
-
157
- # Verbose debugging
158
- echo "4. Debug with verbose output:"
159
- echo " gemiterm -v status"
160
- echo " # Shows detailed logs"
161
- echo ""
162
- }
163
-
164
- #------------------------------------------
165
- # SECTION 5: Configuration
166
- #------------------------------------------
167
- section5() {
168
- echo ""
169
- echo "=== Section 5: Configuration ==="
170
- echo ""
171
-
172
- # Custom config directory
173
- echo "1. Use custom config directory:"
174
- echo " export GEMITERM_CONFIG_DIR=/path/to/config"
175
- echo " gemiterm auth"
176
- echo ""
177
-
178
- # Verbose mode
179
- echo "2. Enable verbose logging:"
180
- echo " export GEMITERM_VERBOSE=true"
181
- echo " gemiterm list"
182
- echo ""
183
-
184
- # View config location
185
- echo "3. Default config location:"
186
- echo " ~/gemiterm/"
187
- echo " Contains:"
188
- echo " - storage_state.json (cookies)"
189
- echo " - config (future use)"
190
- echo ""
191
- }
192
-
193
- #------------------------------------------
194
- # SECTION 6: Demo Script
195
- #------------------------------------------
196
- section6() {
197
- echo ""
198
- echo "=== Section 6: Using the Demo Script ==="
199
- echo ""
200
-
201
- echo "Run the demo script to see a complete workflow:"
202
- echo " python scripts/demo.py"
203
- echo ""
204
- echo "Demo script:"
205
- echo "1. Loads cookies"
206
- echo "2. Fetches chat list"
207
- echo "3. Shows 5 most recent chats"
208
- echo "4. Appends message to most recent chat"
209
- echo ""
210
- }
211
-
212
- # Run all sections
213
- section1
214
- section2
215
- section3
216
- section4
217
- section5
218
- section6
219
-
220
- echo "========================================"
221
- echo "For more help: gemiterm --help"
222
- echo "========================================"
package/install.ps1 DELETED
@@ -1,264 +0,0 @@
1
- #Requires -Version 7.0
2
- <#
3
- .SYNOPSIS
4
- GemiTerm v2.0.0 installer for Windows.
5
-
6
- .DESCRIPTION
7
- v1.4.1 → v2.0.0 upgrades replace the binary in place; the config dir at
8
- %APPDATA%\gemiterm\ (Windows) is NEVER deleted by this installer.
9
-
10
- .PARAMETER Tag
11
- GitHub release tag to install (default: "latest").
12
-
13
- .PARAMETER Uninstall
14
- Remove GemiTerm binary and PATH entry. Preserves config dir at
15
- %APPDATA%\gemiterm\.
16
-
17
- .PARAMETER WhatIf
18
- Show what the installer would do without making any changes.
19
-
20
- .PARAMETER InstallDir
21
- Override the default install directory (default: $env:LOCALAPPDATA\GemiTerm).
22
-
23
- .EXAMPLE
24
- irm https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.ps1 | iex
25
- pwsh -File install.ps1 -Tag v2.0.0-rc.1
26
- pwsh -File install.ps1 -Uninstall
27
- pwsh -File install.ps1 -WhatIf
28
- #>
29
- param(
30
- [string]$Tag = 'latest',
31
- [switch]$Uninstall,
32
- [switch]$WhatIf,
33
- [string]$InstallDir = "$env:LOCALAPPDATA\GemiTerm"
34
- )
35
-
36
- $ErrorActionPreference = 'Stop'
37
-
38
- $Repo = 'expert-vision-software/GemiTerm'
39
- $ExePath = Join-Path $InstallDir 'GemiTerm.exe'
40
-
41
- # --- Detect Python v1.4.1 (task 8.1) ---
42
- if (Get-Command pip -ErrorAction SilentlyContinue) {
43
- try {
44
- $null = pip show gemiterm 2>$null
45
- if ($LASTEXITCODE -eq 0) {
46
- Write-Host "WARNING: Python v1.4.1 detected. Your config data at %APPDATA%\gemiterm\ is safe and will be preserved. Please run 'pip uninstall gemiterm' before re-running this installer to complete the v2.0.0 installation."
47
- exit 1
48
- }
49
- } catch {
50
- # pip show returned non-zero — gemiterm not installed via pip
51
- }
52
- }
53
-
54
- # --- Uninstall (task 2.2) ---
55
- if ($Uninstall) {
56
- Write-Host "Removing GemiTerm..."
57
-
58
- if (Test-Path $ExePath) {
59
- if ($WhatIf) {
60
- Write-Host "[WhatIf] Would delete $ExePath"
61
- } else {
62
- Remove-Item $ExePath -Force
63
- Write-Host " Removed $ExePath"
64
- }
65
- } else {
66
- Write-Host " $ExePath not found (already removed)"
67
- }
68
-
69
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
70
- if ($userPath -and $userPath.ToLower().Contains($InstallDir.ToLower())) {
71
- $newPath = ($userPath -split ';' | Where-Object { $_ -and $_.ToLower() -ne $InstallDir.ToLower() }) -join ';'
72
- if ($WhatIf) {
73
- Write-Host "[WhatIf] Would remove $InstallDir from user PATH"
74
- } else {
75
- [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
76
- Write-Host " Removed $InstallDir from PATH"
77
- }
78
- }
79
-
80
- if ($WhatIf) {
81
- Write-Host "[WhatIf] Would update current session PATH"
82
- } else {
83
- $env:Path = ($env:Path -split ';' | Where-Object { $_ -and $_.ToLower() -ne $InstallDir.ToLower() }) -join ';'
84
- }
85
-
86
- Write-Host "GemiTerm uninstall review complete. No changes were made." -ForegroundColor Cyan
87
- exit 0
88
- }
89
-
90
- # --- Upgrade detection (task 2.3) ---
91
- if (Test-Path $ExePath) {
92
- Write-Host "Detected existing install at $ExePath; upgrading in place."
93
- }
94
-
95
- # --- v1.4.1 → v2.0.0 config migration (task 11.1) ---
96
- # v1.4.1 wrote config to $env:USERPROFILE\.config\gemiterm\ on Windows
97
- # (Python config.py:14 has no Windows branch; Path.home() returns the
98
- # user profile and the literal ".config/gemiterm" is appended). v2.0.0
99
- # reads from $env:APPDATA\gemiterm\. If the old path exists and the new
100
- # one does not, copy the tree forward. The v1.4.1 directory is left in
101
- # place as a safety net; the user can delete it manually.
102
- $V14ConfigDir = Join-Path $env:USERPROFILE '.config\gemiterm'
103
- $V2ConfigDir = Join-Path $env:APPDATA 'gemiterm'
104
- if ((Test-Path $V14ConfigDir) -and -not (Test-Path $V2ConfigDir)) {
105
- Write-Host "Detected v1.4.1 config at $V14ConfigDir; migrating to $V2ConfigDir"
106
- if ($WhatIf) {
107
- Write-Host "[WhatIf] Would create directory $V2ConfigDir"
108
- Write-Host "[WhatIf] Would copy $V14ConfigDir\* to $V2ConfigDir"
109
- } else {
110
- New-Item -ItemType Directory -Path $V2ConfigDir -Force | Out-Null
111
- Copy-Item -Recurse -Force -Path (Join-Path $V14ConfigDir '*') -Destination $V2ConfigDir
112
- Write-Host "v1.4.1 config copied to $V2ConfigDir. The original at $V14ConfigDir is left in place as a backup."
113
- }
114
- }
115
-
116
- # Determine package-manager executable (prefer bunx over npx)
117
- $PackageManagerX = if (Get-Command bun -ErrorAction SilentlyContinue) { 'bunx' } elseif (Get-Command npm -ErrorAction SilentlyContinue) { 'npx' } else { 'bunx' }
118
-
119
- # --- Package-manager prompt (task 11.2) ---
120
- # If bun or npm is on PATH, recommend installing via the package manager
121
- # (npm: `npm i -g gemiterm`; bun: `bun i -g gemiterm`) instead of this
122
- # binary drop. The prompt is skipped when stdin is not a TTY (e.g. the
123
- # `irm | iex` one-liner flow runs unattended and must not block).
124
- if (((Get-Command bun -ErrorAction SilentlyContinue) -or (Get-Command npm -ErrorAction SilentlyContinue)) -and [Console]::IsInputRedirected -eq $false) {
125
- Write-Host ""
126
- Write-Host "It is recommended to install via bun or npm package manager."
127
- Write-Host "Are you sure you want to continue with binary install? [y/N]"
128
- $reply = Read-Host
129
- if ($reply -notmatch '^[yY]([eE][sS])?$') {
130
- $pm = if (Get-Command bun -ErrorAction SilentlyContinue) { 'bun i -g gemiterm' } else { 'npm i -g gemiterm' }
131
- Write-Host "Aborted. Install via: $pm"
132
- exit 0
133
- }
134
- }
135
-
136
- # --- Resolve release (task 2.4 + 2.10) ---
137
- $ApiUrl = if ($Tag -eq 'latest') {
138
- "https://api.github.com/repos/$Repo/releases/latest"
139
- } else {
140
- "https://api.github.com/repos/$Repo/releases/tags/$Tag"
141
- }
142
-
143
- $release = $null
144
- try {
145
- $release = Invoke-RestMethod $ApiUrl -TimeoutSec 30
146
- } catch {
147
- Write-Host "Cannot reach GitHub releases. Check your network connection or use the 'build from source' instructions in docs/INSTALL.md."
148
- exit 1
149
- }
150
-
151
- if (-not $release -or -not $release.assets) {
152
- Write-Host "No releases found."
153
- Write-Host "Build from source: git clone https://github.com/$Repo.git && cd GemiTerm && bun install && bun run build"
154
- exit 1
155
- }
156
-
157
- $asset = $release.assets | Where-Object { $_.name -eq 'gemiterm.exe' }
158
- if (-not $asset) {
159
- Write-Host "No gemiterm.exe asset found in release."
160
- exit 1
161
- }
162
-
163
- $version = $release.tag_name
164
-
165
- # --- Download (task 2.5) ---
166
- if (-not (Test-Path $InstallDir)) {
167
- if ($WhatIf) {
168
- Write-Host "[WhatIf] Would create directory $InstallDir"
169
- } else {
170
- New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
171
- }
172
- }
173
-
174
- $tempPath = Join-Path $InstallDir 'GemiTerm.exe.new'
175
- if ($WhatIf) {
176
- Write-Host "[WhatIf] Would download GemiTerm $version from $($asset.browser_download_url) to $tempPath"
177
- Write-Host "[WhatIf] Would move $tempPath to $ExePath"
178
- } else {
179
- try {
180
- Write-Host "Downloading GemiTerm $version..."
181
- Invoke-WebRequest $asset.browser_download_url -OutFile $tempPath
182
- Move-Item -Force $tempPath $ExePath
183
- } catch {
184
- if (Test-Path $tempPath) { Remove-Item $tempPath -Force -ErrorAction SilentlyContinue }
185
- Write-Host "Download failed: $_"
186
- exit 1
187
- }
188
- }
189
-
190
- # --- Bootstrap package manager if needed (task 9.1 + 9.3) ---
191
- if ($PackageManagerX -eq 'bunx' -and -not (Get-Command bun -ErrorAction SilentlyContinue)) {
192
- if ($WhatIf) {
193
- Write-Host "[WhatIf] Would install Bun from https://bun.sh/install.ps1"
194
- } else {
195
- Write-Host "Installing Bun..."
196
- try {
197
- irm https://bun.sh/install.ps1 | iex
198
- } catch {
199
- if (Get-Command npm -ErrorAction SilentlyContinue) {
200
- Write-Host "Bun installation failed, falling back to npx."
201
- $PackageManagerX = 'npx'
202
- } else {
203
- Write-Host "Bun installation failed and npm is not available. Install Bun from https://bun.sh or npm from https://nodejs.org and re-run this installer."
204
- exit 1
205
- }
206
- }
207
- if ($PackageManagerX -eq 'bunx' -and -not (Get-Command bun -ErrorAction SilentlyContinue)) {
208
- if (Get-Command npm -ErrorAction SilentlyContinue) {
209
- Write-Host "Bun installation failed, falling back to npx."
210
- $PackageManagerX = 'npx'
211
- } else {
212
- Write-Host "Bun installation failed. Install Bun manually from https://bun.sh and re-run this installer."
213
- exit 1
214
- }
215
- }
216
- }
217
- }
218
-
219
- # --- Install Chromium (task 2.6) ---
220
- if ($WhatIf) {
221
- Write-Host "[WhatIf] Would run: $PackageManagerX @playwright/cli install chromium"
222
- } else {
223
- Write-Host "Installing Chromium browser for Playwright..."
224
- try {
225
- & $PackageManagerX @playwright/cli install chromium
226
- if ($LASTEXITCODE -ne 0) {
227
- throw "$PackageManagerX exited with code $LASTEXITCODE"
228
- }
229
- } catch {
230
- Write-Host "Chromium installation failed. Re-run the installer after fixing the issue, or run '$PackageManagerX @playwright/cli install chromium' manually."
231
- exit 1
232
- }
233
- }
234
-
235
- # --- Verify Chromium (task 2.7) ---
236
- $chromeExe = Get-ChildItem "$env:LOCALAPPDATA\ms-playwright\chromium-*\chrome.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
237
- if ($chromeExe) {
238
- Write-Host "Chromium verified at $($chromeExe.FullName)"
239
- } else {
240
- Write-Host "Chromium installation verification failed. Re-run the installer after fixing the network, or run '$PackageManagerX @playwright/cli install chromium' manually."
241
- exit 1
242
- }
243
-
244
- # --- PATH augmentation (task 2.8) ---
245
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
246
- if ($userPath -and -not $userPath.ToLower().Contains($InstallDir.ToLower())) {
247
- if ($WhatIf) {
248
- Write-Host "[WhatIf] Would add $InstallDir to user PATH"
249
- } else {
250
- [Environment]::SetEnvironmentVariable('Path', "$userPath;$InstallDir", 'User')
251
- $env:Path = "$env:Path;$InstallDir"
252
- Write-Host "Added $InstallDir to PATH"
253
- }
254
- }
255
-
256
- # --- Success (task 2.9) ---
257
- if ($WhatIf) {
258
- Write-Host ""
259
- Write-Host "WhatIf review complete. No changes were made." -ForegroundColor Cyan
260
- Write-Host "GemiTerm $version would be installed to $ExePath using $($PackageManagerX).Chromium."
261
- Write-Host "Run without -WhatIf to apply."
262
- } else {
263
- Write-Host "GemiTerm $version installed to $ExePath. Run 'gemiterm status' to verify, then 'gemiterm auth' to authenticate."
264
- }