@thegitai/cli 1.0.0-beta.4 → 1.0.0-beta.6

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
@@ -20,6 +20,7 @@ ai login sign in via your browser (--no-browser for SSH/headless)
20
20
  ai whoami show the signed-in account
21
21
  ai --usage show account usage and reset times
22
22
  ai logout sign out
23
+ ai --version print the version and exit
23
24
  ```
24
25
 
25
26
  Run `ai --help` for sessions, modes, keys, and chat commands.
package/dist/bin/ai.js CHANGED
@@ -13,59 +13,13 @@ import { runClientInteractive, shouldUseClientRatatuiShell, } from '../src/ui/re
13
13
  import { appendPromptToFile } from '../src/ui/prompt-history-store.js';
14
14
  import { formatSessionExitNotice } from '../src/session-exit.js';
15
15
  import { formatUsageText } from '../src/usage.js';
16
+ import { formatVersionLine } from '../src/version.js';
17
+ import { parseArgs } from '../src/cli-args.js';
16
18
  const DEFAULT_SERVER_URL = 'https://thegit.ai';
17
- const AUTH_COMMANDS = new Set(['login', 'whoami', 'logout']);
18
19
  const { auth, chat, models, sessions } = ServerApi;
19
20
  function printUsage() {
20
21
  console.log(formatCliHelpText({ color: process.stdout.isTTY === true }));
21
22
  }
22
- export function parseArgs(argv) {
23
- const args = argv.slice(2);
24
- const firstArg = args[0];
25
- const command = firstArg && AUTH_COMMANDS.has(firstArg) ? firstArg : null;
26
- const commandArgs = command ? args.slice(1) : [];
27
- let autoYes = false;
28
- let help = false;
29
- let usage = false;
30
- let session = null;
31
- let listSessions = false;
32
- const promptParts = [];
33
- for (let i = 0; i < args.length; i++) {
34
- const arg = args[i];
35
- if (arg === '--yes' || arg === '-y') {
36
- autoYes = true;
37
- continue;
38
- }
39
- if ((arg === '--session' || arg === '--resume') && i + 1 < args.length) {
40
- session = args[i + 1] ?? null;
41
- i += 1;
42
- continue;
43
- }
44
- if (arg === '--list-sessions') {
45
- listSessions = true;
46
- continue;
47
- }
48
- if (arg === '--help' || arg === '-h') {
49
- help = true;
50
- continue;
51
- }
52
- if (arg === '--usage') {
53
- usage = true;
54
- continue;
55
- }
56
- promptParts.push(arg);
57
- }
58
- return {
59
- command,
60
- commandArgs,
61
- autoYes,
62
- help,
63
- usage,
64
- session,
65
- listSessions,
66
- prompt: promptParts.join(' ').trim(),
67
- };
68
- }
69
23
  function commandFlagValue(args, name) {
70
24
  const index = args.indexOf(name);
71
25
  if (index === -1)
@@ -340,11 +294,21 @@ async function mainInteractive({ authConfig, projectIndex, serverModels, serverS
340
294
  }
341
295
  }
342
296
  export async function main() {
343
- const { autoYes, help, usage, command, commandArgs, session: sessionIdentifier, listSessions, prompt, } = parseArgs(process.argv);
297
+ const { autoYes, help, version, usage, command, commandArgs, session: sessionIdentifier, listSessions, unknownOption, prompt, } = parseArgs(process.argv);
298
+ if (version) {
299
+ console.log(formatVersionLine());
300
+ return;
301
+ }
344
302
  if (help) {
345
303
  printUsage();
346
304
  return;
347
305
  }
306
+ if (unknownOption) {
307
+ console.error(`Unknown option: ${unknownOption}`);
308
+ console.error("Run 'ai --help' to see available commands and options.");
309
+ process.exitCode = 2;
310
+ return;
311
+ }
348
312
  if (command) {
349
313
  await runAuthCommand(command, commandArgs);
350
314
  return;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { getClientStateDir } from '../client-state.js';
4
- import { authorizedJson, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
4
+ import { ServerApiError, authorizedJson, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
5
5
  export function getAuthConfigPath(env = process.env) {
6
6
  const configured = String(env.THEGITAI_AUTH_CONFIG ?? '').trim();
7
7
  if (configured) {
@@ -68,14 +68,16 @@ export async function fetchWhoamiResponse({ config, fetchImpl = globalThis.fetch
68
68
  };
69
69
  }
70
70
  export async function logoutFromServer({ config, fetchImpl = globalThis.fetch, }) {
71
+ const trace = createTraceContext();
71
72
  const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/auth/logout`, {
72
73
  method: 'POST',
73
74
  headers: {
74
75
  authorization: `Bearer ${config.token}`,
76
+ ...trace.headers,
75
77
  },
76
78
  });
