@thegitai/cli 1.0.0-preview.21 → 1.0.0-preview.23

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.
package/README.md CHANGED
@@ -34,7 +34,7 @@ always be viewed or resumed from that computer. Continuing it through the
34
34
  service requires the TheGitAI account used for that session. Local sessions can
35
35
  also be listed while signed out or offline.
36
36
 
37
- CLI login tokens use a rolling 24-hour inactivity timeout. If one expires during
37
+ CLI login tokens use a rolling 48-hour inactivity timeout. If one expires during
38
38
  any server request, the CLI saves the local session, removes the expired
39
39
  credential, and asks you to run `ai login` before resuming.
40
40
 
@@ -3,7 +3,7 @@ import { createPromptCheckpoint, sanitizeSessionSafetyForServer, } from '../sess
3
3
  import { applySessionSnapshot, saveSessionState, snapshotFromSession, } from '../session-store.js';
4
4
  import { executeLocalToolCall } from '../tool-executor.js';
5
5
  import { isUserInputQuestionArray } from './contracts.js';
6
- import { createTraceContext, normalizeServerUrl, readErrorResponse, } from './http.js';
6
+ import { createTraceContext, gatewayFailureCategory, normalizeServerUrl, readErrorResponse, } from './http.js';
7
7
  import { collectClientEnvironment } from '../client-environment.js';
8
8
  import { collectProjectOrientation } from '../project-orientation.js';
9
9
  import { autoAttachImages } from '../core/image-path-extractor.js';
@@ -587,7 +587,10 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
587
587
  ? error
588
588
  : new TurnCancelledError();
589
589
  }
590
- const category = error instanceof ChatTurnFailedError ? error.category : 'unknown_error';
590
+ const category = error instanceof ChatTurnFailedError
591
+ ? error.category
592
+ :
593
+ gatewayFailureCategory(error) ?? 'unknown_error';
591
594
  const partial = error instanceof ChatTurnFailedError ? error.partialSnapshot : undefined;
592
595
  if (partial) {
593
596
  applySessionSnapshot(session, partial, { preserveAgentMode: true });
@@ -84,6 +84,41 @@ export function normalizeServerUrl(serverUrl) {
84
84
  }
85
85
  return normalized;
86
86
  }
87
+ const MAX_NON_JSON_BODY_CHARS = 200;
88
+ const GATEWAY_STATUS_MESSAGES = {
89
+ 502: 'Could not reach the TheGitAI server (bad gateway).',
90
+ 503: 'The TheGitAI server is temporarily unavailable.',
91
+ 504: 'The TheGitAI server did not respond in time (gateway timeout).',
92
+ 520: 'The connection to the TheGitAI server failed (edge error 520).',
93
+ 521: 'The TheGitAI server is not accepting connections (edge error 521).',
94
+ 522: 'The connection to the TheGitAI server timed out (edge error 522).',
95
+ 523: 'The TheGitAI server is unreachable (edge error 523).',
96
+ 524: 'The TheGitAI server did not respond in time (edge error 524).',
97
+ };
98
+ function truncateByCodePoint(text, maxChars) {
99
+ if (text.length <= maxChars)
100
+ return text;
101
+ const points = Array.from(text);
102
+ if (points.length <= maxChars)
103
+ return text;
104
+ return `${points.slice(0, maxChars).join('').trimEnd()}…`;
105
+ }
106
+ export function nonJsonErrorMessage(body, status) {
107
+ const rayId = /Cloudflare Ray ID:\s*(?:<[^>]*>\s*)*([0-9a-f]{8,})/i.exec(body)?.[1];
108
+ const gateway = GATEWAY_STATUS_MESSAGES[status];
109
+ const summary = truncateByCodePoint(body
110
+ .replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, ' ')
111
+ .replace(/<[^>]*>/g, ' ')
112
+ .replace(/\s+/g, ' ')
113
+ .trim(), MAX_NON_JSON_BODY_CHARS);
114
+ const base = gateway ?? (summary || `Request failed with ${status}`);
115
+ return rayId ? `${base} (Cloudflare Ray ID: ${rayId})` : base;
116
+ }
117
+ export function gatewayFailureCategory(error) {
118
+ if (!(error instanceof ServerApiError))
119
+ return null;
120
+ return error.status in GATEWAY_STATUS_MESSAGES ? 'gateway_error' : null;
121
+ }
87
122
  export async function readJsonResponse(response) {
88
123
  const text = await response.text();
89
124
  if (!text.trim())
@@ -92,7 +127,9 @@ export async function readJsonResponse(response) {
92
127
  return JSON.parse(text);
93
128
  }
94
129
  catch {
95
- return { error: { message: text } };
130
+ return {
131
+ error: { message: nonJsonErrorMessage(text, response.status) },
132
+ };
96
133
  }
97
134
  }
