gemiterm 2.0.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 (184) hide show
  1. package/.env +2 -0
  2. package/.github/workflows/manual-release.yml +34 -0
  3. package/.github/workflows/release.yml +198 -0
  4. package/.github/workflows/test.yml +29 -0
  5. package/.opencode/commands/opsx-apply.md +152 -0
  6. package/.opencode/commands/opsx-archive.md +157 -0
  7. package/.opencode/commands/opsx-explore.md +169 -0
  8. package/.opencode/commands/opsx-propose.md +104 -0
  9. package/.opencode/commands/opsx-sync.md +140 -0
  10. package/.opencode/commands/search-intelligently.md +9 -0
  11. package/.opencode/opencode.json +20 -0
  12. package/.opencode/skills/intellisearch/.version +1 -0
  13. package/.opencode/skills/intellisearch/SKILL.md +84 -0
  14. package/.opencode/skills/intellisearch/references/brave-search.md +67 -0
  15. package/.opencode/skills/intellisearch/references/ddg-search.md +79 -0
  16. package/.opencode/skills/intellisearch/references/deepwiki-tools.md +246 -0
  17. package/.opencode/skills/intellisearch/references/examples.md +97 -0
  18. package/.opencode/skills/intellisearch/references/gh-cli.md +143 -0
  19. package/.opencode/skills/intellisearch/references/google-search.md +40 -0
  20. package/.opencode/skills/intellisearch/references/search-workflow.md +143 -0
  21. package/.opencode/skills/intellisearch/references/workflow.md +115 -0
  22. package/.opencode/skills/openspec-apply-change/SKILL.md +159 -0
  23. package/.opencode/skills/openspec-archive-change/SKILL.md +117 -0
  24. package/.opencode/skills/openspec-explore/SKILL.md +287 -0
  25. package/.opencode/skills/openspec-propose/SKILL.md +111 -0
  26. package/.opencode/skills/openspec-sync-specs/SKILL.md +147 -0
  27. package/.serena/project.yml +133 -0
  28. package/AGENTS.md +136 -0
  29. package/CHANGELOG.md +3 -0
  30. package/README.md +245 -0
  31. package/bun.lock +105 -0
  32. package/docs/INSTALL.md +185 -0
  33. package/docs/PLAYWRIGHT_CLI_API.md +306 -0
  34. package/docs/python-release-for-reference.md +175 -0
  35. package/examples/sample_json_export.json +32 -0
  36. package/examples/sample_markdown_export.md +221 -0
  37. package/examples/usage_examples.sh +222 -0
  38. package/install.ps1 +264 -0
  39. package/install.sh +275 -0
  40. package/openspec/MAESTRO_MIGRATION.md +180 -0
  41. package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +2 -0
  42. package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +194 -0
  43. package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +52 -0
  44. package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +133 -0
  45. package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +95 -0
  46. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +2 -0
  47. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +28 -0
  48. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +22 -0
  49. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +35 -0
  50. package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +28 -0
  51. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +2 -0
  52. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +323 -0
  53. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +122 -0
  54. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +29 -0
  55. package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +172 -0
  56. package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +2 -0
  57. package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +241 -0
  58. package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +41 -0
  59. package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +205 -0
  60. package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +97 -0
  61. package/openspec/changes/commander-cli-parser/.openspec.yaml +2 -0
  62. package/openspec/changes/commander-cli-parser/design.md +107 -0
  63. package/openspec/changes/commander-cli-parser/proposal.md +31 -0
  64. package/openspec/changes/commander-cli-parser/specs/cli/spec.md +48 -0
  65. package/openspec/changes/commander-cli-parser/tasks.md +33 -0
  66. package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +2 -0
  67. package/openspec/changes/cross-platform-build-and-ci/design.md +213 -0
  68. package/openspec/changes/cross-platform-build-and-ci/proposal.md +50 -0
  69. package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +335 -0
  70. package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +95 -0
  71. package/openspec/changes/cross-platform-build-and-ci/tasks.md +164 -0
  72. package/openspec/config.yaml +20 -0
  73. package/openspec/specs/auth/spec.md +394 -0
  74. package/openspec/specs/cli/spec.md +108 -0
  75. package/openspec/specs/commands/spec.md +409 -0
  76. package/openspec/specs/configuration/spec.md +130 -0
  77. package/openspec/specs/conversations/spec.md +139 -0
  78. package/openspec/specs/domain-model/spec.md +128 -0
  79. package/openspec/specs/formatters/spec.md +158 -0
  80. package/openspec/specs/install-browser/spec.md +146 -0
  81. package/openspec/specs/interactive-prompt-loop/spec.md +41 -0
  82. package/openspec/specs/logger/spec.md +71 -0
  83. package/openspec/specs/mediator/spec.md +72 -0
  84. package/openspec/specs/multi-profile-conversations/spec.md +145 -0
  85. package/openspec/specs/path-utils/spec.md +101 -0
  86. package/openspec/specs/profiles/spec.md +94 -0
  87. package/openspec/specs/storage/spec.md +198 -0
  88. package/openspec/specs/testing/spec.md +237 -0
  89. package/openspec/specs/v2-installer/spec.md +205 -0
  90. package/openspec/specs/validators/spec.md +65 -0
  91. package/package.json +45 -0
  92. package/scripts/build.ts +88 -0
  93. package/scripts/clean-build.ps1 +4 -0
  94. package/scripts/clean-build.sh +5 -0
  95. package/scripts/install-browser.ps1 +5 -0
  96. package/scripts/install-browser.sh +6 -0
  97. package/scripts/lint-path-mediation.ps1 +65 -0
  98. package/scripts/lint-path-mediation.sh +40 -0
  99. package/src/cli/.gitkeep +0 -0
  100. package/src/cli/command-registry.ts +62 -0
  101. package/src/cli/commands/auth-command.ts +179 -0
  102. package/src/cli/commands/continue-command.ts +158 -0
  103. package/src/cli/commands/delete-command.ts +163 -0
  104. package/src/cli/commands/export-all-command.ts +243 -0
  105. package/src/cli/commands/export-command.ts +156 -0
  106. package/src/cli/commands/fetch-command.ts +176 -0
  107. package/src/cli/commands/help.ts +45 -0
  108. package/src/cli/commands/install-browser-command.ts +33 -0
  109. package/src/cli/commands/list-command.ts +226 -0
  110. package/src/cli/commands/new-command.ts +147 -0
  111. package/src/cli/commands/profile-command.ts +226 -0
  112. package/src/cli/commands/status-command.ts +59 -0
  113. package/src/cli/index.ts +213 -0
  114. package/src/cli/utils/interactive-prompt.ts +64 -0
  115. package/src/core/.gitkeep +0 -0
  116. package/src/core/command-handlers.ts +240 -0
  117. package/src/core/errors.ts +41 -0
  118. package/src/core/mediator.ts +58 -0
  119. package/src/core/query-handlers.ts +163 -0
  120. package/src/core/types.ts +43 -0
  121. package/src/infrastructure/.gitkeep +0 -0
  122. package/src/infrastructure/cli-table.ts +45 -0
  123. package/src/infrastructure/config.ts +52 -0
  124. package/src/infrastructure/formatters.ts +130 -0
  125. package/src/infrastructure/io.ts +161 -0
  126. package/src/infrastructure/logger.ts +41 -0
  127. package/src/infrastructure/path-utils.ts +157 -0
  128. package/src/infrastructure/storage.ts +209 -0
  129. package/src/infrastructure/validators.ts +28 -0
  130. package/src/services/.gitkeep +0 -0
  131. package/src/services/auth-service.ts +135 -0
  132. package/src/services/cookie-monitor.ts +159 -0
  133. package/src/services/cookie-storage-service.ts +74 -0
  134. package/src/services/gemini-client-wrapper.ts +239 -0
  135. package/src/services/install-browser-service.ts +205 -0
  136. package/src/services/playwright-cli-driver.ts +269 -0
  137. package/src/services/profile-auth-manager.ts +65 -0
  138. package/src/services/profile-service.ts +121 -0
  139. package/tests/cli/auth-command.test.ts +220 -0
  140. package/tests/cli/command-registry.test.ts +67 -0
  141. package/tests/cli/continue-command.test.ts +100 -0
  142. package/tests/cli/delete-command.test.ts +128 -0
  143. package/tests/cli/export-all-command.test.ts +375 -0
  144. package/tests/cli/install-browser-command.test.ts +48 -0
  145. package/tests/cli/list-command.test.ts +179 -0
  146. package/tests/cli/new-command.test.ts +139 -0
  147. package/tests/cli/status-command.test.ts +106 -0
  148. package/tests/core/query-handlers.test.ts +187 -0
  149. package/tests/fixtures/auth-fixtures.ts +98 -0
  150. package/tests/fixtures/chat-fixtures.ts +87 -0
  151. package/tests/infrastructure/cli-table.test.ts +107 -0
  152. package/tests/infrastructure/formatters.test.ts +202 -0
  153. package/tests/infrastructure/io.test.ts +197 -0
  154. package/tests/infrastructure/logger.test.ts +74 -0
  155. package/tests/infrastructure/storage.test.ts +335 -0
  156. package/tests/infrastructure/validators.test.ts +67 -0
  157. package/tests/integration/commands/auth.test.ts +186 -0
  158. package/tests/integration/commands/continue.test.ts +130 -0
  159. package/tests/integration/commands/delete.test.ts +140 -0
  160. package/tests/integration/commands/export.test.ts +266 -0
  161. package/tests/integration/commands/fetch.test.ts +238 -0
  162. package/tests/integration/commands/list.test.ts +281 -0
  163. package/tests/integration/commands/profile.test.ts +258 -0
  164. package/tests/integration/commands/status.test.ts +223 -0
  165. package/tests/integration/installer-script-shape.test.ts +67 -0
  166. package/tests/parity/compare-outputs.ts +359 -0
  167. package/tests/parity/test-commands-parity.ps1 +82 -0
  168. package/tests/parity/test-commands-parity.sh +61 -0
  169. package/tests/services/auth-service.test.ts +315 -0
  170. package/tests/services/cookie-monitor.test.ts +229 -0
  171. package/tests/services/cookie-storage-service.test.ts +230 -0
  172. package/tests/services/gemini-client-wrapper.test.ts +688 -0
  173. package/tests/services/install-browser-service.test.ts +83 -0
  174. package/tests/services/playwright-cli-driver.test.ts +468 -0
  175. package/tests/services/profile-auth-manager.test.ts +334 -0
  176. package/tests/services/profile-service.test.ts +224 -0
  177. package/tests/setup.ts +95 -0
  178. package/tests/smoke/smoke.test.ts +52 -0
  179. package/tests/unit/config.test.ts +159 -0
  180. package/tests/unit/errors.test.ts +119 -0
  181. package/tests/unit/formatters.test.ts +316 -0
  182. package/tests/unit/mediator.test.ts +152 -0
  183. package/tests/unit/path-utils.test.ts +289 -0
  184. package/tsconfig.json +16 -0
