gemiterm 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +32 -9
  2. package/package.json +13 -4
  3. package/src/cli/command-registry.ts +66 -62
  4. package/src/cli/commands/continue-command.ts +75 -1
  5. package/src/cli/commands/install-skills-command.ts +35 -0
  6. package/src/cli/commands/new-command.ts +62 -0
  7. package/src/cli/index.ts +19 -44
  8. package/src/cli/utils/long-arg-guard.ts +28 -0
  9. package/src/cli/utils/prompt-file.ts +26 -0
  10. package/src/infrastructure/cli-parser.ts +77 -0
  11. package/src/infrastructure/io.ts +9 -0
  12. package/src/services/skills-cli-driver.ts +58 -0
  13. package/src/services/skills-service.ts +52 -0
  14. package/.env +0 -2
  15. package/.github/workflows/manual-release.yml +0 -34
  16. package/.github/workflows/release.yml +0 -198
  17. package/.github/workflows/test.yml +0 -29
  18. package/.opencode/commands/opsx-apply.md +0 -152
  19. package/.opencode/commands/opsx-archive.md +0 -157
  20. package/.opencode/commands/opsx-explore.md +0 -169
  21. package/.opencode/commands/opsx-propose.md +0 -104
  22. package/.opencode/commands/opsx-sync.md +0 -140
  23. package/.opencode/commands/search-intelligently.md +0 -9
  24. package/.opencode/opencode.json +0 -20
  25. package/.opencode/skills/intellisearch/.version +0 -1
  26. package/.opencode/skills/intellisearch/SKILL.md +0 -84
  27. package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
  28. package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
  29. package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
  30. package/.opencode/skills/intellisearch/references/examples.md +0 -97
  31. package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
  32. package/.opencode/skills/intellisearch/references/google-search.md +0 -40
  33. package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
  34. package/.opencode/skills/intellisearch/references/workflow.md +0 -115
  35. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
  36. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
  37. package/.opencode/skills/openspec-explore/SKILL.md +0 -287
  38. package/.opencode/skills/openspec-propose/SKILL.md +0 -111
  39. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
  40. package/.serena/project.yml +0 -133
  41. package/AGENTS.md +0 -136
  42. package/CHANGELOG.md +0 -3
  43. package/bun.lock +0 -105
  44. package/docs/INSTALL.md +0 -185
  45. package/docs/PLAYWRIGHT_CLI_API.md +0 -306
  46. package/docs/python-release-for-reference.md +0 -175
  47. package/examples/sample_json_export.json +0 -32
  48. package/examples/sample_markdown_export.md +0 -221
  49. package/examples/usage_examples.sh +0 -222
  50. package/install.ps1 +0 -264
  51. package/install.sh +0 -275
  52. package/openspec/MAESTRO_MIGRATION.md +0 -180
  53. package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
  54. package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
  55. package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
  56. package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
  57. package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
  58. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
  59. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
  60. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
  61. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
  62. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
  63. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
  64. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
  65. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
  66. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
  67. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
  68. package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
  69. package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
  70. package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
  71. package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
  72. package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
  73. package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
  74. package/openspec/changes/commander-cli-parser/design.md +0 -107
  75. package/openspec/changes/commander-cli-parser/proposal.md +0 -31
  76. package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
  77. package/openspec/changes/commander-cli-parser/tasks.md +0 -33
  78. package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
  79. package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
  80. package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
  81. package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
  82. package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
  83. package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
  84. package/openspec/config.yaml +0 -20
  85. package/openspec/specs/auth/spec.md +0 -394
  86. package/openspec/specs/cli/spec.md +0 -108
  87. package/openspec/specs/commands/spec.md +0 -409
  88. package/openspec/specs/configuration/spec.md +0 -130
  89. package/openspec/specs/conversations/spec.md +0 -139
  90. package/openspec/specs/domain-model/spec.md +0 -128
  91. package/openspec/specs/formatters/spec.md +0 -158
  92. package/openspec/specs/install-browser/spec.md +0 -146
  93. package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
  94. package/openspec/specs/logger/spec.md +0 -71
  95. package/openspec/specs/mediator/spec.md +0 -72
  96. package/openspec/specs/multi-profile-conversations/spec.md +0 -145
  97. package/openspec/specs/path-utils/spec.md +0 -101
  98. package/openspec/specs/profiles/spec.md +0 -94
  99. package/openspec/specs/storage/spec.md +0 -198
  100. package/openspec/specs/testing/spec.md +0 -237
  101. package/openspec/specs/v2-installer/spec.md +0 -205
  102. package/openspec/specs/validators/spec.md +0 -65
  103. package/scripts/build.ts +0 -88
  104. package/scripts/clean-build.ps1 +0 -4
  105. package/scripts/clean-build.sh +0 -5
  106. package/scripts/install-browser.ps1 +0 -5
  107. package/scripts/install-browser.sh +0 -6
  108. package/scripts/lint-path-mediation.ps1 +0 -65
  109. package/scripts/lint-path-mediation.sh +0 -40
  110. package/tests/cli/auth-command.test.ts +0 -220
  111. package/tests/cli/command-registry.test.ts +0 -67
  112. package/tests/cli/continue-command.test.ts +0 -100
  113. package/tests/cli/delete-command.test.ts +0 -128
  114. package/tests/cli/export-all-command.test.ts +0 -375
  115. package/tests/cli/install-browser-command.test.ts +0 -48
  116. package/tests/cli/list-command.test.ts +0 -179
  117. package/tests/cli/new-command.test.ts +0 -139
  118. package/tests/cli/status-command.test.ts +0 -106
  119. package/tests/core/query-handlers.test.ts +0 -187
  120. package/tests/fixtures/auth-fixtures.ts +0 -98
  121. package/tests/fixtures/chat-fixtures.ts +0 -87
  122. package/tests/infrastructure/cli-table.test.ts +0 -107
  123. package/tests/infrastructure/formatters.test.ts +0 -202
  124. package/tests/infrastructure/io.test.ts +0 -197
  125. package/tests/infrastructure/logger.test.ts +0 -74
  126. package/tests/infrastructure/storage.test.ts +0 -335
  127. package/tests/infrastructure/validators.test.ts +0 -67
  128. package/tests/integration/commands/auth.test.ts +0 -186
  129. package/tests/integration/commands/continue.test.ts +0 -130
  130. package/tests/integration/commands/delete.test.ts +0 -140
  131. package/tests/integration/commands/export.test.ts +0 -266
  132. package/tests/integration/commands/fetch.test.ts +0 -238
  133. package/tests/integration/commands/list.test.ts +0 -281
  134. package/tests/integration/commands/profile.test.ts +0 -258
  135. package/tests/integration/commands/status.test.ts +0 -223
  136. package/tests/integration/installer-script-shape.test.ts +0 -67
  137. package/tests/parity/compare-outputs.ts +0 -359
  138. package/tests/parity/test-commands-parity.ps1 +0 -82
  139. package/tests/parity/test-commands-parity.sh +0 -61
  140. package/tests/services/auth-service.test.ts +0 -315
  141. package/tests/services/cookie-monitor.test.ts +0 -229
  142. package/tests/services/cookie-storage-service.test.ts +0 -230
  143. package/tests/services/gemini-client-wrapper.test.ts +0 -688
  144. package/tests/services/install-browser-service.test.ts +0 -83
  145. package/tests/services/playwright-cli-driver.test.ts +0 -468
  146. package/tests/services/profile-auth-manager.test.ts +0 -334
  147. package/tests/services/profile-service.test.ts +0 -224
  148. package/tests/setup.ts +0 -95
  149. package/tests/smoke/smoke.test.ts +0 -52
  150. package/tests/unit/config.test.ts +0 -159
  151. package/tests/unit/errors.test.ts +0 -119
  152. package/tests/unit/formatters.test.ts +0 -316
  153. package/tests/unit/mediator.test.ts +0 -152
  154. package/tests/unit/path-utils.test.ts +0 -289
  155. package/tsconfig.json +0 -16
