@thegitai/cli 1.0.0-beta.11 → 1.0.0-beta.13
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/dist/src/artifact-policy.js +7 -0
- package/dist/src/tool-executor.js +27 -1
- package/dist/src/tools/patch-file.js +2 -1
- package/dist/src/tools/path-suggest.js +112 -2
- package/dist/src/tools/read-document.js +2 -2
- package/dist/src/tools/read-file.js +14 -3
- package/dist/src/tools/replace-document-text.js +6 -2
- package/dist/src/tools/str-replace.js +2 -1
- package/dist/src/ui/repl.js +11 -2
- package/dist/src/ui/tui/build-frame.js +6 -1
- package/package.json +5 -5
|
@@ -148,12 +148,18 @@ export const ARTIFACT_FALLBACK_IGNORE_GLOBS = [
|
|
|
148
148
|
'**/pnpm-lock.yaml',
|
|
149
149
|
...ARTIFACT_IGNORE_PATH_PREFIXES.map((prefix) => `${prefix}/**`),
|
|
150
150
|
];
|
|
151
|
+
// Kept in lockstep with the server's secret-path check: the client repair guard
|
|
152
|
+
// and the server-side secret check must agree on what counts as a secret, or a
|
|
153
|
+
// quoted/curly secret path the client repairs (e.g. service-account.json) slips
|
|
154
|
+
// past the server check, which keys off the original tool-call args.
|
|
151
155
|
const SENSITIVE_BASENAME_PATTERNS = [
|
|
152
156
|
/^\.env(?:\..+)?$/i,
|
|
153
157
|
/^\.?npmrc$/i,
|
|
154
158
|
/^\.?pypirc$/i,
|
|
155
159
|
/^credentials(?:\..*)?$/i,
|
|
156
160
|
/^secrets?(?:\..*)?$/i,
|
|
161
|
+
/^service[-_]?account(?:\..*)?\.json$/i,
|
|
162
|
+
/^.*credentials.*\.json$/i,
|
|
157
163
|
];
|
|
158
164
|
const SENSITIVE_PATH_PATTERNS = [
|
|
159
165
|
/(^|[/\\])\.aws[/\\]credentials$/i,
|
|
@@ -161,6 +167,7 @@ const SENSITIVE_PATH_PATTERNS = [
|
|
|
161
167
|
/(^|[/\\])credentials?([._-]|$)/i,
|
|
162
168
|
/(^|[/\\])secrets?([._-]|$)/i,
|
|
163
169
|
/(^|[/\\])private[-_]?key([._-]|$)/i,
|
|
170
|
+
/(^|[/\\])service[-_]?account/i,
|
|
164
171
|
/\.(?:pem|key|p12|pfx)$/i,
|
|
165
172
|
];
|
|
166
173
|
export function normalizeArtifactPath(relPath) {
|
|
@@ -2,6 +2,7 @@ import { canStoreEditSnapshot, isEditToolName, isGitWorkTree, MAX_EDIT_JOURNAL_R
|
|
|
2
2
|
import { clearEditFailure, collectCommandMutations, captureMutationBaseline, ensureActiveCheckpoint, recordEditFailure, recordSessionEdit, rememberCheckpointFiles, } from './session-safety.js';
|
|
3
3
|
import { buildAgentModeToolBlockedResult, } from './agent-mode.js';
|
|
4
4
|
import { dispatchTool } from './tools/index.js';
|
|
5
|
+
import { PATH_REPAIRING_EDIT_TOOLS, repairFilePath } from './tools/path-suggest.js';
|
|
5
6
|
import { invalidateShellDiagnosticsCache, runShellDiagnostics, } from './tools/shell-diagnostics.js';
|
|
6
7
|
const EDIT_FILE_PATH_ARG_ALIASES = [
|
|
7
8
|
'filePath',
|
|
@@ -45,6 +46,17 @@ function getEditToolFilePath(call) {
|
|
|
45
46
|
}
|
|
46
47
|
return '';
|
|
47
48
|
}
|
|
49
|
+
// replace_document_text repairs its source filePath but writes a separate
|
|
50
|
+
// outputPath verbatim (a write target must not be fold-matched onto a different
|
|
51
|
+
// existing file). When an outputPath is given, the snapshot path it returns is
|
|
52
|
+
// that raw output, so it must not be repaired.
|
|
53
|
+
function editToolWritesSeparateOutput(call) {
|
|
54
|
+
if (call.name !== 'replace_document_text')
|
|
55
|
+
return false;
|
|
56
|
+
const args = call.args && typeof call.args === 'object' ? call.args : {};
|
|
57
|
+
const output = args.outputPath ?? args.output_path;
|
|
58
|
+
return typeof output === 'string' && output.trim().length > 0;
|
|
59
|
+
}
|
|
48
60
|
function recordAssistantEdit(session, call, result, before) {
|
|
49
61
|
if (!before || !isEditToolName(call.name))
|
|
50
62
|
return;
|
|
@@ -119,7 +131,21 @@ export async function executeLocalToolCall(toolContext, session, call) {
|
|
|
119
131
|
session.onToolEvent?.({ call, result });
|
|
120
132
|
return result;
|
|
121
133
|
}
|
|
122
|
-
|
|
134
|
+
// Snapshot the real file the edit tool will touch. Tools that repair their
|
|
135
|
+
// path internally (str_replace/patch_file/replace_document_text) must be
|
|
136
|
+
// snapshotted against the repaired path, or the pre-edit snapshot targets
|
|
137
|
+
// the unrepaired path and the edit is journaled as a `create` and undone by
|
|
138
|
+
// deleting the user's file. write_file/delete_file consume the raw path, so
|
|
139
|
+
// repairing their snapshot would instead journal a phantom edit of a
|
|
140
|
+
// different file — keep them on the raw path.
|
|
141
|
+
const rawEditFilePath = isEditToolName(call.name)
|
|
142
|
+
? getEditToolFilePath(call)
|
|
143
|
+
: '';
|
|
144
|
+
const filePathBeforeEdit = rawEditFilePath &&
|
|
145
|
+
PATH_REPAIRING_EDIT_TOOLS.has(call.name) &&
|
|
146
|
+
!editToolWritesSeparateOutput(call)
|
|
147
|
+
? repairFilePath(session.rootDir, rawEditFilePath)
|
|
148
|
+
: rawEditFilePath;
|
|
123
149
|
if (filePathBeforeEdit) {
|
|
124
150
|
rememberCheckpointFiles(session.clientState.safety, session.rootDir, [filePathBeforeEdit], session.turnState.id);
|
|
125
151
|
}
|
|
@@ -3,13 +3,14 @@ import path from 'node:path';
|
|
|
3
3
|
import { normalizeProjectRelativePath } from '../artifact-policy.js';
|
|
4
4
|
import { applyUnifiedPatch, readProjectFile, renderDiffPreview, writeProjectFile, } from '../patcher.js';
|
|
5
5
|
import { upsertIndexFile } from '../project-index.js';
|
|
6
|
+
import { repairFilePath } from './path-suggest.js';
|
|
6
7
|
import { isTuiMode } from '../runtime-mode.js';
|
|
7
8
|
import { getCurrentFileHash, resolveRedactionTokens } from '../session-safety.js';
|
|
8
9
|
import { invalidateShellDiagnosticsCache, runShellDiagnostics, } from './shell-diagnostics.js';
|
|
9
10
|
const DOCUMENT_EXTENSIONS = new Set(['.pdf', '.xlsx', '.docx']);
|
|
10
11
|
export async function patchFile(context, args) {
|
|
11
12
|
const { rootDir, projectIndex, autoYes, confirmPatch } = context;
|
|
12
|
-
const filePath = String(args.filePath ?? '').trim();
|
|
13
|
+
const filePath = repairFilePath(rootDir, String(args.filePath ?? '').trim());
|
|
13
14
|
let patch = typeof args.patch === 'string' ? args.patch : '';
|
|
14
15
|
if (!filePath) {
|
|
15
16
|
return { ok: false, error: 'filePath is required' };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readdirSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isSensitiveProjectPath } from '../artifact-policy.js';
|
|
3
|
+
import { isSensitiveProjectPath, shouldIgnoreArtifactPath, } from '../artifact-policy.js';
|
|
4
4
|
// "File not found" recovery hint: when a model mistypes a filename (most
|
|
5
5
|
// often Unicode punctuation — a straight ' for a curly ’ — or a small typo),
|
|
6
6
|
// suggest the closest real file from the same directory.
|
|
@@ -64,3 +64,113 @@ export function suggestClosestPath(rootDir, missingPath) {
|
|
|
64
64
|
const relative = path.relative(rootDir, suggested);
|
|
65
65
|
return relative && !relative.startsWith('..') ? relative : suggested;
|
|
66
66
|
}
|
|
67
|
+
// Fold only the punctuation/whitespace a model routinely alters when it echoes
|
|
68
|
+
// a filename — a curly apostrophe ’ flattened to a straight ', smart double
|
|
69
|
+
// quotes, and a non-breaking space — WITHOUT touching case, so a path is only
|
|
70
|
+
// auto-corrected when nothing but this punctuation differs from a real file.
|
|
71
|
+
function foldPunctuation(name) {
|
|
72
|
+
return name
|
|
73
|
+
.normalize('NFC')
|
|
74
|
+
.replace(/[‘’ʼ]/g, "'")
|
|
75
|
+
.replace(/[“”]/g, '"')
|
|
76
|
+
.replace(/ /g, ' ');
|
|
77
|
+
}
|
|
78
|
+
// Strip ONE matched pair of surrounding quotes. A path pasted from a file
|
|
79
|
+
// manager's "Copy as path" or dragged into a terminal arrives wrapped in
|
|
80
|
+
// '…' / "…", and that wrapping is captured verbatim as part of the filename.
|
|
81
|
+
function stripSurroundingQuotes(p) {
|
|
82
|
+
if (p.length >= 2) {
|
|
83
|
+
const first = p[0];
|
|
84
|
+
const last = p[p.length - 1];
|
|
85
|
+
if ((first === "'" && last === "'") || (first === '"' && last === '"')) {
|
|
86
|
+
return p.slice(1, -1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return p;
|
|
90
|
+
}
|
|
91
|
+
// A single backslash is a legal filename byte on POSIX, but models routinely
|
|
92
|
+
// double it (it is JSON's escape character) when echoing a name, turning
|
|
93
|
+
// `back\slash.js` into `back\\slash.js`. Collapse doubled backslashes to one.
|
|
94
|
+
function collapseDoubledBackslashes(p) {
|
|
95
|
+
return p.replace(/\\\\/g, '\\');
|
|
96
|
+
}
|
|
97
|
+
function resolveAgainst(rootDir, p) {
|
|
98
|
+
return path.isAbsolute(p) ? p : path.resolve(rootDir, p);
|
|
99
|
+
}
|
|
100
|
+
function existsAgainst(rootDir, p) {
|
|
101
|
+
try {
|
|
102
|
+
return existsSync(resolveAgainst(rootDir, p));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Repair must never resolve a protected file (a secret like `.env`/credentials).
|
|
109
|
+
// Repair only runs when the literal path is missing, so without this a quoted or
|
|
110
|
+
// curly-flattened secret path — which used to fail as not-found — would be
|
|
111
|
+
// silently resolved to the real secret, bypassing the redaction that keys off
|
|
112
|
+
// the original tool-call args (e.g. read_document, str_replace, patch_file).
|
|
113
|
+
function isProtectedRepairTarget(rootDir, candidate) {
|
|
114
|
+
const rel = path.relative(rootDir, resolveAgainst(rootDir, candidate));
|
|
115
|
+
const projectPath = rel && !rel.startsWith('..') ? rel : candidate;
|
|
116
|
+
return (isSensitiveProjectPath(projectPath) ||
|
|
117
|
+
(rel !== '' && !rel.startsWith('..') && shouldIgnoreArtifactPath(rel)));
|
|
118
|
+
}
|
|
119
|
+
// Edit tools that call repairFilePath on their path argument internally. The
|
|
120
|
+
// executor repairs the pre-edit snapshot path only for these, so its snapshot
|
|
121
|
+
// targets the same file the tool writes; write_file/delete_file consume the raw
|
|
122
|
+
// path, so their snapshot must too.
|
|
123
|
+
export const PATH_REPAIRING_EDIT_TOOLS = new Set([
|
|
124
|
+
'str_replace',
|
|
125
|
+
'patch_file',
|
|
126
|
+
'replace_document_text',
|
|
127
|
+
]);
|
|
128
|
+
// Repair a model/user-supplied path to a real on-disk file WITHOUT changing
|
|
129
|
+
// intent, for tools that act on a file expected to already exist. Literal
|
|
130
|
+
// first: any path that already resolves — including one that legitimately
|
|
131
|
+
// contains quotes or backslashes — is returned untouched. Only when the path
|
|
132
|
+
// does not resolve do we try safe de-manglings: strip surrounding quotes,
|
|
133
|
+
// collapse doubled backslashes, and finally match a directory entry that
|
|
134
|
+
// differs only by foldable punctuation (the "model flattened a curly ’ to a
|
|
135
|
+
// straight '" case, which no transform of the input can reproduce). Returns the
|
|
136
|
+
// input unchanged when nothing better exists, so the caller's normal not-found
|
|
137
|
+
// handling (and its recovery hint) still fires.
|
|
138
|
+
export function repairFilePath(rootDir, raw) {
|
|
139
|
+
if (!raw || existsAgainst(rootDir, raw))
|
|
140
|
+
return raw;
|
|
141
|
+
const dequoted = stripSurroundingQuotes(raw);
|
|
142
|
+
for (const candidate of [
|
|
143
|
+
dequoted,
|
|
144
|
+
collapseDoubledBackslashes(raw),
|
|
145
|
+
collapseDoubledBackslashes(dequoted),
|
|
146
|
+
]) {
|
|
147
|
+
if (candidate !== raw &&
|
|
148
|
+
existsAgainst(rootDir, candidate) &&
|
|
149
|
+
!isProtectedRepairTarget(rootDir, candidate)) {
|
|
150
|
+
return candidate;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const probe = resolveAgainst(rootDir, dequoted);
|
|
154
|
+
const directory = path.dirname(probe);
|
|
155
|
+
const wanted = foldPunctuation(path.basename(probe));
|
|
156
|
+
if (!wanted)
|
|
157
|
+
return raw;
|
|
158
|
+
let entries;
|
|
159
|
+
try {
|
|
160
|
+
entries = readdirSync(directory);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return raw;
|
|
164
|
+
}
|
|
165
|
+
const matches = entries.filter((entry) => foldPunctuation(entry) === wanted);
|
|
166
|
+
if (matches.length !== 1)
|
|
167
|
+
return raw; // none, or ambiguous — never guess
|
|
168
|
+
const matchedAbs = path.join(directory, matches[0]);
|
|
169
|
+
const matchedRel = path.relative(rootDir, matchedAbs);
|
|
170
|
+
const matched = path.isAbsolute(dequoted) ? matchedAbs : matchedRel || matchedAbs;
|
|
171
|
+
// Never auto-resolve into a protected file: a fold-match that lands on `.env`
|
|
172
|
+
// would confirm its existence to the model and bypass redaction.
|
|
173
|
+
if (isProtectedRepairTarget(rootDir, matched))
|
|
174
|
+
return raw;
|
|
175
|
+
return matched;
|
|
176
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { isSensitiveProjectPath, normalizeProjectRelativePath, } from '../artifact-policy.js';
|
|
4
|
-
import { suggestClosestPath } from './path-suggest.js';
|
|
4
|
+
import { repairFilePath, suggestClosestPath } from './path-suggest.js';
|
|
5
5
|
import { readCliAuthConfig } from '../api/auth.js';
|
|
6
6
|
export function normalizeDocumentText(raw) {
|
|
7
7
|
const text = String(raw ?? '').replace(/\r\n?/g, '\n');
|
|
@@ -71,7 +71,7 @@ async function parseDocumentOnServer(config, fileName, fileData, ext, args) {
|
|
|
71
71
|
return data;
|
|
72
72
|
}
|
|
73
73
|
export async function readDocument(rootDir, args, env) {
|
|
74
|
-
const raw = String(args.filePath ?? '').trim();
|
|
74
|
+
const raw = repairFilePath(rootDir, String(args.filePath ?? '').trim());
|
|
75
75
|
if (!raw) {
|
|
76
76
|
return { ok: false, error: 'filePath is required' };
|
|
77
77
|
}
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { normalizeProjectRelativePath, shouldIgnoreArtifactPath, } from '../artifact-policy.js';
|
|
4
4
|
import { buildSecretFilePreview, isDotenvLikePath, looksLikeEditableDotenv, shouldUseSecretFilePreview, } from '../secret-preview.js';
|
|
5
5
|
import { readProjectFile } from '../patcher.js';
|
|
6
|
+
import { repairFilePath } from './path-suggest.js';
|
|
6
7
|
import { dotenvFitsRedactionBudget, getCurrentFileHash, recordReadCoverage, redactContentWithStableTokens, redactDotenvWithStableTokens, } from '../session-safety.js';
|
|
7
8
|
import { readFileRange, truncate } from '../utils.js';
|
|
8
9
|
const MAX_FILE_READ_CHARS = 12000;
|
|
@@ -10,7 +11,7 @@ const DOCUMENT_EXTENSIONS = new Set(['.pdf', '.xlsx', '.docx']);
|
|
|
10
11
|
export async function readFile(context, args) {
|
|
11
12
|
const rootDir = typeof context === 'string' ? context : context.rootDir;
|
|
12
13
|
const safety = typeof context === 'string' ? undefined : context.safety;
|
|
13
|
-
const filePath = String(args.filePath ?? '').trim();
|
|
14
|
+
const filePath = repairFilePath(rootDir, String(args.filePath ?? '').trim());
|
|
14
15
|
if (!filePath) {
|
|
15
16
|
return { ok: false, error: 'filePath is required' };
|
|
16
17
|
}
|
|
@@ -41,13 +42,23 @@ export async function readFile(context, args) {
|
|
|
41
42
|
content = readProjectFile(rootDir, filePath);
|
|
42
43
|
}
|
|
43
44
|
catch (err) {
|
|
44
|
-
|
|
45
|
+
const message = String(err?.message ?? err);
|
|
46
|
+
const notFound = /^File does not exist:/.test(message);
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
error: message,
|
|
50
|
+
...(notFound ? { failureCategory: 'not_found' } : {}),
|
|
51
|
+
};
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
else {
|
|
48
55
|
const absPath = path.resolve(filePath);
|
|
49
56
|
if (!existsSync(absPath)) {
|
|
50
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: `File does not exist: ${filePath}`,
|
|
60
|
+
failureCategory: 'not_found',
|
|
61
|
+
};
|
|
51
62
|
}
|
|
52
63
|
try {
|
|
53
64
|
content = readFileSync(absPath, 'utf-8');
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { isSensitiveProjectPath, normalizeProjectRelativePath, } from '../artifact-policy.js';
|
|
5
5
|
import { readCliAuthConfig } from '../api/auth.js';
|
|
6
6
|
import { resolveProjectPath, writeProjectFileBuffer } from '../patcher.js';
|
|
7
|
-
import { suggestClosestPath } from './path-suggest.js';
|
|
7
|
+
import { repairFilePath, suggestClosestPath } from './path-suggest.js';
|
|
8
8
|
import { isTuiMode } from '../runtime-mode.js';
|
|
9
9
|
function normalizeReplacements(value) {
|
|
10
10
|
if (!Array.isArray(value))
|
|
@@ -76,7 +76,7 @@ async function replaceDocumentTextOnServer(config, fileName, fileData, replaceme
|
|
|
76
76
|
return data;
|
|
77
77
|
}
|
|
78
78
|
export async function replaceDocumentText(context, args) {
|
|
79
|
-
const sourceRaw = String(args.filePath ?? args.file_path ?? '').trim();
|
|
79
|
+
const sourceRaw = repairFilePath(context.rootDir, String(args.filePath ?? args.file_path ?? '').trim());
|
|
80
80
|
if (!sourceRaw) {
|
|
81
81
|
return { ok: false, error: 'filePath is required' };
|
|
82
82
|
}
|
|
@@ -95,6 +95,10 @@ export async function replaceDocumentText(context, args) {
|
|
|
95
95
|
failureCategory: 'invalid_argument',
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
+
// outputPath is a write target, not an existing input, so it must NOT be
|
|
99
|
+
// path-repaired: fold-match could redirect a "create Review '24.docx" onto an
|
|
100
|
+
// existing Review ’24.docx and overwrite it. The executor mirrors this by not
|
|
101
|
+
// repairing the snapshot path when an outputPath is present.
|
|
98
102
|
const outputRaw = String(args.outputPath ?? args.output_path ?? '').trim();
|
|
99
103
|
const targetPath = outputRaw
|
|
100
104
|
? relativeEditablePath(context.rootDir, outputRaw)
|
|
@@ -2,6 +2,7 @@ import chalk from '../colors.js';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { normalizeProjectRelativePath } from '../artifact-policy.js';
|
|
4
4
|
import { readProjectFile, writeProjectFile } from '../patcher.js';
|
|
5
|
+
import { repairFilePath } from './path-suggest.js';
|
|
5
6
|
import { upsertIndexFile } from '../project-index.js';
|
|
6
7
|
import { isTuiMode } from '../runtime-mode.js';
|
|
7
8
|
import { getCurrentFileHash, resolveRedactionTokens } from '../session-safety.js';
|
|
@@ -76,7 +77,7 @@ function buildStrReplacePreview(oldString, newString) {
|
|
|
76
77
|
}
|
|
77
78
|
export async function strReplace(context, args) {
|
|
78
79
|
const { rootDir, projectIndex, autoYes, confirmPatch } = context;
|
|
79
|
-
const filePath = String(args.filePath ?? args.file_path ?? '').trim();
|
|
80
|
+
const filePath = repairFilePath(rootDir, String(args.filePath ?? args.file_path ?? '').trim());
|
|
80
81
|
let oldString = typeof args.old_string === 'string'
|
|
81
82
|
? args.old_string
|
|
82
83
|
: typeof args.oldString === 'string'
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -997,7 +997,12 @@ export function getInputCommandToken(input) {
|
|
|
997
997
|
if (!trimmed.startsWith('/'))
|
|
998
998
|
return '';
|
|
999
999
|
const firstSpaceIndex = trimmed.indexOf(' ');
|
|
1000
|
-
|
|
1000
|
+
const token = firstSpaceIndex === -1 ? trimmed : trimmed.slice(0, firstSpaceIndex);
|
|
1001
|
+
// A token with a second '/' is a filesystem path (e.g. /home/user/repo),
|
|
1002
|
+
// not a slash command — no command contains a slash, so don't treat it as one.
|
|
1003
|
+
if (token.indexOf('/', 1) !== -1)
|
|
1004
|
+
return '';
|
|
1005
|
+
return token;
|
|
1001
1006
|
}
|
|
1002
1007
|
function shouldShowCommandPalette(state) {
|
|
1003
1008
|
const trimmed = String(state.input ?? '').trim();
|
|
@@ -1006,7 +1011,11 @@ function shouldShowCommandPalette(state) {
|
|
|
1006
1011
|
!state.modelPickerOpen &&
|
|
1007
1012
|
!state.resumePickerOpen &&
|
|
1008
1013
|
trimmed.startsWith('/') &&
|
|
1009
|
-
!trimmed.includes(' ')
|
|
1014
|
+
!trimmed.includes(' ') &&
|
|
1015
|
+
// A '/'-prefixed token with a second '/' is a filesystem path, not a
|
|
1016
|
+
// command — getInputCommandToken returns '' for it, so the palette stays
|
|
1017
|
+
// closed when a folder path is pasted.
|
|
1018
|
+
getInputCommandToken(trimmed) !== '');
|
|
1010
1019
|
}
|
|
1011
1020
|
export function shouldRemountLiveFrameForComposerInputChange(current, nextInput) {
|
|
1012
1021
|
const currentShowsCommands = shouldShowCommandPalette(current);
|
|
@@ -177,7 +177,12 @@ function buildModelPickerOptions(currentModelId, serverModels) {
|
|
|
177
177
|
function getInputCommandToken(input) {
|
|
178
178
|
const trimmed = String(input ?? '').trimStart();
|
|
179
179
|
const match = trimmed.match(/^\/[^\s]*/);
|
|
180
|
-
|
|
180
|
+
const token = match?.[0] ?? '';
|
|
181
|
+
// A token with a second '/' is a filesystem path (e.g. /home/user/repo),
|
|
182
|
+
// not a slash command — no command contains a slash, so don't treat it as one.
|
|
183
|
+
if (token.indexOf('/', 1) !== -1)
|
|
184
|
+
return '';
|
|
185
|
+
return token;
|
|
181
186
|
}
|
|
182
187
|
function scoreSlashCommand(option, token) {
|
|
183
188
|
if (option.command === token)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.13",
|
|
4
4
|
"description": "TheGitAI CLI client (source-visible, proprietary)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://thegit.ai",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
26
26
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
27
27
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
28
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-beta.
|
|
29
|
-
"@thegitai/tui-darwin-x64": "1.0.0-beta.
|
|
30
|
-
"@thegitai/tui-linux-x64": "1.0.0-beta.
|
|
31
|
-
"@thegitai/tui-win32-x64": "1.0.0-beta.
|
|
28
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-beta.13",
|
|
29
|
+
"@thegitai/tui-darwin-x64": "1.0.0-beta.13",
|
|
30
|
+
"@thegitai/tui-linux-x64": "1.0.0-beta.13",
|
|
31
|
+
"@thegitai/tui-win32-x64": "1.0.0-beta.13",
|
|
32
32
|
"@vscode/ripgrep": "1.18.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|