@@ -0,0 +1,359 @@
1
+ import { execSync } from "node:child_process";
2
+ import { resolve, join } from "node:path";
3
+ import { platform, tmpdir } from "node:os";
4
+ import { mkdtempSync } from "node:fs";
5
+
6
+ interface CliRunResult {
7
+ stdout: string;
8
+ stderr: string;
9
+ exitCode: number;
10
+ command: string;
11
+ timedOut: boolean;
12
+ }
13
+
14
+ interface ParityResult {
15
+ command: string[];
16
+ python: CliRunResult | null;
17
+ bun: CliRunResult;
18
+ exitCodeMatch: boolean;
19
+ stdoutMatch: boolean;
20
+ stderrMatch: boolean;
21
+ normalizedStdoutMatch: boolean;
22
+ normalizedStderrMatch: boolean;
23
+ discrepancies: string[];
24
+ }
25
+
26
+ interface ParityReport {
27
+ timestamp: string;
28
+ platform: string;
29
+ pythonCli: string;
30
+ bunCli: string;
31
+ bunVersion: string;
32
+ results: ParityResult[];
33
+ summary: {
34
+ total: number;
35
+ passed: number;
36
+ failed: number;
37
+ skipped: number;
38
+ };
39
+ }
40
+
41
+ function normalizeOutput(output: string): string {
42
+ return output
43
+ .replace(/\r\n/g, "\n")
44
+ .replace(/\s+$/gm, "")
45
+ .trim();
46
+ }
47
+
48
+ function normalizeVersionSpecific(output: string, cliType: "python" | "bun"): string {
49
+ let normalized = output;
50
+ if (cliType === "python") {
51
+ normalized = normalized.replace(/gemiterm\s+v?\d+\.\d+\.\d+/g, "gemiterm vX.Y.Z");
52
+ } else {
53
+ normalized = normalized.replace(/gemiterm\s+v?\d+\.\d+\.\d+/g, "gemiterm vX.Y.Z");
54
+ }
55
+ normalized = normalized.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[.]\d+Z/g, "<timestamp>");
56
+ normalized = normalized.replace(/\/tmp\/[^\s/]+/g, "<tmpdir>");
57
+ normalized = normalized.replace(/C:\\[Uu]sers\\[^\s\\]+/g, "<userdir>");
58
+ normalized = normalized.replace(/\/home\/[^\s/]+/g, "<homedir>");
59
+ return normalized;
60
+ }
61
+
62
+ function runPythonCli(
63
+ command: string[],
64
+ configDir: string,
65
+ timeoutMs = 10_000,
66
+ ): CliRunResult | null {
67
+ const pythonCli = process.env.GEMITERM_PYTHON_CLI || "gemiterm";
68
+ try {
69
+ const fullCmd = `${pythonCli} ${command.join(" ")}`;
70
+ const stdout = execSync(fullCmd, {
71
+ encoding: "utf-8",
72
+ timeout: timeoutMs,
73
+ env: { ...process.env, GEMITERM_CONFIG_DIR: configDir },
74
+ stdio: ["pipe", "pipe", "pipe"],
75
+ });
76
+ return {
77
+ stdout: stdout || "",
78
+ stderr: "",
79
+ exitCode: 0,
80
+ command: fullCmd,
81
+ timedOut: false,
82
+ };
83
+ } catch (error: any) {
84
+ if (error.status === null) {
85
+ return {
86
+ stdout: "",
87
+ stderr: `Command timed out after ${timeoutMs}ms`,
88
+ exitCode: -1,
89
+ command: `${pythonCli} ${command.join(" ")}`,
90
+ timedOut: true,
91
+ };
92
+ }
93
+ return {
94
+ stdout: error.stdout || "",
95
+ stderr: error.stderr || "",
96
+ exitCode: error.status || 1,
97
+ command: `${pythonCli} ${command.join(" ")}`,
98
+ timedOut: false,
99
+ };
100
+ }
101
+ }
102
+
103
+ function runBunCli(
104
+ command: string[],
105
+ configDir: string,
106
+ timeoutMs = 10_000,
107
+ ): CliRunResult {
108
+ const bunCli = resolve(import.meta.dir, "..", "..", "src", "cli", "index.ts");
109
+ const fullCmd = `bun ${bunCli} ${command.join(" ")}`;
110
+ try {
111
+ const stdout = execSync(fullCmd, {
112
+ encoding: "utf-8",
113
+ timeout: timeoutMs,
114
+ env: { ...process.env, GEMITERM_CONFIG_DIR: configDir },
115
+ stdio: ["pipe", "pipe", "pipe"],
116
+ });
117
+ return {
118
+ stdout: stdout || "",
119
+ stderr: "",
120
+ exitCode: 0,
121
+ command: fullCmd,
122
+ timedOut: false,
123
+ };
124
+ } catch (error: any) {
125
+ if (error.status === null) {
126
+ return {
127
+ stdout: "",
128
+ stderr: `Command timed out after ${timeoutMs}ms`,
129
+ exitCode: -1,
130
+ command: fullCmd,
131
+ timedOut: true,
132
+ };
133
+ }
134
+ return {
135
+ stdout: error.stdout || "",
136
+ stderr: error.stderr || "",
137
+ exitCode: error.status || 1,
138
+ command: fullCmd,
139
+ timedOut: false,
140
+ };
141
+ }
142
+ }
143
+
144
+ function compareResults(command: string[], configDir: string): ParityResult {
145
+ const bunResult = runBunCli(command, configDir);
146
+ const pythonResult = runPythonCli(command, configDir);
147
+
148
+ const discrepancies: string[] = [];
149
+
150
+ let exitCodeMatch = false;
151
+ if (pythonResult === null) {
152
+ discrepancies.push("Python CLI not available - could not compare");
153
+ } else {
154
+ exitCodeMatch = bunResult.exitCode === pythonResult.exitCode;
155
+ if (!exitCodeMatch) {
156
+ discrepancies.push(
157
+ `Exit code mismatch: Bun=${bunResult.exitCode}, Python=${pythonResult.exitCode}`,
158
+ );
159
+ }
160
+
161
+ const bunNorm = normalizeVersionSpecific(normalizeOutput(bunResult.stdout), "bun");
162
+ const pyNorm = normalizeVersionSpecific(normalizeOutput(pythonResult.stdout), "python");
163
+ if (bunNorm !== pyNorm) {
164
+ discrepancies.push("stdout content differs (after normalization)");
165
+ }
166
+
167
+ const bunErrNorm = normalizeVersionSpecific(normalizeOutput(bunResult.stderr), "bun");
168
+ const pyErrNorm = normalizeVersionSpecific(normalizeOutput(pythonResult.stderr), "python");
169
+ if (bunErrNorm !== pyErrNorm) {
170
+ discrepancies.push("stderr content differs (after normalization)");
171
+ }
172
+ }
173
+
174
+ const bunNorm = normalizeOutput(bunResult.stdout);
175
+ const pyNorm = pythonResult
176
+ ? normalizeVersionSpecific(normalizeOutput(pythonResult.stdout), "python")
177
+ : "";
178
+ const bunErrNorm = normalizeOutput(bunResult.stderr);
179
+ const pyErrNorm = pythonResult
180
+ ? normalizeVersionSpecific(normalizeOutput(pythonResult.stderr), "python")
181
+ : "";
182
+
183
+ return {
184
+ command,
185
+ python: pythonResult,
186
+ bun: bunResult,
187
+ exitCodeMatch: pythonResult === null ? true : exitCodeMatch,
188
+ stdoutMatch: pythonResult === null ? true : bunResult.stdout === pythonResult.stdout,
189
+ stderrMatch: pythonResult === null ? true : bunResult.stderr === pythonResult.stderr,
190
+ normalizedStdoutMatch: pythonResult === null ? true : bunNorm === pyNorm,
191
+ normalizedStderrMatch: pythonResult === null ? true : bunErrNorm === pyErrNorm,
192
+ discrepancies,
193
+ };
194
+ }
195
+
196
+ function generateReport(results: ParityResult[]): ParityReport {
197
+ const summary = {
198
+ total: results.length,
199
+ passed: 0,
200
+ failed: 0,
201
+ skipped: 0,
202
+ };
203
+
204
+ for (const result of results) {
205
+ if (result.python === null) {
206
+ summary.skipped++;
207
+ } else if (result.discrepancies.length === 0) {
208
+ summary.passed++;
209
+ } else {
210
+ summary.failed++;
211
+ }
212
+ }
213
+
214
+ return {
215
+ timestamp: new Date().toISOString(),
216
+ platform: `${platform()}-${process.arch}`,
217
+ pythonCli: process.env.GEMITERM_PYTHON_CLI || "gemiterm",
218
+ bunCli: "bun run src/cli/index.ts",
219
+ bunVersion: Bun.version,
220
+ results,
221
+ summary,
222
+ };
223
+ }
224
+
225
+ function formatReport(report: ParityReport): string {
226
+ const lines: string[] = [];
227
+ lines.push("=".repeat(70));
228
+ lines.push("PARITY REPORT: Python gemiterm vs Bun gemiterm");
229
+ lines.push("=".repeat(70));
230
+ lines.push(`Timestamp : ${report.timestamp}`);
231
+ lines.push(`Platform : ${report.platform}`);
232
+ lines.push(`Python CLI: ${report.pythonCli}`);
233
+ lines.push(`Bun CLI : ${report.bunCli}`);
234
+ lines.push(`Bun version: ${report.bunVersion}`);
235
+ lines.push("");
236
+ lines.push(`Summary: ${report.summary.passed} passed, ${report.summary.failed} failed, ${report.summary.skipped} skipped out of ${report.summary.total} total`);
237
+ lines.push("");
238
+
239
+ for (const result of report.results) {
240
+ const status =
241
+ result.python === null
242
+ ? "SKIPPED"
243
+ : result.discrepancies.length === 0
244
+ ? "PASS"
245
+ : "FAIL";
246
+ const icon = status === "PASS" ? "OK" : status === "FAIL" ? "!!" : "--";
247
+ lines.push(`[${icon}] ${result.command.join(" ")}`);
248
+
249
+ if (status === "FAIL") {
250
+ for (const d of result.discrepancies) {
251
+ lines.push(` - ${d}`);
252
+ }
253
+
254
+ if (result.python) {
255
+ const pyOut = normalizeOutput(result.python.stdout);
256
+ const bunOut = normalizeOutput(result.bun.stdout);
257
+ if (pyOut !== bunOut) {
258
+ lines.push(" Python stdout:");
259
+ lines.push(indentLines(pyOut, 6));
260
+ lines.push(" Bun stdout:");
261
+ lines.push(indentLines(bunOut, 6));
262
+ }
263
+
264
+ const pyErr = normalizeOutput(result.python.stderr);
265
+ const bunErr = normalizeOutput(result.bun.stderr);
266
+ if (pyErr !== bunErr) {
267
+ lines.push(" Python stderr:");
268
+ lines.push(indentLines(pyErr, 6));
269
+ lines.push(" Bun stderr:");
270
+ lines.push(indentLines(bunErr, 6));
271
+ }
272
+ }
273
+ }
274
+
275
+ lines.push("");
276
+ }
277
+
278
+ return lines.join("\n");
279
+ }
280
+
281
+ function indentLines(text: string, indent: number): string {
282
+ const prefix = " ".repeat(indent);
283
+ return text
284
+ .split("\n")
285
+ .map((line) => prefix + line)
286
+ .join("\n");
287
+ }
288
+
289
+ const HELP_COMMANDS: string[][] = [
290
+ ["--help"],
291
+ ["--version"],
292
+ ["status", "--help"],
293
+ ["list", "--help"],
294
+ ["fetch", "--help"],
295
+ ["export", "--help"],
296
+ ["export-all", "--help"],
297
+ ["delete", "--help"],
298
+ ["new", "--help"],
299
+ ["continue", "--help"],
300
+ ["profile", "--help"],
301
+ ["auth", "--help"],
302
+ ];
303
+
304
+ const STATUS_COMMANDS: string[][] = [
305
+ ["status"],
306
+ ];
307
+
308
+ const LIST_COMMANDS: string[][] = [
309
+ ["list"],
310
+ ["list", "--limit", "5"],
311
+ ["list", "--format", "json"],
312
+ ];
313
+
314
+ const DEFAULT_TEST_COMMANDS: string[][] = [
315
+ ...HELP_COMMANDS,
316
+ ...STATUS_COMMANDS,
317
+ ...LIST_COMMANDS,
318
+ ];
319
+
320
+ function createTestConfigDir(): string {
321
+ return mkdtempSync(join(tmpdir(), "gemiterm-parity-"));
322
+ }
323
+
324
+ export async function runParityComparison(
325
+ commands?: string[][],
326
+ configDir?: string,
327
+ ): Promise<ParityReport> {
328
+ const useConfigDir = configDir || createTestConfigDir();
329
+ const cmds = commands || DEFAULT_TEST_COMMANDS;
330
+ const results: ParityResult[] = [];
331
+
332
+ for (const cmd of cmds) {
333
+ const result = compareResults(cmd, useConfigDir);
334
+ results.push(result);
335
+ }
336
+
337
+ return generateReport(results);
338
+ }
339
+
340
+ if (import.meta.main) {
341
+ const args = process.argv.slice(2);
342
+ let commands = DEFAULT_TEST_COMMANDS;
343
+ let configDir: string | undefined;
344
+
345
+ for (let i = 0; i < args.length; i++) {
346
+ if (args[i] === "--commands" && args[i + 1]) {
347
+ commands = args[i + 1].split(",").map((c) => c.split(" "));
348
+ i++;
349
+ } else if (args[i] === "--config-dir" && args[i + 1]) {
350
+ configDir = args[i + 1];
351
+ i++;
352
+ }
353
+ }
354
+
355
+ const report = await runParityComparison(commands, configDir);
356
+ console.log(formatReport(report));
357
+
358
+ process.exit(report.summary.failed > 0 ? 1 : 0);
359
+ }
@@ -0,0 +1,82 @@
1
+ #Requires -Version 5.1
2
+ [CmdletBinding()]
3
+ param(
4
+ [string[]]$Commands,
5
+ [string]$PythonCli = $(if ($env:GEMITERM_PYTHON_CLI) { $env:GEMITERM_PYTHON_CLI } else { "gemiterm" }),
6
+ [string]$ReportDir
7
+ )
8
+
9
+ $ErrorActionPreference = "Stop"
10
+
11
+ $ScriptDir = $PSScriptRoot
12
+ $ProjectRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path
13
+ $CompareScript = Join-Path $ProjectRoot "tests\parity\compare-outputs.ts"
14
+
15
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
16
+ Write-Error "ERROR: bun is not installed or not on PATH"
17
+ exit 1
18
+ }
19
+
20
+ if (-not $ReportDir) {
21
+ $ReportDir = Join-Path $ProjectRoot "reports\parity"
22
+ }
23
+
24
+ $DefaultCommands = @(
25
+ "--help",
26
+ "--version",
27
+ "auth --help",
28
+ "status --help",
29
+ "list --help",
30
+ "fetch --help",
31
+ "continue --help",
32
+ "new --help",
33
+ "delete --help",
34
+ "export --help",
35
+ "export-all --help",
36
+ "profile --help",
37
+ "status",
38
+ "list",
39
+ "list --limit 5",
40
+ "list --format json",
41
+ "auth"
42
+ )
43
+
44
+ $Cmds = if ($Commands -and $Commands.Count -gt 0) { $Commands } else { $DefaultCommands }
45
+
46
+ Write-Host "=== GemiTerm Parity Test Suite ==="
47
+ Write-Host "Python CLI : $PythonCli"
48
+ Write-Host "Bun CLI : bun run $CompareScript"
49
+ Write-Host "Commands : $($Cmds.Count)"
50
+ Write-Host ""
51
+
52
+ if (-not (Test-Path $ReportDir)) {
53
+ New-Item -ItemType Directory -Path $ReportDir -Force | Out-Null
54
+ }
55
+
56
+ $Timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
57
+ $ReportFile = Join-Path $ReportDir "parity-$Timestamp.txt"
58
+
59
+ $CommaSeparated = $Cmds -join ","
60
+
61
+ Write-Host "Running parity comparison..."
62
+
63
+ $env:GEMITERM_PYTHON_CLI = $PythonCli
64
+
65
+ try {
66
+ $output = & bun $CompareScript --commands $CommaSeparated 2>&1
67
+ $output | Out-File -FilePath $ReportFile -Encoding utf8
68
+ $output | ForEach-Object { Write-Host $_ }
69
+
70
+ Write-Host ""
71
+ Write-Host "Parity report saved to: $ReportFile"
72
+ exit 0
73
+ } catch {
74
+ $exitCode = $LASTEXITCODE
75
+ $output | Out-File -FilePath $ReportFile -Encoding utf8
76
+ $output | ForEach-Object { Write-Host $_ }
77
+
78
+ Write-Host ""
79
+ Write-Host "Parity report saved to: $ReportFile"
80
+ Write-Host "Some tests FAILED. Review the report above."
81
+ exit 1
82
+ }
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
6
+ COMPARE_SCRIPT="$PROJECT_ROOT/tests/parity/compare-outputs.ts"
7
+ REPORT_DIR="${REPORT_DIR:-$SCRIPT_DIR/../../reports/parity}"
8
+ TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
9
+ PYTHON_CLI="${GEMITERM_PYTHON_CLI:-gemiterm}"
10
+
11
+ if ! command -v bun &>/dev/null; then
12
+ echo "ERROR: bun is not installed or not on PATH"
13
+ exit 1
14
+ fi
15
+
16
+ COMMANDS=(
17
+ "--help"
18
+ "--version"
19
+ "auth --help"
20
+ "status --help"
21
+ "list --help"
22
+ "fetch --help"
23
+ "continue --help"
24
+ "new --help"
25
+ "delete --help"
26
+ "export --help"
27
+ "export-all --help"
28
+ "profile --help"
29
+ "status"
30
+ "list"
31
+ "list --limit 5"
32
+ "list --format json"
33
+ "auth"
34
+ )
35
+
36
+ if [[ $# -gt 0 ]]; then
37
+ COMMANDS=("$@")
38
+ fi
39
+
40
+ echo "=== GemiTerm Parity Test Suite ==="
41
+ echo "Python CLI : $PYTHON_CLI"
42
+ echo "Bun CLI : bun run $COMPARE_SCRIPT"
43
+ echo "Commands : ${#COMMANDS[@]}"
44
+ echo ""
45
+
46
+ mkdir -p "$REPORT_DIR"
47
+
48
+ COMMA_SEPARATED=$(IFS=,; echo "${COMMANDS[*]}")
49
+ REPORT_FILE="$REPORT_DIR/parity-$TIMESTAMP.txt"
50
+
51
+ echo "Running parity comparison..."
52
+ if GEMITERM_PYTHON_CLI="$PYTHON_CLI" bun "$COMPARE_SCRIPT" --commands "$COMMA_SEPARATED" | tee "$REPORT_FILE"; then
53
+ echo ""
54
+ echo "Parity report saved to: $REPORT_FILE"
55
+ exit 0
56
+ else
57
+ echo ""
58
+ echo "Parity report saved to: $REPORT_FILE"
59
+ echo "Some tests FAILED. Review the report above."
60
+ exit 1
61
+ fi