@iinm/plain-agent 1.7.19 → 1.7.21
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 +82 -118
- package/config/agents.predefined/sandbox-configurator.md +16 -18
- package/config/config.predefined.json +15 -15
- package/config/prompts.predefined/shortcuts/configure.md +60 -0
- package/package.json +1 -3
- package/src/agentLoop.mjs +3 -1
- package/src/cliCost.mjs +67 -32
- package/src/cliFormatter.mjs +1 -1
- package/src/cliInteractive.mjs +1 -1
- package/src/config.d.ts +2 -2
- package/src/config.mjs +1 -1
- package/src/costTracker.mjs +58 -19
- package/src/env.mjs +0 -6
- package/src/main.mjs +2 -6
- package/src/model.d.ts +1 -1
- package/src/tools/patchFile.mjs +11 -12
- package/src/utils/notify.mjs +3 -2
- package/src/voiceInputGemini.mjs +58 -210
- package/src/voiceInputOpenAI.mjs +63 -220
- package/src/voiceInputSession.mjs +295 -2
- package/bin/plain-notify-terminal-bell +0 -3
package/src/cliCost.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { styleText } from "node:util";
|
|
6
|
-
import
|
|
6
|
+
import * as usageStore from "./usageStore.mjs";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {Object} CostPeriod
|
|
@@ -72,15 +72,15 @@ export function parseDateOnly(value) {
|
|
|
72
72
|
if (!match) {
|
|
73
73
|
throw new Error(`Invalid date: "${value}" (expected YYYY-MM-DD)`);
|
|
74
74
|
}
|
|
75
|
-
const [,
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const date = new Date(
|
|
75
|
+
const [, year, month, day] = match;
|
|
76
|
+
const y = Number(year);
|
|
77
|
+
const m = Number(month);
|
|
78
|
+
const d = Number(day);
|
|
79
|
+
const date = new Date(y, m - 1, d);
|
|
80
80
|
if (
|
|
81
|
-
date.getFullYear() !==
|
|
82
|
-
date.getMonth() !==
|
|
83
|
-
date.getDate() !==
|
|
81
|
+
date.getFullYear() !== y ||
|
|
82
|
+
date.getMonth() !== m - 1 ||
|
|
83
|
+
date.getDate() !== d
|
|
84
84
|
) {
|
|
85
85
|
throw new Error(`Invalid date: "${value}"`);
|
|
86
86
|
}
|
|
@@ -109,6 +109,10 @@ export function aggregateUsage(records, period) {
|
|
|
109
109
|
let excludedOutOfRange = 0;
|
|
110
110
|
|
|
111
111
|
for (const record of records) {
|
|
112
|
+
if (record.timestamp == null) {
|
|
113
|
+
excludedOutOfRange++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
112
116
|
const recordedAt = new Date(record.timestamp);
|
|
113
117
|
if (Number.isNaN(recordedAt.getTime())) {
|
|
114
118
|
excludedOutOfRange++;
|
|
@@ -123,12 +127,13 @@ export function aggregateUsage(records, period) {
|
|
|
123
127
|
noPricingSessionCount++;
|
|
124
128
|
continue;
|
|
125
129
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
perDate = new Map();
|
|
130
|
-
byCurrency.set(record.currency, perDate);
|
|
130
|
+
if (!record.currency || typeof record.currency !== "string") {
|
|
131
|
+
excludedOutOfRange++;
|
|
132
|
+
continue;
|
|
131
133
|
}
|
|
134
|
+
|
|
135
|
+
const perDate = byCurrency.get(record.currency) ?? new Map();
|
|
136
|
+
byCurrency.set(record.currency, perDate);
|
|
132
137
|
const existing = perDate.get(localDate);
|
|
133
138
|
if (existing) {
|
|
134
139
|
existing.totalCost += record.totalCost;
|
|
@@ -146,13 +151,14 @@ export function aggregateUsage(records, period) {
|
|
|
146
151
|
const aggregations = [];
|
|
147
152
|
for (const [currency, perDate] of byCurrency) {
|
|
148
153
|
const daily = Array.from(perDate.values()).sort((a, b) =>
|
|
149
|
-
a.date
|
|
150
|
-
);
|
|
151
|
-
const totalCost = daily.reduce((sum, entry) => sum + entry.totalCost, 0);
|
|
152
|
-
const sessionCount = daily.reduce(
|
|
153
|
-
(sum, entry) => sum + entry.sessionCount,
|
|
154
|
-
0,
|
|
154
|
+
a.date.localeCompare(b.date),
|
|
155
155
|
);
|
|
156
|
+
let totalCost = 0;
|
|
157
|
+
let sessionCount = 0;
|
|
158
|
+
for (const entry of daily) {
|
|
159
|
+
totalCost += entry.totalCost;
|
|
160
|
+
sessionCount += entry.sessionCount;
|
|
161
|
+
}
|
|
156
162
|
aggregations.push({ currency, daily, totalCost, sessionCount });
|
|
157
163
|
}
|
|
158
164
|
aggregations.sort((a, b) => a.currency.localeCompare(b.currency));
|
|
@@ -166,6 +172,14 @@ export function aggregateUsage(records, period) {
|
|
|
166
172
|
};
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @param {number} count
|
|
177
|
+
* @returns {string}
|
|
178
|
+
*/
|
|
179
|
+
function formatSessions(count) {
|
|
180
|
+
return `${count} session${count === 1 ? "" : "s"}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
169
183
|
/**
|
|
170
184
|
* Render a cost report as a human-readable string.
|
|
171
185
|
*
|
|
@@ -175,10 +189,9 @@ export function aggregateUsage(records, period) {
|
|
|
175
189
|
*/
|
|
176
190
|
export function formatCostReport(report, options = {}) {
|
|
177
191
|
const color = options.color ?? true;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
text;
|
|
192
|
+
/** @param {string | string[]} _modifiers @param {string} text @returns {string} */
|
|
193
|
+
const plainStyle = (_modifiers, text) => text;
|
|
194
|
+
const style = color ? styleText : plainStyle;
|
|
182
195
|
|
|
183
196
|
const lines = [];
|
|
184
197
|
lines.push(
|
|
@@ -204,14 +217,14 @@ export function formatCostReport(report, options = {}) {
|
|
|
204
217
|
lines.push(style("bold", `Daily cost (${agg.currency}):`));
|
|
205
218
|
for (const entry of agg.daily) {
|
|
206
219
|
lines.push(
|
|
207
|
-
` ${entry.date} ${formatCost(entry.totalCost)} ${agg.currency} (${entry.sessionCount}
|
|
220
|
+
` ${entry.date} ${formatCost(entry.totalCost)} ${agg.currency} (${formatSessions(entry.sessionCount)})`,
|
|
208
221
|
);
|
|
209
222
|
}
|
|
210
223
|
lines.push("");
|
|
211
224
|
lines.push(
|
|
212
225
|
style(
|
|
213
226
|
"bold",
|
|
214
|
-
`Total: ${formatCost(agg.totalCost)} ${agg.currency} (${agg.sessionCount}
|
|
227
|
+
`Total: ${formatCost(agg.totalCost)} ${agg.currency} (${formatSessions(agg.sessionCount)})`,
|
|
215
228
|
),
|
|
216
229
|
);
|
|
217
230
|
}
|
|
@@ -242,28 +255,50 @@ function formatCost(value) {
|
|
|
242
255
|
* Run the `plain cost` subcommand.
|
|
243
256
|
*
|
|
244
257
|
* @param {{ from: string | null, to: string | null }} args
|
|
258
|
+
* @param {{ readUsageRecords?: typeof import("./usageStore.mjs").readUsageRecords }} [deps]
|
|
245
259
|
* @returns {Promise<number>} exit code
|
|
246
260
|
*/
|
|
247
|
-
export async function runCostCommand(args) {
|
|
248
|
-
|
|
261
|
+
export async function runCostCommand(args, deps = {}) {
|
|
262
|
+
let from;
|
|
263
|
+
let to;
|
|
264
|
+
try {
|
|
265
|
+
({ from, to } = resolvePeriod(args));
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (err instanceof Error) {
|
|
268
|
+
console.error(`Error: ${err.message}`);
|
|
269
|
+
} else {
|
|
270
|
+
console.error("Error: invalid period arguments");
|
|
271
|
+
}
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
249
274
|
|
|
250
|
-
const { records, skipped } = await
|
|
275
|
+
const { records, skipped } = await (
|
|
276
|
+
deps.readUsageRecords ?? usageStore.readUsageRecords
|
|
277
|
+
)();
|
|
251
278
|
if (skipped.length > 0) {
|
|
279
|
+
const details = skipped
|
|
280
|
+
.slice(0, 3)
|
|
281
|
+
.map((s) => `line ${s.line}: ${s.reason}`)
|
|
282
|
+
.join(", ");
|
|
283
|
+
const ellipsis =
|
|
284
|
+
skipped.length > 3 ? `, and ${skipped.length - 3} more` : "";
|
|
252
285
|
console.error(
|
|
253
|
-
`Warning: skipped ${skipped.length} malformed line(s) in usage log.`,
|
|
286
|
+
`Warning: skipped ${skipped.length} malformed line(s) in usage log (${details}${ellipsis}).`,
|
|
254
287
|
);
|
|
255
288
|
}
|
|
256
289
|
|
|
257
290
|
const report = aggregateUsage(records, { from, to });
|
|
258
291
|
console.log(formatCostReport(report));
|
|
259
|
-
return 0;
|
|
292
|
+
return skipped.length > 0 ? 1 : 0;
|
|
260
293
|
}
|
|
261
294
|
|
|
262
295
|
/**
|
|
296
|
+
* Resolve a period from CLI arguments, falling back to the current month.
|
|
297
|
+
*
|
|
263
298
|
* @param {{ from: string | null, to: string | null }} args
|
|
264
299
|
* @returns {CostPeriod}
|
|
265
300
|
*/
|
|
266
|
-
function resolvePeriod(args) {
|
|
301
|
+
export function resolvePeriod(args) {
|
|
267
302
|
const fallback = defaultPeriod();
|
|
268
303
|
const from = args.from ?? fallback.from;
|
|
269
304
|
const to = args.to ?? fallback.to;
|
package/src/cliFormatter.mjs
CHANGED
|
@@ -91,7 +91,7 @@ export function formatToolUse(toolUse) {
|
|
|
91
91
|
const diffs = [];
|
|
92
92
|
const matches = Array.from(
|
|
93
93
|
diff.matchAll(
|
|
94
|
-
|
|
94
|
+
/<<< [0-9a-z]{3} <<< SEARCH\n(.*?)\n=== [0-9a-z]{3} ===\n(.*?)\n?>>> [0-9a-z]{3} >>> REPLACE/gs,
|
|
95
95
|
),
|
|
96
96
|
);
|
|
97
97
|
for (const match of matches) {
|
package/src/cliInteractive.mjs
CHANGED
|
@@ -57,7 +57,7 @@ const HELP_MESSAGE = [
|
|
|
57
57
|
* @property {AgentCommands} agentCommands
|
|
58
58
|
* @property {string} sessionId
|
|
59
59
|
* @property {string} modelName
|
|
60
|
-
* @property {string} notifyCmd
|
|
60
|
+
* @property {{ command: string; args?: string[] } | undefined} notifyCmd
|
|
61
61
|
* @property {boolean} sandbox
|
|
62
62
|
* @property {() => Promise<void>} onStop
|
|
63
63
|
* @property {ClaudeCodePlugin[]} [claudeCodePlugins]
|
package/src/config.d.ts
CHANGED
|
@@ -21,14 +21,14 @@ export type AppConfig = {
|
|
|
21
21
|
askURL?: AskURLToolOptions;
|
|
22
22
|
};
|
|
23
23
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
24
|
-
notifyCmd?: string;
|
|
24
|
+
notifyCmd?: { command: string; args?: string[] };
|
|
25
25
|
voiceInput?: VoiceInputConfig;
|
|
26
26
|
claudeCodePlugins?: ClaudeCodePluginRepo[];
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export type MCPServerConfig = {
|
|
30
30
|
command: string;
|
|
31
|
-
args
|
|
31
|
+
args?: string[];
|
|
32
32
|
env?: Record<string, string>;
|
|
33
33
|
options?: {
|
|
34
34
|
enabledTools?: string[];
|
package/src/config.mjs
CHANGED
|
@@ -93,7 +93,7 @@ export async function loadAppConfig(options = {}) {
|
|
|
93
93
|
...(merged.mcpServers ?? {}),
|
|
94
94
|
...(config.mcpServers ?? {}),
|
|
95
95
|
},
|
|
96
|
-
notifyCmd: config.notifyCmd
|
|
96
|
+
notifyCmd: config.notifyCmd ?? merged.notifyCmd,
|
|
97
97
|
claudeCodePlugins: [
|
|
98
98
|
...(merged.claudeCodePlugins ?? []),
|
|
99
99
|
...(config.claudeCodePlugins ?? []),
|
package/src/costTracker.mjs
CHANGED
|
@@ -31,23 +31,58 @@
|
|
|
31
31
|
* @property {() => boolean} hasUsage - Check if any usage recorded
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Validate a cost configuration object at runtime.
|
|
36
|
+
* @param {unknown} config
|
|
37
|
+
*/
|
|
38
|
+
function validateCostConfig(config) {
|
|
39
|
+
if (config === undefined) return;
|
|
40
|
+
if (typeof config !== "object" || config === null) {
|
|
41
|
+
throw new TypeError("CostConfig must be an object");
|
|
42
|
+
}
|
|
43
|
+
const c = /** @type {Record<string, unknown>} */ (config);
|
|
44
|
+
if (typeof c.currency !== "string") {
|
|
45
|
+
throw new TypeError("CostConfig.currency must be a string");
|
|
46
|
+
}
|
|
47
|
+
if (typeof c.unit !== "string") {
|
|
48
|
+
throw new TypeError("CostConfig.unit must be a string");
|
|
49
|
+
}
|
|
50
|
+
if (typeof c.costs !== "object" || c.costs === null) {
|
|
51
|
+
throw new TypeError("CostConfig.costs must be an object");
|
|
52
|
+
}
|
|
53
|
+
for (const [key, value] of Object.entries(
|
|
54
|
+
/** @type {Record<string, unknown>} */ (c.costs),
|
|
55
|
+
)) {
|
|
56
|
+
if (typeof value !== "number") {
|
|
57
|
+
throw new TypeError(
|
|
58
|
+
`CostConfig.costs["${key}"] must be a number, got ${typeof value}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
34
64
|
/**
|
|
35
65
|
* Create a cost tracker for session token usage
|
|
36
66
|
* @param {CostConfig} [costConfig] - Optional cost configuration
|
|
37
67
|
* @returns {CostTracker}
|
|
38
68
|
*/
|
|
39
69
|
export function createCostTracker(costConfig) {
|
|
70
|
+
validateCostConfig(costConfig);
|
|
71
|
+
|
|
40
72
|
/** @type {ProviderTokenUsage[]} */
|
|
41
73
|
const usageHistory = [];
|
|
42
74
|
|
|
43
75
|
/**
|
|
44
|
-
* Record token usage from a provider
|
|
76
|
+
* Record token usage from a provider.
|
|
77
|
+
* Throws when usage is not a non-null object.
|
|
45
78
|
* @param {ProviderTokenUsage} usage
|
|
79
|
+
* @throws {TypeError} when usage is null, undefined, or not an object
|
|
46
80
|
*/
|
|
47
81
|
function recordUsage(usage) {
|
|
48
|
-
if (typeof usage
|
|
49
|
-
|
|
82
|
+
if (typeof usage !== "object" || usage === null) {
|
|
83
|
+
throw new TypeError("usage must be a non-null object");
|
|
50
84
|
}
|
|
85
|
+
usageHistory.push(usage);
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
/**
|
|
@@ -75,12 +110,12 @@ export function createCostTracker(costConfig) {
|
|
|
75
110
|
return usageHistory.length > 0;
|
|
76
111
|
}
|
|
77
112
|
|
|
78
|
-
return {
|
|
113
|
+
return Object.freeze({
|
|
79
114
|
recordUsage,
|
|
80
115
|
getAggregatedUsage,
|
|
81
116
|
calculateCost,
|
|
82
117
|
hasUsage,
|
|
83
|
-
};
|
|
118
|
+
});
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
/**
|
|
@@ -132,40 +167,44 @@ function calculateCostFromConfig(aggregated, config) {
|
|
|
132
167
|
/** @type {Record<string, TokenBreakdown>} */
|
|
133
168
|
const breakdown = {};
|
|
134
169
|
let totalCost = 0;
|
|
135
|
-
const hasPricing = config?.costs;
|
|
136
170
|
|
|
137
171
|
for (const [key, tokens] of Object.entries(aggregated)) {
|
|
138
|
-
breakdown[key] = { tokens, cost: undefined };
|
|
172
|
+
breakdown[key] = Object.freeze({ tokens, cost: undefined });
|
|
139
173
|
|
|
140
|
-
if (!
|
|
174
|
+
if (!config?.costs?.[key]) {
|
|
141
175
|
continue;
|
|
142
176
|
}
|
|
143
177
|
|
|
144
178
|
const costValue = config.costs[key];
|
|
145
179
|
const unitSize = parseUnit(config.unit);
|
|
146
180
|
|
|
147
|
-
if (typeof costValue
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
181
|
+
if (typeof costValue !== "number") {
|
|
182
|
+
throw new TypeError(
|
|
183
|
+
`config.costs["${key}"] must be a number, got ${typeof costValue}`,
|
|
184
|
+
);
|
|
151
185
|
}
|
|
186
|
+
|
|
187
|
+
const cost = (tokens * costValue) / unitSize;
|
|
188
|
+
breakdown[key] = Object.freeze({ tokens, cost });
|
|
189
|
+
totalCost += cost;
|
|
152
190
|
}
|
|
153
191
|
|
|
154
|
-
return {
|
|
155
|
-
currency: config?.currency
|
|
156
|
-
unit: config?.unit
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
currency: config?.currency ?? "USD",
|
|
194
|
+
unit: config?.unit ?? "1M",
|
|
157
195
|
breakdown,
|
|
158
|
-
totalCost:
|
|
159
|
-
};
|
|
196
|
+
totalCost: config?.costs ? totalCost : undefined,
|
|
197
|
+
});
|
|
160
198
|
}
|
|
161
199
|
|
|
162
200
|
/**
|
|
163
|
-
* Parse unit string to number
|
|
201
|
+
* Parse unit string to number.
|
|
164
202
|
* @param {string} unit
|
|
165
203
|
* @returns {number}
|
|
204
|
+
* @throws {Error} when the unit is not recognized
|
|
166
205
|
*/
|
|
167
206
|
function parseUnit(unit) {
|
|
168
207
|
if (unit === "1M") return 1_000_000;
|
|
169
208
|
if (unit === "1K") return 1_000;
|
|
170
|
-
|
|
209
|
+
throw new Error(`Unknown cost unit: "${unit}"`);
|
|
171
210
|
}
|
package/src/env.mjs
CHANGED
|
@@ -41,10 +41,4 @@ export const MESSAGES_DUMP_FILE_PATH = path.join(
|
|
|
41
41
|
"messages.json",
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
export const AGENT_NOTIFY_CMD_DEFAULT = path.join(
|
|
45
|
-
AGENT_ROOT,
|
|
46
|
-
"bin",
|
|
47
|
-
"plain-notify-terminal-bell",
|
|
48
|
-
);
|
|
49
|
-
|
|
50
44
|
export const USER_NAME = process.env.USER || "unknown";
|
package/src/main.mjs
CHANGED
|
@@ -15,11 +15,7 @@ import { startInteractiveSession } from "./cliInteractive.mjs";
|
|
|
15
15
|
import { loadAppConfig } from "./config.mjs";
|
|
16
16
|
import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
|
|
17
17
|
import { loadPrompts } from "./context/loadPrompts.mjs";
|
|
18
|
-
import {
|
|
19
|
-
AGENT_NOTIFY_CMD_DEFAULT,
|
|
20
|
-
AGENT_PROJECT_METADATA_DIR,
|
|
21
|
-
USER_NAME,
|
|
22
|
-
} from "./env.mjs";
|
|
18
|
+
import { AGENT_PROJECT_METADATA_DIR, USER_NAME } from "./env.mjs";
|
|
23
19
|
import { setupMCPServer } from "./mcp.mjs";
|
|
24
20
|
import { createModelCaller } from "./modelCaller.mjs";
|
|
25
21
|
import { createPrompt } from "./prompt.mjs";
|
|
@@ -270,7 +266,7 @@ if (cliArgs.subcommand.type === "cost") {
|
|
|
270
266
|
} else {
|
|
271
267
|
startInteractiveSession({
|
|
272
268
|
...sessionOptions,
|
|
273
|
-
notifyCmd: appConfig.notifyCmd
|
|
269
|
+
notifyCmd: appConfig.notifyCmd,
|
|
274
270
|
claudeCodePlugins: resolvePluginPaths(appConfig.claudeCodePlugins ?? []),
|
|
275
271
|
voiceInput: appConfig.voiceInput,
|
|
276
272
|
});
|
package/src/model.d.ts
CHANGED
package/src/tools/patchFile.mjs
CHANGED
|
@@ -26,22 +26,21 @@ export function createPatchFileTool(
|
|
|
26
26
|
},
|
|
27
27
|
diff: {
|
|
28
28
|
description: `
|
|
29
|
-
- Content is searched as an exact match including indentation and line breaks.
|
|
30
|
-
- The first match found will be replaced if there are multiple matches.
|
|
31
|
-
- Use multiple SEARCH/REPLACE blocks with session-scoped nonce (${nonce}) to replace multiple contents.
|
|
32
|
-
|
|
33
29
|
Format:
|
|
34
|
-
|
|
30
|
+
<<< ${nonce} <<< SEARCH
|
|
35
31
|
old content
|
|
36
|
-
|
|
32
|
+
=== ${nonce} ===
|
|
37
33
|
new content
|
|
38
|
-
|
|
34
|
+
>>> ${nonce} >>> REPLACE
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
<<< ${nonce} <<< SEARCH
|
|
41
37
|
other old content
|
|
42
|
-
|
|
38
|
+
=== ${nonce} ===
|
|
43
39
|
other new content
|
|
44
|
-
|
|
40
|
+
>>> ${nonce} >>> REPLACE
|
|
41
|
+
|
|
42
|
+
- Content is searched as an exact match including indentation and line breaks.
|
|
43
|
+
- The first match found will be replaced if there are multiple matches.
|
|
45
44
|
`.trim(),
|
|
46
45
|
type: "string",
|
|
47
46
|
},
|
|
@@ -62,14 +61,14 @@ other new content
|
|
|
62
61
|
const matches = Array.from(
|
|
63
62
|
diff.matchAll(
|
|
64
63
|
new RegExp(
|
|
65
|
-
|
|
64
|
+
`<<< ${nonce} <<< SEARCH\\n(.*?)\\n=== ${nonce} ===\\n(.*?)\\n?>>> ${nonce} >>> REPLACE`,
|
|
66
65
|
"gs",
|
|
67
66
|
),
|
|
68
67
|
),
|
|
69
68
|
);
|
|
70
69
|
if (matches.length === 0) {
|
|
71
70
|
throw new Error(
|
|
72
|
-
`Invalid diff format.
|
|
71
|
+
`Invalid diff format. Each markers must include the nonce: <<< ${nonce} <<< SEARCH, === ${nonce} ===, >>> ${nonce} >>> REPLACE`,
|
|
73
72
|
);
|
|
74
73
|
}
|
|
75
74
|
let newContent = content;
|
package/src/utils/notify.mjs
CHANGED
|
@@ -2,16 +2,17 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { noThrowSync } from "./noThrow.mjs";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @param {string
|
|
5
|
+
* @param {{ command: string; args?: string[] } | undefined} notifyCmd
|
|
6
6
|
* @returns {void | Error}
|
|
7
7
|
*/
|
|
8
8
|
export function notify(notifyCmd) {
|
|
9
9
|
if (!notifyCmd) {
|
|
10
|
+
process.stdout.write("\x07");
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
return noThrowSync(() => {
|
|
14
|
-
execFileSync(
|
|
15
|
+
execFileSync(notifyCmd.command, notifyCmd.args ?? [], {
|
|
15
16
|
shell: false,
|
|
16
17
|
stdio: ["ignore", "inherit", "pipe"],
|
|
17
18
|
env: {
|