gemiterm 2.1.0 → 2.1.1
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/package.json
CHANGED
|
@@ -9,13 +9,18 @@ import {
|
|
|
9
9
|
type SendMessageCommandResult,
|
|
10
10
|
} from "../../core/command-handlers.ts";
|
|
11
11
|
import { runInteractiveLoop, type MessageHandlerResult } from "../utils/interactive-prompt.ts";
|
|
12
|
+
import { checkArgLength } from "../utils/long-arg-guard.ts";
|
|
13
|
+
import { loadPromptFromFile, spillOverToTempFile } from "../utils/prompt-file.ts";
|
|
14
|
+
import { removeFile } from "../../infrastructure/io.ts";
|
|
12
15
|
|
|
13
16
|
interface ContinueCommandOptions {
|
|
14
17
|
help: boolean;
|
|
18
|
+
promptFile: string | null;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
const DEFAULT_OPTIONS: ContinueCommandOptions = {
|
|
18
22
|
help: false,
|
|
23
|
+
promptFile: null,
|
|
19
24
|
};
|
|
20
25
|
|
|
21
26
|
export class ContinueCommand implements CliCommand {
|
|
@@ -36,6 +41,7 @@ export class ContinueCommand implements CliCommand {
|
|
|
36
41
|
|
|
37
42
|
for (const arg of args) {
|
|
38
43
|
if (arg.startsWith("--") || arg.startsWith("-")) continue;
|
|
44
|
+
if (options.promptFile && arg === options.promptFile) continue;
|
|
39
45
|
if (!conversationId) {
|
|
40
46
|
conversationId = arg;
|
|
41
47
|
} else if (!message) {
|
|
@@ -43,11 +49,66 @@ export class ContinueCommand implements CliCommand {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
if (options.promptFile) {
|
|
53
|
+
if (!conversationId) {
|
|
54
|
+
console.error(
|
|
55
|
+
chalk.red(
|
|
56
|
+
`Error: --prompt-file requires a conversation_id. ` +
|
|
57
|
+
`Specify a conversation to continue, e.g. \`gemiterm continue <conversation_id> --prompt-file <path>\`.`,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
if (message) {
|
|
63
|
+
console.error(
|
|
64
|
+
chalk.red(
|
|
65
|
+
`Error: cannot use --prompt-file together with a positional message argument. ` +
|
|
66
|
+
`Use one or the other, not both.`,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
if (!conversationId) {
|
|
47
74
|
await this.invokeListCommand(context);
|
|
48
75
|
return;
|
|
49
76
|
}
|
|
50
77
|
|
|
78
|
+
let effectivePromptFile: string | null = null;
|
|
79
|
+
let isSpillover = false;
|
|
80
|
+
if (options.promptFile) {
|
|
81
|
+
effectivePromptFile = options.promptFile;
|
|
82
|
+
} else if (message) {
|
|
83
|
+
const guard = checkArgLength(message);
|
|
84
|
+
if (!guard.safe) {
|
|
85
|
+
const spilled = await spillOverToTempFile(message);
|
|
86
|
+
effectivePromptFile = spilled;
|
|
87
|
+
isSpillover = true;
|
|
88
|
+
console.log(
|
|
89
|
+
chalk.dim(
|
|
90
|
+
`[gemiterm] Message is ${guard.length} UTF-16 code units, exceeding the ${guard.limit} limit. ` +
|
|
91
|
+
`Spilled to temp file '${spilled}' and loading from there.`,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (effectivePromptFile) {
|
|
98
|
+
try {
|
|
99
|
+
message = await loadPromptFromFile(effectivePromptFile);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (isSpillover) {
|
|
105
|
+
try {
|
|
106
|
+
removeFile(effectivePromptFile);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
51
112
|
const mediator: Mediator = context.mediator;
|
|
52
113
|
|
|
53
114
|
if (message) {
|
|
@@ -128,9 +189,19 @@ export class ContinueCommand implements CliCommand {
|
|
|
128
189
|
private parseArgs(args: string[]): ContinueCommandOptions {
|
|
129
190
|
const options = { ...DEFAULT_OPTIONS };
|
|
130
191
|
|
|
131
|
-
for (
|
|
192
|
+
for (let i = 0; i < args.length; i++) {
|
|
193
|
+
const arg = args[i];
|
|
132
194
|
if (arg === "--help" || arg === "-h") {
|
|
133
195
|
options.help = true;
|
|
196
|
+
} else if (arg === "--prompt-file" || arg === "-f") {
|
|
197
|
+
const next = args[i + 1];
|
|
198
|
+
if (next && !next.startsWith("-")) {
|
|
199
|
+
options.promptFile = next;
|
|
200
|
+
i++;
|
|
201
|
+
} else {
|
|
202
|
+
console.error(chalk.red(`Error: --prompt-file requires a path`));
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
134
205
|
}
|
|
135
206
|
}
|
|
136
207
|
|
|
@@ -149,6 +220,9 @@ export class ContinueCommand implements CliCommand {
|
|
|
149
220
|
);
|
|
150
221
|
console.log("");
|
|
151
222
|
console.log(chalk.bold("Options:"));
|
|
223
|
+
console.log(
|
|
224
|
+
` ${chalk.cyan("--prompt-file, -f <path>".padEnd(22))}${chalk.dim("Read the message from a file (bypasses the 2048 code unit arg limit)")}`,
|
|
225
|
+
);
|
|
152
226
|
console.log(` ${chalk.cyan("--help, -h".padEnd(22))}${chalk.dim("Show this help message")}`);
|
|
153
227
|
console.log("");
|
|
154
228
|
console.log(chalk.dim("If no conversation_id is provided, the list command will be invoked."));
|
|
@@ -8,15 +8,20 @@ import {
|
|
|
8
8
|
type StartNewChatCommandResult,
|
|
9
9
|
} from "../../core/command-handlers.ts";
|
|
10
10
|
import { runInteractiveLoop, type MessageHandlerResult } from "../utils/interactive-prompt.ts";
|
|
11
|
+
import { checkArgLength } from "../utils/long-arg-guard.ts";
|
|
12
|
+
import { loadPromptFromFile, spillOverToTempFile } from "../utils/prompt-file.ts";
|
|
13
|
+
import { removeFile } from "../../infrastructure/io.ts";
|
|
11
14
|
|
|
12
15
|
interface NewCommandOptions {
|
|
13
16
|
help: boolean;
|
|
14
17
|
profile: string | null;
|
|
18
|
+
promptFile: string | null;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
const DEFAULT_OPTIONS: NewCommandOptions = {
|
|
18
22
|
help: false,
|
|
19
23
|
profile: null,
|
|
24
|
+
promptFile: null,
|
|
20
25
|
};
|
|
21
26
|
|
|
22
27
|
export class NewCommand implements CliCommand {
|
|
@@ -37,11 +42,56 @@ export class NewCommand implements CliCommand {
|
|
|
37
42
|
for (const arg of args) {
|
|
38
43
|
if (arg.startsWith("--") || arg.startsWith("-")) continue;
|
|
39
44
|
if (options.profile && arg === options.profile) continue;
|
|
45
|
+
if (options.promptFile && arg === options.promptFile) continue;
|
|
40
46
|
if (!message) {
|
|
41
47
|
message = arg;
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
if (options.promptFile && message) {
|
|
52
|
+
console.error(
|
|
53
|
+
chalk.red(
|
|
54
|
+
`Error: cannot use --prompt-file together with a positional message argument. ` +
|
|
55
|
+
`Use one or the other, not both.`,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let effectivePromptFile: string | null = null;
|
|
62
|
+
let isSpillover = false;
|
|
63
|
+
if (options.promptFile) {
|
|
64
|
+
effectivePromptFile = options.promptFile;
|
|
65
|
+
} else if (message) {
|
|
66
|
+
const guard = checkArgLength(message);
|
|
67
|
+
if (!guard.safe) {
|
|
68
|
+
const spilled = await spillOverToTempFile(message);
|
|
69
|
+
effectivePromptFile = spilled;
|
|
70
|
+
isSpillover = true;
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.dim(
|
|
73
|
+
`[gemiterm] Message is ${guard.length} UTF-16 code units, exceeding the ${guard.limit} limit. ` +
|
|
74
|
+
`Spilled to temp file '${spilled}' and loading from there.`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (effectivePromptFile) {
|
|
81
|
+
try {
|
|
82
|
+
message = await loadPromptFromFile(effectivePromptFile);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
if (isSpillover) {
|
|
88
|
+
try {
|
|
89
|
+
removeFile(effectivePromptFile);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
45
95
|
const mediator: Mediator = context.mediator;
|
|
46
96
|
|
|
47
97
|
if (message) {
|
|
@@ -121,6 +171,15 @@ export class NewCommand implements CliCommand {
|
|
|
121
171
|
console.error(chalk.red(`Error: --profile requires a profile name`));
|
|
122
172
|
process.exit(1);
|
|
123
173
|
}
|
|
174
|
+
} else if (arg === "--prompt-file" || arg === "-f") {
|
|
175
|
+
const next = args[i + 1];
|
|
176
|
+
if (next && !next.startsWith("-")) {
|
|
177
|
+
options.promptFile = next;
|
|
178
|
+
i++;
|
|
179
|
+
} else {
|
|
180
|
+
console.error(chalk.red(`Error: --prompt-file requires a path`));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
124
183
|
}
|
|
125
184
|
}
|
|
126
185
|
|
|
@@ -139,6 +198,9 @@ export class NewCommand implements CliCommand {
|
|
|
139
198
|
console.log(
|
|
140
199
|
` ${chalk.cyan("--profile, -p <name>".padEnd(22))}${chalk.dim("Use a specific profile (default profile used if omitted)")}`,
|
|
141
200
|
);
|
|
201
|
+
console.log(
|
|
202
|
+
` ${chalk.cyan("--prompt-file, -f <path>".padEnd(22))}${chalk.dim("Read the message from a file (bypasses the 2048 code unit arg limit)")}`,
|
|
203
|
+
);
|
|
142
204
|
console.log(` ${chalk.cyan("--help, -h".padEnd(22))}${chalk.dim("Show this help message")}`);
|
|
143
205
|
console.log("");
|
|
144
206
|
console.log(chalk.dim("If no message is provided, an interactive chat session will start."));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const WINDOWS_COMMAND_LINE_ARG_LIMIT = 2048;
|
|
2
|
+
|
|
3
|
+
export type LongArgGuardResult =
|
|
4
|
+
| { safe: true; arg: string }
|
|
5
|
+
| { safe: false; arg: string; length: number; limit: number; suggestion: string };
|
|
6
|
+
|
|
7
|
+
export function checkArgLength(arg: string): LongArgGuardResult {
|
|
8
|
+
const length = utf16CodeUnitLength(arg);
|
|
9
|
+
if (length <= WINDOWS_COMMAND_LINE_ARG_LIMIT) {
|
|
10
|
+
return { safe: true, arg };
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
safe: false,
|
|
14
|
+
arg,
|
|
15
|
+
length,
|
|
16
|
+
limit: WINDOWS_COMMAND_LINE_ARG_LIMIT,
|
|
17
|
+
suggestion:
|
|
18
|
+
`Argument is ${length} UTF-16 code units, which exceeds the ${WINDOWS_COMMAND_LINE_ARG_LIMIT} ` +
|
|
19
|
+
`code unit limit imposed by Bun's Windows process spawn path (appendWindowsArgument in ` +
|
|
20
|
+
`Bun's run_command.zig panics with "index out of bounds" when this limit is exceeded). ` +
|
|
21
|
+
`Pipe the message via stdin (e.g. \`echo "..." | gemiterm new\`) or save it to a file and ` +
|
|
22
|
+
`pass it with \`--prompt-file <path>\`.`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function utf16CodeUnitLength(s: string): number {
|
|
27
|
+
return s.length;
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IOError, readTextFile, writeTextFile } from "../../infrastructure/io.ts";
|
|
2
|
+
import { getTempFilePath } from "../../infrastructure/path-utils.ts";
|
|
3
|
+
|
|
4
|
+
export async function loadPromptFromFile(filePath: string): Promise<string> {
|
|
5
|
+
try {
|
|
6
|
+
return readTextFile(filePath);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
if (err instanceof IOError) {
|
|
9
|
+
throw new Error(`Could not read prompt file '${filePath}': ${err.message}`);
|
|
10
|
+
}
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function spillOverToTempFile(content: string): Promise<string> {
|
|
16
|
+
const path = getTempFilePath("gemiterm-arg-spill", ".txt");
|
|
17
|
+
try {
|
|
18
|
+
writeTextFile(path, content);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err instanceof IOError) {
|
|
21
|
+
throw new Error(`Could not spill message to temp file: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
return path;
|
|
26
|
+
}
|
package/src/infrastructure/io.ts
CHANGED
|
@@ -114,6 +114,14 @@ function removeDir(path: string): void {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function removeFile(path: string): void {
|
|
118
|
+
try {
|
|
119
|
+
rmSync(path, { force: true });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
throw wrap("removeFile", path, err instanceof Error ? err : undefined);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
function renameDir(src: string, dest: string): void {
|
|
118
126
|
try {
|
|
119
127
|
renameSync(src, dest);
|
|
@@ -155,6 +163,7 @@ export {
|
|
|
155
163
|
readJsonFile,
|
|
156
164
|
writeJsonFile,
|
|
157
165
|
removeDir,
|
|
166
|
+
removeFile,
|
|
158
167
|
renameDir,
|
|
159
168
|
isDirectory,
|
|
160
169
|
listSubdirectories,
|