77
79
  if (!response.ok && response.status !== 401) {
78
80
  const data = (await readJsonResponse(response));
79
- throw new Error(failureMessage(data, response.status));
81
+ throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
80
82
  }
81
83
  }
@@ -2,7 +2,7 @@ import crypto from 'node:crypto';
2
2
  import http from 'node:http';
3
3
  import os from 'node:os';
4
4
  import { openUrl } from '../core/open-url.js';
5
- import { failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
5
+ import { ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
6
6
  const DEFAULT_WEBSITE_URL = 'https://thegit.ai';
7
7
  const DEFAULT_DEV_WEBSITE_URL = 'http://localhost:3002';
8
8
  const DEFAULT_SERVER_URL = 'https://thegit.ai';
@@ -73,14 +73,15 @@ const RESULT_PAGE = (heading, detail) => `<!doctype html><html><head><meta chars
73
73
  `p{color:#9aa0a6}</style></head><body><div class="card"><h1>${heading}</h1>` +
74
74
  `<p>${detail}</p></div></body></html>`;
75
75
  async function exchangeCodeForToken({ serverUrl, code, codeVerifier, fetchImpl, }) {
76
+ const trace = createTraceContext();
76
77
  const response = await fetchImpl(`${serverUrl}/v1/cli/auth/token`, {
77
78
  method: 'POST',
78
- headers: { 'content-type': 'application/json' },
79
+ headers: { 'content-type': 'application/json', ...trace.headers },
79
80
  body: JSON.stringify({ code, code_verifier: codeVerifier }),
80
81
  });
81
82
  const data = (await readJsonResponse(response));
82
83
  if (!response.ok) {
83
- throw new Error(failureMessage(data, response.status));
84
+ throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
84
85
  }
85
86
  const token = String(data?.token ?? '').trim();
86
87
  const customer = data?.customer;
@@ -1,7 +1,7 @@
1
1
  import { createPromptCheckpoint, sanitizeSessionSafetyForServer, } from '../session-safety.js';
2
2
  import { applySessionSnapshot, snapshotFromSession, } from '../session-store.js';
3
3
  import { executeLocalToolCall } from '../tool-executor.js';
4
- import { normalizeServerUrl, readErrorResponse, } from './http.js';
4
+ import { createTraceContext, normalizeServerUrl, readErrorResponse, } from './http.js';
5
5
  export class TurnCancelledError extends Error {
6
6
  name = 'TurnCancelledError';
7
7
  constructor(message = 'Turn cancelled.') {
@@ -12,10 +12,12 @@ export class ChatTurnFailedError extends Error {
12
12
  name = 'ChatTurnFailedError';
13
13
  category;
14
14
  retryable;
15
- constructor(message, category = 'unknown_error', retryable = false) {
16
- super(message);
15
+ traceId;
16
+ constructor(message, category = 'unknown_error', retryable = false, traceId = '') {
17
+ super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
17
18
  this.category = category;
18
19
  this.retryable = retryable;
20
+ this.traceId = traceId;
19
21
  }
20
22
  }
21
23
  export function isTurnCancelledError(error) {
@@ -139,17 +141,19 @@ function publicStatusMessage(data) {
139
141
  return `Running ${toolName} locally...`;
140
142
  return null;
141
143
  }
142
- async function postToolResult({ config, turnId, event, result, session, fetchImpl, }) {
144
+ async function postToolResult({ config, turnId, event, result, session, fetchImpl, traceId, }) {
143
145
  const payload = {
144
146
  toolCallId: event.call.id,
145
147
  result,
146
148
  toolState: toolStateFromSession(session),
147
149
  };
150
+ const trace = createTraceContext(traceId);
148
151
  const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn/${encodeURIComponent(turnId)}/tool-result`, {
149
152
  method: 'POST',
150
153
  headers: {
151
154
  authorization: `Bearer ${config.token}`,
152
155
  'content-type': 'application/json',
156
+ ...trace.headers,
153
157
  },
154
158
  body: JSON.stringify(payload),
155
159
  });
@@ -157,10 +161,10 @@ async function postToolResult({ config, turnId, event, result, session, fetchImp
157
161
  return;
158
162
  }
159
163
  if (!response.ok) {
160
- throw await readErrorResponse(response);
164
+ throw await readErrorResponse(response, trace.traceId);
161
165
  }
162
166
  }
163
- async function executeAndPostToolResult({ config, projectIndex, session, event, input, fetchImpl, signal, }) {
167
+ async function executeAndPostToolResult({ config, projectIndex, session, event, input, fetchImpl, signal, traceId, }) {
164
168
  const turnId = String(event?.turnId ?? '').trim();
165
169
  if (!turnId || !event?.call?.id || !event.call.name) {
166
170
  throw new Error('Server emitted an invalid tool-call event.');
@@ -189,13 +193,14 @@ async function executeAndPostToolResult({ config, projectIndex, session, event,
189
193
  result: rawResult,
190
194
  session,
191
195
  fetchImpl,
196
+ traceId,
192
197
  });
193
198
  }
194
199
  finally {
195
200
  session.turnState.id = previousTurnId;
196
201
  }
197
202
  }
198
- async function consumeTurnStream({ response, config, projectIndex, session, input, fetchImpl, signal, }) {
203
+ async function consumeTurnStream({ response, config, projectIndex, session, input, fetchImpl, signal, traceId, }) {
199
204
  if (!response.body) {
200
205
  throw new Error('Server returned an empty chat stream.');
201
206
  }
@@ -224,6 +229,7 @@ async function consumeTurnStream({ response, config, projectIndex, session, inpu
224
229
  input,
225
230
  fetchImpl,
226
231
  signal,
232
+ traceId,
227
233
  });
228
234
  return;
229
235
  }
@@ -243,7 +249,7 @@ async function consumeTurnStream({ response, config, projectIndex, session, inpu
243
249
  if (event.event === 'cancelled') {
244
250
  throw new TurnCancelledError(message);
245
251
  }
246
- throw new ChatTurnFailedError(message, typeof event.data?.category === 'string' ? event.data.category : 'unknown_error', Boolean(event.data?.retryable));
252
+ throw new ChatTurnFailedError(message, typeof event.data?.category === 'string' ? event.data.category : 'unknown_error', Boolean(event.data?.retryable), typeof event.data?.traceId === 'string' ? event.data.traceId : traceId);
247
253
  }
248
254
  }
249
255
  while (true) {
@@ -287,6 +293,7 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
287
293
  autoYes: session.autoYes,
288
294
  agentMode: session.agentMode,
289
295
  };
296
+ const trace = createTraceContext();
290
297
  const preTurnHistoryLength = session.history.length;
291
298
  const preserveOnAbort = () => preserveCancelledTurnInput(session, input);
292
299
  if (signal?.aborted) {
@@ -302,12 +309,13 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
302
309
  accept: 'text/event-stream',
303
310
  authorization: `Bearer ${config.token}`,
304
311
  'content-type': 'application/json',
312
+ ...trace.headers,
305
313
  },
306
314
  body: JSON.stringify(request),
307
315
  signal,
308
316
  });
309
317
  if (!response.ok) {
310
- throw await readErrorResponse(response);
318
+ throw await readErrorResponse(response, trace.traceId);
311
319
  }
312
320
  const result = await consumeTurnStream({
313
321
  response,
@@ -317,6 +325,7 @@ export async function sendServerUserMessage({ config, projectIndex, session, inp
317
325
  input,
318
326
  fetchImpl,
319
327
  signal,
328
+ traceId: trace.traceId,
320
329
  });
321
330
  applySessionSnapshot(session, result.snapshot, { preserveAgentMode: true });
322
331
  return {
@@ -1,4 +1,31 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  const DEFAULT_SERVER_URL = 'https://thegit.ai';
3
+ export const TRACE_ID_HEADER = 'x-thegitai-trace-id';
4
+ export const CLIENT_HEADER = 'x-thegitai-client';
5
+ export const CLIENT_PLATFORM_HEADER = 'x-thegitai-client-platform';
6
+ export class ServerApiError extends Error {
7
+ status;
8
+ traceId;
9
+ constructor(message, status, traceId) {
10
+ super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
11
+ this.name = 'ServerApiError';
12
+ this.status = status;
13
+ this.traceId = traceId;
14
+ }
15
+ }
16
+ export function createTraceId() {
17
+ return `tr_${randomUUID().replace(/-/g, '')}`;
18
+ }
19
+ export function createTraceContext(traceId = createTraceId()) {
20
+ return {
21
+ traceId,
22
+ headers: {
23
+ [TRACE_ID_HEADER]: traceId,
24
+ [CLIENT_HEADER]: 'cli',
25
+ [CLIENT_PLATFORM_HEADER]: `${process.platform}/${process.arch}`,
26
+ },
27
+ };
28
+ }
2
29
  export function normalizeServerUrl(serverUrl) {
3
30
  const normalized = String(serverUrl || DEFAULT_SERVER_URL)
4
31
  .trim()
@@ -22,15 +49,17 @@ export async function readJsonResponse(response) {
22
49
  export function failureMessage(data, status) {
23
50
  return String(data?.error?.message ?? data?.message ?? `Request failed with ${status}`);
24
51
  }
25
- export async function readErrorResponse(response) {
52
+ export async function readErrorResponse(response, traceId = response.headers.get(TRACE_ID_HEADER) ?? '') {
26
53
  const data = await readJsonResponse(response);
27
- return new Error(failureMessage(data, response.status));
54
+ return new ServerApiError(failureMessage(data, response.status), response.status, traceId);
28
55
  }
29
56
  export async function authorizedJson({ config, path, method = 'GET', body = null, headers = {}, fetchImpl = globalThis.fetch, }) {
57
+ const trace = createTraceContext();
30
58
  const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}${path}`, {
31
59
  method,
32
60
  headers: {
33
61
  authorization: `Bearer ${config.token}`,
62
+ ...trace.headers,
34
63
  ...headers,
35
64
  ...(body === null ? {} : { 'content-type': 'application/json' }),
36
65
  },
@@ -38,7 +67,7 @@ export async function authorizedJson({ config, path, method = 'GET', body = null
38
67
  });
39
68
  const data = await readJsonResponse(response);
40
69
  if (!response.ok) {
41
- throw new Error(failureMessage(data, response.status));
70
+ throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
42
71
  }
43
72
  return data;
44
73
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { getClientStateDir } from '../client-state.js';
4
- import { failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
4
+ import { ServerApiError, createTraceContext, failureMessage, normalizeServerUrl, readJsonResponse, } from './http.js';
5
5
  function sanitizeModelInfo(raw) {
6
6
  if (!raw || typeof raw !== 'object') {
7
7
  return null;
@@ -54,14 +54,16 @@ export function writeCachedServerModels(cache, env = process.env) {
54
54
  });
55
55
  }
56
56
  export async function fetchServerModels({ config, fetchImpl = globalThis.fetch, }) {
57
+ const trace = createTraceContext();
57
58
  const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/models`, {
58
59
  headers: {
59
60
  authorization: `Bearer ${config.token}`,
61
+ ...trace.headers,
60
62
  },
61
63
  });
62
64
  const data = (await readJsonResponse(response));
63
65
  if (!response.ok) {
64
- throw new Error(failureMessage(data, response.status));
66
+ throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId);
65
67
  }
66
68
  const models = Array.isArray(data?.models)
67
69
  ? data.models.map(sanitizeModelInfo).filter(Boolean)
@@ -0,0 +1,65 @@
1
+ export const AUTH_COMMANDS = new Set(['login', 'whoami', 'logout']);
2
+ export function parseArgs(argv) {
3
+ const args = argv.slice(2);
4
+ const firstArg = args[0];
5
+ const command = firstArg && AUTH_COMMANDS.has(firstArg) ? firstArg : null;
6
+ const commandArgs = command ? args.slice(1) : [];
7
+ let autoYes = false;
8
+ let help = false;
9
+ let version = false;
10
+ let usage = false;
11
+ let session = null;
12
+ let listSessions = false;
13
+ let unknownOption = null;
14
+ const promptParts = [];
15
+ for (let i = 0; i < args.length; i++) {
16
+ const arg = args[i];
17
+ if (arg === '--yes' || arg === '-y') {
18
+ autoYes = true;
19
+ continue;
20
+ }
21
+ if ((arg === '--session' || arg === '--resume') && i + 1 < args.length) {
22
+ session = args[i + 1] ?? null;
23
+ i += 1;
24
+ continue;
25
+ }
26
+ if (arg === '--list-sessions') {
27
+ listSessions = true;
28
+ continue;
29
+ }
30
+ if (arg === '--help' || arg === '-h') {
31
+ help = true;
32
+ continue;
33
+ }
34
+ if (arg === '--version' || arg === '-v') {
35
+ version = true;
36
+ continue;
37
+ }
38
+ if (arg === '--usage') {
39
+ usage = true;
40
+ continue;
41
+ }
42
+ // An unrecognized dashed token is a mistyped flag, not prompt text. Without
43
+ // an auth subcommand (whose flags are parsed separately) it would otherwise
44
+ // be swept into the prompt and silently start a billable session. Flag the
45
+ // first one so the caller can fail fast instead. Quoted prompts are a single
46
+ // argv entry with spaces, so they never look like a bare option here.
47
+ if (command === null && unknownOption === null && /^-/.test(arg)) {
48
+ unknownOption = arg;
49
+ continue;
50
+ }
51
+ promptParts.push(arg);
52
+ }
53
+ return {
54
+ command,
55
+ commandArgs,
56
+ autoYes,
57
+ help,
58
+ version,
59
+ usage,
60
+ session,
61
+ listSessions,
62
+ unknownOption,
63
+ prompt: promptParts.join(' ').trim(),
64
+ };
65
+ }
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { getCliVersion, getPlatformTag } from './version.js';
2
3
  // The bound keys (Enter/Esc/Ctrl+C/Tab/arrows) are identical across platforms in
3
4
  // a terminal. The one thing that genuinely differs is the terminal's paste
4
5
  // shortcut, so surface the one for the host OS (right-click paste works
@@ -45,6 +46,7 @@ const HELP_MARKDOWN = [
45
46
  '',
46
47
  '- `-y, --yes` — start in Auto-Accept mode',
47
48
  '- `-h, --help` — show this help',
49
+ '- `-v, --version` — print the version and exit',
48
50
  '',
49
51
  '## Modes',
50
52
  '',
@@ -70,6 +72,7 @@ const HELP_MARKDOWN = [
70
72
  '## Chat commands',
71
73
  '',
72
74
  '- `/help` — show this help',
75
+ '- `/about` — show version and platform info',
73
76
  '- `/usage` — show account usage percentage and reset times',
74
77
  '- `/model` — list supported models and pick one',
75
78
  '- `/model <id>` — switch the active model without clearing history',
@@ -100,6 +103,17 @@ const HELP_MARKDOWN = [
100
103
  '- For anything else, re-run the command and report the printed error',
101
104
  ' message — there is no client-side debug mode by design.',
102
105
  ].join('\n');
106
+ export function formatAboutCard() {
107
+ // Fenced so the column alignment survives terminal markdown rendering.
108
+ return [
109
+ '```',
110
+ 'TheGitAI',
111
+ ` Version ${getCliVersion()}`,
112
+ ` Platform ${getPlatformTag()}`,
113
+ ` Node ${process.version}`,
114
+ '```',
115
+ ].join('\n');
116
+ }
103
117
  export function formatHelpMarkdown() {
104
118
  return HELP_MARKDOWN;
105
119
  }
@@ -11,7 +11,7 @@ import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSes
11
11
  import { truncate } from '../utils.js';
12
12
  import { writeClipboardText } from '../core/clipboard.js';
13
13
  import { openUrl } from '../core/open-url.js';
14
- import { formatInteractiveHelpText } from '../help-text.js';
14
+ import { formatAboutCard, formatInteractiveHelpText } from '../help-text.js';
15
15
  import { expandPastedChunks, } from './paste-collapse.js';
16
16
  import { loadPromptHistory, MAX_PROMPT_HISTORY_ENTRIES, } from './prompt-history-store.js';
17
17
  const RESPONSE_STREAM_CHUNK_SIZE = 4;
@@ -86,6 +86,10 @@ export const SLASH_COMMANDS = [
86
86
  command: '/help',
87
87
  description: 'Show available chat commands',
88
88
  },
89
+ {
90
+ command: '/about',
91
+ description: 'Show version and platform info',
92
+ },
89
93
  {
90
94
  command: '/usage',
91
95
  description: 'Show account usage percentage and reset times',
@@ -1875,6 +1879,14 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
1875
1879
  });
1876
1880
  return;
1877
1881
  }
1882
+ if (input === '/about') {
1883
+ appendStaticEntry({
1884
+ body: formatAboutCard(),
1885
+ kind: 'system',
1886
+ title: 'About',
1887
+ });
1888
+ return;
1889
+ }
1878
1890
  if (input === '/usage') {
1879
1891
  store.update((current) => ({
1880
1892
  ...current,
@@ -0,0 +1,35 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const PACKAGE_NAME = '@thegitai/cli';
5
+ const UNKNOWN_VERSION = '0.0.0';
6
+ // Resolve the package version at runtime by walking up from this module to the
7
+ // nearest package.json named @thegitai/cli. This works in both layouts: the
8
+ // compiled binary (dist/bin/ai.js → ../../package.json) and the source tree run
9
+ // under tsx in tests (src/version.ts → ../package.json). The name guard avoids
10
+ // picking up an unrelated manifest if the file is ever nested elsewhere.
11
+ export function getCliVersion() {
12
+ let dir = path.dirname(fileURLToPath(import.meta.url));
13
+ for (let depth = 0; depth < 6; depth++) {
14
+ try {
15
+ const pkg = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8'));
16
+ if (pkg.name === PACKAGE_NAME && typeof pkg.version === 'string') {
17
+ return pkg.version;
18
+ }
19
+ }
20
+ catch {
21
+ // No package.json at this level (or unreadable); keep walking up.
22
+ }
23
+ const parent = path.dirname(dir);
24
+ if (parent === dir)
25
+ break;
26
+ dir = parent;
27
+ }
28
+ return UNKNOWN_VERSION;
29
+ }
30
+ export function getPlatformTag() {
31
+ return `${process.platform}-${process.arch}`;
32
+ }
33
+ export function formatVersionLine() {
34
+ return `ai ${getCliVersion()} (${getPlatformTag()}, node ${process.version})`;
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegitai/cli",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "TheGitAI CLI client (source-visible, proprietary)",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://thegit.ai",
@@ -27,10 +27,10 @@
27
27
  "web-tree-sitter": "^0.26.6"
28
28
  },
29
29
  "optionalDependencies": {
30
- "@thegitai/tui-darwin-arm64": "1.0.0-beta.4",
31
- "@thegitai/tui-darwin-x64": "1.0.0-beta.4",
32
- "@thegitai/tui-linux-x64": "1.0.0-beta.4",
33
- "@thegitai/tui-win32-x64": "1.0.0-beta.4"
30
+ "@thegitai/tui-darwin-arm64": "1.0.0-beta.6",
31
+ "@thegitai/tui-darwin-x64": "1.0.0-beta.6",
32
+ "@thegitai/tui-linux-x64": "1.0.0-beta.6",
33
+ "@thegitai/tui-win32-x64": "1.0.0-beta.6"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"