98
135
  export function failureMessage(data, status) {
@@ -106,7 +143,7 @@ export function isAuthenticationError(error) {
106
143
  }
107
144
  export function authenticationErrorMessage(error) {
108
145
  return error.code === 'AUTH_TOKEN_EXPIRED'
109
- ? 'Your login expired after 24 hours of inactivity. Run `ai login` and resume this saved session.'
146
+ ? 'Your login expired after 48 hours of inactivity. Run `ai login` and resume this saved session.'
110
147
  : 'Your login is no longer valid. Run `ai login` and resume this saved session.';
111
148
  }
112
149
  export async function readErrorResponse(response, traceId = response.headers.get(TRACE_ID_HEADER) ?? '') {
@@ -1,6 +1,6 @@
1
1
  import chalk from './colors.js';
2
2
  import { spawn } from 'child_process';
3
- import { buildCommandEnv, commandUsesSudo, sanitizeCommandText, terminateChild, } from './executor.js';
3
+ import { buildCommandEnv, commandUsesSudo, resolveCommandShell, sanitizeCommandText, terminateChild, } from './executor.js';
4
4
  import { isTuiMode } from './runtime-mode.js';
5
5
  import { redactConnectionStringCredentials } from './secret-preview.js';
6
6
  const MAX_RUNNING_JOBS = 8;
@@ -196,7 +196,7 @@ export async function startBackgroundJob(command, cwd, { startupWaitMs, sessionI
196
196
  const id = `bg_${++jobCounter}`;
197
197
  const child = spawn(command, {
198
198
  cwd,
199
- shell: true,
199
+ shell: resolveCommandShell(),
200
200
  detached: process.platform !== 'win32',
201
201
  stdio: ['pipe', 'pipe', 'pipe'],
202
202
  env: buildCommandEnv(cwd),
@@ -1,6 +1,6 @@
1
1
  import chalk from './colors.js';
2
2
  import { execFileSync, spawn } from 'child_process';
3
- import { existsSync, statSync } from 'fs';
3
+ import { accessSync, constants as fsConstants, existsSync, statSync } from 'fs';
4
4
  import { createRequire } from 'node:module';
5
5
  import os from 'os';
6
6
  import path from 'path';
@@ -637,6 +637,28 @@ export function buildCommandEnv(cwd) {
637
637
  THEGITAI_SCRATCH_DIR: ensureSessionScratchDir(),
638
638
  };
639
639
  }
640
+ let cachedCommandShell;
641
+ function findBashPath() {
642
+ const fromPath = (process.env.PATH ?? '')
643
+ .split(path.delimiter)
644
+ .filter(Boolean)
645
+ .map((dir) => path.join(dir, 'bash'));
646
+ for (const candidate of [...fromPath, '/bin/bash', '/usr/bin/bash']) {
647
+ try {
648
+ accessSync(candidate, fsConstants.X_OK);
649
+ return candidate;
650
+ }
651
+ catch { }
652
+ }
653
+ return null;
654
+ }
655
+ export function resolveCommandShell() {
656
+ if (cachedCommandShell !== undefined)
657
+ return cachedCommandShell;
658
+ cachedCommandShell =
659
+ process.platform === 'win32' ? true : (findBashPath() ?? true);
660
+ return cachedCommandShell;
661
+ }
640
662
  function sanitizePtyOutput(command, output, cwd, secrets) {
641
663
  return sanitizeCommandText(command, stripSudoPromptText(redactSecrets(output, secrets)), cwd);
642
664
  }
@@ -822,7 +844,7 @@ export async function runCommand(command, cwd, { requestSudoPassword, timeout, }
822
844
  let killTimer = null;
823
845
  const child = spawn(command, {
824
846
  cwd,
825
- shell: true,
847
+ shell: resolveCommandShell(),
826
848
  detached: process.platform !== 'win32',
827
849
  stdio: ['pipe', 'pipe', 'pipe'],
828
850
  env: buildCommandEnv(cwd),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegitai/cli",
3
- "version": "1.0.0-preview.21",
3
+ "version": "1.0.0-preview.23",
4
4
  "description": "TheGitAI is an AI coding agent for your terminal. It indexes your repository, writes and edits files, runs commands, and builds features with you.",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,10 +37,10 @@
37
37
  "@lydell/node-pty-linux-x64": "1.1.0",
38
38
  "@lydell/node-pty-win32-arm64": "1.1.0",
39
39
  "@lydell/node-pty-win32-x64": "1.1.0",
40
- "@thegitai/tui-darwin-arm64": "1.0.0-preview.21",
41
- "@thegitai/tui-darwin-x64": "1.0.0-preview.21",
42
- "@thegitai/tui-linux-x64": "1.0.0-preview.21",
43
- "@thegitai/tui-win32-x64": "1.0.0-preview.21",
40
+ "@thegitai/tui-darwin-arm64": "1.0.0-preview.23",
41
+ "@thegitai/tui-darwin-x64": "1.0.0-preview.23",
42
+ "@thegitai/tui-linux-x64": "1.0.0-preview.23",
43
+ "@thegitai/tui-win32-x64": "1.0.0-preview.23",
44
44
  "@vscode/ripgrep": "1.18.0"
45
45
  },
46
46
  "publishConfig": {