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
package/README.md CHANGED
@@ -4,13 +4,37 @@ Access and manage your Gemini web chats from the command line. GemiTerm bridges
4
4
 
5
5
  ## Prerequisites
6
6
 
7
- - **[Bun](https://bun.sh)** runtime
7
+ - **[Bun](https://bun.sh)** runtime ≥ 1.0.0
8
8
  - **Chromium Browser** — GemiTerm uses your system Chrome/Edge if available, otherwise installs Playwright's Chromium automatically
9
9
  - **Google Account** with access to [Gemini](https://gemini.google.com)
10
10
 
11
+ ## Quick Start (no install)
12
+
13
+ Use `bunx` to run GemiTerm directly — no install step needed:
14
+
15
+ ```bash
16
+ bunx gemiterm auth # authenticate with Google
17
+ bunx gemiterm list # list your chats
18
+ ```
19
+
11
20
  ## Installation
12
21
 
13
- See [docs/INSTALL.md](docs/INSTALL.md) for the full guide, uninstall instructions, troubleshooting, and build-from-source steps.
22
+ ### Global install (recommended for daily use)
23
+
24
+ ```bash
25
+ bun install gemiterm -g
26
+ ```
27
+
28
+ Then use from anywhere:
29
+
30
+ ```bash
31
+ gemiterm auth
32
+ gemiterm list
33
+ ```
34
+
35
+ ### Install scripts (binary drop)
36
+
37
+ For systems without Bun — the script auto-bootstraps Bun if needed.
14
38
 
15
39
  **Windows** (PowerShell 7+):
16
40
 
@@ -24,14 +48,9 @@ irm https://github.com/expert-vision-software/GemiTerm/releases/latest/download/
24
48
  curl -fsSL https://github.com/expert-vision-software/GemiTerm/releases/latest/download/install.sh | bash
25
49
  ```
26
50
 
27
- > **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](docs/INSTALL.md) for details.
28
-
29
- ## Quick Start
51
+ See [docs/INSTALL.md](docs/INSTALL.md) for the full guide, uninstall instructions, troubleshooting, and build-from-source steps.
30
52
 
31
- ```bash
32
- gemiterm auth # authenticate with Google
33
- gemiterm list # list your chats
34
- ```
53
+ > **Upgrading from v1.4.1?** Your profiles, cookies, and default profile marker are preserved. See the "Upgrade from v1.4.1" section in [docs/INSTALL.md](docs/INSTALL.md) for details.
35
54
 
36
55
  ## Development
37
56
 
@@ -197,6 +216,10 @@ Checks for system Chrome/Edge first, falls back to installing Playwright's Chrom
197
216
  gemiterm -v <command>
198
217
  ```
199
218
 
219
+ ## Skills
220
+
221
+ GemiTerm has an associated skills repository for AI coding agents (Copilot, Claude, etc.) that provides domain-specific instructions for working with **gemiterm** and automating common workflows. There's also a fun `debate-with-gemini` skill that created an interactive turn-based conversation between your agent and gemini. See [opencode-gemiterm-skills](https://www.npmjs.com/package/opencode-gemiterm-skills).
222
+
200
223
  ## Configuration
201
224
 
202
225
  ### Configuration Directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemiterm",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {
@@ -14,9 +14,16 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
- "devDependencies": {
18
- "@types/bun": "^1.3.14"
17
+ "bin": {
18
+ "gemiterm": "src/cli/index.ts"
19
+ },
20
+ "engines": {
21
+ "bun": ">=1.0.0"
19
22
  },
23
+ "files": [
24
+ "src/",
25
+ "README.md"
26
+ ],
20
27
  "scripts": {
21
28
  "start": "bun run src/cli/index.ts",
22
29
  "dev": "bun run src/cli/index.ts",
@@ -34,12 +41,14 @@
34
41
  "build:windows": "bun run scripts/build.ts windows",
35
42
  "build:release": "bun run scripts/build.ts release"
36
43
  },
37
- "peerDependencies": {
44
+ "devDependencies": {
45
+ "@types/bun": "^1.3.14",
38
46
  "typescript": "^6.0.3"
39
47
  },
40
48
  "dependencies": {
41
49
  "chalk": "^5.6.2",
42
50
  "cli-table3": "^0.6.5",
51
+ "commander": "^15.0.0",
43
52
  "gemini-reverse": "^1.0.12"
44
53
  }
45
54
  }
@@ -1,62 +1,66 @@
1
- import type { Mediator } from "../core/mediator.ts";
2
- import type { ProfileAuthManager } from "../services/profile-auth-manager.ts";
3
- import { AuthCommand } from "./commands/auth-command.ts";
4
- import { ProfileCommand } from "./commands/profile-command.ts";
5
- import { StatusCommand } from "./commands/status-command.ts";
6
- import { ListCommand } from "./commands/list-command.ts";
7
- import { FetchCommand } from "./commands/fetch-command.ts";
8
- import { ContinueCommand } from "./commands/continue-command.ts";
9
- import { NewCommand } from "./commands/new-command.ts";
10
- import { DeleteCommand } from "./commands/delete-command.ts";
11
- import { ExportCommand } from "./commands/export-command.ts";
12
- import { ExportAllCommand } from "./commands/export-all-command.ts";
13
- import { InstallBrowserCommand } from "./commands/install-browser-command.ts";
14
-
15
- export interface CliCommandContext {
16
- verbose: boolean;
17
- mediator: Mediator;
18
- profileAuthManager: ProfileAuthManager;
19
- }
20
-
21
- export interface CliCommand {
22
- readonly name: string;
23
- readonly description: string;
24
- execute(args: string[], context: CliCommandContext): Promise<void>;
25
- }
26
-
27
- export class CommandRegistry {
28
- private handlers = new Map<string, CliCommand>();
29
-
30
- register(commandName: string, handler: CliCommand): void {
31
- if (this.handlers.has(commandName)) {
32
- throw new Error(`Command already registered: ${commandName}`);
33
- }
34
- this.handlers.set(commandName, handler);
35
- }
36
-
37
- getHandler(commandName: string): CliCommand | undefined {
38
- return this.handlers.get(commandName);
39
- }
40
-
41
- has(commandName: string): boolean {
42
- return this.handlers.has(commandName);
43
- }
44
-
45
- getRegisteredNames(): string[] {
46
- return [...this.handlers.keys()];
47
- }
48
-
49
- registerAllCommands(): void {
50
- this.register("auth", new AuthCommand());
51
- this.register("profile", new ProfileCommand());
52
- this.register("status", new StatusCommand());
53
- this.register("list", new ListCommand());
54
- this.register("fetch", new FetchCommand());
55
- this.register("continue", new ContinueCommand());
56
- this.register("new", new NewCommand());
57
- this.register("delete", new DeleteCommand());
58
- this.register("export", new ExportCommand());
59
- this.register("export-all", new ExportAllCommand());
60
- this.register("install-browser", new InstallBrowserCommand());
61
- }
62
- }
1
+ import type { Mediator } from "../core/mediator.ts";
2
+ import type { ProfileAuthManager } from "../services/profile-auth-manager.ts";
3
+ import { AuthCommand } from "./commands/auth-command.ts";
4
+ import { ProfileCommand } from "./commands/profile-command.ts";
5
+ import { StatusCommand } from "./commands/status-command.ts";
6
+ import { ListCommand } from "./commands/list-command.ts";
7
+ import { FetchCommand } from "./commands/fetch-command.ts";
8
+ import { ContinueCommand } from "./commands/continue-command.ts";
9
+ import { NewCommand } from "./commands/new-command.ts";
10
+ import { DeleteCommand } from "./commands/delete-command.ts";
11
+ import { ExportCommand } from "./commands/export-command.ts";
12
+ import { ExportAllCommand } from "./commands/export-all-command.ts";
13
+ import { InstallBrowserCommand } from "./commands/install-browser-command.ts";
14
+ import { InstallSkillsCommand } from "./commands/install-skills-command.ts";
15
+
16
+ export interface CliCommandContext {
17
+ verbose: boolean;
18
+ mediator: Mediator;
19
+ profileAuthManager: ProfileAuthManager;
20
+ }
21
+
22
+ export interface CliCommand {
23
+ readonly name: string;
24
+ readonly description: string;
25
+ execute(args: string[], context: CliCommandContext): Promise<void>;
26
+ }
27
+
28
+ export class CommandRegistry {
29
+ private handlers = new Map<string, CliCommand>();
30
+
31
+ register(commandName: string, handler: CliCommand): void {
32
+ if (this.handlers.has(commandName)) {
33
+ throw new Error(`Command already registered: ${commandName}`);
34
+ }
35
+ this.handlers.set(commandName, handler);
36
+ }
37
+
38
+ getHandler(commandName: string): CliCommand | undefined {
39
+ return this.handlers.get(commandName);
40
+ }
41
+
42
+ has(commandName: string): boolean {
43
+ return this.handlers.has(commandName);
44
+ }
45
+
46
+ getRegisteredNames(): string[] {
47
+ return [...this.handlers.keys()];
48
+ }
49
+
50
+ registerAllCommands(): void {
51
+ const authCommand = new AuthCommand();
52
+ this.register("auth", authCommand);
53
+ this.register("login", authCommand);
54
+ this.register("profile", new ProfileCommand());
55
+ this.register("status", new StatusCommand());
56
+ this.register("list", new ListCommand());
57
+ this.register("fetch", new FetchCommand());
58
+ this.register("continue", new ContinueCommand());
59
+ this.register("new", new NewCommand());
60
+ this.register("delete", new DeleteCommand());
61
+ this.register("export", new ExportCommand());
62
+ this.register("export-all", new ExportAllCommand());
63
+ this.register("install-browser", new InstallBrowserCommand());
64
+ this.register("install-skills", new InstallSkillsCommand());
65
+ }
66
+ }
@@ -0,0 +1,35 @@
1
+ import chalk from "chalk";
2
+ import type { CliCommand, CliCommandContext } from "../command-registry.ts";
3
+ import { Logger } from "../../infrastructure/logger.ts";
4
+ import { InstallSkillsService, SkillsInstallError } from "../../services/skills-service.ts";
5
+
6
+ export class InstallSkillsCommand implements CliCommand {
7
+ readonly name = "install-skills";
8
+ readonly description = "Install GemiTerm opencode skills from expert-vision-software/opencode-gemiterm-skills";
9
+
10
+ async execute(_args: string[], _context: CliCommandContext): Promise<void> {
11
+ const logger = new Logger("install-skills-command");
12
+ logger.debug("Executing install-skills command");
13
+ const service = new InstallSkillsService({ logger });
14
+
15
+ console.log(chalk.dim("Installing GemiTerm opencode skills..."));
16
+
17
+ try {
18
+ const output = await service.install();
19
+ if (output) {
20
+ console.log(output);
21
+ }
22
+ console.log(chalk.green("Skills installed successfully."));
23
+ } catch (error) {
24
+ if (error instanceof SkillsInstallError) {
25
+ logger.error(error.message);
26
+ if (error.cause) {
27
+ logger.error(`Cause: ${error.cause.message}`);
28
+ }
29
+ console.error(chalk.red("Failed to install skills."));
30
+ process.exit(1);
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+ }
package/src/cli/index.ts CHANGED
@@ -3,13 +3,13 @@
3
3
  import { CommandRegistry } from "./command-registry.ts";
4
4
  import { Logger } from "../infrastructure/logger.ts";
5
5
  import { Mediator } from "../core/mediator.ts";
6
- import { showHelp } from "./commands/help.ts";
7
6
  import { GeminiClientService } from "../services/gemini-client-wrapper.ts";
8
7
  import { CookieStorageService } from "../services/cookie-storage-service.ts";
9
8
  import { ProfileAuthManager } from "../services/profile-auth-manager.ts";
10
9
  import { CookieStorage, ProfileManager } from "../infrastructure/storage.ts";
11
- import { getDefaultProfileName, listProfiles, ensureConfigDir } from "../infrastructure/config.ts";
10
+ import { getDefaultProfileName, listProfiles } from "../infrastructure/config.ts";
12
11
  import { getPackageJson } from "../infrastructure/path-utils.ts";
12
+ import { parseGlobalArgs, printVersion, printHelp } from "../infrastructure/cli-parser.ts";
13
13
  import { AuthenticationError } from "../core/errors.ts";
14
14
  import {
15
15
  AuthenticateCommandHandler,
@@ -30,39 +30,6 @@ import {
30
30
 
31
31
  const pkg = getPackageJson(import.meta.url);
32
32
 
33
- interface GlobalFlags {
34
- verbose: boolean;
35
- version: boolean;
36
- help: boolean;
37
- }
38
-
39
- function parseGlobalFlags(args: string[]): { flags: GlobalFlags; remaining: string[] } {
40
- const flags: GlobalFlags = {
41
- verbose: process.env.GEMITERM_VERBOSE === "true",
42
- version: false,
43
- help: false,
44
- };
45
- const remaining: string[] = [];
46
-
47
- for (const arg of args) {
48
- if (arg === "--verbose" || arg === "-v") {
49
- flags.verbose = true;
50
- } else if (arg === "--version") {
51
- flags.version = true;
52
- } else if (arg === "--help" || arg === "-h") {
53
- if (remaining.length === 0) {
54
- flags.help = true;
55
- } else {
56
- remaining.push(arg);
57
- }
58
- } else {
59
- remaining.push(arg);
60
- }
61
- }
62
-
63
- return { flags, remaining };
64
- }
65
-
66
33
  async function setupMediator(mediator: Mediator): Promise<ProfileAuthManager> {
67
34
  const logger = new Logger("mediator");
68
35
  const cookieStorage = new CookieStorage();
@@ -159,31 +126,39 @@ async function setupMediator(mediator: Mediator): Promise<ProfileAuthManager> {
159
126
  async function main(): Promise<void> {
160
127
  const args = process.argv.slice(2);
161
128
  const logger = new Logger("cli");
162
- const { flags, remaining } = parseGlobalFlags(args);
163
129
 
164
- if (flags.verbose) {
130
+ let flags: ReturnType<typeof parseGlobalArgs>["flags"];
131
+ let subcommand: string | null;
132
+ let subcommandArgs: string[];
133
+ try {
134
+ ({ flags, subcommand, subcommandArgs } = parseGlobalArgs(args));
135
+ } catch (error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ console.error(message);
138
+ process.exit(1);
139
+ }
140
+
141
+ const verbose = flags.verbose || process.env.GEMITERM_VERBOSE === "true";
142
+ if (verbose) {
165
143
  Logger.setVerbose(true);
166
144
  }
167
145
 
168
146
  if (flags.version) {
169
- console.log(`gemiterm v${pkg.version}`);
147
+ printVersion(pkg.version);
170
148
  process.exit(0);
171
149
  }
172
150
 
173
151
  const registry = new CommandRegistry();
174
152
  registry.registerAllCommands();
175
153
 
176
- if (remaining.length === 0) {
177
- showHelp(registry);
154
+ if (!subcommand) {
155
+ printHelp(registry);
178
156
  process.exit(0);
179
157
  }
180
158
 
181
159
  const mediator = new Mediator();
182
160
  const profileAuthManager = await setupMediator(mediator);
183
161
 
184
- const subcommand = remaining[0];
185
- const subcommandArgs = remaining.slice(1);
186
-
187
162
  const handler = registry.getHandler(subcommand);
188
163
  if (!handler) {
189
164
  const known = registry.getRegisteredNames();
@@ -198,7 +173,7 @@ async function main(): Promise<void> {
198
173
  }
199
174
 
200
175
  try {
201
- await handler.execute(subcommandArgs, { verbose: flags.verbose, mediator, profileAuthManager });
176
+ await handler.execute(subcommandArgs, { verbose, mediator, profileAuthManager });
202
177
  } catch (error) {
203
178
  const message = error instanceof Error ? error.message : String(error);
204
179
  logger.error(`Command '${subcommand}' failed: ${message}`);
@@ -0,0 +1,77 @@
1
+ import { Command, CommanderError } from "commander";
2
+ import type { CommandRegistry } from "../cli/command-registry.ts";
3
+ import { showHelp } from "../cli/commands/help.ts";
4
+
5
+ export interface ParsedFlags {
6
+ verbose: boolean;
7
+ version: boolean;
8
+ help: boolean;
9
+ }
10
+
11
+ export interface ParsedArgs {
12
+ flags: ParsedFlags;
13
+ subcommand: string | null;
14
+ subcommandArgs: string[];
15
+ }
16
+
17
+ function splitAtFirstNonFlag(argv: string[]): { pre: string[]; post: string[] } {
18
+ const pre: string[] = [];
19
+ const post: string[] = [];
20
+ let found = false;
21
+ for (const arg of argv) {
22
+ if (!found && !arg.startsWith("-")) {
23
+ found = true;
24
+ post.push(arg);
25
+ continue;
26
+ }
27
+ if (found) {
28
+ post.push(arg);
29
+ } else {
30
+ pre.push(arg);
31
+ }
32
+ }
33
+ return { pre, post };
34
+ }
35
+
36
+ export function parseGlobalArgs(argv: string[]): ParsedArgs {
37
+ const { pre, post } = splitAtFirstNonFlag(argv);
38
+ const subcommand = post[0] ?? null;
39
+ const subcommandArgs = post.slice(1);
40
+
41
+ const program = new Command();
42
+ program
43
+ .exitOverride()
44
+ .allowUnknownOption(false)
45
+ .allowExcessArguments(true)
46
+ .helpOption(false)
47
+ .configureOutput({ writeErr: () => {} })
48
+ .option("-v, --verbose", "enable verbose output")
49
+ .option("--version", "show version number")
50
+ .option("-h, --help", "show help");
51
+
52
+ try {
53
+ program.parse(pre, { from: "user" });
54
+ } catch (error) {
55
+ if (error instanceof CommanderError) {
56
+ throw new Error("gemiterm: " + error.message);
57
+ }
58
+ throw error;
59
+ }
60
+
61
+ const opts = program.opts<{ verbose?: boolean; version?: boolean; help?: boolean }>();
62
+ const flags: ParsedFlags = {
63
+ verbose: Boolean(opts.verbose),
64
+ version: Boolean(opts.version),
65
+ help: Boolean(opts.help),
66
+ };
67
+
68
+ return { flags, subcommand, subcommandArgs };
69
+ }
70
+
71
+ export function printVersion(pkgVersion: string): void {
72
+ console.log(`gemiterm v${pkgVersion}`);
73
+ }
74
+
75
+ export function printHelp(registry: CommandRegistry): void {
76
+ showHelp(registry);
77
+ }
@@ -0,0 +1,58 @@
1
+ import { Logger } from "../infrastructure/logger.ts";
2
+
3
+ export class SkillsCliError extends Error {
4
+ constructor(command: string, exitCode: number, stderr: string) {
5
+ super(`skills '${command}' exited with code ${exitCode}: ${stderr}`);
6
+ this.name = "SkillsCliError";
7
+ }
8
+ }
9
+
10
+ export interface SkillsRunnerResult {
11
+ exitCode: number;
12
+ stdout: string;
13
+ stderr: string;
14
+ }
15
+
16
+ export interface SkillsRunner {
17
+ run(args: string[]): Promise<SkillsRunnerResult>;
18
+ }
19
+
20
+ export class BunSkillsRunner implements SkillsRunner {
21
+ async run(args: string[]): Promise<SkillsRunnerResult> {
22
+ const proc = Bun.spawn(["npx", "skills", ...args], {
23
+ stdout: "pipe",
24
+ stderr: "pipe",
25
+ stdin: "ignore",
26
+ });
27
+ const [stdout, stderr, exitCode] = await Promise.all([
28
+ new Response(proc.stdout).text(),
29
+ new Response(proc.stderr).text(),
30
+ proc.exited,
31
+ ]);
32
+ return { exitCode: exitCode ?? -1, stdout: stdout.trim(), stderr: stderr.trim() };
33
+ }
34
+ }
35
+
36
+ export interface SkillsCliDriverOptions {
37
+ logger?: Logger;
38
+ runner?: SkillsRunner;
39
+ }
40
+
41
+ export class SkillsCliDriver {
42
+ private readonly logger: Logger;
43
+ private readonly runner: SkillsRunner;
44
+
45
+ constructor(opts: SkillsCliDriverOptions = {}) {
46
+ this.logger = opts.logger ?? new Logger("skills-cli-driver");
47
+ this.runner = opts.runner ?? new BunSkillsRunner();
48
+ }
49
+
50
+ async run(args: string[]): Promise<string> {
51
+ this.logger.debug(`Running: npx skills ${args.join(" ")}`);
52
+ const result = await this.runner.run(args);
53
+ if (result.exitCode !== 0) {
54
+ throw new SkillsCliError(args.join(" "), result.exitCode, result.stderr);
55
+ }
56
+ return result.stdout;
57
+ }
58
+ }
@@ -0,0 +1,52 @@
1
+ import { Logger } from "../infrastructure/logger.ts";
2
+ import { SkillsCliDriver, SkillsCliDriverOptions, SkillsCliError } from "./skills-cli-driver.ts";
3
+
4
+ export class SkillsInstallError extends Error {
5
+ constructor(message: string, public readonly cause?: Error) {
6
+ super(message);
7
+ this.name = "SkillsInstallError";
8
+ }
9
+ }
10
+
11
+ export interface InstallSkillsServiceOptions {
12
+ logger?: Logger;
13
+ driver?: SkillsCliDriver;
14
+ repo?: string;
15
+ skills?: string[];
16
+ }
17
+
18
+ export class InstallSkillsService {
19
+ private readonly logger: Logger;
20
+ private readonly driver: SkillsCliDriver;
21
+ private readonly repo: string;
22
+ private readonly skills: string[];
23
+
24
+ constructor(opts: InstallSkillsServiceOptions = {}) {
25
+ this.logger = opts.logger ?? new Logger("install-skills-service");
26
+ this.repo = opts.repo ?? "expert-vision-software/opencode-gemiterm-skills";
27
+ this.skills = opts.skills ?? ["gemiterm", "debate-with-gemini"];
28
+ this.driver = opts.driver ?? new SkillsCliDriver({ logger: this.logger });
29
+ }
30
+
31
+ async install(): Promise<string> {
32
+ this.logger.info(`Installing skills from ${this.repo}: ${this.skills.join(", ")}`);
33
+ const args = ["add", this.repo];
34
+ for (const skill of this.skills) {
35
+ args.push("--skill", skill);
36
+ }
37
+
38
+ try {
39
+ const output = await this.driver.run(args);
40
+ this.logger.info(`Skills installed successfully: ${output}`);
41
+ return output;
42
+ } catch (error) {
43
+ if (error instanceof SkillsCliError) {
44
+ throw new SkillsInstallError(error.message, error);
45
+ }
46
+ throw new SkillsInstallError(
47
+ `Failed to install skills: ${error instanceof Error ? error.message : String(error)}`,
48
+ error instanceof Error ? error : undefined,
49
+ );
50
+ }
51
+ }
52
+ }
package/.env DELETED
@@ -1,2 +0,0 @@
1
- GEMITERM_CONFIG_DIR=./.gemiterm
2
- GEMITERM_VERBOSE=true
@@ -1,34 +0,0 @@
1
- name: Manual Release
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- ref:
7
- description: 'Commit hash, branch, or tag to build'
8
- required: true
9
- type: string
10
- draft:
11
- description: 'Create a draft release (recommended for testing)'
12
- required: true
13
- type: boolean
14
- default: true
15
- pre_release:
16
- description: 'Mark as pre-release'
17
- required: true
18
- type: boolean
19
- default: true
20
- publish:
21
- description: 'Publish to npm (DANGEROUS — only enable if you mean it)'
22
- required: true
23
- type: boolean
24
- default: false
25
-
26
- jobs:
27
- build:
28
- uses: ./.github/workflows/release.yml
29
- with:
30
- ref: ${{ inputs.ref }}
31
- tag_name: manual-${{ inputs.ref }}-${{ github.run_id }}
32
- publish: ${{ inputs.publish }}
33
- draft: ${{ inputs.draft }}
34
- prerelease: ${{ inputs.pre_release }}