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,205 +0,0 @@
1
- ## ADDED Requirements
2
-
3
- ### Requirement: PowerShell Installer Ships With Every v2.0.0+ Release
4
- The system MUST attach a `install.ps1` script to every GitHub release tagged `v*` that targets the v2.0.0+ series. The `install.ps1` script MUST be a PowerShell Core 7+ script that, when invoked as `irm https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.ps1 | iex`, downloads the latest `GemiTerm.exe` release asset, installs it to `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe`, runs `gemiterm install-browser`, verifies Chromium is present, and augments the user `PATH` idempotently.
5
-
6
- #### Scenario: One-liner install on a fresh Windows machine
7
- - **WHEN** a user with no prior GemiTerm install runs `irm https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.ps1 | iex` in PowerShell Core 7+
8
- - **THEN** the latest `GemiTerm.exe` is downloaded to `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe`, `gemiterm install-browser` is invoked to install Chromium, the user `PATH` is augmented to include `$env:LOCALAPPDATA\GemiTerm`, and the script prints a success message
9
-
10
- #### Scenario: Fresh install places binary at the v1.4.1-equivalent path
11
- - **WHEN** `install.ps1` runs on a system with no prior install
12
- - **THEN** the binary is placed at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` (note capital `G` in `GemiTerm`, matching the v1.4.1 install path) and a fresh `%APPDATA%\gemiterm\` config directory is created only if the user later runs `gemiterm` for the first time
13
-
14
- #### Scenario: `gemiterm --version` reports the installed version
15
- - **WHEN** a user runs `gemiterm --version` after a successful install
16
- - **THEN** the command exits 0 and prints a version string of the form `gemiterm v2.x.y`
17
-
18
- ### Requirement: POSIX Installer Ships With Every v2.0.0+ Release
19
- The system MUST attach a `install.sh` script to every GitHub release tagged `v*` that targets the v2.0.0+ series. The `install.sh` script MUST be a POSIX bash 3.2+ script that, when invoked as `curl -fsSL https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.sh | bash`, downloads the latest `GemiTerm` release asset, installs it to `~/.local/bin/gemiterm`, runs `gemiterm install-browser`, verifies Chromium is present, and writes a `source`-able snippet to `~/gemiterm/env.sh` that is appended to `~/.bashrc` (and `~/.zshrc` if it exists) for `PATH` discovery.
20
-
21
- #### Scenario: One-liner install on a fresh Linux machine
22
- - **WHEN** a user with no prior GemiTerm install runs `curl -fsSL https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.sh | bash` in bash
23
- - **THEN** the latest `GemiTerm` binary is downloaded to `~/.local/bin/gemiterm` (creating the directory if it does not exist), `chmod +x` is applied, `gemiterm install-browser` is invoked, and a `source` line for `~/gemiterm/env.sh` is appended to `~/.bashrc` if not already present
24
-
25
- #### Scenario: `gemiterm --version` reports the installed version on POSIX
26
- - **WHEN** a user runs `gemiterm --version` after a successful POSIX install (in a new shell, or after `source ~/gemiterm/env.sh`)
27
- - **THEN** the command exits 0 and prints a version string of the form `gemiterm v2.x.y`
28
-
29
- #### Scenario: POSIX install creates `~/.local/bin` if missing
30
- - **WHEN** `install.sh` runs on a system where `~/.local/bin` does not exist
31
- - **THEN** the directory is created with `mkdir -p` and the binary is placed inside; a message is printed if the directory was created
32
-
33
- ### Requirement: Upgrade From v1.4.1 Preserves User Data
34
- The system MUST upgrade an existing v1.4.1 installation in place without modifying the user's profile data. The installer MUST detect an existing install by the presence of `GemiTerm.exe` at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` (Windows) or `gemiterm` at `~/.local/bin/gemiterm` (POSIX), MUST replace the binary atomically, and MUST NOT delete, move, or rewrite anything under `%APPDATA%\gemiterm\` (Windows) or `~/gemiterm/` (POSIX).
35
-
36
- #### Scenario: Binary replaced in place, config dir untouched on Windows upgrade
37
- - **WHEN** a user with v1.4.1 already installed runs the v2.0.0 `install.ps1`
38
- - **THEN** the binary at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` is overwritten, the installer prints `"Detected existing install at …; upgrading in place."`, and every file under `%APPDATA%\gemiterm\profiles\<name>\storage_state.json` is byte-identical to its pre-upgrade content (verified by hash)
39
-
40
- #### Scenario: `.default` profile marker preserved on upgrade
41
- - **WHEN** a user with v1.4.1 has a `.default` marker file under `%APPDATA%\gemiterm\profiles\` pointing to profile `work` and runs the v2.0.0 installer
42
- - **THEN** after the upgrade, `%APPDATA%\gemiterm\profiles\.default` still exists, still contains `work` (or the prior content), and `gemiterm status` still lists `work` as the default profile
43
-
44
- #### Scenario: `gemiterm status` shows the same profile list after upgrade
45
- - **WHEN** a user runs `gemiterm status` before and after the v1.4.1 → v2.0.0 upgrade
46
- - **THEN** both invocations print the same set of profile names and the same authentication-state column for each profile
47
-
48
- #### Scenario: PowerShell installer detects Python v1.4.1 and prompts uninstall without touching config
49
- - **WHEN** `install.ps1` runs and `pip show gemiterm` (or `gemiterm --version`) indicates a Python v1.4.1 install is present
50
- - **THEN** the installer prints `"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."` and exits non-zero; the existing binary is NOT replaced and no config data is modified
51
-
52
- #### Scenario: POSIX installer detects Python v1.4.1 and prompts uninstall without touching config
53
- - **WHEN** `install.sh` runs and `pip show gemiterm` (or `gemiterm --version`) indicates a Python v1.4.1 install is present
54
- - **THEN** the installer prints `"WARNING: Python v1.4.1 detected. Your config data at ~/gemiterm/ is safe and will be preserved. Please run 'pip uninstall gemiterm' before re-running this installer to complete the v2.0.0 installation."` and exits non-zero; the existing binary is NOT replaced and no config data is modified
55
-
56
- ### Requirement: `--uninstall` Removes Binary And PATH Entry, Preserves Config Dir
57
- The system MUST support a `--uninstall` flag on both `install.ps1` and `install.sh`. The uninstall flow MUST delete the binary, remove the install dir from the persistent user `PATH` (or remove the `~/gemiterm/env.sh` snippet and the `source` line from `~/.bashrc` / `~/.zshrc` on POSIX), and MUST NOT delete or modify the user's config dir at `%APPDATA%\gemiterm\` (Windows) or `~/gemiterm/` (POSIX). After uninstall, a re-install MUST restore the binary and find the same profiles as before the uninstall.
58
-
59
- #### Scenario: PowerShell uninstall removes binary and PATH entry, leaves config dir intact
60
- - **WHEN** a user runs `pwsh -File install.ps1 --uninstall`
61
- - **THEN** `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` is deleted, `$env:LOCALAPPDATA\GemiTerm` is removed from the user `PATH` (persistent registry value), and `%APPDATA%\gemiterm\` is **NOT** deleted; the script prints `"GemiTerm uninstalled successfully."` and exits 0
62
-
63
- #### Scenario: POSIX uninstall removes binary, env snippet, and bashrc source line, leaves config dir intact
64
- - **WHEN** a user runs `bash install.sh --uninstall`
65
- - **THEN** `~/.local/bin/gemiterm` is deleted, `~/gemiterm/env.sh` is deleted, the `source ~/gemiterm/env.sh` line is removed from `~/.bashrc` (and `~/.zshrc` if it exists), and `~/gemiterm/profiles/` is **NOT** deleted
66
-
67
- #### Scenario: Re-install after uninstall restores the binary and profiles
68
- - **WHEN** a user runs `install.ps1` (or `install.sh`) after a prior `--uninstall`
69
- - **THEN** the binary is re-installed at the same path and `gemiterm status` lists the same profiles that existed before the uninstall (proving the config dir was preserved across the uninstall/reinstall cycle)
70
-
71
- ### Requirement: Tag Override For Canary And Release-Candidate Installs
72
- The system MUST accept a tag override on both installers. On PowerShell, the `-Tag <version>` parameter overrides the default "latest" release resolution. On POSIX, the `GEMITERM_TAG=<version>` environment variable overrides the default. When a tag is provided, the installer MUST fetch the named release (e.g. `v2.0.0-rc.1`) via `https://api.github.com/repos/expert-vision-software/GemiTerm/releases/tags/$Tag` and download the asset from `https://github.com/expert-vision-software/GemiTerm/releases/download/$Tag/`.
73
-
74
- #### Scenario: PowerShell `-Tag` parameter downloads a specific release
75
- - **WHEN** a user runs `pwsh -File install.ps1 -Tag v2.0.0-rc.1`
76
- - **THEN** the installer fetches the `v2.0.0-rc.1` release from the GitHub API, downloads `GemiTerm.exe` from the `v2.0.0-rc.1` release assets, and installs it; `gemiterm --version` after install prints `gemiterm v2.0.0-rc.1` (or the equivalent)
77
-
78
- #### Scenario: POSIX `GEMITERM_TAG` env var downloads a specific release
79
- - **WHEN** a user runs `GEMITERM_TAG=v2.0.0-rc.1 bash install.sh`
80
- - **THEN** the installer fetches the `v2.0.0-rc.1` release, downloads `GemiTerm` from the `v2.0.0-rc.1` release assets, installs it, and `gemiterm --version` prints the rc.1 version
81
-
82
- #### Scenario: Default tag is "latest"
83
- - **WHEN** a user runs the install one-liner with no `-Tag` and no `GEMITERM_TAG` env var
84
- - **THEN** the installer fetches `https://api.github.com/repos/expert-vision-software/GemiTerm/releases/latest` and downloads the asset from the resolved tag
85
-
86
- ### Requirement: PATH Augmentation Is Idempotent
87
- The system MUST add the install dir to the user `PATH` on a fresh install and MUST NOT add a duplicate entry on a re-install. On Windows, the comparison MUST be case-insensitive (Windows file paths are case-insensitive). On POSIX, the comparison MUST be case-sensitive (POSIX filesystems are case-sensitive by default). The current shell's `PATH` MUST also be updated so the user does not have to open a new terminal.
88
-
89
- #### Scenario: Fresh install adds install dir to user PATH
90
- - **WHEN** `install.ps1` runs and `$env:LOCALAPPDATA\GemiTerm` is not in the user `PATH`
91
- - **THEN** the install dir is appended to the persistent user `PATH` (via `[Environment]::SetEnvironmentVariable('Path', ..., 'User')`) and to the current session's `$env:Path`
92
-
93
- #### Scenario: Re-install does not duplicate the PATH entry
94
- - **WHEN** `install.ps1` runs a second time and `$env:LOCALAPPDATA\GemiTerm` is already in the user `PATH` (case-insensitive match)
95
- - **THEN** the user `PATH` is unchanged (no duplicate entry) and the script prints no "PATH updated" message
96
-
97
- #### Scenario: POSIX install writes env.sh and appends source line to ~/.bashrc idempotently
98
- - **WHEN** `install.sh` runs and `~/gemiterm/env.sh` is written and the `source` line is appended to `~/.bashrc` only if not already present (case-insensitive substring check)
99
- - **THEN** a second `install.sh` run does not append a second `source` line and does not duplicate the `export PATH=...` line inside `env.sh`
100
-
101
- ### Requirement: Browser Install Step Runs As Part Of Install And Is Verified
102
- The system MUST invoke `bunx @playwright/cli install chromium` directly (not via the `gemiterm install-browser` command) as part of the install flow, MUST verify that Chromium is on disk after the invocation, and MUST exit non-zero with a clear message if the verification fails. The verification check is a glob over `$env:LOCALAPPDATA\ms-playwright\chromium-*\chrome.exe` (Windows) or `~/.cache/ms-playwright/chromium-*/chrome-linux/chrome` (POSIX).
103
-
104
- #### Scenario: Fresh install downloads Chromium via playwright-cli
105
- - **WHEN** `install.ps1` (or `install.sh`) runs on a system without Chromium and `bun` is in PATH
106
- - **THEN** `bunx @playwright/cli install chromium` is invoked, which downloads Chromium via Playwright, and the installer verifies the Chromium binary is present under the appropriate path before exiting 0
107
-
108
- #### Scenario: Bun not present on system — installer bootstraps Bun first
109
- - **WHEN** `install.ps1` (or `install.sh`) runs and `bun` is not in PATH
110
- - **THEN** the installer downloads and installs Bun via the official installer (`irm https://bun.sh/install.ps1 | iex` on Windows, `curl -fsSL https://bun.sh/install | bash` on POSIX) before running `bunx @playwright/cli install chromium`
111
-
112
- #### Scenario: Bun bootstrap failure fails the installer
113
- - **WHEN** the Bun bootstrap step fails (network error)
114
- - **THEN** the installer prints `"Bun installation failed. Install Bun manually from https://bun.sh and re-run this installer."` and exits non-zero; the existing binary (if upgrading) is left in place
115
-
116
- #### Scenario: playwright-cli install failure fails the installer
117
- - **WHEN** `bunx @playwright/cli install chromium` exits non-zero (e.g. network failure during the Chromium download)
118
- - **THEN** the installer prints `"Chromium installation verification failed. Re-run the installer after fixing the network, or run 'bunx @playwright/cli install chromium' manually."` and exits non-zero; the existing binary (if upgrading) is left in place
119
-
120
- #### Scenario: Re-install on a system with Chromium is a no-op for the browser step
121
- - **WHEN** `install.ps1` (or `install.sh`) runs on a system that already has Chromium
122
- - **THEN** `bunx @playwright/cli install chromium` returns quickly (no re-download), the verification step confirms Chromium is present, and the installer exits 0
123
-
124
- ### Requirement: Network Failure Is Handled Gracefully
125
- The system MUST detect when the GitHub API or the release download is unreachable and MUST exit non-zero with a clear remediation message. The installer MUST NOT replace the existing binary on a failed download (idempotency of the upgrade flow).
126
-
127
- #### Scenario: api.github.com unreachable
128
- - **WHEN** `install.ps1` (or `install.sh`) runs on a system where `https://api.github.com/repos/expert-vision-software/GemiTerm/releases/latest` returns a network error
129
- - **THEN** the installer prints `"Cannot reach GitHub releases. Check your network connection or use the 'build from source' instructions in docs/INSTALL.md."` and exits non-zero; the existing binary (if any) is **NOT** modified
130
-
131
- #### Scenario: Asset download fails mid-stream
132
- - **WHEN** the download of `GemiTerm.exe` (or `GemiTerm`) starts but fails partway (e.g. connection reset)
133
- - **THEN** the installer exits non-zero, the partial file is deleted (or, on Windows, not yet renamed into place), and the existing binary (if upgrading) is **NOT** replaced
134
-
135
- #### Scenario: Existing binary is untouched on a failed install
136
- - **WHEN** a fresh install fails partway and the user had an existing v1.4.1 binary
137
- - **THEN** `gemiterm --version` after the failure still reports the v1.4.1 version (the original binary is byte-identical)
138
-
139
- ### Requirement: No Breaking Changes To User Data Or Config Paths
140
- The system MUST NOT change the on-disk layout of the user config dir relative to v1.4.1. The Windows config dir MUST remain `%APPDATA%\gemiterm\`, the POSIX config dir MUST be `~/gemiterm/`, and the `GEMITERM_CONFIG_DIR` env-var override MUST be honored by the installed binary. The v1.4.1 source config dir at `~/.config/gemiterm/` is the **only** dir the installer may copy from; it MUST NOT be read, written, or deleted by v2.0.0 itself.
141
-
142
- #### Scenario: Windows config path is unchanged
143
- - **WHEN** a user inspects the config dir after install
144
- - **THEN** profiles, cookies, and the `.default` marker live at `%APPDATA%\gemiterm\profiles\<name>\storage_state.json` and `%APPDATA%\gemiterm\profiles\.default` exactly as in v1.4.1
145
-
146
- #### Scenario: POSIX config path is unchanged
147
- - **WHEN** a user inspects the config dir after a POSIX install
148
- - **THEN** profiles, cookies, and the `.default` marker live at `~/gemiterm/profiles/<name>/storage_state.json` and `~/gemiterm/profiles/.default` exactly as the v2.0.0 binary's path-utils expects
149
-
150
- #### Scenario: GEMITERM_CONFIG_DIR override is honored
151
- - **WHEN** a user sets `GEMITERM_CONFIG_DIR=/custom/path` and runs `gemiterm status`
152
- - **THEN** the installed binary reads from `/custom/path` (overriding the platform default), as verified by `src/infrastructure/path-utils.ts:8-18`
153
-
154
- ### Requirement: v1.4.1 Config Dir Is Migrated Forward On First v2.0.0 Install
155
- The system MUST detect a v1.4.1 config dir at `~/.config/gemiterm/` (the path v1.4.1 used on every platform; see `src/gemiterm/config.py:14` in the v1.4.1 reference at `C:\dev\projects\github\webgemini-cli`) and, on the first v2.0.0 install where the v2.0.0 target dir does not yet exist, MUST copy the v1.4.1 tree to the v2.0.0 location. The Windows target is `%APPDATA%\gemiterm\`; the POSIX target is `~/gemiterm/`. The v1.4.1 source dir MUST be left in place as a safety net. The copy is a one-time operation: if the v2.0.0 target dir already exists (e.g. from a prior install or manual setup), the copy MUST be skipped.
156
-
157
- #### Scenario: Windows user with v1.4.1 Python config upgrades to v2.0.0
158
- - **WHEN** a user with `~/.config/gemiterm/profiles/work/storage_state.json` and `%APPDATA%\gemiterm\` either missing or empty runs `install.ps1`
159
- - **THEN** the installer copies the contents of `~/.config/gemiterm/` to `%APPDATA%\gemiterm\` (preserving `profiles/`, the `.default` marker, and any `storage_state.json` files byte-for-byte), prints `"v1.4.1 config copied to %APPDATA%\gemiterm\. The original at ... is left in place as a backup."`, and `gemiterm status` after install lists the same profile names that existed in the v1.4.1 dir
160
-
161
- #### Scenario: POSIX user with v1.4.1 Python config upgrades to v2.0.0
162
- - **WHEN** a user with `~/.config/gemiterm/profiles/work/storage_state.json` and `~/gemiterm/` either missing or empty runs `install.sh`
163
- - **THEN** the installer copies the contents of `~/.config/gemiterm/` to `~/gemiterm/`, prints the equivalent copy message, and `gemiterm status` lists the same profile names as the v1.4.1 dir
164
-
165
- #### Scenario: v2.0.0 target dir already exists — copy is skipped
166
- - **WHEN** a user with `%APPDATA%\gemiterm\profiles\work\storage_state.json` (v2.0.0) and `~/.config/gemiterm\profiles\old\storage_state.json` (v1.4.1) runs `install.ps1`
167
- - **THEN** the installer prints no copy message, `%APPDATA%\gemiterm\profiles\work\storage_state.json` is byte-identical to its pre-install hash, and `gemiterm status` lists `work` (not `old`) as the active profile
168
-
169
- #### Scenario: Neither v1.4.1 nor v2.0.0 config exists — installer proceeds without copy
170
- - **WHEN** a fresh-install user has neither `~/.config/gemiterm/` nor `%APPDATA%\gemiterm\` (or `~/gemiterm/`) and runs the installer
171
- - **THEN** the installer does not print a copy message and does not create a config dir; the config dir is created lazily on the first `gemiterm auth` / `gemiterm list` invocation
172
-
173
- ### Requirement: Installer Prompts For Package-Manager Install When Bun Or Npm Is On PATH
174
- The system MUST detect whether `bun` or `npm` is on `PATH` at install time. If either is present and stdin is a TTY, the installer MUST prompt the user with `"It is recommended to install via bun or npm package manager. Are you sure you want to continue with binary install? [y/N]"` and MUST default to N. The user MUST type `y` or `yes` to proceed with the binary install. If the user declines, the installer MUST print `"Aborted. Install via: bun i -g gemiterm"` (or `npm i -g gemiterm`, whichever is on PATH) and exit 0. The prompt MUST be suppressed when stdin is not a TTY (e.g. the `irm | iex` and `curl | bash` one-liner flows), so unattended installs proceed without blocking.
175
-
176
- #### Scenario: User with bun on PATH answers the prompt with y
177
- - **WHEN** `install.sh` runs in a TTY, `bun` is on PATH, and the user types `y` at the prompt
178
- - **THEN** the installer proceeds with the binary download, install-browser, and PATH-augmentation steps, and exits 0
179
-
180
- #### Scenario: User with npm on PATH answers the prompt with N (default)
181
- - **WHEN** `install.ps1` runs in a TTY, `npm` is on PATH, and the user presses Enter (or types anything other than `y`/`yes`) at the prompt
182
- - **THEN** the installer prints `"Aborted. Install via: npm i -g gemiterm"` and exits 0; no binary is downloaded and no `PATH` entry is added
183
-
184
- #### Scenario: Unattended one-liner install with npm on PATH skips the prompt
185
- - **WHEN** a user runs `irm ... | iex` or `curl -fsSL ... | bash` (stdin is not a TTY) and `bun`/`npm` is on PATH
186
- - **THEN** the installer does not prompt and proceeds with the binary install
187
-
188
- #### Scenario: Neither bun nor npm is on PATH — no prompt
189
- - **WHEN** the installer runs and neither `bun` nor `npm` is on PATH
190
- - **THEN** the installer does not print the prompt and proceeds with the binary install (the package-manager install is not an option for this user)
191
-
192
- ### Requirement: Installer Scripts Are Syntax-Validated In CI
193
- The system MUST provide a CI gate that verifies both `install.ps1` and `install.sh` parse without syntax errors. The gate MUST be a single test file at `tests/integration/installer-script-shape.test.ts` that invokes the PowerShell parser API and `bash -n` on the scripts and fails the CI run if either reports an error.
194
-
195
- #### Scenario: PowerShell parser accepts install.ps1
196
- - **WHEN** CI runs `pwsh -NoProfile -Command "& { [System.Management.Automation.Language.Parser]::ParseFile('install.ps1', [ref]$null, [ref]$null) }"`
197
- - **THEN** the command exits 0 and reports no parse errors
198
-
199
- #### Scenario: Bash parser accepts install.sh
200
- - **WHEN** CI runs `bash -n install.sh`
201
- - **THEN** the command exits 0 and reports no syntax errors
202
-
203
- #### Scenario: A malformed install script fails the shape test
204
- - **WHEN** `install.sh` is edited to contain a syntax error (e.g. an unclosed brace)
205
- - **THEN** `tests/integration/installer-script-shape.test.ts` fails in CI, blocking the merge; the failure message includes the `bash` error output for debugging
@@ -1,97 +0,0 @@
1
- ## 1. Design data paths and conventions
2
-
3
- - [x] 1.1 Document the install path, config path, and Chromium cache path for each platform in `design.md` (already done) and re-verify against `src/infrastructure/path-utils.ts:8-18` that the v2.0.0 binary reads from the same `%APPDATA%\gemiterm\` (Windows) and `~/.config/gemiterm/` (POSIX) that v1.4.1 wrote to. The install path on Windows is `$env:LOCALAPPDATA\GemiTerm\` (capital G) — different from the config path on purpose, matching the v1.4.1 contract.
4
- - [x] 1.2 Write down the migration promise in a one-paragraph comment block at the top of both `install.ps1` and `install.sh` (to be added in tasks 2 and 3): "v1.4.1 → v2.0.0 upgrades replace the binary in place; the config dir at `%APPDATA%\gemiterm\` (Windows) or `~/.config/gemiterm/` (POSIX) is NEVER deleted by this installer." This is the single most important contract for users coming from v1.4.1.
5
- - [x] 1.3 Confirm the `GEMITERM_CONFIG_DIR` env-var override is honored by the installed binary by re-reading `src/infrastructure/path-utils.ts:9-10`. The install scripts MUST NOT clobber this env var; the install-browser step inherits it from the parent shell.
6
-
7
- ## 2. Write install.ps1 (Windows)
8
-
9
- - [x] 2.1 Create `install.ps1` at the repo root with a header comment that lists usage, parameters (`-Tag`, `-Uninstall`, `-InstallDir`), and the data-paths contract (binary at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe`, config at `%APPDATA%\gemiterm\`). Set `$ErrorActionPreference = 'Stop'` at the top so any unhandled error exits non-zero with a clear message.
10
- - [x] 2.2 Implement `--uninstall` as the first branch. The uninstall function MUST: delete `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` if present, remove `$env:LOCALAPPDATA\GemiTerm` from the persistent user `PATH` (use `[Environment]::GetEnvironmentVariable('Path', 'User')` and case-insensitive substring match, then `SetEnvironmentVariable` with the cleaned value), update the current session's `$env:Path`, and exit 0. The function MUST NOT touch `%APPDATA%\gemiterm\`. Reference the v1.4.1 `install.ps1` at `C:\dev\projects\github\webgemini-cli\install.ps1:6-23` for the function structure.
11
- - [x] 2.3 Implement the upgrade-in-place detection: if `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` already exists, print `"Detected existing install at <path>; upgrading in place."` and proceed to download (the next step will overwrite the binary). Do NOT delete the install dir; do NOT touch `%APPDATA%\gemiterm\`. Reference the v1.4.1 script's lack of an "uninstall before install" step at `C:\dev\projects\github\webgemini-cli\install.ps1:29-31` for the "create dir if missing, then download" pattern.
12
- - [x] 2.4 Implement the release resolution. Accept a `-Tag <string>` parameter (default `"latest"`). Build the GitHub API URL: if `$Tag -eq 'latest'`, use `https://api.github.com/repos/expert-vision-software/GemiTerm/releases/latest`; otherwise use `https://api.github.com/repos/expert-vision-software/GemiTerm/releases/tags/$Tag`. Use `Invoke-RestMethod` with a reasonable timeout and `-ErrorAction Stop`. If the response is null or has no `assets`, print the build-from-source fallback (next task) and exit 1. Use TLS only (no `http://` fallback).
13
- - [x] 2.5 Implement the asset download. Find the asset named exactly `GemiTerm.exe` in the response's `assets` array (case-insensitive match on `name` is acceptable; do not match on partial names). Download to a temp file (`$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe.new` or `%TEMP%\GemiTerm.exe`), then `Move-Item -Force` to `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` to replace the existing binary atomically. On any failure during download, delete the temp file, print a clear error, and exit 1 — do NOT replace the existing binary.
14
- - [x] 2.6 Implement the browser install step. First check if `bun` is in PATH (via `Get-Command bun -ErrorAction SilentlyContinue`); if not, bootstrap Bun via `irm https://bun.sh/install.ps1 | iex`. Then run `bunx @playwright/cli install chromium`. Capture the exit code; on non-zero, print `"Chromium installation failed. Re-run the installer after fixing the issue, or run 'bunx @playwright/cli install chromium' manually."` and exit 1.
15
- - [x] 2.7 Implement the Chromium verification. Use `Get-ChildItem "$env:LOCALAPPDATA\ms-playwright\chromium-*\chrome.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1`. If the result is null, print the same "Chromium installation verification failed" message and exit 1. Match the v1.4.1 installer's glob at `C:\dev\projects\github\webgemini-cli\install.ps1:58-65`.
16
- - [x] 2.8 Implement the idempotent `PATH` augmentation. Read the user `PATH` via `[Environment]::GetEnvironmentVariable('Path', 'User')`. Check whether the install dir is already present using a case-insensitive substring match (Windows paths are case-insensitive; on Windows, normalize both sides to lowercase before comparing, or use `-notlike` with a lowercase pattern). If not present, append `$installDir` separated by `;` and write back via `SetEnvironmentVariable('Path', ..., 'User')`. Also update the current session's `$env:Path` so the user can run `gemiterm` in the same shell.
17
- - [x] 2.9 Print the final success message: `"GemiTerm v<X.Y.Z> installed to <path>. Run 'gemiterm status' to verify, then 'gemiterm auth' to authenticate."` Match the tone of the v1.4.1 installer's final message at `C:\dev\projects\github\webgemini-cli\install.ps1:73-74`.
18
- - [x] 2.10 Add a network-failure fallback path: if the `Invoke-RestMethod` to `api.github.com` throws a `WebException` (HTTP error or DNS failure), catch it and print `"Cannot reach GitHub releases. Check your network connection or use the 'build from source' instructions in docs/INSTALL.md."` then exit 1. Do NOT delete the existing binary (if upgrading). Do NOT point the user at `pip` (that was the v1.4.1 fallback and does not apply to the Bun-built v2.0.0 binary).
19
-
20
- ## 3. Write install.sh (Linux/WSL)
21
-
22
- - [x] 3.1 Create `install.sh` at the repo root with a header comment that lists usage, env-var inputs (`GEMITERM_TAG`, `GEMITERM_INSTALL_DIR`), and the data-paths contract. Start with `set -euo pipefail` so any unhandled error exits non-zero. Make the script `chmod +x`-friendly (no Windows line endings; the file is created on a Unix-friendly host or via a Git-aware tool).
23
- - [x] 3.2 Implement the configuration block at the top: `REPO="expert-vision-software/GemiTerm"`, `INSTALL_DIR="${GEMITERM_INSTALL_DIR:-$HOME/.local/bin}"`, `BIN_PATH="$INSTALL_DIR/gemiterm"`, `CONFIG_DIR="$HOME/.config/gemiterm"`, `ENV_SNIPPET="$CONFIG_DIR/env.sh"`, `CHROMIUM_GLOB="$HOME/.cache/ms-playwright/chromium-*/chrome-linux/chrome"`, `TAG="${GEMITERM_TAG:-latest}"`. Default the `GEMITERM_TAG` to `latest` exactly as the PowerShell installer defaults the `-Tag` parameter to `latest`.
24
- - [x] 3.3 Implement `--uninstall` as the first branch (check `"$1" = "--uninstall"`). Delete `$BIN_PATH` if present, delete `$ENV_SNIPPET` if present, and remove the `source` line from `~/.bashrc` and `~/.zshrc` (use `grep -v` and a temp-file `mv`; the line is `[[ -f "$ENV_SNIPPET" ]] && source "$ENV_SNIPPET"` or the equivalent). Do NOT delete `$CONFIG_DIR` or anything under `$CONFIG_DIR/profiles/`. Print a success message and exit 0.
25
- - [x] 3.4 Implement the upgrade-in-place detection: if `[[ -x "$BIN_PATH" ]]`, print `"Detected existing install at <path>; upgrading in place."` and proceed. Do NOT delete `$BIN_PATH` first; the next step overwrites it atomically. Do NOT touch `$CONFIG_DIR`.
26
- - [x] 3.5 Implement the release resolution. Use `curl -fsSL` to fetch `https://api.github.com/repos/$REPO/releases/latest` (default) or `https://api.github.com/repos/$REPO/releases/tags/$TAG` (when `GEMITERM_TAG` is set). Pipe the response through `grep '"browser_download_url"'` and `grep '"GemiTerm"'` to extract the asset URL for the `GemiTerm` (no extension) asset. If no matching URL is found, print the build-from-source fallback and exit 1.
27
- - [x] 3.6 Implement the asset download. `mkdir -p "$INSTALL_DIR"`. Download to a temp file (`"$BIN_PATH.new"`), then `mv` it to `"$BIN_PATH"` to overwrite atomically. Use `curl -fSL -o "$BIN_PATH.new" "$ASSET_URL"` and `chmod +x "$BIN_PATH"`. On any failure, `rm -f "$BIN_PATH.new"`, print a clear error, and exit 1 — do NOT replace the existing binary.
28
- - [x] 3.7 Implement the browser install step. First check if `bun` is in PATH (via `command -v bun`); if not, bootstrap Bun via `curl -fsSL https://bun.sh/install | bash`. Then run `bunx @playwright/cli install chromium`. Capture the exit code; on non-zero, print the same "Chromium installation failed" message as the PowerShell installer and exit 1.
29
- - [x] 3.8 Implement the Chromium verification. Use a `find "$HOME/.cache/ms-playwright/chromium-*" -name chrome -type f -executable 2>/dev/null | head -n 1` (or an equivalent glob/loop). If empty, print the verification-failed message and exit 1.
30
- - [x] 3.9 Implement the idempotent `PATH` augmentation. Write `mkdir -p "$CONFIG_DIR"; cat > "$ENV_SNIPPET" <<'EOF'` with a body of `export PATH="$HOME/.local/bin:$PATH"` (so the snippet is one line). Then check `~/.bashrc` for the `source` line (case-insensitive substring check); if not present, append it. Repeat for `~/.zshrc` if that file exists. Use a `grep -qiF` check to avoid duplicates. The current shell's `PATH` is updated by having the installer `export PATH="$INSTALL_DIR:$PATH"` at the end (so the user's running shell picks it up; the persistent change is the `~/.bashrc` snippet).
31
- - [x] 3.10 Print the final success message matching the PowerShell installer's tone: `"GemiTerm v<X.Y.Z> installed to <path>. Run 'gemiterm status' to verify, then 'gemiterm auth' to authenticate. Restart your shell (or 'source ~/.config/gemiterm/env.sh') to pick up the new PATH."`
32
- - [x] 3.11 Add the network-failure fallback: wrap the `curl` call to the GitHub API in a check (e.g. `if ! curl -fsSI -o /dev/null "$API_URL"; then ...`). On failure, print `"Cannot reach GitHub releases. Check your network connection or use the 'build from source' instructions in docs/INSTALL.md."` and exit 1.
33
-
34
- ## 4. Write docs/INSTALL.md
35
-
36
- - [x] 4.1 Create `docs/INSTALL.md` with sections: "Install" (one-liners for both platforms), "Upgrade from v1.4.1" (preservation guarantee), "Uninstall", "Data paths" (binary vs config vs Chromium per platform), "Tag override (canary/R installs)", "Troubleshooting" (PATH not refreshed, Chromium install fails, network unreachable), and "Build from source" (the `git clone && bun install && bun run build` recipe).
37
- - [x] 4.2 The "Install" section MUST show the canonical one-liner for each platform at the top: `irm https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.ps1 | iex` for Windows and `curl -fsSL https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.sh | bash` for Linux/WSL. Both one-liners MUST be the very first code block in the file.
38
- - [x] 4.3 The "Upgrade from v1.4.1" section MUST call out explicitly: "Your profiles, cookies, and default-profile marker are preserved. The installer replaces the binary in place; it does NOT touch `%APPDATA%\gemiterm\` (Windows) or `~/.config/gemiterm/` (POSIX)." Reference `src/infrastructure/path-utils.ts:8-18` as the proof.
39
- - [x] 4.4 The "Data paths" section MUST document the three distinct paths per platform: the binary install dir, the config dir, and the Chromium cache dir. Use a table for clarity. Call out the Windows quirk that the install dir is **capital G** (`GemiTerm`) but the config dir is **lowercase** (`gemiterm`).
40
- - [x] 4.5 The "Troubleshooting" section MUST include the "PATH did not refresh in current shell" entry for both platforms: PowerShell users run `$env:Path = [Environment]::GetEnvironmentVariable('Path', 'User'); gemiterm --version` or open a new terminal; bash users run `source ~/.config/gemiterm/env.sh` or open a new terminal. Also include "Cannot reach GitHub releases" pointing to the "Build from source" section.
41
- - [x] 4.6 The "Build from source" section MUST give the `git clone`, `bun install`, `bun run build` recipe and note that the resulting `dist/gemiterm` (or `dist/gemiterm.exe`) can be moved manually to the install path. State the minimum Bun version as 1.3.13 (matches `cross-platform-build-and-ci/design.md:181`).
42
-
43
- ## 5. Update README.md
44
-
45
- - [x] 5.1 Replace the "Quick Start" section's "bun run src/cli/index.ts auth" line with a 1-line "see `docs/INSTALL.md` for installation" pointer followed by the canonical one-liners (Windows + Linux/WSL). Keep the "Development" section's "bun install" + "bun run dev" workflow unchanged — the new install section is for **end users**, not contributors.
46
- - [x] 5.2 Add a callout block immediately under the new install section: `> **Upgrading from v1.4.1?** Your profiles, cookies, and default profile marker are preserved. The installer replaces the binary in place; it does NOT touch your config dir. See the "Upgrade from v1.4.1" section in docs/INSTALL.md for details.` Make the callout visually distinct (a `>` blockquote is the standard GitHub-flavored markdown convention).
47
- - [x] 5.3 Update the "Configuration" section's "Default locations" list to clarify: the binary installs to `$env:LOCALAPPDATA\GemiTerm\` (Windows) or `~/.local/bin/gemiterm` (POSIX), but the **config** directory is `%APPDATA%\gemiterm\` (Windows) or `~/.config/gemiterm/` (POSIX). The two are different and both are correct.
48
-
49
- ## 6. Add tests/integration/installer-script-shape.test.ts
50
-
51
- - [x] 6.1 Create `tests/integration/installer-script-shape.test.ts` that exports a `bun:test` `describe` / `test` block. The test reads the repo-root `install.ps1` and `install.sh` files (use `Bun.file('install.ps1')` and `Bun.file('install.sh')` — Bun's runtime gives synchronous reads), then spawns the appropriate parser as a child process.
52
- - [x] 6.2 The `install.ps1` test MUST spawn `pwsh -NoProfile -Command "& { [System.Management.Automation.Language.Parser]::ParseFile('install.ps1', [ref]\$null, [ref]\$null) }"` and assert the process exits 0. Skip the test (with `test.skip`) if `pwsh` is not on `PATH` (so the test does not fail on Linux CI runners that lack PowerShell). Log a clear message when skipping.
53
- - [x] 6.3 The `install.sh` test MUST spawn `bash -n install.sh` and assert the process exits 0. Skip if `bash` is not on `PATH` (it always is on Linux/macOS; the skip is defensive).
54
- - [x] 6.4 Add a third test that asserts the header comment is present in both files (a `grep`-style substring check for "v1.4.1" or "in place" — the migration promise from task 1.2). This is a guard against accidentally removing the comment block in a future refactor.
55
- - [x] 6.5 Run `bun test tests/integration/installer-script-shape.test.ts` locally and confirm all tests pass (or skip with a clear message on Windows when `pwsh` resolves to a different binary). Add the new test to any existing test-discovery config if needed (check `bunfig.toml` and the `package.json` test script).
56
- - [x] 6.6 Run the full `bun test` and confirm the 432/432 baseline is maintained. The new file adds at most 3 tests; the baseline moves to 432+3 = 435/435.
57
-
58
- ## 7. Local manual verification
59
-
60
- - [x] 7.1 On the current Windows host, download the v1.4.1 reference binary (or build a stub `GemiTerm.exe` that prints `gemiterm v1.4.1` on `--version`) and place it at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe`. Create a fake profile at `%APPDATA%\gemiterm\profiles\test\storage_state.json` with a known hash. This is the "v1.4.1 installed" baseline.
61
- - [x] 7.2 Run `pwsh -File install.ps1` against a v2.0.0-rc.1 release. Confirm: the binary at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe` is replaced; the message "Detected existing install at …; upgrading in place." is printed; `%APPDATA%\gemiterm\profiles\test\storage_state.json` is byte-identical to the pre-install hash; `$env:LOCALAPPDATA\GemiTerm` is in the user `PATH`; `gemiterm --version` reports `gemiterm v2.0.0-rc.1`.
62
- - [x] 7.3 Run `pwsh -File install.ps1 --uninstall`. Confirm: `GemiTerm.exe` is deleted; the user `PATH` no longer contains `$env:LOCALAPPDATA\GemiTerm`; `%APPDATA%\gemiterm\` is **NOT** deleted (the fake profile is still there).
63
- - [x] 7.4 Re-run `pwsh -File install.ps1`. Confirm: the binary is re-installed at `$env:LOCALAPPDATA\GemiTerm\GemiTerm.exe`; `%APPDATA%\gemiterm\profiles\test\` is still present. (If `install-browser` is the long step, time it and verify it does not hang on a network error.)
64
- - [x] 7.5 On WSL (or a Linux VM if WSL is not available), repeat 7.2-7.4 with `install.sh`. The binary goes to `~/.local/bin/gemiterm`; the config dir to `~/.config/gemiterm/`; the env snippet to `~/.config/gemiterm/env.sh`; the `source` line is appended to `~/.bashrc` exactly once.
65
- - [x] 7.6 Run `pwsh -File install.ps1` twice in a row and confirm the user `PATH` contains exactly one entry for `$env:LOCALAPPDATA\GemiTerm` (no duplicates). Run `bash install.sh` twice in a row and confirm `~/.bashrc` has exactly one `source` line for `~/.config/gemiterm/env.sh`.
66
- - [x] 7.7 Verify the `GEMITERM_TAG=v2.0.0-rc.1 bash install.sh` override works (downloads from the rc.1 release, not from `latest`). Verify `-Tag v2.0.0-rc.1` on PowerShell the same way.
67
-
68
- ## 8. Handle Python v1.4.1 uninstall detection
69
-
70
- - [x] 8.1 In `install.ps1`: detect Python v1.4.1 by checking `pip show gemiterm 2>$null` (exit 0 with output = installed) before doing anything else; if detected, print the warning message and exit non-zero without touching the binary or config dir.
71
- - [x] 8.2 In `install.sh`: detect Python v1.4.1 by checking `pip show gemiterm 2>/dev/null` (exit 0 with output = installed) before doing anything else; if detected, print the warning message and exit non-zero without touching the binary or config dir.
72
-
73
- ## 9. Bootstrap Bun in installer
74
-
75
- - [x] 9.1 In `install.ps1`: before running `bunx @playwright/cli install chromium`, check if `Get-Command bun -ErrorAction SilentlyContinue` returns a path; if not, bootstrap via `irm https://bun.sh/install.ps1 | iex`. If bootstrap fails, print the Bun installation failed message and exit non-zero.
76
- - [x] 9.2 In `install.sh`: before running `bunx @playwright/cli install chromium`, check if `command -v bun` returns a path; if not, bootstrap via `curl -fsSL https://bun.sh/install | bash`. If bootstrap fails, print the Bun installation failed message and exit non-zero.
77
- - [x] 9.3 In both scripts: after bootstrapping Bun, verify `bun --version` succeeds before proceeding to the playwright-cli install step.
78
-
79
- ## 10. Cross-change coordination
80
-
81
- - [x] 10.1 Read `openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md:38-42` and confirm the `GitHub Release Published On Version Tags` requirement mandates attaching `install.sh` and `install.ps1` as release assets. If the sibling change has not yet added this, file a follow-up task in the sibling change's `tasks.md` to amend the spec (do not amend the spec from this change).
82
- - [x] 10.2 Confirm the sibling change's `.github/workflows/release.yml` (after the sibling change lands) has a `files:` list that includes `install.sh` and `install.ps1`. If the list is missing either file, file a follow-up task in the sibling change.
83
- - [x] 10.3 Coordinate with the maintainer of the sibling change (or, if solo, with the maintainer's release-runbook) to push a `v2.0.0-rc.1` tag and verify the release page on GitHub has both installer scripts attached. Open `https://github.com/expert-vision-software/GemiTerm/releases/download/v2.0.0-rc.1/install.ps1` in a browser and confirm the file downloads. Delete the tag and the draft release after verification.
84
- - [x] 10.4 Add a note to `docs/INSTALL.md`'s intro: "The installer scripts are attached to every GitHub release by the `release.yml` workflow. If you see a 404 on the one-liner URL, the release pipeline (owned by the `cross-platform-build-and-ci` change) may not have attached the scripts; check the GitHub release page directly."
85
-
86
- ## 11. Amend POSIX config path and add v1.4.1 copy-forward + package-manager prompt
87
-
88
- These tasks **amend** the deliverables of sections 1–7 above to match the
89
- final path contract agreed on 2026-06-09. The original `[x]`'s on tasks
90
- 1.1, 3.2, 3.10, 4.3, 4.5, 5.3, 7.5, 7.6 stand as the audit trail of the
91
- prior implementation; the amendments below are the live contract.
92
-
93
- - [x] 11.1 **v1.4.1 → v2.0.0 config copy-forward.** v1.4.1 wrote config to `~/.config/gemiterm/` on every platform (Python `config.py:14` has no Windows branch; it always returns `Path.home() / ".config/gemiterm"`). v2.0.0 reads from `%APPDATA%\gemiterm\` (Windows) and `~/gemiterm/` (POSIX). These are different directories. The installer MUST detect a v1.4.1 config dir at the old path and, if the v2.0.0 dir does not yet exist, copy the tree forward automatically. The v1.4.1 directory is left in place as a safety net; the user can `rm -rf` it after verifying the migration. Implementation: `install.ps1` block at the upgrade-detection step (before download); `install.sh` block at the upgrade-detection step. Documented in `docs/INSTALL.md` "Upgrade from v1.4.1" section.
94
- - [x] 11.2 **Package-manager install prompt.** Both installers MUST detect `bun` or `npm` on `PATH` and, when stdin is a TTY, prompt the user: "It is recommended to install via bun or npm package manager. Are you sure you want to continue with binary install? [y/N]". Default is N; the user must type `y`/`yes` to proceed. The prompt is suppressed when stdin is not a TTY (the `irm | iex` / `curl | bash` one-liners are unattended and must not block). If the user declines, the installer prints `Aborted. Install via: bun i -g gemiterm` (or `npm i -g gemiterm`) and exits 0. Implementation: `install.ps1` line 92-107; `install.sh` line 50-71.
95
- - [x] 11.3 **POSIX config path is `~/gemiterm/`, not `~/.config/gemiterm/`.** `src/infrastructure/path-utils.ts:18` returns `join(homedir(), "gemiterm")`. `tests/unit/path-utils.test.ts` lines 97, 104, 111 updated. `install.sh` `CONFIG_DIR` is `$HOME/gemiterm`. `docs/INSTALL.md` and `README.md` updated everywhere the POSIX path is shown. The OpenSpec committed specs at `openspec/specs/path-utils/spec.md` and `openspec/specs/configuration/spec.md` are updated to match. `AGENTS.md` updated. The v2-install-migration `design.md` is amended: the D3 "Shell snippet at `~/.config/gemiterm/env.sh`" section is renamed to "Shell snippet at `~/gemiterm/env.sh`"; all POSIX config-path references in the design and in `specs/v2-installer/spec.md` are updated.
96
- - [x] 11.4 **Path contract clarification: Windows install dir remains capital G.** Per the user's 2026-06-09 confirmation, the Windows install path is `$env:LOCALAPPDATA\GemiTerm\` (capital G, matching v1.4.1) and the Windows config path is `%APPDATA%\gemiterm\` (lowercase). The two are different and both are intentional. This was already the prior contract; task 11.4 confirms it stands and re-emphasizes it in the design.
97
-
@@ -1,2 +0,0 @@
1
- schema: spec-driven
2
- created: 2026-06-09
@@ -1,107 +0,0 @@
1
- ## Context
2
-
3
- The current top-level argv parser is a 60-line hand-rolled function in `src/cli/index.ts:39-60` called `parseGlobalFlags`. It walks `process.argv.slice(2)`, picks out `--verbose` / `-v`, `--version`, `--help` / `-h`, and returns `{ flags, remaining }`. The first non-flag token in `remaining` is the subcommand; the rest become `subcommandArgs` and are passed to `CommandRegistry.getHandler(subcommand).execute(args, context)`.
4
-
5
- This change activates `commander@^15.0.0` (already in `package.json`, never imported) and replaces the hand-rolled parser with a thin wrapper around commander that preserves the same observable behavior. Per the proposal, no subcommand-level parsing is touched: each `CliCommand.execute(args, context)` continues to receive a `string[]` and parses its own flags.
6
-
7
- A previous design round considered rolling our own `renderTable()` helper for tables (commit `e974923`) without adding a dep. The tables work adopted `cli-table3` via an `src/infrastructure/cli-table.ts` wrapper. This change follows the same encapsulation pattern for `commander` via `src/infrastructure/cli-parser.ts`.
8
-
9
- ## Goals / Non-Goals
10
-
11
- **Goals:**
12
-
13
- - Replace the 60-line hand-rolled `parseGlobalFlags` in `src/cli/index.ts` with a call to a new `parseGlobalArgs` in `src/infrastructure/cli-parser.ts` that wraps `commander`.
14
- - Preserve every observable behavior of the current parser: recognized flags, exit codes, error messages, and the "no args → show help" path. The existing test suite must pass without modification.
15
- - Encapsulate `commander` so it is imported by exactly one file. The rest of the codebase never sees `import { Command } from "commander"`.
16
- - Make it trivial to add a future global flag (e.g. `--profile`, `--config-dir`, `--json`) by editing only `src/infrastructure/cli-parser.ts` and adding a one-liner to the commander `.option()` chain.
17
-
18
- **Non-Goals:**
19
-
20
- - Refactoring the 11 subcommand argument parsers in `src/cli/commands/*.ts` to also use commander. Each subcommand has its own bespoke parsing (e.g. `ListCommand.parseArgs`) that would require per-command migration. This is deferred to a future change.
21
- - Changing the `CliCommand.execute(args, context)` contract.
22
- - Changing the `CommandRegistry` interface.
23
- - Removing `commander` from `package.json` (the opposite: this change activates it). The `cross-platform-build-and-ci` change's task 14.3 ("remove unused commander dep") is superseded by this change.
24
- - Adding new global flags. The wrapper exposes `--verbose / -v`, `--version`, `--help / -h` only — the same set the hand-rolled parser supported.
25
-
26
- ## Decisions
27
-
28
- ### Decision 1: Wrap commander, don't replace the public surface
29
-
30
- `src/infrastructure/cli-parser.ts` exports a single function:
31
-
32
- ```ts
33
- export interface ParsedArgs {
34
- flags: { verbose: boolean; version: boolean; help: boolean };
35
- subcommand: string | null;
36
- subcommandArgs: string[];
37
- }
38
-
39
- export function parseGlobalArgs(argv: string[]): ParsedArgs;
40
- ```
41
-
42
- `src/cli/index.ts` calls `parseGlobalArgs(process.argv.slice(2))` and uses the returned fields. The shape matches the existing `{ flags, subcommand, subcommandArgs }` that `main()` already destructures (lines 156-181 of the current file), so the consumer side needs only a rename of the local variable.
43
-
44
- **Rationale:** Mirrors the `renderTable()` wrapper pattern from the tables refactor (commit `e974923`, `src/infrastructure/cli-table.ts`). The wrapper file owns the third-party import; callers see a stable, project-shaped API. If commander is later replaced with `yargs` or `cac`, the change is a one-file edit.
45
-
46
- **Alternatives considered:**
47
-
48
- - *(A) Let subcommands import commander directly.* Rejected: spreads the third-party import across 11+ files, violating the "wrap at the infrastructure layer" pattern.
49
- - *(B) Define a full `CliArgs` builder DSL.* Rejected: overkill for three flags. A 30-line wrapper is the right size.
50
- - *(C) Migrate each subcommand's flag parser to commander in the same change.* Rejected: this is a 500-1000 line refactor across 11 files, with a much higher test surface. Out of scope per the proposal.
51
-
52
- ### Decision 2: Subcommand dispatch stays in `index.ts`
53
-
54
- `parseGlobalArgs` does not know which subcommands exist. It returns the raw subcommand string + args; `CommandRegistry.getHandler(subcommand)` is still called from `main()`. Commander's `.command()` API is not used.
55
-
56
- **Rationale:** The existing `CommandRegistry` is the source of truth for registered subcommands. Routing commander through it would either require duplicating the registry into commander (and keeping them in sync) or making commander call into the registry (an awkward inversion). The simpler approach: commander parses the global flags, hand-off to the registry unchanged.
57
-
58
- **Alternatives considered:**
59
-
60
- - *(A) Use `program.command("auth").action(handler)`. Rejected: each handler is currently a class that takes constructor args from the registry. The action callback would lose the `CliCommandContext` (mediator, profileAuthManager, verbose). Either we thread that context through commander (extra plumbing) or we drop the registry (large refactor). Neither is justified.
61
- - *(B) `program.allowUnknownOption(true)` and let the registry handle dispatch. This is what we'll actually do: commander parses only the three recognized flags, everything else becomes `subcommandArgs`.
62
-
63
- ### Decision 3: Help and version output stays project-shaped
64
-
65
- Commander's built-in `--help` and `--version` formatting is bypassed. The wrapper exposes a `printHelp(registry)` and `printVersion(pkgVersion)` helper that delegates to the existing `showHelp(registry)` and the existing `console.log("gemiterm v" + pkg.version)` paths.
66
-
67
- **Rationale:** The existing help screen (from `src/cli/commands/help.ts`) is project-styled and lists subcommands with descriptions. Commander's default help would replace it with a generic, less informative screen. Same for version: existing output is `gemiterm v2.0.0`; commander's default would print a different format.
68
-
69
- **Implementation:** The wrapper calls `program.exitOverride()` so commander never calls `process.exit` itself. The wrapper inspects the parsed result and calls the project's helpers when `--help` or `--version` is detected.
70
-
71
- **Alternatives considered:**
72
-
73
- - *(A) Reformat `showHelp(registry)` into commander's `.addHelpText()` API.* Rejected: doesn't save code (same content, different shape) and the project uses the existing format in the smoke test.
74
- - *(B) Print commander's default help. Rejected: worse UX.
75
-
76
- ### Decision 4: Errors are handled, not thrown
77
-
78
- When the user passes an unknown global flag (e.g. `--bogus`), commander throws a `CommanderError`. The wrapper catches `CommanderError` and re-formats the message to match the existing "Unknown command" / suggestion path style. The process exits with code 1 (matching the existing `process.exit(1)` at `index.ts:194`).
79
-
80
- **Rationale:** Preserves the existing error UX (`Unknown command: 'foo' / Did you mean one of: ...`) and the existing exit-code contract.
81
-
82
- ### Decision 5: No new tests, reuse existing
83
-
84
- The proposal promises "existing test suite must pass without modification". The smoke tests in `tests/smoke/smoke.test.ts` exercise `--help`, `--version`, and `status` end-to-end. The `tests/cli/*.test.ts` files exercise subcommand dispatch. The unit tests for `parseGlobalFlags` (if any exist) need to be migrated to test `parseGlobalArgs` instead.
85
-
86
- A new `tests/infrastructure/cli-parser.test.ts` adds 5-8 focused unit tests for the wrapper to lock in the contract: known flags, unknown flags, no-args, help short alias, version short alias, subcommand passthrough, subcommand-args passthrough. This is the only new test file.
87
-
88
- ## Risks / Trade-offs
89
-
90
- - **[Risk] Commander's default help text leaks through if a future flag is added incorrectly.** → Mitigation: the wrapper does not call `program.helpInformation()` or `program.outputHelp()`. All help output is routed through the project's `showHelp(registry)`.
91
- - **[Risk] Commander's exit codes differ from the existing code for the same error.** → Mitigation: the wrapper catches `CommanderError` and re-emits the existing error format. The smoke tests assert exit code 0 for `--help` and `--version`; we route through the same code path that previously set those codes.
92
- - **[Risk] Test for unknown-command message format breaks because commander rewords the suggestion.** → Mitigation: commander is not used for subcommand lookup. The "Did you mean one of:" path is unchanged in `index.ts:184-194`. Commander is only responsible for flag parsing; it never sees a subcommand string.
93
- - **[Risk] `commander@^15.0.0` ESM/CJS interop issue with Bun.** → Mitigation: commander 15 is published as ESM with `exports` map. Bun has first-class ESM support. If an issue is found, fall back to `commander@^12.x` (the last CJS line).
94
- - **[Risk] Adding this change re-enables `commander` in `package.json` while the queued `cross-platform-build-and-ci` change has a task to *remove* it as cleanup.** → Mitigation: the apply pass for this change should land first; then the CI change's task 14.3 needs to be edited to remove the cleanup task, or rebased on top of this change.
95
-
96
- ## Migration Plan
97
-
98
- 1. Apply this change in a single commit on `gemiterm-bun-rewrite`.
99
- 2. After the change lands, edit `openspec/changes/cross-platform-build-and-ci/tasks.md` task 14.3 to mark it as superseded (or delete it). The cleanup PR for the CI change should be rebased onto this commit before merging.
100
- 3. No data migration. No user-visible change.
101
-
102
- Rollback: `git revert <commit>`; the previous hand-rolled parser is still in the commit history.
103
-
104
- ## Open Questions
105
-
106
- - Should we expose `--no-color` as a global flag in the same change, given that `chalk` is now in use? **Recommendation: defer to a future change.** It is not in scope of the current proposal and would require touching every subcommand's output path. Not blocking this change.
107
- - Should we add `--json` as a global flag for machine-readable output? **Recommendation: defer.** The per-subcommand `--format json` is already supported where needed (e.g. `list --format json`).
@@ -1,31 +0,0 @@
1
- ## Why
2
-
3
- The top-level argv parser in `src/cli/index.ts` is a hand-rolled `parseGlobalFlags` function (60 lines) that recognizes only `--verbose/-v`, `--version`, and `--help/-h` and splits the rest into `remaining` for the `CommandRegistry`. This works but is the kind of boilerplate that a well-known library (already declared in `package.json` as `commander@^15.0.0` but never imported) eliminates cleanly. Replacing the hand-rolled parser with commander, encapsulated behind a `src/infrastructure/cli-parser.ts` wrapper, gives us the existing behavior plus battle-tested flag composition, future `--profile` / `--config-dir` / `--json` global flags without code growth, and consistent error output for free.
4
-
5
- This change was originally bundled with the table-rendering refactor that landed in commit `e974923`; it was deferred to a separate change because it touches every subcommand's exit-path and is a much larger scope than table rendering.
6
-
7
- ## What Changes
8
-
9
- - Add `commander@^15.0.0` as a real runtime dependency in `package.json` (already declared, currently unused). Reverse the cleanup task in the queued `cross-platform-build-and-ci` change that planned to remove it.
10
- - Create `src/infrastructure/cli-parser.ts` that wraps `commander` behind a `parseGlobalArgs(argv): { flags, subcommand, subcommandArgs }` function. `commander` is imported only by this file.
11
- - Replace the hand-rolled `parseGlobalFlags` in `src/cli/index.ts` with `parseGlobalArgs`. The hand-rolled function is deleted.
12
- - Keep the `CommandRegistry` and the per-subcommand `execute(args, context)` contract unchanged. Subcommands continue to parse their own flags from the `args` string array they receive. **No changes** to `src/cli/commands/*.ts` are required for this change.
13
- - No visual or behavioral change to `--help` / `--version` / `--verbose` output. The existing test suite must continue to pass without modification of the formatters/CLI-output tests.
14
- - Update the `cli` capability's "Global Flags Parsing" requirement to describe the commander-backed interface, with the same observable behavior preserved.
15
-
16
- ## Capabilities
17
-
18
- ### New Capabilities
19
-
20
- None. The change fits into the existing `cli` capability.
21
-
22
- ### Modified Capabilities
23
-
24
- - `cli`: The "Global Flags Parsing" requirement is updated to specify that the parser is backed by the `commander` library (encapsulated in `src/infrastructure/cli-parser.ts`). The observable behavior (recognized flags, exit codes, error messages) is preserved, so the existing scenarios remain valid. The new requirement additionally documents that future global flags are added via commander in the same wrapper, not via the hand-rolled parser.
25
-
26
- ## Impact
27
-
28
- - **Code:** `src/cli/index.ts` (replace `parseGlobalFlags`, ~50 lines deleted), new `src/infrastructure/cli-parser.ts` (~30 lines).
29
- - **Dependencies:** `commander@^15.0.0` is already in `package.json`; this change activates it. No new dep is added.
30
- - **Tests:** Existing CLI tests (smoke, status command, etc.) should pass unchanged. New unit tests for `parseGlobalArgs` in `tests/infrastructure/cli-parser.test.ts`.
31
- - **Cross-cutting:** Task 14.3 in the `cross-platform-build-and-ci` change currently plans to *remove* `commander` from `package.json` as a cleanup step. That task is superseded by this change; the cleanup PR should be updated before the CI work lands, or the cleanup PR can be rebased onto this change first.
@@ -1,48 +0,0 @@
1
- ## MODIFIED Requirements
2
-
3
- ### Requirement: Global Flags Parsing
4
- The system MUST parse global flags from the raw process arguments via a function `parseGlobalArgs(argv: string[])` exported from `src/infrastructure/cli-parser.ts`. The function MUST return an object `{ flags, subcommand, subcommandArgs }` where `flags` is an object with three boolean fields (`verbose`, `version`, `help`) and `subcommand` is the first non-flag token in `argv` (or `null` if none). The parser MUST be implemented on top of the `commander` npm library, which is imported only by `src/infrastructure/cli-parser.ts` and not by any other file in `src/`. The parser MUST recognize `--verbose` and its short alias `-v` as setting `verbose=true`, MUST recognize `--version` as setting `version=true`, and MUST recognize `--help` and its short alias `-h` as setting `help=true`. All non-flag arguments MUST be returned as `subcommandArgs` (the array after the first non-flag token) and the first non-flag token MUST be returned as `subcommand`. The parser MUST NOT call `process.exit` itself; it MUST return a result that the caller can act on. If commander raises a `CommanderError` (e.g. for an unknown option), the wrapper MUST catch it and re-throw a project-shaped `Error` whose message starts with `gemiterm: ` followed by the commander's message, so the caller can format it consistently with the existing "Unknown command" / "Did you mean one of:" output.
5
-
6
- #### Scenario: Verbose flag sets verbose
7
- - **WHEN** the user invokes `gemiterm --verbose list`
8
- - **THEN** `parseGlobalArgs(["--verbose", "list"])` returns `flags.verbose === true`, `subcommand === "list"`, and `subcommandArgs === []`
9
-
10
- #### Scenario: Verbose short alias works
11
- - **WHEN** the user invokes `gemiterm -v list`
12
- - **THEN** `parseGlobalArgs(["-v", "list"])` returns `flags.verbose === true`, `subcommand === "list"`, and `subcommandArgs === []`
13
-
14
- #### Scenario: Version flag sets version
15
- - **WHEN** the user invokes `gemiterm --version`
16
- - **THEN** `parseGlobalArgs(["--version"])` returns `flags.version === true`, `subcommand === null`, and `subcommandArgs === []`
17
-
18
- #### Scenario: Help flag sets help
19
- - **WHEN** the user invokes `gemiterm --help`
20
- - **THEN** `parseGlobalArgs(["--help"])` returns `flags.help === true`, `subcommand === null`, and `subcommandArgs === []`
21
-
22
- #### Scenario: Help short alias works
23
- - **WHEN** the user invokes `gemiterm -h`
24
- - **THEN** `parseGlobalArgs(["-h"])` returns `flags.help === true`, `subcommand === null`, and `subcommandArgs === []`
25
-
26
- #### Scenario: Subcommand args passthrough
27
- - **WHEN** the user invokes `gemiterm list --limit 5 --format json`
28
- - **THEN** `parseGlobalArgs(["list", "--limit", "5", "--format", "json"])` returns `subcommand === "list"` and `subcommandArgs === ["--limit", "5", "--format", "json"]`
29
-
30
- #### Scenario: Unknown option re-thrown as project-shaped error
31
- - **WHEN** the user invokes `gemiterm --bogus`
32
- - **THEN** `parseGlobalArgs` throws an error whose message starts with `gemiterm: ` and the process exits with code 1
33
-
34
- ## ADDED Requirements
35
-
36
- ### Requirement: Commander Library Encapsulation
37
- The `commander` npm package MUST be imported only by `src/infrastructure/cli-parser.ts`. No other file under `src/` MAY import from `commander`. The dependency MUST be listed under `dependencies` (not `devDependencies`) in `package.json` at version `^15.0.0` or compatible.
38
-
39
- #### Scenario: Only one file imports commander
40
- - **WHEN** `src/cli/index.ts` is read
41
- - **THEN** it does not contain `import` or `require` of `commander`
42
-
43
- - **WHEN** `src/cli/commands/*.ts` is read
44
- - **THEN** none of those files contain `import` or `require` of `commander`
45
-
46
- #### Scenario: Commander is a runtime dependency
47
- - **WHEN** `package.json` is read
48
- - **THEN** the `dependencies` object includes a key `"commander"` with value `"^15.0.0"` (or any compatible semver)
@@ -1,33 +0,0 @@
1
- ## 1. Setup
2
-
3
- - [ ] 1.1 Verify `commander@^15.0.0` is in `package.json` dependencies; if not, run `bun add commander@^15.0.0` and commit the `package.json` + `bun.lock` change.
4
- - [ ] 1.2 Confirm the `commander` package is importable from `src/` with `bun -e 'import { Command } from "commander"; console.log(Command.name)'` (smoke check, no code change).
5
-
6
- ## 2. Create the wrapper
7
-
8
- - [ ] 2.1 Create `src/infrastructure/cli-parser.ts` exporting `parseGlobalArgs(argv: string[]): { flags, subcommand, subcommandArgs }` and a `printHelp(registry)` + `printVersion(pkgVersion)` helper. Internally use `import { Command, CommanderError } from "commander"`. The file is the only one that imports `commander`.
9
- - [ ] 2.2 Wire commander to expose `-v/--verbose`, `--version`, `-h/--help` as global flags; treat everything else as subcommand args. Use `program.exitOverride()` and `program.allowUnknownOption(true)` so the wrapper controls exit and dispatches unknown subcommands to the existing `CommandRegistry.getHandler` path.
10
- - [ ] 2.3 Catch `CommanderError` in the wrapper; re-throw a `new Error("gemiterm: " + commanderError.message)`.
11
-
12
- ## 3. Wire up `src/cli/index.ts`
13
-
14
- - [ ] 3.1 Delete the hand-rolled `parseGlobalFlags` function (lines 39-60 of the current `src/cli/index.ts`).
15
- - [ ] 3.2 Replace the call in `main()`: `const { flags, remaining } = parseGlobalFlags(args)` becomes `const { flags, subcommand, subcommandArgs } = parseGlobalArgs(args)`. Update the subsequent `remaining[0]` / `remaining.slice(1)` usages to use the destructured fields.
16
- - [ ] 3.3 Replace the inline `console.log("gemiterm v" + pkg.version)` / `process.exit(0)` in the version branch with a call to the wrapper's `printVersion(pkg.version)`. Same for `--help` → `printHelp(registry)`.
17
- - [ ] 3.4 Verify no `import ... from "commander"` was added to `src/cli/index.ts` or any file under `src/cli/commands/`.
18
-
19
- ## 4. Tests
20
-
21
- - [ ] 4.1 Create `tests/infrastructure/cli-parser.test.ts` with unit tests covering: `--verbose` long, `-v` short, `--version`, `--help` long, `-h` short, subcommand passthrough, subcommand-args passthrough, and the unknown-option error path. Each test asserts on the shape returned by `parseGlobalArgs`, not on process state.
22
- - [ ] 4.2 If any existing unit test references `parseGlobalFlags` (search `tests/` for that symbol), rename it to `parseGlobalArgs` and update assertions. Expected: zero matches given the function is module-private to `index.ts` and was never exported.
23
-
24
- ## 5. Verify
25
-
26
- - [ ] 5.1 Run `bun run test` and confirm the test count is at least 502 (current baseline) + new cli-parser tests. The one pre-existing `Smoke Tests > status runs without crashing` failure remains (unrelated).
27
- - [ ] 5.2 Run `bun run typecheck` and confirm clean.
28
- - [ ] 5.3 Run `bun run lint:mediation:ps` and confirm clean (no new `node:fs` / `node:path` / `node:os` imports introduced).
29
- - [ ] 5.4 Manual smoke: `bun run src/cli/index.ts --version` prints `gemiterm v2.0.0` and exits 0; `bun run src/cli/index.ts --help` prints the existing help screen; `bun run src/cli/index.ts --bogus` exits 1 with a `gemiterm: ...` message; `bun run src/cli/index.ts status` runs the status command end-to-end against `.gemiterm/profiles/default/storage_state.json`.
30
-
31
- ## 6. Cross-cutting cleanup
32
-
33
- - [ ] 6.1 Update `openspec/changes/cross-platform-build-and-ci/tasks.md` task 14.3 to mark it as superseded by `commander-cli-parser`, or delete the task entirely. The CI change's cleanup PR must be rebased onto this change's commit.
@@ -1,2 +0,0 @@
1
- schema: spec-driven
2
- created: 2026-06-08