@@ -1,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,65 +0,0 @@
1
- ## Purpose
2
-
3
- Input validation helpers used by command handlers and services to enforce invariants on user-supplied data: conversation IDs, ISO date strings, and profile names. All helpers throw a `GemitermError` (the project's base domain error) on invalid input so the calling layer can treat validation failures uniformly.
4
-
5
- ## Requirements
6
-
7
- ### Requirement: validateConversationId
8
- The system MUST export a `validateConversationId(id: string): void` function. The function MUST throw a `GemitermError` whose message starts with `Conversation ID must not be empty` when the supplied id is falsy, an empty string, or a whitespace-only string (i.e. `id.trim().length === 0`). The function MUST return `void` and MUST NOT throw for any other string.
9
-
10
- #### Scenario: Non-empty id passes
11
- - **WHEN** `validateConversationId("abc123")` is called
12
- - **THEN** the function does not throw
13
-
14
- #### Scenario: Empty string throws
15
- - **WHEN** `validateConversationId("")` is called
16
- - **THEN** the function throws a `GemitermError` whose message includes `Conversation ID must not be empty`
17
-
18
- #### Scenario: Whitespace-only id throws
19
- - **WHEN** `validateConversationId(" ")` is called
20
- - **THEN** the function throws a `GemitermError`
21
-
22
- ### Requirement: parseIsoDate
23
- The system MUST export a `parseIsoDate(dateStr: string, fieldName: string): number` function. The function MUST parse the supplied string with `Date.parse`. If parsing yields a `NaN` value, the function MUST throw a `GemitermError` whose message names the supplied `fieldName` and the invalid input. If parsing succeeds, the function MUST return the resulting timestamp in milliseconds (a `number`).
24
-
25
- #### Scenario: Valid ISO date returns timestamp
26
- - **WHEN** `parseIsoDate("2025-01-15T10:30:00.000Z", "createdAt")` is called
27
- - **THEN** the function returns a `number` equal to `new Date("2025-01-15T10:30:00.000Z").getTime()`
28
-
29
- #### Scenario: Invalid date string throws
30
- - **WHEN** `parseIsoDate("not-a-date", "field")` is called
31
- - **THEN** the function throws a `GemitermError` whose message contains `Invalid ISO date for 'field'`
32
-
33
- #### Scenario: Field name appears in error
34
- - **WHEN** `parseIsoDate("bad", "updatedAt")` is called
35
- - **THEN** the thrown error's message contains the literal string `'updatedAt'`
36
-
37
- ### Requirement: validateProfileName Allowed Character Set
38
- The system MUST export a `validateProfileName(name: string): void` function. The function MUST treat a name as valid iff (a) the name is non-empty after trimming, and (b) the un-trimmed name matches the regular expression `/^[a-zA-Z0-9_-]+$/` — i.e. the name contains only ASCII letters, ASCII digits, hyphens, and underscores, and is at least one character long. Any other input MUST cause a `GemitermError` to be thrown.
39
-
40
- #### Scenario: Valid names pass
41
- - **WHEN** `validateProfileName` is called with `"default"`, `"my-profile"`, `"profile_2"`, or `"ABC123"`
42
- - **THEN** the function does not throw
43
-
44
- #### Scenario: Empty string throws
45
- - **WHEN** `validateProfileName("")` is called
46
- - **THEN** the function throws a `GemitermError` whose message includes `Profile name must not be empty`
47
-
48
- #### Scenario: Whitespace-only name throws
49
- - **WHEN** `validateProfileName(" ")` is called
50
- - **THEN** the function throws a `GemitermError`
51
-
52
- #### Scenario: Names with spaces throw
53
- - **WHEN** `validateProfileName("my profile")` is called
54
- - **THEN** the function throws a `GemitermError`
55
-
56
- #### Scenario: Names with disallowed special characters throw
57
- - **WHEN** `validateProfileName` is called with `"profile@bad"`, `"pro/ject"`, or `"pro.file"`
58
- - **THEN** the function throws a `GemitermError`
59
-
60
- ### Requirement: Validation Errors Use GemitermError
61
- All three validators (`validateConversationId`, `parseIsoDate`, `validateProfileName`) MUST throw instances of the project's base `GemitermError` class on invalid input. Validation errors therefore MUST be catchable as `GemitermError` (or as `Error`) by upstream layers.
62
-
63
- #### Scenario: Validation errors are GemitermErrors
64
- - **WHEN** any of the three validators is called with invalid input
65
- - **THEN** the thrown value is an instance of `GemitermError`
package/scripts/build.ts DELETED
@@ -1,88 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { mkdirSync, readFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
-
5
- type Target = "bun-linux-x64" | "bun-windows-x64" | "bun-darwin-x64" | "bun-darwin-arm64";
6
-
7
- interface Profile {
8
- target: Target | "host";
9
- outfile: string;
10
- minify: boolean;
11
- }
12
-
13
- function hostTarget(): Target {
14
- switch (process.platform) {
15
- case "win32":
16
- return "bun-windows-x64";
17
- case "darwin":
18
- return process.arch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
19
- case "linux":
20
- default:
21
- return "bun-linux-x64";
22
- }
23
- }
24
-
25
- const PROFILES: Record<string, Profile> = {
26
- default: { target: "host", outfile: "dist/gemiterm", minify: false },
27
- linux: { target: "bun-linux-x64", outfile: "dist/gemiterm", minify: false },
28
- windows: { target: "bun-windows-x64", outfile: "dist/gemiterm.exe", minify: false },
29
- release: { target: "host", outfile: "dist/gemiterm", minify: true },
30
- };
31
-
32
- function loadPackageJson(): { name: string; version: string } {
33
- const raw = readFileSync(resolve(import.meta.dir, "..", "package.json"), "utf-8");
34
- const parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };
35
- if (typeof parsed.name !== "string" || typeof parsed.version !== "string") {
36
- throw new Error("scripts/build.ts: package.json is missing name/version strings");
37
- }
38
- return { name: parsed.name, version: parsed.version };
39
- }
40
-
41
- function pickProfile(): Profile {
42
- const requested = process.argv[2];
43
- if (!requested) return PROFILES.default!;
44
- const profile = PROFILES[requested];
45
- if (!profile) {
46
- const known = Object.keys(PROFILES).join(", ");
47
- throw new Error(`scripts/build.ts: unknown profile "${requested}". Known: ${known}`);
48
- }
49
- return profile;
50
- }
51
-
52
- function ensureWindowsExeSuffix(path: string, target: Target): string {
53
- if (target !== "bun-windows-x64") return path;
54
- return path.toLowerCase().endsWith(".exe") ? path : `${path}.exe`;
55
- }
56
-
57
- async function main(): Promise<void> {
58
- const profile = pickProfile();
59
- const pkg = loadPackageJson();
60
- const target = profile.target === "host" ? hostTarget() : profile.target;
61
-
62
- const projectRoot = resolve(import.meta.dir, "..");
63
- const outfile = resolve(projectRoot, ensureWindowsExeSuffix(profile.outfile, target));
64
- mkdirSync(dirname(outfile), { recursive: true });
65
-
66
- const result = await Bun.build({
67
- entrypoints: [resolve(projectRoot, "src/cli/index.ts")],
68
- target: "bun",
69
- compile: {
70
- target,
71
- outfile,
72
- },
73
- minify: profile.minify,
74
- define: {
75
- __GEMITERM_VERSION__: JSON.stringify(pkg.version),
76
- __GEMITERM_NAME__: JSON.stringify(pkg.name),
77
- },
78
- });
79
-
80
- if (!result.success) {
81
- for (const log of result.logs) console.error(log);
82
- throw new Error(`scripts/build.ts: build failed for profile ${JSON.stringify(profile)}`);
83
- }
84
-
85
- console.log(`built ${profile.outfile} (target=${target}, minify=${profile.minify}, version=${pkg.version})`);
86
- }
87
-
88
- await main();
@@ -1,4 +0,0 @@
1
- $ErrorActionPreference = 'Stop'
2
- Remove-Item -Recurse -Force dist/ -ErrorAction SilentlyContinue
3
- Remove-Item -Force bun.lock.bak -ErrorAction SilentlyContinue
4
- Write-Host "Build artifacts cleaned."
@@ -1,5 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
- rm -rf dist/
4
- rm -f bun.lock.bak
5
- echo "Build artifacts cleaned."
@@ -1,5 +0,0 @@
1
- $ErrorActionPreference = 'Stop'
2
- Write-Host "Running: bunx @playwright/cli install chromium"
3
- $output = & bunx @playwright/cli install chromium 2>&1
4
- Write-Host $output
5
- Write-Host "Browser installation complete."
@@ -1,6 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
- echo "Running: bunx @playwright/cli install chromium"
4
- output=$(bunx @playwright/cli install chromium 2>&1)
5
- echo "$output"
6
- echo "Browser installation complete."
@@ -1,65 +0,0 @@
1
- # scripts/lint-path-mediation.ps1
2
- #
3
- # PowerShell Core port of scripts/lint-path-mediation.sh.
4
- # Enforce the path-and-file mediation rule: no file in src/ outside the
5
- # allowed exemptions may import from node:fs, node:path, or node:os.
6
- #
7
- # Allowed exemptions:
8
- # - src/infrastructure/path-utils.ts (canonical home)
9
- # - src/services/install-browser-service.ts (WSL mount parser)
10
- #
11
- # Exit 0 when the rule is satisfied, non-zero with a clear message otherwise.
12
- # This script is called by the CI test.yml workflow as the last step.
13
-
14
- $ErrorActionPreference = "Stop"
15
-
16
- $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
17
- $srcDir = Join-Path $scriptDir "..\src"
18
- if (-not (Test-Path $srcDir)) {
19
- Write-Error "src/ directory not found at $srcDir"
20
- exit 1
21
- }
22
-
23
- $tsFiles = Get-ChildItem -Path $srcDir -Recurse -Filter "*.ts" -File
24
- $forbidden = @()
25
- foreach ($file in $tsFiles) {
26
- $matches_found = Select-String -Path $file.FullName -Pattern 'from\s+["''](node:fs|node:path|node:os)["'']'
27
- if ($matches_found) {
28
- $isExempt = $false
29
- $normalized = $file.FullName -replace "\\", "/"
30
- $normalized = $normalized -replace "^.*/gemiterm-bun-rewrite/", ""
31
- if ($normalized -eq "src/infrastructure/path-utils.ts") { $isExempt = $true }
32
- if ($normalized -eq "src/infrastructure/io.ts") { $isExempt = $true }
33
- if ($normalized -eq "src/services/install-browser-service.ts") { $isExempt = $true }
34
- if (-not $isExempt) {
35
- foreach ($m in $matches_found) {
36
- $forbidden += [PSCustomObject]@{
37
- Path = $normalized
38
- LineNumber = $m.LineNumber
39
- Line = $m.Line.Trim()
40
- }
41
- }
42
- }
43
- }
44
- }
45
-
46
- if ($forbidden.Count -gt 0) {
47
- Write-Host "ERROR: forbidden direct imports of node:fs / node:path / node:os in src/." -ForegroundColor Red
48
- Write-Host ""
49
- Write-Host "All file-system and path operations must route through:"
50
- Write-Host " - src/infrastructure/path-utils.ts (path values)"
51
- Write-Host " - src/infrastructure/io.ts (file-system side effects)"
52
- Write-Host ""
53
- Write-Host "Offending imports:"
54
- foreach ($f in $forbidden) {
55
- Write-Host " $($f.Path):$($f.LineNumber): $($f.Line)"
56
- }
57
- Write-Host ""
58
- Write-Host "If you have a legitimate need for a direct import, add the file to"
59
- Write-Host "the exemption list in this script and the CI workflow with a comment"
60
- Write-Host "explaining why."
61
- exit 1
62
- }
63
-
64
- Write-Host "OK: no forbidden node:fs / node:path / node:os imports in src/"
65
- exit 0
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env bash
2
- # scripts/lint-path-mediation.sh
3
- #
4
- # Enforce the path-and-file mediation rule: no file in src/ outside the
5
- # allowed exemptions may import from node:fs, node:path, or node:os.
6
- #
7
- # Allowed exemptions:
8
- # - src/infrastructure/path-utils.ts (canonical home for paths)
9
- # - src/infrastructure/io.ts (canonical home for file ops)
10
- # - src/services/install-browser-service.ts (WSL mount parser)
11
- #
12
- # Exit 0 when the rule is satisfied, non-zero with a clear message otherwise.
13
- # This script is called by the CI test.yml workflow as the last step.
14
-
15
- set -euo pipefail
16
-
17
- FORBIDDEN=$(grep -rn --include='*.ts' "from \"node:" src/ \
18
- | grep -E "from \"node:(fs|path|os)\"" \
19
- | grep -v "src/infrastructure/path-utils.ts" \
20
- | grep -v "src/infrastructure/io.ts" \
21
- | grep -v "src/services/install-browser-service.ts" \
22
- || true)
23
-
24
- if [ -n "$FORBIDDEN" ]; then
25
- echo "ERROR: forbidden direct imports of node:fs / node:path / node:os in src/." >&2
26
- echo "" >&2
27
- echo "All file-system and path operations must route through:" >&2
28
- echo " - src/infrastructure/path-utils.ts (path values)" >&2
29
- echo " - src/infrastructure/io.ts (file-system side effects)" >&2
30
- echo "" >&2
31
- echo "Offending imports:" >&2
32
- echo "$FORBIDDEN" >&2
33
- echo "" >&2
34
- echo "If you have a legitimate need for a direct import, add the file to" >&2
35
- echo "the exemption list in this script and the CI workflow with a comment" >&2
36
- echo "explaining why." >&2
37
- exit 1
38
- fi
39
-
40
- echo "OK: no forbidden node:fs / node:path / node:os imports in src/"