@adyingdeath/meatproxy 1.0.0 → 1.1.0

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
@@ -14,10 +14,10 @@ It is a small bridge between your clipboard and chatbots: paste the prompt into
14
14
  ## Install
15
15
 
16
16
  ```sh
17
- npm install -g meatproxy
17
+ npm install -g @adyingdeath/meatproxy
18
18
  ```
19
19
 
20
- This provides the `mtp` command (also available as `meatproxy`).
20
+ This provides the `mtp` command.
21
21
 
22
22
  ## Usage
23
23
 
@@ -31,18 +31,20 @@ mtp init
31
31
  mtp
32
32
  ```
33
33
 
34
+ Run `mtp --help` to see the workflow again at any time.
35
+
34
36
  `mtp init` copies the prompt and prints what to do next. `mtp` reads the clipboard, extracts the snippet, runs it, prints the output, and copies that output back to the clipboard.
35
37
 
36
38
  The snippet runs with the same access as your terminal, so only paste replies you trust.
37
39
 
38
40
  ## Tools
39
41
 
40
- The snippet gets a `tools` object with four methods: `read`, `write`, `edit` and `pwsh`, enough to inspect files, change them, and run PowerShell commands.
42
+ The snippet gets a `tools` object with four methods: `read`, `write`, `edit` and `pwsh`, enough to inspect files, change them, and run PowerShell commands. Each call prints a small card with its result, and `edit` also shows a diff of the change.
41
43
 
42
44
  ## Development
43
45
 
44
46
  ```sh
45
47
  npm install
46
48
  npm run build # bundles to dist/
47
- npm run dev # runs src/index.ts directly
49
+ npm run dev # runs src/index.ts directly
48
50
  ```
package/dist/index.js CHANGED
@@ -3,41 +3,408 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/core.ts
6
+ // src/runner/core.ts
7
7
  import clipboard from "clipboardy";
8
8
 
9
- // src/console.ts
9
+ // src/runner/console.ts
10
10
  import { format } from "util";
11
- var OutputConsole = class {
11
+ var Console = class {
12
12
  /** Everything written so far, one entry per console call. */
13
13
  lines = [];
14
14
  log(...args) {
15
- console.log(...args);
16
15
  this.lines.push(format(...args));
17
16
  }
18
- info(...args) {
19
- console.info(...args);
20
- this.lines.push(format(...args));
17
+ };
18
+
19
+ // src/tools/index.ts
20
+ import { spawnSync } from "child_process";
21
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
22
+ import path from "path";
23
+
24
+ // src/tools/types.ts
25
+ function isRecord(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+ function isNonEmptyString(value) {
29
+ return typeof value === "string" && value.length > 0;
30
+ }
31
+ function isOptionalBoolean(value) {
32
+ return value === void 0 || typeof value === "boolean";
33
+ }
34
+ function isOptionalString(value) {
35
+ return value === void 0 || typeof value === "string";
36
+ }
37
+ function isOptionalPositiveInteger(value) {
38
+ return value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 1;
39
+ }
40
+ function isReadParams(value) {
41
+ return isRecord(value) && isNonEmptyString(value.file_path) && isOptionalPositiveInteger(value.offset) && isOptionalPositiveInteger(value.limit);
42
+ }
43
+ function isWriteParams(value) {
44
+ return isRecord(value) && isNonEmptyString(value.file_path) && typeof value.content === "string";
45
+ }
46
+ function isEditParams(value) {
47
+ return isRecord(value) && isNonEmptyString(value.file_path) && typeof value.old_string === "string" && typeof value.new_string === "string" && isOptionalBoolean(value.replace_all);
48
+ }
49
+ function isPwshParams(value) {
50
+ return isRecord(value) && typeof value.command === "string" && isOptionalString(value.cwd) && isOptionalPositiveInteger(value.timeoutMs);
51
+ }
52
+
53
+ // src/tools/tool-cards.ts
54
+ import chalk from "chalk";
55
+ import terminalSize from "terminal-size";
56
+
57
+ // src/tools/card-diff.ts
58
+ import { diffLines } from "diff";
59
+ function splitChangeValue(value) {
60
+ const parts = value.split("\n");
61
+ if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
62
+ return parts;
63
+ }
64
+ function toDiffLines(original, updated) {
65
+ const changes = diffLines(original, updated, { stripTrailingCr: true });
66
+ const rows = [];
67
+ let oldLine = 1;
68
+ let newLine = 1;
69
+ for (const change of changes) {
70
+ for (const text of splitChangeValue(change.value)) {
71
+ if (change.added) {
72
+ rows.push({ kind: "add", newLine: newLine++, text });
73
+ } else if (change.removed) {
74
+ rows.push({ kind: "del", oldLine: oldLine++, text });
75
+ } else {
76
+ rows.push({ kind: "ctx", oldLine: oldLine++, newLine: newLine++, text });
77
+ }
78
+ }
21
79
  }
22
- warn(...args) {
23
- console.warn(...args);
24
- this.lines.push(format(...args));
80
+ return rows;
81
+ }
82
+ function hunkRows(lines, context) {
83
+ const keep = new Array(lines.length).fill(false);
84
+ for (let i = 0; i < lines.length; i++) {
85
+ const current = lines[i];
86
+ if (!current || current.kind === "ctx") continue;
87
+ const from = Math.max(0, i - context);
88
+ const to = Math.min(lines.length - 1, i + context);
89
+ for (let j = from; j <= to; j++) keep[j] = true;
25
90
  }
26
- error(...args) {
27
- console.error(...args);
28
- this.lines.push(format(...args));
91
+ const rows = [];
92
+ for (let i = 0; i < lines.length; ) {
93
+ if (keep[i]) {
94
+ const kept = lines[i];
95
+ if (kept) rows.push(kept);
96
+ i++;
97
+ continue;
98
+ }
99
+ let j = i;
100
+ while (j < lines.length && !keep[j]) j++;
101
+ const skipped = j - i;
102
+ rows.push({
103
+ kind: "gap",
104
+ text: "... " + skipped + " unchanged line" + (skipped === 1 ? "" : "s")
105
+ });
106
+ i = j;
29
107
  }
30
- debug(...args) {
31
- console.debug(...args);
32
- this.lines.push(format(...args));
108
+ return rows;
109
+ }
110
+ function groupDiffHunks(lines, context = 2) {
111
+ const all = hunkRows(lines, context);
112
+ let start = 0;
113
+ let end = all.length;
114
+ while (start < end && all[start]?.kind === "gap") start++;
115
+ while (end > start && all[end - 1]?.kind === "gap") end--;
116
+ const trimmed = all.slice(start, end);
117
+ if (trimmed.length === 0) return [];
118
+ const hunks = [];
119
+ let pendingGap;
120
+ let cur = [];
121
+ for (const row of trimmed) {
122
+ if (row.kind === "gap") {
123
+ if (cur.length > 0) {
124
+ hunks.push({ gap: pendingGap, rows: cur });
125
+ cur = [];
126
+ }
127
+ pendingGap = row;
128
+ } else {
129
+ cur.push(row);
130
+ }
131
+ }
132
+ if (cur.length > 0) hunks.push({ gap: pendingGap, rows: cur });
133
+ return hunks;
134
+ }
135
+ function toPairs(rows) {
136
+ const pairs = [];
137
+ for (let i = 0; i < rows.length; ) {
138
+ const row = rows[i];
139
+ if (!row) {
140
+ i++;
141
+ continue;
142
+ }
143
+ if (row.kind === "gap") {
144
+ pairs.push({ kind: "gap", text: row.text });
145
+ i++;
146
+ continue;
147
+ }
148
+ if (row.kind === "ctx") {
149
+ pairs.push({ kind: "pair", left: row, right: row });
150
+ i++;
151
+ continue;
152
+ }
153
+ const lefts = [];
154
+ const rights = [];
155
+ while (i < rows.length) {
156
+ const next = rows[i];
157
+ if (!next || next.kind === "ctx" || next.kind === "gap") break;
158
+ if (next.kind === "del") lefts.push(next);
159
+ else rights.push(next);
160
+ i++;
161
+ }
162
+ const count = Math.max(lefts.length, rights.length);
163
+ for (let k = 0; k < count; k++) {
164
+ pairs.push({ kind: "pair", left: lefts[k], right: rights[k] });
165
+ }
166
+ }
167
+ return pairs;
168
+ }
169
+
170
+ // src/tools/tool-cards.ts
171
+ function width() {
172
+ let cols = FALLBACK_WIDTH;
173
+ try {
174
+ const size = terminalSize();
175
+ if (Number.isFinite(size.columns) && size.columns > 0) cols = size.columns;
176
+ } catch {
177
+ }
178
+ if (cols <= 64) return cols;
179
+ if (cols <= 74) return 64;
180
+ return cols - 10;
181
+ }
182
+ function truncate(text, maxLen) {
183
+ if (text.length <= maxLen) return text;
184
+ if (maxLen <= 0) return "";
185
+ if (maxLen <= 3) return ".".repeat(maxLen);
186
+ return text.slice(0, maxLen - 3) + "...";
187
+ }
188
+ var PAD = " ";
189
+ var FALLBACK_WIDTH = 80;
190
+ var SPLIT_MIN_WIDTH = 80;
191
+ var INNER_WIDTH = width() - PAD.length * 2;
192
+ var S = {
193
+ header: (t) => chalk.bgHex("#fcebdf").hex("#783f09")(t),
194
+ headerContent: (t) => chalk.bgHex("#fcebdf").hex("#34251a")(t),
195
+ body: (t) => chalk.bgHex("#f5f1ef").hex("#34251a")(t),
196
+ ok: (t) => chalk.bgHex("#f5f1ef").hex("#005E19")(t),
197
+ fail: (t) => chalk.bgHex("#f5f1ef").hex("#94020d")(t),
198
+ warn: (t) => chalk.bgHex("#f5f1ef").hex("#595101")(t),
199
+ del: (t) => chalk.bgHex("#f5f1ef").hex("#94020d")(t),
200
+ add: (t) => chalk.bgHex("#f5f1ef").hex("#005E19")(t)
201
+ };
202
+ var ToolCards = class _ToolCards {
203
+ constructor() {
204
+ }
205
+ /** Total width of tools cards. */
206
+ static width() {
207
+ let cols = FALLBACK_WIDTH;
208
+ try {
209
+ const size = terminalSize();
210
+ if (Number.isFinite(size.columns) && size.columns > 0) cols = size.columns;
211
+ } catch {
212
+ }
213
+ if (cols <= 64) return cols;
214
+ if (cols <= 74) return 64;
215
+ return cols - 10;
216
+ }
217
+ /** Keep maxLines of text, every lines truncated by maxLen */
218
+ static blockLines(text, maxLines, maxLen) {
219
+ const parts = text.split("\n");
220
+ const shown = parts.slice(0, maxLines);
221
+ return shown.map((raw) => {
222
+ return truncate(raw.replace(/\r$/, ""), Math.max(0, maxLen));
223
+ });
224
+ }
225
+ static line(parts, padStyle = S.body) {
226
+ let used = 0;
227
+ let lastStyle = padStyle;
228
+ const chunks = [];
229
+ for (const part of parts) {
230
+ if (used >= INNER_WIDTH) break;
231
+ const room = INNER_WIDTH - used;
232
+ const text = part.text.length > room ? part.text.slice(0, room - 3) + "..." : part.text;
233
+ if (text.length === 0) continue;
234
+ chunks.push(part.style(text));
235
+ used += text.length;
236
+ lastStyle = part.style;
237
+ }
238
+ let padding = "";
239
+ if (used < INNER_WIDTH) {
240
+ padding = lastStyle(" ".repeat(INNER_WIDTH - used));
241
+ }
242
+ console.log(padStyle(PAD) + chunks.join("") + padding + padStyle(PAD));
243
+ }
244
+ // Cards for different tools.
245
+ static read(opts) {
246
+ this.line([{
247
+ text: "read ",
248
+ style: S.header
249
+ }, {
250
+ text: opts.filePath,
251
+ style: S.headerContent
252
+ }], S.header);
253
+ if (opts.ok) {
254
+ this.line([{ text: "success", style: S.ok }], S.body);
255
+ } else {
256
+ this.line([{ text: "failed", style: S.fail }], S.body);
257
+ }
258
+ console.log();
259
+ }
260
+ static write(opts) {
261
+ const BLOCK_MAX_LINES = 2;
262
+ this.line([{
263
+ text: "write ",
264
+ style: S.header
265
+ }, {
266
+ text: opts.filePath,
267
+ style: S.headerContent
268
+ }], S.header);
269
+ if (opts.content) {
270
+ for (const text of _ToolCards.blockLines(opts.content, BLOCK_MAX_LINES, INNER_WIDTH)) {
271
+ this.line([{ text, style: S.body }], S.body);
272
+ }
273
+ }
274
+ if (opts.ok) {
275
+ this.line([{ text: "success", style: S.ok }], S.body);
276
+ } else {
277
+ this.line([{ text: "failed", style: S.fail }], S.body);
278
+ }
279
+ console.log();
280
+ }
281
+ static edit(opts) {
282
+ _ToolCards.line([{
283
+ text: "edit ",
284
+ style: S.header
285
+ }, {
286
+ text: opts.filePath,
287
+ style: S.headerContent
288
+ }], S.header);
289
+ if (opts.ok && opts.diff && opts.diff.length > 0) {
290
+ const MAX_HUNKS = 3;
291
+ const MAX_ROWS = 10;
292
+ const hunks = groupDiffHunks(opts.diff);
293
+ const moreHunks = hunks.length - MAX_HUNKS;
294
+ const shownHunks = moreHunks > 0 ? hunks.slice(0, MAX_HUNKS) : hunks;
295
+ const renderSingle = (rows) => {
296
+ const capped = _ToolCards.capRows(rows, MAX_ROWS);
297
+ let gutter = 1;
298
+ for (const row of capped) {
299
+ if (row.kind === "gap") continue;
300
+ gutter = Math.max(
301
+ gutter,
302
+ String(row.oldLine ?? row.newLine ?? 0).length
303
+ );
304
+ }
305
+ const textMax = Math.max(4, INNER_WIDTH - 2 - gutter - 1);
306
+ for (const row of capped) {
307
+ if (row.kind === "gap") {
308
+ _ToolCards.line([{ text: row.text, style: S.body }], S.body);
309
+ continue;
310
+ }
311
+ const [sign, ln, style] = row.kind === "add" ? ["+", row.newLine, S.add] : row.kind === "del" ? ["-", row.oldLine, S.del] : [" ", "", S.body];
312
+ const text = truncate(row.text.replace(/\r$/, ""), textMax);
313
+ _ToolCards.line([{ text: sign + String(ln ?? "").padStart(gutter) + " " + text, style }], S.body);
314
+ }
315
+ };
316
+ const renderSplit = (rows) => {
317
+ const capped = _ToolCards.capPairs(toPairs(rows), MAX_ROWS);
318
+ const SEP = " \u2502 ";
319
+ const cellTotal = INNER_WIDTH - SEP.length;
320
+ const leftW = Math.floor(cellTotal / 2);
321
+ const rightW = cellTotal - leftW;
322
+ let leftGutter = 1;
323
+ let rightGutter = 1;
324
+ for (const pair of capped) {
325
+ if (pair.kind === "gap") continue;
326
+ leftGutter = Math.max(leftGutter, String(pair.left?.oldLine ?? 0).length);
327
+ rightGutter = Math.max(rightGutter, String(pair.right?.newLine ?? 0).length);
328
+ }
329
+ const leftMax = Math.max(4, leftW - 2 - leftGutter);
330
+ const rightMax = Math.max(4, rightW - 2 - rightGutter);
331
+ const cell = (line, side, cellW, gutterW, maxLen) => {
332
+ if (!line) return { text: "\u2591".repeat(cellW), style: S.body };
333
+ const ln = side === "old" ? line.oldLine : line.newLine;
334
+ const [sign, style] = line.kind === "add" ? ["+", S.add] : line.kind === "del" ? ["-", S.del] : [" ", S.body];
335
+ const text = truncate(line.text.replace(/\r$/, ""), maxLen);
336
+ return { text: (sign + String(ln ?? "").padStart(gutterW) + " " + text).padEnd(cellW), style };
337
+ };
338
+ for (const pair of capped) {
339
+ if (pair.kind === "gap") {
340
+ _ToolCards.line([{ text: pair.text, style: S.body }], S.body);
341
+ continue;
342
+ }
343
+ _ToolCards.line([
344
+ cell(pair.left, "old", leftW, leftGutter, leftMax),
345
+ { text: SEP, style: S.body },
346
+ cell(pair.right, "new", rightW, rightGutter, rightMax)
347
+ ], S.body);
348
+ }
349
+ };
350
+ shownHunks.forEach((hunk, index) => {
351
+ if (index > 0) {
352
+ const gapText = hunk.gap?.text ?? "...";
353
+ _ToolCards.line([{ text: gapText, style: S.body }], S.body);
354
+ }
355
+ if (INNER_WIDTH < SPLIT_MIN_WIDTH) {
356
+ renderSingle(hunk.rows);
357
+ } else {
358
+ renderSplit(hunk.rows);
359
+ }
360
+ });
361
+ if (moreHunks > 0) {
362
+ _ToolCards.line([{ text: "... " + moreHunks + " more hunk" + (moreHunks === 1 ? "" : "s"), style: S.body }], S.body);
363
+ }
364
+ }
365
+ if (opts.ok) {
366
+ _ToolCards.line([{ text: "success", style: S.ok }], S.body);
367
+ } else {
368
+ _ToolCards.line([{ text: "failed", style: S.fail }], S.body);
369
+ }
370
+ console.log();
371
+ }
372
+ static pwsh(opts) {
373
+ _ToolCards.line([{
374
+ text: "pwsh ",
375
+ style: S.header
376
+ }, {
377
+ text: opts.command.replace(/\r?\n/g, " "),
378
+ style: S.headerContent
379
+ }], S.header);
380
+ if (!opts.ok) {
381
+ _ToolCards.line([{ text: "failed", style: S.fail }], S.body);
382
+ } else {
383
+ if (opts.exitCode === 0) {
384
+ _ToolCards.line([{ text: "success", style: S.ok }], S.body);
385
+ } else {
386
+ _ToolCards.line([{ text: "exit " + opts.exitCode, style: S.warn }], S.body);
387
+ }
388
+ }
389
+ console.log();
390
+ }
391
+ static capRows(rows, maxRows) {
392
+ if (rows.length <= maxRows) return rows;
393
+ const shown = rows.slice(0, maxRows - 1);
394
+ const hidden = rows.length - shown.length;
395
+ return [...shown, { kind: "gap", text: "... " + hidden + " more line" + (hidden === 1 ? "" : "s") }];
396
+ }
397
+ static capPairs(pairs, maxRows) {
398
+ if (pairs.length <= maxRows) return pairs;
399
+ const shown = pairs.slice(0, maxRows - 1);
400
+ const hidden = pairs.length - shown.length;
401
+ return [...shown, { kind: "gap", text: "... " + hidden + " more line" + (hidden === 1 ? "" : "s") }];
33
402
  }
34
403
  };
35
404
 
36
- // src/tools.ts
37
- import { spawnSync } from "child_process";
38
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
39
- import path from "path";
405
+ // src/tools/index.ts
40
406
  var DEFAULT_PWSH_TIMEOUT = 12e4;
407
+ var DEFAULT_READ_LIMIT = 2e3;
41
408
  var MAX_PWSH_OUTPUT_BYTES = 16 * 1024 * 1024;
42
409
  var PWSH_PRELUDE = [
43
410
  "$ProgressPreference = 'SilentlyContinue'",
@@ -63,60 +430,61 @@ function shellCandidates() {
63
430
  function describeError(error) {
64
431
  return error instanceof Error ? error.message : String(error);
65
432
  }
66
- function requirePath(value, method) {
67
- if (typeof value !== "string" || value.length === 0) {
68
- throw new TypeError(`${method} failed: file_path must be a non-empty string.`);
69
- }
70
- return value;
71
- }
72
- function requireText(value, method, field) {
73
- if (typeof value !== "string") {
74
- throw new TypeError(`${method} failed: ${field} must be a string.`);
75
- }
76
- return value;
77
- }
78
- function countOccurrences(text, search) {
79
- let count = 0;
80
- let index = text.indexOf(search);
81
- while (index !== -1) {
82
- count += 1;
83
- index = text.indexOf(search, index + search.length);
84
- }
85
- return count;
86
- }
87
- function replaceFirst(text, search, replacement) {
88
- const index = text.indexOf(search);
89
- if (index === -1) {
90
- return text;
91
- }
92
- return text.slice(0, index) + replacement + text.slice(index + search.length);
93
- }
94
433
  var Tools = class {
95
434
  root;
96
435
  constructor(root = process.cwd()) {
97
436
  this.root = path.resolve(root);
98
437
  }
99
- /** Read a UTF-8 text file and return its full contents. */
100
- read(request) {
101
- const filePath = requirePath(request?.file_path, "read");
438
+ /** Read a UTF-8 text file and return line-numbered content. */
439
+ read(params) {
440
+ if (!isReadParams(params)) {
441
+ ToolCards.read({ filePath: "", ok: false });
442
+ throw new Error("Read failed: invalid parameters.");
443
+ }
444
+ const filePath = params.file_path;
102
445
  const target = this.resolve(filePath);
446
+ let content;
103
447
  try {
104
- return readFileSync(target, "utf8");
448
+ content = readFileSync(target, "utf8");
105
449
  } catch (error) {
450
+ ToolCards.read({ filePath, ok: false });
106
451
  throw new Error(`Read failed for ${filePath}: ${describeError(error)}`);
107
452
  }
453
+ const normalized = content.replace(/\r\n/g, "\n");
454
+ const allLines = normalized.split("\n");
455
+ if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
456
+ allLines.pop();
457
+ }
458
+ const offset = (params.offset ?? 1) - 1;
459
+ const limit = params.limit ?? DEFAULT_READ_LIMIT;
460
+ ToolCards.read({ filePath, ok: true });
461
+ return {
462
+ path: filePath,
463
+ offset: offset + 1,
464
+ // 1-based
465
+ lines: allLines.slice(offset, offset + limit).map((text, index) => ({
466
+ number: offset + index + 1,
467
+ text
468
+ })),
469
+ totalLines: allLines.length
470
+ };
108
471
  }
109
472
  /** Create or fully overwrite a UTF-8 text file, creating parent directories. */
110
- write(request) {
111
- const filePath = requirePath(request?.file_path, "write");
112
- const content = requireText(request?.content, "write", "content");
473
+ write(params) {
474
+ if (!isWriteParams(params)) {
475
+ ToolCards.write({ filePath: "", content: "", ok: false });
476
+ throw new Error("Write failed: invalid parameters.");
477
+ }
478
+ const filePath = params.file_path;
113
479
  const target = this.resolve(filePath);
114
480
  try {
115
481
  mkdirSync(path.dirname(target), { recursive: true });
116
- writeFileSync(target, content, "utf8");
482
+ writeFileSync(target, params.content, "utf8");
117
483
  } catch (error) {
484
+ ToolCards.write({ filePath, content: params.content, ok: false });
118
485
  throw new Error(`Write failed for ${filePath}: ${describeError(error)}`);
119
486
  }
487
+ ToolCards.write({ filePath, content: params.content, ok: true });
120
488
  }
121
489
  /**
122
490
  * Replace literal text in a file.
@@ -124,39 +492,65 @@ var Tools = class {
124
492
  * `old_string` has to be unique unless `replace_all` is true, and an empty
125
493
  * `old_string` is rejected because it would match everywhere.
126
494
  */
127
- edit(request) {
128
- const filePath = requirePath(request?.file_path, "edit");
129
- const oldText = requireText(request?.old_string, "edit", "old_string");
130
- const newText = requireText(request?.new_string, "edit", "new_string");
495
+ edit(params) {
496
+ if (!isEditParams(params)) {
497
+ ToolCards.edit({ filePath: "", replaceAll: false, ok: false });
498
+ throw new Error("Edit failed: invalid parameters.");
499
+ }
500
+ const filePath = params.file_path;
501
+ const oldText = params.old_string;
502
+ const newText = params.new_string;
503
+ const replaceAll = params.replace_all === true;
131
504
  if (oldText.length === 0) {
505
+ ToolCards.edit({ filePath, replaceAll, ok: false });
132
506
  throw new Error("Edit failed: old_string must not be empty.");
133
507
  }
134
- const original = this.read({ file_path: filePath });
135
- const occurrences = countOccurrences(original, oldText);
136
- if (occurrences === 0) {
508
+ const target = this.resolve(filePath);
509
+ let original;
510
+ try {
511
+ original = readFileSync(target, "utf8");
512
+ } catch (error) {
513
+ ToolCards.edit({ filePath, replaceAll, ok: false });
514
+ throw new Error(`Edit failed for ${filePath}: ${describeError(error)}`);
515
+ }
516
+ const eol = original.includes("\r\n") ? "\r\n" : "\n";
517
+ const lf = (text) => text.replace(/\r\n/g, "\n");
518
+ const lfOriginal = lf(original);
519
+ const lfOldText = lf(oldText);
520
+ const firstIndex = lfOriginal.indexOf(lfOldText);
521
+ if (firstIndex === -1) {
522
+ ToolCards.edit({ filePath, replaceAll, ok: false });
137
523
  throw new Error(`Edit failed for ${filePath}: old_string was not found.`);
138
524
  }
139
- if (occurrences > 1 && !request.replace_all) {
525
+ const secondIndex = lfOriginal.indexOf(lfOldText, firstIndex + lfOldText.length);
526
+ if (secondIndex !== -1 && !replaceAll) {
527
+ ToolCards.edit({ filePath, replaceAll, ok: false });
140
528
  throw new Error(
141
- `Edit failed for ${filePath}: old_string appears ${occurrences} times. Make it unique or pass replace_all = true.`
529
+ `Edit failed for ${filePath}: old_string appears multiple times. Make it unique or pass replace_all = true.`
142
530
  );
143
531
  }
144
- const updated = request.replace_all ? original.split(oldText).join(newText) : replaceFirst(original, oldText, newText);
145
- this.write({ file_path: filePath, content: updated });
532
+ const updated = lfOriginal.replaceAll(lfOldText, () => lf(newText)).replace(/\n/g, eol);
533
+ try {
534
+ writeFileSync(target, updated, "utf8");
535
+ } catch (error) {
536
+ ToolCards.edit({ filePath, replaceAll, ok: false });
537
+ throw new Error(`Edit failed for ${filePath}: ${describeError(error)}`);
538
+ }
539
+ const diff = toDiffLines(original, updated);
540
+ ToolCards.edit({ filePath, replaceAll, ok: true, diff });
146
541
  }
147
542
  /**
148
543
  * Run a PowerShell command synchronously and return its output.
149
- *
150
- * A non-zero `exitCode` is returned instead of thrown, so the code can inspect
151
- * `stdout` and `stderr` itself. The command runs with `pwsh` when it is
152
- * available and falls back to `powershell.exe` on Windows.
153
544
  */
154
- pwsh(request) {
155
- const command = requireText(request?.command, "pwsh", "command");
156
- const timeout = request.timeoutMs ?? DEFAULT_PWSH_TIMEOUT;
157
- const cwd = request.cwd ? this.resolve(request.cwd) : this.root;
158
- const encoded = Buffer.from(`${PWSH_PRELUDE}
159
- ${command}`, "utf16le").toString("base64");
545
+ pwsh(params) {
546
+ if (!isPwshParams(params)) {
547
+ ToolCards.pwsh({ command: "", exitCode: null, ok: false });
548
+ throw new Error("Pwsh failed: invalid parameters.");
549
+ }
550
+ const command = params.command;
551
+ const timeout = params.timeoutMs ?? DEFAULT_PWSH_TIMEOUT;
552
+ const cwd = params.cwd ? this.resolve(params.cwd) : this.root;
553
+ const encoded = Buffer.from(PWSH_PRELUDE + "\n" + command, "utf16le").toString("base64");
160
554
  for (const shell of shellCandidates()) {
161
555
  const result = spawnSync(
162
556
  shell,
@@ -171,20 +565,21 @@ ${command}`, "utf16le").toString("base64");
171
565
  );
172
566
  if (result.error) {
173
567
  const code = result.error.code;
174
- if (code === "ENOENT") {
175
- continue;
176
- }
568
+ if (code === "ENOENT") continue;
569
+ ToolCards.pwsh({ command, exitCode: null, ok: false });
177
570
  if (code === "ETIMEDOUT") {
178
- throw new Error(`Pwsh timed out after ${timeout} ms.`);
571
+ throw new Error("Pwsh timed out after " + timeout + " ms.");
179
572
  }
180
- throw new Error(`Pwsh failed: ${result.error.message}`);
573
+ throw new Error("Pwsh failed: " + result.error.message);
181
574
  }
575
+ ToolCards.pwsh({ command, exitCode: result.status, ok: true });
182
576
  return {
183
577
  stdout: result.stdout,
184
578
  stderr: decodePowerShellStderr(result.stderr),
185
579
  exitCode: result.status
186
580
  };
187
581
  }
582
+ ToolCards.pwsh({ command, exitCode: null, ok: false });
188
583
  throw new Error("Pwsh failed: PowerShell is not available on this machine.");
189
584
  }
190
585
  resolve(filePath) {
@@ -192,9 +587,12 @@ ${command}`, "utf16le").toString("base64");
192
587
  }
193
588
  };
194
589
 
195
- // src/core.ts
590
+ // src/runner/core.ts
591
+ import chalk2 from "chalk";
196
592
  var HEAD_MARKER = "<<<<<<< MEATPROXY";
197
593
  var TAIL_MARKER = ">>>>>>> MEATPROXY";
594
+ var OUTPUT_HEAD_MARKER = "<<<<<<< MEATPROXY OUTPUT\n";
595
+ var OUTPUT_TAIL_MARKER = "\n>>>>>>> MEATPROXY OUTPUT\n\n";
198
596
  var INVALID_MARKER_MESSAGE = `Error: Invalid format.
199
597
  Expected format:
200
598
  ${HEAD_MARKER}
@@ -213,33 +611,21 @@ function extractCodeBlock(text) {
213
611
  }
214
612
  });
215
613
  if (heads.length === 0 && tails.length === 0) {
216
- console.log(INVALID_MARKER_MESSAGE);
217
- return "";
614
+ return { status: "error", message: INVALID_MARKER_MESSAGE };
218
615
  }
219
616
  if (heads.length > 1 || tails.length > 1) {
220
- console.log("Error: Found more than one pair of MEATPROXY markers.");
221
- return "";
617
+ return { status: "error", message: "Error: Found more than one pair of MEATPROXY markers." };
222
618
  }
223
619
  const head = heads[0];
224
620
  const tail = tails[0];
225
- if (head === void 0) {
226
- console.log(INVALID_MARKER_MESSAGE);
227
- return "";
228
- }
229
- if (tail === void 0) {
230
- console.log(INVALID_MARKER_MESSAGE);
231
- return "";
232
- }
233
- if (tail < head) {
234
- console.log(INVALID_MARKER_MESSAGE);
235
- return "";
621
+ if (head === void 0 || tail === void 0 || tail < head) {
622
+ return { status: "error", message: INVALID_MARKER_MESSAGE };
236
623
  }
237
624
  const code = lines.slice(head + 1, tail).join("\n").trim();
238
625
  if (code.length === 0) {
239
- console.log("No code. Nothing happens.");
240
- return "";
626
+ return { status: "empty" };
241
627
  }
242
- return code;
628
+ return { status: "ok", code };
243
629
  }
244
630
  var AsyncFunction = Object.getPrototypeOf(async function() {
245
631
  }).constructor;
@@ -248,28 +634,35 @@ async function run() {
248
634
  try {
249
635
  text = await clipboard.read();
250
636
  } catch (error) {
251
- console.log("could not read the clipboard");
637
+ console.log(chalk2.red("could not read the clipboard"));
252
638
  return;
253
639
  }
254
- const code = extractCodeBlock(text);
255
- if (code.length === 0) {
640
+ const vmConsole = new Console();
641
+ const result = extractCodeBlock(text);
642
+ if (result.status === "empty") {
643
+ console.log("Nothing happens.");
256
644
  return;
257
645
  }
258
- const captured = new OutputConsole();
259
- try {
260
- const func = new AsyncFunction("tools", "console", code);
261
- await func(new Tools(), captured);
262
- } catch (error) {
263
- captured.log(error);
646
+ if (result.status === "error") {
647
+ vmConsole.log(result.message);
648
+ } else {
649
+ try {
650
+ const func = new AsyncFunction("tools", "console", result.code);
651
+ await func(new Tools(), vmConsole);
652
+ } catch (error) {
653
+ vmConsole.log(error);
654
+ }
264
655
  }
265
- if (captured.lines.length === 0) {
656
+ if (vmConsole.lines.length === 0) {
266
657
  console.log("No output to copy.");
267
658
  return;
268
659
  }
269
660
  try {
270
- await clipboard.write(captured.lines.join("\n"));
661
+ await clipboard.write(
662
+ OUTPUT_HEAD_MARKER + vmConsole.lines.join("\n") + OUTPUT_TAIL_MARKER
663
+ );
271
664
  } catch (error) {
272
- console.log("could not write to the clipboard");
665
+ console.log(chalk2.red("could not write to the clipboard"));
273
666
  return;
274
667
  }
275
668
  console.log("Output copied to the clipboard. Paste it back to the agent.");
@@ -278,34 +671,76 @@ async function run() {
278
671
  // src/commands/init.ts
279
672
  import clipboard2 from "clipboardy";
280
673
 
281
- // src/prompt.ts
282
- var INIT_PROMPT = [
283
- "You help through MeatProxy: the user pastes your replies into a terminal, which runs the JavaScript you send locally.",
284
- "",
285
- "To inspect or edit files, reply with one snippet shaped like this:",
286
- "",
287
- "```js",
288
- "<<<<<<< MEATPROXY",
289
- "// your JavaScript here",
290
- ">>>>>>> MEATPROXY",
291
- "```",
292
- "",
293
- "Keep the marker lines exactly as shown, each on its own line, inside the code block. The terminal reads everything between them and ignores the rest of your reply.",
294
- "",
295
- "Guidelines:",
296
- "- Use relative paths; the working directory is the project root.",
297
- "- Do one step per snippet: the user runs it, then pastes the output back to you.",
298
- "- Use console.log for results and progress; everything printed is copied to the clipboard automatically.",
299
- "- Keep explanations outside the code block.",
300
- "",
301
- "The snippet runs in Node with a `tools` object:",
302
- "tools.read({ file_path }) -> string",
303
- "tools.write({ file_path, content })",
304
- "tools.edit({ file_path, old_string, new_string, replace_all? })",
305
- "tools.pwsh({ command, cwd?, timeoutMs? }) -> { stdout, stderr, exitCode }",
306
- "",
307
- ""
308
- ].join("\n");
674
+ // src/constants/prompt.ts
675
+ var INIT_PROMPT = `<<<<<<< MEATPROXY SYSTEM
676
+ You are an AI agent powered by MeatProxy, which is a cli tool that reads your replies copied by user and runs the JavaScript in it locally.
677
+
678
+ To inspect or edit files, reply with one snippet shaped like this. There should be no more than one pair of MEATPROXY markers. Keep the marker lines exactly as shown, each on its own line, inside the code block. The cli reads everything between them and ignores the rest of your reply.
679
+
680
+ \`\`\`js
681
+ <<<<<<< MEATPROXY
682
+ // your JavaScript here, which is the code for a function body.
683
+ >>>>>>> MEATPROXY
684
+ \`\`\`
685
+
686
+ Guidelines:
687
+ - Use relative paths; the root is current working directory.
688
+ - Do one step per snippet: the user runs it, then pastes the output back to you.
689
+ - Use console.log for results and progress; you will see everything it prints.
690
+ - Keep explanations outside the code block, best to place it at the very end of the whole replies.
691
+ - Newlines: \`read\`/\`edit\` work in LF; the cli converts CRLF automatically. Just use LF in \`old_string\`/\`new_string\`.
692
+
693
+ The snippet runs in Node with a \`tools\` object:
694
+
695
+ \`\`\`
696
+ tools.read({
697
+ /** Path of the file to read, resolved against current working directory. */
698
+ file_path: string;
699
+ /** 1-based first line to return. Defaults to 1. */
700
+ offset?: number;
701
+ /** Maximum number of lines to return. Defaults to 2000. */
702
+ limit?: number;
703
+ }): {
704
+ path: string;
705
+ offset: number;
706
+ lines: {
707
+ number: number;
708
+ text: string;
709
+ }[];
710
+ totalLines: number;
711
+ }
712
+ tools.write({
713
+ /** Path of the file to write, resolved against the tool root. */
714
+ file_path: string;
715
+ /** Full UTF-8 contents of the file. */
716
+ content: string;
717
+ }): void
718
+ tools.edit({
719
+ /** Path of the file to edit, resolved against the tool root. */
720
+ file_path: string;
721
+ /** Literal text to look for. It has to be unique unless \`replace_all\` is set. */
722
+ old_string: string;
723
+ /** Replacement text. An empty string deletes the match. */
724
+ new_string: string;
725
+ /** Replace every occurrence instead of requiring a unique match. */
726
+ replace_all?: boolean;
727
+ }): void
728
+ tools.pwsh({
729
+ /** PowerShell command or script to run. */
730
+ command: string;
731
+ /** Working directory, resolved against the tool root. Defaults to the tool root. */
732
+ cwd?: string;
733
+ /** Timeout in milliseconds. Defaults to 120000. */
734
+ timeoutMs?: number;
735
+ }): {
736
+ stdout: string;
737
+ stderr: string;
738
+ exitCode: number | null;
739
+ }
740
+ \`\`\`
741
+ >>>>>>> MEATPROXY SYSTEM
742
+
743
+ `;
309
744
 
310
745
  // src/commands/init.ts
311
746
  function registerInit(program2) {
@@ -324,7 +759,16 @@ function registerInit(program2) {
324
759
 
325
760
  // src/index.ts
326
761
  var program = new Command();
327
- program.name("mp").description("MeatProxy CLI").version("1.0.0").action(run);
762
+ program.name("mtp").description("MeatProxy CLI").version("1.1.0").addHelpText("after", `
763
+ Usage:
764
+ 1. mtp init | copy the agent prompt to the clipboard
765
+ 2. | paste it into a web chatbot and describe what you want
766
+ 3. | copy the chatbot's reply
767
+ 4. mtp | run the snippet locally; its output is copied back to the clipboard
768
+ 5. | paste the output back to the chatbot and repeat
769
+
770
+ Snippets run with the same access as your terminal; only run replies you trust.
771
+ `).action(run);
328
772
  registerInit(program);
329
773
  await program.parseAsync();
330
774
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core.ts","../src/console.ts","../src/tools.ts","../src/commands/init.ts","../src/prompt.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { run } from \"./core.js\";\nimport { registerInit } from \"./commands/init.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"mp\")\n .description(\"MeatProxy CLI\")\n .version(\"1.0.0\")\n .action(run);\n\nregisterInit(program);\n\nawait program.parseAsync();\n","import clipboard from \"clipboardy\";\r\nimport { OutputConsole } from \"./console.js\";\r\nimport { Tools } from \"./tools.js\";\r\n\r\nexport const HEAD_MARKER = \"<<<<<<< MEATPROXY\";\r\nexport const TAIL_MARKER = \">>>>>>> MEATPROXY\";\r\n\r\nconst INVALID_MARKER_MESSAGE = `Error: Invalid format.\r\nExpected format:\r\n${HEAD_MARKER}\r\n...code...\r\n${TAIL_MARKER}`\r\n\r\n/**\r\n * Extract the code between the MEATPROXY markers.\r\n *\r\n * Exactly one head marker and one tail marker are expected, with the tail after\r\n * the head.\r\n */\r\nexport function extractCodeBlock(text: string): string {\r\n const lines = text.split(/\\r?\\n/);\r\n const heads: number[] = [];\r\n const tails: number[] = [];\r\n\r\n lines.forEach((line, index) => {\r\n const trimmed = line.trim();\r\n if (trimmed === HEAD_MARKER) {\r\n heads.push(index);\r\n } else if (trimmed === TAIL_MARKER) {\r\n tails.push(index);\r\n }\r\n });\r\n\r\n if (heads.length === 0 && tails.length === 0) {\r\n console.log(INVALID_MARKER_MESSAGE);\r\n return \"\";\r\n }\r\n\r\n if (heads.length > 1 || tails.length > 1) {\r\n console.log(\"Error: Found more than one pair of MEATPROXY markers.\");\r\n return \"\";\r\n }\r\n\r\n const head = heads[0];\r\n const tail = tails[0];\r\n\r\n if (head === undefined) {\r\n console.log(INVALID_MARKER_MESSAGE);\r\n return \"\";\r\n }\r\n\r\n if (tail === undefined) {\r\n console.log(INVALID_MARKER_MESSAGE);\r\n return \"\";\r\n }\r\n\r\n if (tail < head) {\r\n console.log(INVALID_MARKER_MESSAGE);\r\n return \"\";\r\n }\r\n\r\n const code = lines\r\n .slice(head + 1, tail)\r\n .join(\"\\n\")\r\n .trim();\r\n\r\n if (code.length === 0) {\r\n console.log(\"No code. Nothing happens.\");\r\n return \"\";\r\n }\r\n\r\n return code;\r\n}\r\n\r\n\r\n// Function bodies are normally sync; an async body lets the clipboard code use\r\n// top-level await (for example `await tools.pwsh(...)`).\r\nconst AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as new (\r\n ...args: string[]\r\n) => (...params: unknown[]) => Promise<unknown>;\r\n\r\n/**\r\n * Read the clipboard, pull the MEATPROXY code block out of it, run that code and\r\n * copy whatever it printed back to the clipboard for the next round.\r\n */\r\nexport async function run(): Promise<void> {\r\n let text: string;\r\n\r\n try {\r\n text = await clipboard.read();\r\n } catch (error) {\r\n console.log(\"could not read the clipboard\");\r\n return;\r\n }\r\n\r\n const code = extractCodeBlock(text);\r\n if (code.length === 0) {\r\n return;\r\n }\r\n\r\n const captured = new OutputConsole();\r\n\r\n try {\r\n const func = new AsyncFunction(\"tools\", \"console\", code);\r\n await func(new Tools(), captured);\r\n } catch (error) {\r\n captured.log(error);\r\n }\r\n\r\n if (captured.lines.length === 0) {\r\n console.log(\"No output to copy.\");\r\n return;\r\n }\r\n\r\n try {\r\n await clipboard.write(captured.lines.join(\"\\n\"));\r\n } catch (error) {\r\n console.log(\"could not write to the clipboard\");\r\n return;\r\n }\r\n\r\n console.log(\"Output copied to the clipboard. Paste it back to the agent.\");\r\n}\r\n","import { format } from \"node:util\";\n\n/**\n * Console handed to the clipboard code as the `console` parameter.\n *\n * Every call is forwarded to the real console and also recorded as formatted\n * text, so the output can be copied to the clipboard afterwards. Methods are\n * bound, which keeps `const { log }console` working.\n */\nexport class OutputConsole {\n /** Everything written so far, one entry per console call. */\n readonly lines: string[] = [];\n\n log(...args: unknown[]): void {\n console.log(...args);\n this.lines.push(format(...args));\n };\n\n info(...args: unknown[]): void {\n console.info(...args);\n this.lines.push(format(...args));\n };\n\n warn(...args: unknown[]): void {\n console.warn(...args);\n this.lines.push(format(...args));\n };\n\n error(...args: unknown[]): void {\n console.error(...args);\n this.lines.push(format(...args));\n };\n\n debug(...args: unknown[]): void {\n console.debug(...args);\n this.lines.push(format(...args));\n };\n}\n","import { spawnSync } from \"node:child_process\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"path\";\n\nexport interface ReadRequest {\n /** Path of the file to read, resolved against the tool root. */\n file_path: string;\n}\n\nexport interface WriteRequest {\n /** Path of the file to write, resolved against the tool root. */\n file_path: string;\n /** Full UTF-8 contents of the file. */\n content: string;\n}\n\nexport interface EditRequest {\n /** Path of the file to edit, resolved against the tool root. */\n file_path: string;\n /** Literal text to look for. It has to be unique unless `replace_all` is set. */\n old_string: string;\n /** Replacement text. An empty string deletes the match. */\n new_string: string;\n /** Replace every occurrence instead of requiring a unique match. */\n replace_all?: boolean;\n}\n\nexport interface PwshRequest {\n /** PowerShell command or script to run. */\n command: string;\n /** Working directory, resolved against the tool root. Defaults to the tool root. */\n cwd?: string;\n /** Timeout in milliseconds. Defaults to 120000. */\n timeoutMs?: number;\n}\n\nexport interface PwshResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nconst DEFAULT_PWSH_TIMEOUT = 120_000;\nconst MAX_PWSH_OUTPUT_BYTES = 16 * 1024 * 1024;\n\n// PowerShell encodes redirected output with the console code page by default, which\n// mangles non-ASCII text, and it reports progress records on stderr. Switch the\n// output to UTF-8 without a BOM and silence progress before running anything.\nconst PWSH_PRELUDE = [\n \"$ProgressPreference = 'SilentlyContinue'\",\n \"try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}\",\n].join(\"\\n\");\n\n// Windows PowerShell serializes error records as CLIXML when stderr is redirected.\nconst CLIXML_ERROR_PATTERN = /<S S=\"Error\">([\\s\\S]*?)<\\/S>/g;\n\nfunction unescapeClixml(text: string): string {\n return text\n .replace(/_x([0-9a-f]{4})_/gi, (_match, code: string) => String.fromCharCode(Number.parseInt(code, 16)))\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&');\n}\n\nfunction decodePowerShellStderr(stderr: string): string {\n if (!stderr.startsWith(\"#< CLIXML\")) {\n return stderr;\n }\n\n const messages = Array.from(stderr.matchAll(CLIXML_ERROR_PATTERN), (match) =>\n unescapeClixml(match[1] ?? \"\"),\n );\n\n return messages.length > 0 ? messages.join(\"\") : stderr;\n}\n\nfunction shellCandidates(): string[] {\n // PowerShell 7 first (it supports && and friends), Windows PowerShell as fallback.\n return process.platform === \"win32\" ? [\"pwsh\", \"powershell.exe\"] : [\"pwsh\"];\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction requirePath(value: unknown, method: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new TypeError(`${method} failed: file_path must be a non-empty string.`);\n }\n\n return value;\n}\n\nfunction requireText(value: unknown, method: string, field: string): string {\n if (typeof value !== \"string\") {\n throw new TypeError(`${method} failed: ${field} must be a string.`);\n }\n\n return value;\n}\n\nfunction countOccurrences(text: string, search: string): number {\n let count = 0;\n let index = text.indexOf(search);\n\n while (index !== -1) {\n count += 1;\n index = text.indexOf(search, index + search.length);\n }\n\n return count;\n}\n\nfunction replaceFirst(text: string, search: string, replacement: string): string {\n const index = text.indexOf(search);\n if (index === -1) {\n return text;\n }\n\n return text.slice(0, index) + replacement + text.slice(index + search.length);\n}\n\n/**\n * Editing tools handed to the clipboard code, so agent can use this object to\n * do edit.\n *\n * The code is compiled with a single parameter named `tools`, so it can call\n * `tools.read({ file_path })`, `tools.write({ file_path, content })`,\n * `tools.edit({ file_path, old_string, new_string })` and `tools.pwsh({ command })`.\n * All paths are resolved against `root` (the current working directory by default).\n */\nexport class Tools {\n private readonly root: string;\n\n constructor(root: string = process.cwd()) {\n this.root = path.resolve(root);\n }\n\n /** Read a UTF-8 text file and return its full contents. */\n read(request: ReadRequest): string {\n const filePath = requirePath(request?.file_path, \"read\");\n const target = this.resolve(filePath);\n\n try {\n return readFileSync(target, \"utf8\");\n } catch (error) {\n throw new Error(`Read failed for ${filePath}: ${describeError(error)}`);\n }\n }\n\n /** Create or fully overwrite a UTF-8 text file, creating parent directories. */\n write(request: WriteRequest): void {\n const filePath = requirePath(request?.file_path, \"write\");\n const content = requireText(request?.content, \"write\", \"content\");\n const target = this.resolve(filePath);\n\n try {\n mkdirSync(path.dirname(target), { recursive: true });\n writeFileSync(target, content, \"utf8\");\n } catch (error) {\n throw new Error(`Write failed for ${filePath}: ${describeError(error)}`);\n }\n }\n\n /**\n * Replace literal text in a file.\n *\n * `old_string` has to be unique unless `replace_all` is true, and an empty\n * `old_string` is rejected because it would match everywhere.\n */\n edit(request: EditRequest): void {\n const filePath = requirePath(request?.file_path, \"edit\");\n const oldText = requireText(request?.old_string, \"edit\", \"old_string\");\n const newText = requireText(request?.new_string, \"edit\", \"new_string\");\n\n if (oldText.length === 0) {\n throw new Error(\"Edit failed: old_string must not be empty.\");\n }\n\n const original = this.read({ file_path: filePath });\n const occurrences = countOccurrences(original, oldText);\n\n if (occurrences === 0) {\n throw new Error(`Edit failed for ${filePath}: old_string was not found.`);\n }\n\n if (occurrences > 1 && !request.replace_all) {\n throw new Error(\n `Edit failed for ${filePath}: old_string appears ${occurrences} times. ` +\n \"Make it unique or pass replace_all = true.\",\n );\n }\n\n const updated = request.replace_all\n ? original.split(oldText).join(newText)\n : replaceFirst(original, oldText, newText);\n\n this.write({ file_path: filePath, content: updated });\n }\n\n /**\n * Run a PowerShell command synchronously and return its output.\n *\n * A non-zero `exitCode` is returned instead of thrown, so the code can inspect\n * `stdout` and `stderr` itself. The command runs with `pwsh` when it is\n * available and falls back to `powershell.exe` on Windows.\n */\n pwsh(request: PwshRequest): PwshResult {\n const command = requireText(request?.command, \"pwsh\", \"command\");\n const timeout = request.timeoutMs ?? DEFAULT_PWSH_TIMEOUT;\n const cwd = request.cwd ? this.resolve(request.cwd) : this.root;\n const encoded = Buffer.from(`${PWSH_PRELUDE}\\n${command}`, \"utf16le\").toString(\"base64\");\n\n for (const shell of shellCandidates()) {\n const result = spawnSync(\n shell,\n [\"-NoProfile\", \"-NonInteractive\", \"-EncodedCommand\", encoded],\n {\n cwd,\n encoding: \"utf8\",\n maxBuffer: MAX_PWSH_OUTPUT_BYTES,\n timeout,\n windowsHide: true,\n },\n );\n\n if (result.error) {\n const code = (result.error as NodeJS.ErrnoException).code;\n\n if (code === \"ENOENT\") {\n continue;\n }\n\n if (code === \"ETIMEDOUT\") {\n throw new Error(`Pwsh timed out after ${timeout} ms.`);\n }\n\n throw new Error(`Pwsh failed: ${result.error.message}`);\n }\n\n return {\n stdout: result.stdout,\n stderr: decodePowerShellStderr(result.stderr),\n exitCode: result.status,\n };\n }\n\n throw new Error(\"Pwsh failed: PowerShell is not available on this machine.\");\n }\n\n private resolve(filePath: string): string {\n return path.resolve(this.root, filePath);\n }\n}\n","import clipboard from \"clipboardy\";\nimport type { Command } from \"commander\";\nimport { INIT_PROMPT } from \"../prompt.js\";\n\nexport function registerInit(program: Command) {\n program\n .command(\"init\")\n .description(\"Copy the agent prompt to the clipboard\")\n .action(async () => {\n try {\n await clipboard.write(INIT_PROMPT);\n } catch (error) {\n console.log(\"could not write to the clipboard\");\n return;\n }\n\n console.log(\"Prompt copied to the clipboard.\");\n console.log(\"Paste it into a web chatbot, add what you want it to do, and send.\");\n console.log(\"Then copy the chatbot's reply back here and run `mtp`.\");\n });\n}\n","/**\n * Text copied to the clipboard by `mtp init`.\n *\n * It is pasted into a web chatbot so the agent knows how to format the code that\n * this CLI will read back from the clipboard and execute.\n */\nexport const INIT_PROMPT = [\n \"You help through MeatProxy: the user pastes your replies into a terminal, which runs the JavaScript you send locally.\",\n \"\",\n \"To inspect or edit files, reply with one snippet shaped like this:\",\n \"\",\n \"```js\",\n \"<<<<<<< MEATPROXY\",\n \"// your JavaScript here\",\n \">>>>>>> MEATPROXY\",\n \"```\",\n \"\",\n \"Keep the marker lines exactly as shown, each on its own line, inside the code block. The terminal reads everything between them and ignores the rest of your reply.\",\n \"\",\n \"Guidelines:\",\n \"- Use relative paths; the working directory is the project root.\",\n \"- Do one step per snippet: the user runs it, then pastes the output back to you.\",\n \"- Use console.log for results and progress; everything printed is copied to the clipboard automatically.\",\n \"- Keep explanations outside the code block.\",\n \"\",\n \"The snippet runs in Node with a `tools` object:\",\n \"tools.read({ file_path }) -> string\",\n \"tools.write({ file_path, content })\",\n \"tools.edit({ file_path, old_string, new_string, replace_all? })\",\n \"tools.pwsh({ command, cwd?, timeoutMs? }) -> { stdout, stderr, exitCode }\",\n \"\",\n \"\"\n].join(\"\\n\");\n"],"mappings":"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","names":["clipboard","program","clipboard"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/runner/core.ts","../src/runner/console.ts","../src/tools/index.ts","../src/tools/types.ts","../src/tools/tool-cards.ts","../src/tools/card-diff.ts","../src/commands/init.ts","../src/constants/prompt.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport { run } from \"./runner/core.js\";\r\nimport { registerInit } from \"./commands/init.js\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"mtp\")\r\n .description(\"MeatProxy CLI\")\r\n .version(__VERSION__)\r\n .addHelpText(\"after\", `\r\nUsage:\r\n 1. mtp init | copy the agent prompt to the clipboard\r\n 2. | paste it into a web chatbot and describe what you want\r\n 3. | copy the chatbot's reply\r\n 4. mtp | run the snippet locally; its output is copied back to the clipboard\r\n 5. | paste the output back to the chatbot and repeat\r\n\r\nSnippets run with the same access as your terminal; only run replies you trust.\r\n`)\r\n .action(run);\r\n\r\nregisterInit(program);\r\n\r\nawait program.parseAsync();\r\n","import clipboard from \"clipboardy\";\r\nimport { Console } from \"./console.js\";\r\nimport { Tools } from \"../tools/index.js\";\r\nimport chalk from \"chalk\";\r\n\r\nexport const HEAD_MARKER = \"<<<<<<< MEATPROXY\";\r\nexport const TAIL_MARKER = \">>>>>>> MEATPROXY\";\r\n\r\nexport const OUTPUT_HEAD_MARKER = \"<<<<<<< MEATPROXY OUTPUT\\n\";\r\nexport const OUTPUT_TAIL_MARKER = \"\\n>>>>>>> MEATPROXY OUTPUT\\n\\n\";\r\n\r\nconst INVALID_MARKER_MESSAGE = `Error: Invalid format.\r\nExpected format:\r\n${HEAD_MARKER}\r\n...code...\r\n${TAIL_MARKER}`\r\n\r\nexport type ExtractResult =\r\n | { status: \"ok\"; code: string }\r\n | { status: \"empty\" }\r\n | { status: \"error\"; message: string };\r\n\r\n/**\r\n * Extract the code between the MEATPROXY markers.\r\n *\r\n * Exactly one head marker and one tail marker are expected, with the tail after\r\n * the head.\r\n */\r\nexport function extractCodeBlock(text: string): ExtractResult {\r\n const lines = text.split(/\\r?\\n/);\r\n const heads: number[] = [];\r\n const tails: number[] = [];\r\n\r\n lines.forEach((line, index) => {\r\n const trimmed = line.trim();\r\n if (trimmed === HEAD_MARKER) {\r\n heads.push(index);\r\n } else if (trimmed === TAIL_MARKER) {\r\n tails.push(index);\r\n }\r\n });\r\n\r\n if (heads.length === 0 && tails.length === 0) {\r\n return { status: \"error\", message: INVALID_MARKER_MESSAGE };\r\n }\r\n\r\n if (heads.length > 1 || tails.length > 1) {\r\n return { status: \"error\", message: \"Error: Found more than one pair of MEATPROXY markers.\" };\r\n }\r\n\r\n const head = heads[0];\r\n const tail = tails[0];\r\n\r\n if (head === undefined || tail === undefined || tail < head) {\r\n return { status: \"error\", message: INVALID_MARKER_MESSAGE };\r\n }\r\n\r\n const code = lines\r\n .slice(head + 1, tail)\r\n .join(\"\\n\")\r\n .trim();\r\n\r\n if (code.length === 0) {\r\n return { status: \"empty\" };\r\n }\r\n\r\n return { status: \"ok\", code };\r\n}\r\n\r\n\r\n// Function bodies are normally sync; an async body lets the clipboard code use\r\n// top-level await (for example `await tools.pwsh(...)`).\r\nconst AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as new (\r\n ...args: string[]\r\n) => (...params: unknown[]) => Promise<unknown>;\r\n\r\n/**\r\n * Read the clipboard, pull the MEATPROXY code block out of it, run that code and\r\n * copy whatever it printed back to the clipboard for the next round.\r\n */\r\nexport async function run(): Promise<void> {\r\n let text: string;\r\n\r\n try {\r\n text = await clipboard.read();\r\n } catch (error) {\r\n console.log(chalk.red(\"could not read the clipboard\"));\r\n return;\r\n }\r\n\r\n const vmConsole = new Console();\r\n\r\n const result = extractCodeBlock(text);\r\n if (result.status === \"empty\") {\r\n console.log(\"Nothing happens.\");\r\n return;\r\n }\r\n\r\n if (result.status === \"error\") {\r\n vmConsole.log(result.message);\r\n } else {\r\n try {\r\n const func = new AsyncFunction(\"tools\", \"console\", result.code);\r\n await func(new Tools(), vmConsole);\r\n } catch (error) {\r\n vmConsole.log(error);\r\n }\r\n }\r\n\r\n if (vmConsole.lines.length === 0) {\r\n console.log(\"No output to copy.\");\r\n return;\r\n }\r\n\r\n try {\r\n await clipboard.write(\r\n OUTPUT_HEAD_MARKER +\r\n vmConsole.lines.join(\"\\n\") +\r\n OUTPUT_TAIL_MARKER\r\n );\r\n } catch (error) {\r\n console.log(chalk.red(\"could not write to the clipboard\"));\r\n return;\r\n }\r\n\r\n console.log(\"Output copied to the clipboard. Paste it back to the agent.\");\r\n}\r\n","import { format } from \"util\";\n\n/** Console handed to the clipboard code as the `console` parameter. */\nexport class Console {\n /** Everything written so far, one entry per console call. */\n readonly lines: string[] = [];\n\n log(...args: unknown[]): void {\n this.lines.push(format(...args));\n };\n}\n","import { spawnSync } from \"child_process\";\r\nimport { mkdirSync, readFileSync, writeFileSync } from \"fs\";\r\nimport path from \"path\";\r\nimport { isEditParams, isPwshParams, isReadParams, isWriteParams, type PwshResult, type ReadResult } from \"./types.js\";\r\nimport { ToolCards } from \"./tool-cards.js\";\r\nimport { toDiffLines } from \"./card-diff.js\";\r\n\r\nconst DEFAULT_PWSH_TIMEOUT = 120_000;\r\nconst DEFAULT_READ_LIMIT = 2000;\r\nconst MAX_PWSH_OUTPUT_BYTES = 16 * 1024 * 1024;\r\n\r\n// PowerShell encodes redirected output with the console code page by default, which\r\n// mangles non-ASCII text, and it reports progress records on stderr. Switch the\r\n// output to UTF-8 without a BOM and silence progress before running anything.\r\nconst PWSH_PRELUDE = [\r\n \"$ProgressPreference = 'SilentlyContinue'\",\r\n \"try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}\",\r\n].join(\"\\n\");\r\n\r\n// Windows PowerShell serializes error records as CLIXML when stderr is redirected.\r\nconst CLIXML_ERROR_PATTERN = /<S S=\"Error\">([\\s\\S]*?)<\\/S>/g;\r\n\r\nfunction unescapeClixml(text: string): string {\r\n return text\r\n .replace(/_x([0-9a-f]{4})_/gi, (_match, code: string) => String.fromCharCode(Number.parseInt(code, 16)))\r\n .replace(/&lt;/g, \"<\")\r\n .replace(/&gt;/g, \">\")\r\n .replace(/&quot;/g, \"\\\"\")\r\n .replace(/&apos;/g, \"'\")\r\n .replace(/&amp;/g, \"&\");\r\n}\r\n\r\nfunction decodePowerShellStderr(stderr: string): string {\r\n if (!stderr.startsWith(\"#< CLIXML\")) {\r\n return stderr;\r\n }\r\n\r\n const messages = Array.from(stderr.matchAll(CLIXML_ERROR_PATTERN), (match) =>\r\n unescapeClixml(match[1] ?? \"\"),\r\n );\r\n\r\n return messages.length > 0 ? messages.join(\"\") : stderr;\r\n}\r\n\r\nfunction shellCandidates(): string[] {\r\n // PowerShell 7 first (it supports && and friends), Windows PowerShell as fallback.\r\n return process.platform === \"win32\" ? [\"pwsh\", \"powershell.exe\"] : [\"pwsh\"];\r\n}\r\n\r\nfunction describeError(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error);\r\n}\r\n\r\n/**\r\n * Editing tools handed to the clipboard code, so agent can use this object to\r\n * do edit.\r\n */\r\nexport class Tools {\r\n private readonly root: string;\r\n\r\n constructor(root: string = process.cwd()) {\r\n this.root = path.resolve(root);\r\n }\r\n\r\n /** Read a UTF-8 text file and return line-numbered content. */\r\n read(params: unknown): ReadResult {\r\n if (!isReadParams(params)) {\r\n ToolCards.read({ filePath: \"\", ok: false });\r\n throw new Error(\"Read failed: invalid parameters.\");\r\n }\r\n const filePath = params.file_path;\r\n const target = this.resolve(filePath);\r\n let content: string;\r\n try {\r\n content = readFileSync(target, \"utf8\");\r\n } catch (error) {\r\n ToolCards.read({ filePath, ok: false });\r\n throw new Error(`Read failed for ${filePath}: ${describeError(error)}`);\r\n }\r\n const normalized = content.replace(/\\r\\n/g, \"\\n\");\r\n const allLines = normalized.split(\"\\n\");\r\n // A trailing newline produces one empty element; drop it so totalLines\r\n // and the returned lines match what an editor shows.\r\n if (allLines.length > 0 && allLines[allLines.length - 1] === \"\") {\r\n allLines.pop();\r\n }\r\n // 0-based\r\n const offset = (params.offset ?? 1) - 1;\r\n const limit = params.limit ?? DEFAULT_READ_LIMIT;\r\n ToolCards.read({ filePath, ok: true });\r\n return {\r\n path: filePath,\r\n offset: offset + 1, // 1-based\r\n lines: allLines.slice(offset, offset + limit).map((text, index) => ({\r\n number: offset + index + 1,\r\n text,\r\n })),\r\n totalLines: allLines.length,\r\n };\r\n }\r\n\r\n /** Create or fully overwrite a UTF-8 text file, creating parent directories. */\r\n write(params: unknown): void {\r\n if (!isWriteParams(params)) {\r\n ToolCards.write({ filePath: \"\", content: \"\", ok: false });\r\n throw new Error(\"Write failed: invalid parameters.\");\r\n }\r\n const filePath = params.file_path;\r\n const target = this.resolve(filePath);\r\n\r\n try {\r\n mkdirSync(path.dirname(target), { recursive: true });\r\n writeFileSync(target, params.content, \"utf8\");\r\n } catch (error) {\r\n ToolCards.write({ filePath, content: params.content, ok: false });\r\n throw new Error(`Write failed for ${filePath}: ${describeError(error)}`);\r\n }\r\n ToolCards.write({ filePath, content: params.content, ok: true });\r\n }\r\n\r\n /**\r\n * Replace literal text in a file.\r\n *\r\n * `old_string` has to be unique unless `replace_all` is true, and an empty\r\n * `old_string` is rejected because it would match everywhere.\r\n */\r\n edit(params: unknown): void {\r\n if (!isEditParams(params)) {\r\n ToolCards.edit({ filePath: \"\", replaceAll: false, ok: false });\r\n throw new Error(\"Edit failed: invalid parameters.\");\r\n }\r\n\r\n const filePath = params.file_path;\r\n const oldText = params.old_string;\r\n const newText = params.new_string;\r\n const replaceAll = params.replace_all === true;\r\n\r\n if (oldText.length === 0) {\r\n ToolCards.edit({ filePath, replaceAll, ok: false });\r\n throw new Error(\"Edit failed: old_string must not be empty.\");\r\n }\r\n\r\n const target = this.resolve(filePath);\r\n let original: string;\r\n try {\r\n original = readFileSync(target, \"utf8\");\r\n } catch (error) {\r\n ToolCards.edit({ filePath, replaceAll, ok: false });\r\n throw new Error(`Edit failed for ${filePath}: ${describeError(error)}`);\r\n }\r\n\r\n // Work in LF internally and restore the file's own EOL on write, so\r\n // matching does not depend on how the caller spelled newlines.\r\n const eol = original.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\r\n const lf = (text: string) => text.replace(/\\r\\n/g, \"\\n\");\r\n\r\n const lfOriginal = lf(original);\r\n const lfOldText = lf(oldText);\r\n\r\n const firstIndex = lfOriginal.indexOf(lfOldText);\r\n if (firstIndex === -1) {\r\n // Old text not found\r\n ToolCards.edit({ filePath, replaceAll, ok: false });\r\n throw new Error(`Edit failed for ${filePath}: old_string was not found.`);\r\n }\r\n\r\n const secondIndex = lfOriginal.indexOf(lfOldText, firstIndex + lfOldText.length);\r\n if (secondIndex !== -1 && !replaceAll) {\r\n // No replaceAll but find more than one occurrances.\r\n ToolCards.edit({ filePath, replaceAll, ok: false });\r\n throw new Error(\r\n `Edit failed for ${filePath}: old_string appears multiple times. `\r\n + \"Make it unique or pass replace_all = true.\"\r\n );\r\n }\r\n\r\n // Use function as the second parameter, or parameter like \"$$\" will be\r\n // interpreted to have special meaning:\r\n // \"123\".replaceAll(\"2\",\"$$\") will be \"1$3\"\r\n const updated = lfOriginal\r\n .replaceAll(lfOldText, () => lf(newText))\r\n .replace(/\\n/g, eol);\r\n\r\n try {\r\n writeFileSync(target, updated, \"utf8\");\r\n } catch (error) {\r\n ToolCards.edit({ filePath, replaceAll, ok: false });\r\n throw new Error(`Edit failed for ${filePath}: ${describeError(error)}`);\r\n }\r\n\r\n const diff = toDiffLines(original, updated);\r\n\r\n ToolCards.edit({ filePath, replaceAll, ok: true, diff });\r\n }\r\n\r\n /**\r\n * Run a PowerShell command synchronously and return its output.\r\n */\r\n pwsh(params: unknown): PwshResult {\r\n if (!isPwshParams(params)) {\r\n ToolCards.pwsh({ command: \"\", exitCode: null, ok: false });\r\n throw new Error(\"Pwsh failed: invalid parameters.\");\r\n }\r\n\r\n const command = params.command;\r\n const timeout = params.timeoutMs ?? DEFAULT_PWSH_TIMEOUT;\r\n const cwd = params.cwd ? this.resolve(params.cwd) : this.root;\r\n const encoded = Buffer.from(PWSH_PRELUDE + \"\\n\" + command, \"utf16le\").toString(\"base64\");\r\n\r\n for (const shell of shellCandidates()) {\r\n const result = spawnSync(\r\n shell,\r\n [\"-NoProfile\", \"-NonInteractive\", \"-EncodedCommand\", encoded],\r\n {\r\n cwd,\r\n encoding: \"utf8\",\r\n maxBuffer: MAX_PWSH_OUTPUT_BYTES,\r\n timeout,\r\n windowsHide: true,\r\n },\r\n );\r\n\r\n if (result.error) {\r\n const code = (result.error as NodeJS.ErrnoException).code;\r\n\r\n if (code === \"ENOENT\") continue;\r\n ToolCards.pwsh({ command, exitCode: null, ok: false });\r\n if (code === \"ETIMEDOUT\") {\r\n throw new Error(\"Pwsh timed out after \" + timeout + \" ms.\");\r\n }\r\n\r\n throw new Error(\"Pwsh failed: \" + result.error.message);\r\n }\r\n\r\n ToolCards.pwsh({ command, exitCode: result.status, ok: true });\r\n return {\r\n stdout: result.stdout,\r\n stderr: decodePowerShellStderr(result.stderr),\r\n exitCode: result.status,\r\n };\r\n }\r\n\r\n ToolCards.pwsh({ command, exitCode: null, ok: false });\r\n throw new Error(\"Pwsh failed: PowerShell is not available on this machine.\");\r\n }\r\n\r\n private resolve(filePath: string): string {\r\n return path.resolve(this.root, filePath);\r\n }\r\n}\r\n","/** Read a UTF-8 text file and return line-numbered content. */\r\nexport interface ReadParams {\r\n /** Path of the file to read, resolved against current working directory. */\r\n file_path: string;\r\n /** 1-based first line to return. Defaults to 1. */\r\n offset?: number;\r\n /** Maximum number of lines to return. Defaults to 2000. */\r\n limit?: number;\r\n}\r\n\r\nexport interface ReadResult {\r\n path: string;\r\n /** 1-based line number of the first returned line. */\r\n offset: number;\r\n lines: {\r\n number: number;\r\n text: string;\r\n }[];\r\n totalLines: number;\r\n}\r\n\r\nexport interface WriteParams {\r\n /** Path of the file to write, resolved against the tool root. */\r\n file_path: string;\r\n /** Full UTF-8 contents of the file. */\r\n content: string;\r\n}\r\n\r\nexport interface EditParams {\r\n /** Path of the file to edit, resolved against the tool root. */\r\n file_path: string;\r\n /** Literal text to look for. It has to be unique unless `replace_all` is set. */\r\n old_string: string;\r\n /** Replacement text. An empty string deletes the match. */\r\n new_string: string;\r\n /** Replace every occurrence instead of requiring a unique match. */\r\n replace_all?: boolean;\r\n}\r\n\r\nexport interface PwshParams {\r\n /** PowerShell command or script to run. */\r\n command: string;\r\n /** Working directory, resolved against the tool root. Defaults to the tool root. */\r\n cwd?: string;\r\n /** Timeout in milliseconds. Defaults to 120000. */\r\n timeoutMs?: number;\r\n}\r\n\r\nexport interface PwshResult {\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number | null;\r\n}\r\n\r\ntype UnknownRecord = Record<string, unknown>;\r\n\r\nfunction isRecord(value: unknown): value is UnknownRecord {\r\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction isNonEmptyString(value: unknown): value is string {\r\n return typeof value === \"string\" && value.length > 0;\r\n}\r\n\r\nfunction isOptionalBoolean(value: unknown): value is boolean | undefined {\r\n return value === undefined || typeof value === \"boolean\";\r\n}\r\n\r\nfunction isOptionalString(value: unknown): value is string | undefined {\r\n return value === undefined || typeof value === \"string\";\r\n}\r\n\r\nfunction isOptionalPositiveInteger(value: unknown): value is number | undefined {\r\n return value === undefined\r\n || (typeof value === \"number\" && Number.isInteger(value) && value >= 1);\r\n}\r\n\r\nexport function isReadParams(value: unknown): value is ReadParams {\r\n return isRecord(value)\r\n && isNonEmptyString(value.file_path)\r\n && isOptionalPositiveInteger(value.offset)\r\n && isOptionalPositiveInteger(value.limit);\r\n}\r\n\r\nexport function isWriteParams(value: unknown): value is WriteParams {\r\n return isRecord(value)\r\n && isNonEmptyString(value.file_path)\r\n && typeof value.content === \"string\";\r\n}\r\n\r\nexport function isEditParams(value: unknown): value is EditParams {\r\n return isRecord(value)\r\n && isNonEmptyString(value.file_path)\r\n && typeof value.old_string === \"string\"\r\n && typeof value.new_string === \"string\"\r\n && isOptionalBoolean(value.replace_all);\r\n}\r\n\r\nexport function isPwshParams(value: unknown): value is PwshParams {\r\n return isRecord(value)\r\n && typeof value.command === \"string\"\r\n && isOptionalString(value.cwd)\r\n && isOptionalPositiveInteger(value.timeoutMs);\r\n}","import chalk from \"chalk\";\r\nimport terminalSize from \"terminal-size\";\r\nimport { groupDiffHunks, toPairs, type DiffLine, type DiffPair, type DiffRow } from \"./card-diff.js\";\r\nimport { type LinePart, type Style } from \"./card-types.js\";\r\n\r\nfunction width(): number {\r\n let cols = FALLBACK_WIDTH;\r\n try {\r\n const size = terminalSize();\r\n if (Number.isFinite(size.columns) && size.columns > 0) cols = size.columns;\r\n } catch {\r\n // ignore\r\n }\r\n if (cols <= 64) return cols;\r\n if (cols <= 74) return 64;\r\n return cols - 10;\r\n}\r\n\r\nfunction truncate(text: string, maxLen: number): string {\r\n if (text.length <= maxLen) return text;\r\n if (maxLen <= 0) return \"\";\r\n if (maxLen <= 3) return \".\".repeat(maxLen);\r\n return text.slice(0, maxLen - 3) + \"...\";\r\n}\r\n\r\nconst PAD = \" \";\r\nconst FALLBACK_WIDTH = 80;\r\nconst SPLIT_MIN_WIDTH = 80;\r\nconst INNER_WIDTH = width() - PAD.length * 2;\r\n\r\n// #fcebdf oklch(0.95 0.025 56)\r\n// #783f09 oklch(0.43 0.1 56)\r\n// #34251a oklch(0.28 0.03 56)\r\n// #f5f1ef oklch(0.96 0.005 56)\r\n// #005E19 oklch(0.42 0.13 146)\r\n// #94020d oklch(0.42 0.17 27)\r\n// #595101 oklch(0.43 0.09 103)\r\nconst S = {\r\n header: (t: string) => chalk.bgHex(\"#fcebdf\").hex(\"#783f09\")(t),\r\n headerContent: (t: string) => chalk.bgHex(\"#fcebdf\").hex(\"#34251a\")(t),\r\n body: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#34251a\")(t),\r\n ok: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#005E19\")(t),\r\n fail: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#94020d\")(t),\r\n warn: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#595101\")(t),\r\n del: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#94020d\")(t),\r\n add: (t: string) => chalk.bgHex(\"#f5f1ef\").hex(\"#005E19\")(t),\r\n} as const;\r\n\r\nexport interface ReadCardOptions {\r\n filePath: string;\r\n ok: boolean;\r\n}\r\n\r\nexport interface WriteCardOptions {\r\n filePath: string;\r\n content: string;\r\n ok: boolean;\r\n}\r\n\r\nexport interface EditCardOptions {\r\n filePath: string;\r\n replaceAll: boolean;\r\n ok: boolean;\r\n /**\r\n * Line-level diff of the whole file. Present on success; the card renders\r\n * nothing but the header and status when absent.\r\n */\r\n diff?: DiffLine[] | undefined;\r\n}\r\n\r\nexport interface PwshCardOptions {\r\n command: string;\r\n exitCode: number | null;\r\n ok: boolean;\r\n}\r\n\r\nexport class ToolCards {\r\n private constructor() { }\r\n\r\n /** Total width of tools cards. */\r\n private static width(): number {\r\n let cols = FALLBACK_WIDTH;\r\n try {\r\n const size = terminalSize();\r\n if (Number.isFinite(size.columns) && size.columns > 0) cols = size.columns;\r\n } catch {\r\n // ignore\r\n }\r\n if (cols <= 64) return cols;\r\n if (cols <= 74) return 64;\r\n return cols - 10;\r\n }\r\n\r\n /** Keep maxLines of text, every lines truncated by maxLen */\r\n private static blockLines(text: string, maxLines: number, maxLen: number): string[] {\r\n const parts = text.split(\"\\n\");\r\n const shown = parts.slice(0, maxLines);\r\n return shown.map((raw) => {\r\n return truncate(raw.replace(/\\r$/, \"\"), Math.max(0, maxLen));\r\n });\r\n }\r\n\r\n private static line(parts: LinePart[], padStyle: Style = S.body): void {\r\n let used = 0;\r\n let lastStyle = padStyle;\r\n const chunks: string[] = [];\r\n\r\n for (const part of parts) {\r\n if (used >= INNER_WIDTH) break;\r\n\r\n const room = INNER_WIDTH - used;\r\n const text = part.text.length > room\r\n ? part.text.slice(0, room - 3) + \"...\"\r\n : part.text;\r\n\r\n if (text.length === 0) continue;\r\n\r\n chunks.push(part.style(text));\r\n used += text.length;\r\n lastStyle = part.style;\r\n }\r\n\r\n let padding = \"\";\r\n if (used < INNER_WIDTH) {\r\n padding = lastStyle(\" \".repeat(INNER_WIDTH - used));\r\n }\r\n\r\n console.log(padStyle(PAD) + chunks.join(\"\") + padding + padStyle(PAD));\r\n }\r\n\r\n // Cards for different tools.\r\n\r\n static read(opts: ReadCardOptions): void {\r\n this.line([{\r\n text: \"read \",\r\n style: S.header,\r\n }, {\r\n text: opts.filePath,\r\n style: S.headerContent,\r\n }], S.header);\r\n if (opts.ok) {\r\n this.line([{ text: \"success\", style: S.ok }], S.body);\r\n } else {\r\n this.line([{ text: \"failed\", style: S.fail }], S.body);\r\n }\r\n console.log();\r\n }\r\n\r\n static write(opts: WriteCardOptions): void {\r\n const BLOCK_MAX_LINES = 2;\r\n this.line([{\r\n text: \"write \",\r\n style: S.header,\r\n }, {\r\n text: opts.filePath,\r\n style: S.headerContent,\r\n }], S.header);\r\n if (opts.content) {\r\n for (const text of ToolCards.blockLines(opts.content, BLOCK_MAX_LINES, INNER_WIDTH)) {\r\n this.line([{ text, style: S.body }], S.body);\r\n }\r\n }\r\n if (opts.ok) {\r\n this.line([{ text: \"success\", style: S.ok }], S.body);\r\n } else {\r\n this.line([{ text: \"failed\", style: S.fail }], S.body);\r\n }\r\n console.log();\r\n }\r\n\r\n static edit(opts: EditCardOptions): void {\r\n ToolCards.line([{\r\n text: \"edit \",\r\n style: S.header,\r\n }, {\r\n text: opts.filePath,\r\n style: S.headerContent,\r\n }], S.header);\r\n\r\n if (opts.ok && opts.diff && opts.diff.length > 0) {\r\n const MAX_HUNKS = 3;\r\n const MAX_ROWS = 10;\r\n const hunks = groupDiffHunks(opts.diff);\r\n const moreHunks = hunks.length - MAX_HUNKS;\r\n const shownHunks = moreHunks > 0 ? hunks.slice(0, MAX_HUNKS) : hunks;\r\n\r\n const renderSingle = (rows: DiffRow[]): void => {\r\n const capped = ToolCards.capRows(rows, MAX_ROWS);\r\n // Calculate the most characters line numbers can occupy.\r\n let gutter = 1;\r\n for (const row of capped) {\r\n if (row.kind === \"gap\") continue;\r\n gutter = Math.max(\r\n gutter,\r\n String(row.oldLine ?? row.newLine ?? 0).length\r\n );\r\n }\r\n const textMax = Math.max(4, INNER_WIDTH - 2 - gutter - 1);\r\n for (const row of capped) {\r\n if (row.kind === \"gap\") {\r\n ToolCards.line([{ text: row.text, style: S.body }], S.body);\r\n continue;\r\n }\r\n const [sign, ln, style] = (\r\n row.kind === \"add\" ? [\"+\", row.newLine, S.add] :\r\n row.kind === \"del\" ? [\"-\", row.oldLine, S.del] :\r\n [\" \", \"\", S.body]\r\n );\r\n const text = truncate(row.text.replace(/\\r$/, \"\"), textMax);\r\n ToolCards.line([{ text: sign + String(ln ?? \"\").padStart(gutter) + \" \" + text, style }], S.body);\r\n }\r\n };\r\n\r\n const renderSplit = (rows: DiffRow[]): void => {\r\n const capped = ToolCards.capPairs(toPairs(rows), MAX_ROWS);\r\n const SEP = \" \\u2502 \";\r\n const cellTotal = INNER_WIDTH - SEP.length;\r\n const leftW = Math.floor(cellTotal / 2);\r\n const rightW = cellTotal - leftW;\r\n\r\n let leftGutter = 1;\r\n let rightGutter = 1;\r\n for (const pair of capped) {\r\n if (pair.kind === \"gap\") continue;\r\n leftGutter = Math.max(leftGutter, String(pair.left?.oldLine ?? 0).length);\r\n rightGutter = Math.max(rightGutter, String(pair.right?.newLine ?? 0).length);\r\n }\r\n\r\n const leftMax = Math.max(4, leftW - 2 - leftGutter);\r\n const rightMax = Math.max(4, rightW - 2 - rightGutter);\r\n\r\n const cell = (\r\n line: DiffLine | undefined, side: \"old\" | \"new\",\r\n cellW: number, gutterW: number, maxLen: number\r\n ): LinePart => {\r\n if (!line) return { text: \"\\u2591\".repeat(cellW), style: S.body };\r\n const ln = side === \"old\" ? line.oldLine : line.newLine;\r\n const [sign, style] = (\r\n line.kind === \"add\" ? [\"+\", S.add] :\r\n line.kind === \"del\" ? [\"-\", S.del] :\r\n [\" \", S.body]\r\n );\r\n const text = truncate(line.text.replace(/\\r$/, \"\"), maxLen);\r\n return { text: (sign + String(ln ?? \"\").padStart(gutterW) + \" \" + text).padEnd(cellW), style };\r\n };\r\n\r\n for (const pair of capped) {\r\n if (pair.kind === \"gap\") {\r\n ToolCards.line([{ text: pair.text, style: S.body }], S.body);\r\n continue;\r\n }\r\n ToolCards.line([\r\n cell(pair.left, \"old\", leftW, leftGutter, leftMax),\r\n { text: SEP, style: S.body },\r\n cell(pair.right, \"new\", rightW, rightGutter, rightMax),\r\n ], S.body);\r\n }\r\n };\r\n\r\n shownHunks.forEach((hunk, index) => {\r\n if (index > 0) {\r\n const gapText = hunk.gap?.text ?? \"...\";\r\n ToolCards.line([{ text: gapText, style: S.body }], S.body);\r\n }\r\n if (INNER_WIDTH < SPLIT_MIN_WIDTH) {\r\n renderSingle(hunk.rows);\r\n } else {\r\n renderSplit(hunk.rows);\r\n }\r\n });\r\n\r\n if (moreHunks > 0) {\r\n ToolCards.line([{ text: \"... \" + moreHunks + \" more hunk\" + (moreHunks === 1 ? \"\" : \"s\"), style: S.body }], S.body);\r\n }\r\n }\r\n if (opts.ok) {\r\n ToolCards.line([{ text: \"success\", style: S.ok }], S.body);\r\n } else {\r\n ToolCards.line([{ text: \"failed\", style: S.fail }], S.body);\r\n }\r\n console.log();\r\n }\r\n\r\n static pwsh(opts: PwshCardOptions): void {\r\n ToolCards.line([{\r\n text: \"pwsh \",\r\n style: S.header,\r\n }, {\r\n text: opts.command.replace(/\\r?\\n/g, \" \"),\r\n style: S.headerContent,\r\n }], S.header);\r\n\r\n if (!opts.ok) {\r\n ToolCards.line([{ text: \"failed\", style: S.fail }], S.body);\r\n } else {\r\n if (opts.exitCode === 0) {\r\n ToolCards.line([{ text: \"success\", style: S.ok }], S.body);\r\n } else {\r\n ToolCards.line([{ text: \"exit \" + opts.exitCode, style: S.warn }], S.body);\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n private static capRows(rows: DiffRow[], maxRows: number): DiffRow[] {\r\n if (rows.length <= maxRows) return rows;\r\n const shown = rows.slice(0, maxRows - 1);\r\n const hidden = rows.length - shown.length;\r\n return [...shown, { kind: \"gap\", text: \"... \" + hidden + \" more line\" + (hidden === 1 ? \"\" : \"s\") }];\r\n }\r\n\r\n private static capPairs(pairs: DiffPair[], maxRows: number): DiffPair[] {\r\n if (pairs.length <= maxRows) return pairs;\r\n const shown = pairs.slice(0, maxRows - 1);\r\n const hidden = pairs.length - shown.length;\r\n return [...shown, { kind: \"gap\", text: \"... \" + hidden + \" more line\" + (hidden === 1 ? \"\" : \"s\") }];\r\n }\r\n}\r\n","import { diffLines } from \"diff\";\r\n\r\nexport type DiffLineKind = \"add\" | \"del\" | \"ctx\";\r\n\r\n/** One line of a line-level diff, numbered against the old / new file. */\r\nexport interface DiffLine {\r\n kind: DiffLineKind;\r\n /** 1-based line number in the old file; undefined for added lines. */\r\n oldLine?: number | undefined;\r\n /** 1-based line number in the new file; undefined for removed lines. */\r\n newLine?: number | undefined;\r\n text: string;\r\n}\r\n\r\n/** A diff row, or a placeholder for a run of lines that was collapsed away. */\r\nexport type DiffRow = DiffLine | { kind: \"gap\"; text: string };\r\n\r\n/** A split-view row: either a collapsed gap or a left/right pair. */\r\nexport type DiffPair =\r\n | { kind: \"gap\"; text: string }\r\n | { kind: \"pair\"; left?: DiffLine | undefined; right?: DiffLine | undefined };\r\n\r\n/** A contiguous block of changed lines, optionally preceded by a collapsed gap. */\r\nexport interface DiffHunk {\r\n gap: { kind: \"gap\"; text: string } | undefined;\r\n // Here we use DiffRow instead of DiffLine, because render layer might\r\n // insert row like { kind: \"gap\"; text: \"... N more lines\" } for changes\r\n // block that spread too many lines, or similar other situations.\r\n rows: DiffRow[];\r\n}\r\n\r\n/**\r\n * Split one jsdiff change value into its individual lines, dropping the\r\n * trailing empty string produced by a final newline.\r\n */\r\nfunction splitChangeValue(value: string): string[] {\r\n const parts = value.split(\"\\n\");\r\n if (parts.length > 0 && parts[parts.length - 1] === \"\") parts.pop();\r\n return parts;\r\n}\r\n\r\n/**\r\n * Calculate one numbered row per line diff so the tool card can render a real\r\n * line-by-line diff.\r\n */\r\nexport function toDiffLines(original: string, updated: string): DiffLine[] {\r\n const changes = diffLines(original, updated, { stripTrailingCr: true });\r\n const rows: DiffLine[] = [];\r\n let oldLine = 1;\r\n let newLine = 1;\r\n\r\n for (const change of changes) {\r\n for (const text of splitChangeValue(change.value)) {\r\n if (change.added) {\r\n rows.push({ kind: \"add\", newLine: newLine++, text });\r\n } else if (change.removed) {\r\n rows.push({ kind: \"del\", oldLine: oldLine++, text });\r\n } else {\r\n rows.push({ kind: \"ctx\", oldLine: oldLine++, newLine: newLine++, text });\r\n }\r\n }\r\n }\r\n\r\n return rows;\r\n}\r\n\r\n/**\r\n * Keep only lines within [-context, +context] range of a change line; collapse\r\n * the rest into gaps.\r\n * \r\n * After that, we will get something like:\r\n * ```\r\n * [\r\n * { kind: 'gap', text: '... 2 unchanged lines' },\r\n * { kind: 'ctx', oldLine: 3, newLine: 3, text: 'row 3' },\r\n * { kind: 'ctx', oldLine: 4, newLine: 4, text: 'row 4' },\r\n * { kind: 'del', oldLine: 5 , text: 'row 5' },\r\n * { kind: 'ctx', oldLine: 6, newLine: 5, text: 'row 6' },\r\n * { kind: 'ctx', oldLine: 7, newLine: 6, text: 'row 7' },\r\n * { kind: 'gap', text: '... 3 unchanged lines' }\r\n * ]\r\n * ```\r\n */\r\nfunction hunkRows(lines: DiffLine[], context: number): DiffRow[] {\r\n const keep = new Array<boolean>(lines.length).fill(false);\r\n for (let i = 0; i < lines.length; i++) {\r\n const current = lines[i];\r\n if (!current || current.kind === \"ctx\") continue;\r\n const from = Math.max(0, i - context);\r\n const to = Math.min(lines.length - 1, i + context);\r\n for (let j = from; j <= to; j++) keep[j] = true;\r\n }\r\n\r\n const rows: DiffRow[] = [];\r\n for (let i = 0; i < lines.length;) {\r\n if (keep[i]) {\r\n const kept = lines[i];\r\n if (kept) rows.push(kept);\r\n i++;\r\n continue;\r\n }\r\n // Encounter a don't keep line.\r\n let j = i;\r\n while (j < lines.length && !keep[j]) j++;\r\n const skipped = j - i;\r\n rows.push({\r\n kind: \"gap\",\r\n text: \"... \" + skipped + \" unchanged line\" + (skipped === 1 ? \"\" : \"s\"),\r\n });\r\n i = j;\r\n }\r\n return rows;\r\n}\r\n\r\n/**\r\n * Filter a line diff into hunks, dropping leading/trailing unchanged context.\r\n * Each hunk carries the gap that separated it from the previous one.\r\n */\r\nexport function groupDiffHunks(lines: DiffLine[], context = 2): DiffHunk[] {\r\n const all = hunkRows(lines, context);\r\n let start = 0;\r\n let end = all.length;\r\n // Throw away all gaps at the very beginning and at the very end.\r\n while (start < end && all[start]?.kind === \"gap\") start++;\r\n while (end > start && all[end - 1]?.kind === \"gap\") end--;\r\n const trimmed = all.slice(start, end);\r\n if (trimmed.length === 0) return [];\r\n\r\n const hunks: DiffHunk[] = [];\r\n let pendingGap: { kind: \"gap\"; text: string } | undefined;\r\n let cur: DiffRow[] = [];\r\n for (const row of trimmed) {\r\n if (row.kind === \"gap\") {\r\n if (cur.length > 0) {\r\n hunks.push({ gap: pendingGap, rows: cur });\r\n cur = [];\r\n }\r\n pendingGap = row;\r\n } else {\r\n cur.push(row);\r\n }\r\n }\r\n if (cur.length > 0) hunks.push({ gap: pendingGap, rows: cur });\r\n return hunks;\r\n}\r\n\r\n/** Group rows into left/right pairs; unchanged lines pair with themselves. */\r\nexport function toPairs(rows: DiffRow[]): DiffPair[] {\r\n const pairs: DiffPair[] = [];\r\n for (let i = 0; i < rows.length;) {\r\n const row = rows[i];\r\n if (!row) { i++; continue; }\r\n if (row.kind === \"gap\") { pairs.push({ kind: \"gap\", text: row.text }); i++; continue; }\r\n if (row.kind === \"ctx\") { pairs.push({ kind: \"pair\", left: row, right: row }); i++; continue; }\r\n\r\n const lefts: DiffLine[] = [];\r\n const rights: DiffLine[] = [];\r\n while (i < rows.length) {\r\n const next = rows[i];\r\n if (!next || next.kind === \"ctx\" || next.kind === \"gap\") break;\r\n if (next.kind === \"del\") lefts.push(next);\r\n else rights.push(next);\r\n i++;\r\n }\r\n const count = Math.max(lefts.length, rights.length);\r\n for (let k = 0; k < count; k++) {\r\n // One side will be filled undefined if out of index, and those\r\n // undefined rows will be rendered as shadows later.\r\n pairs.push({ kind: \"pair\", left: lefts[k], right: rights[k] });\r\n }\r\n }\r\n return pairs;\r\n}","import clipboard from \"clipboardy\";\nimport type { Command } from \"commander\";\nimport { INIT_PROMPT } from \"../constants/prompt.js\";\n\nexport function registerInit(program: Command) {\n program\n .command(\"init\")\n .description(\"Copy the agent prompt to the clipboard\")\n .action(async () => {\n try {\n await clipboard.write(INIT_PROMPT);\n } catch (error) {\n console.log(\"could not write to the clipboard\");\n return;\n }\n\n console.log(\"Prompt copied to the clipboard.\");\n console.log(\"Paste it into a web chatbot, add what you want it to do, and send.\");\n console.log(\"Then copy the chatbot's reply back here and run `mtp`.\");\n });\n}\n","// Wrap prompt with a marker so that when user pasted it to chatbot, they can\n// add other instruction texts outside of the MEATPROXY SYSTEM block, which\n// enables chatbot to better distinguish user instructions and system\n// instructions.\nexport const INIT_PROMPT = `<<<<<<< MEATPROXY SYSTEM\nYou are an AI agent powered by MeatProxy, which is a cli tool that reads your replies copied by user and runs the JavaScript in it locally.\n\nTo inspect or edit files, reply with one snippet shaped like this. There should be no more than one pair of MEATPROXY markers. Keep the marker lines exactly as shown, each on its own line, inside the code block. The cli reads everything between them and ignores the rest of your reply.\n\n\\`\\`\\`js\n<<<<<<< MEATPROXY\n// your JavaScript here, which is the code for a function body.\n>>>>>>> MEATPROXY\n\\`\\`\\`\n\nGuidelines:\n- Use relative paths; the root is current working directory.\n- Do one step per snippet: the user runs it, then pastes the output back to you.\n- Use console.log for results and progress; you will see everything it prints.\n- Keep explanations outside the code block, best to place it at the very end of the whole replies.\n- Newlines: \\`read\\`/\\`edit\\` work in LF; the cli converts CRLF automatically. Just use LF in \\`old_string\\`/\\`new_string\\`.\n\nThe snippet runs in Node with a \\`tools\\` object:\n\n\\`\\`\\`\ntools.read({\n /** Path of the file to read, resolved against current working directory. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n}): {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n}\ntools.write({\n /** Path of the file to write, resolved against the tool root. */\n file_path: string;\n /** Full UTF-8 contents of the file. */\n content: string;\n}): void\ntools.edit({\n /** Path of the file to edit, resolved against the tool root. */\n file_path: string;\n /** Literal text to look for. It has to be unique unless \\`replace_all\\` is set. */\n old_string: string;\n /** Replacement text. An empty string deletes the match. */\n new_string: string;\n /** Replace every occurrence instead of requiring a unique match. */\n replace_all?: boolean;\n}): void\ntools.pwsh({\n /** PowerShell command or script to run. */\n command: string;\n /** Working directory, resolved against the tool root. Defaults to the tool root. */\n cwd?: string;\n /** Timeout in milliseconds. Defaults to 120000. */\n timeoutMs?: number;\n}): {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\\`\\`\\`\n>>>>>>> MEATPROXY SYSTEM\n\n`;\n"],"mappings":"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","names":["chalk","clipboard","program","clipboard"]}
package/package.json CHANGED
@@ -1,44 +1,52 @@
1
- {
2
- "name": "@adyingdeath/meatproxy",
3
- "version": "1.0.0",
4
- "description": "MeatProxy is a small bridge between your clipboard and chatbots.",
5
- "scripts": {
6
- "dev": "tsx src/index.ts",
7
- "build": "tsup",
8
- "typecheck": "tsc --noEmit",
9
- "start": "node dist/index.js",
10
- "prepublishOnly": "npm run typecheck && npm run build"
11
- },
12
- "keywords": ["cli", "ai", "llm", "agent", "agent harness"],
13
- "author": "adyingdeath",
14
- "license": "MIT",
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/adyingdeath/meatproxy.git"
18
- },
19
- "homepage": "https://github.com/adyingdeath/meatproxy",
20
- "bugs": {
21
- "url": "https://github.com/adyingdeath/meatproxy/issues"
22
- },
23
- "type": "module",
24
- "bin": {
25
- "mtp": "./dist/index.js",
26
- "meatproxy": "./dist/index.js"
27
- },
28
- "files": [
29
- "dist"
30
- ],
31
- "engines": {
32
- "node": ">=22.12.0"
33
- },
34
- "dependencies": {
35
- "clipboardy": "^5.3.2",
36
- "commander": "^15.0.0"
37
- },
38
- "devDependencies": {
39
- "@types/node": "^26.6.5",
40
- "tsup": "^9.0.0",
41
- "tsx": "^4.23.15",
42
- "typescript": "^7.0.2"
43
- }
44
- }
1
+ {
2
+ "name": "@adyingdeath/meatproxy",
3
+ "version": "1.1.0",
4
+ "description": "MeatProxy is a small bridge between your clipboard and chatbots.",
5
+ "scripts": {
6
+ "dev": "tsx src/index.ts",
7
+ "build": "tsup",
8
+ "typecheck": "tsc --noEmit",
9
+ "start": "node dist/index.js",
10
+ "prepublishOnly": "npm run typecheck && npm run build"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "ai",
15
+ "llm",
16
+ "agent",
17
+ "agent harness"
18
+ ],
19
+ "author": "adyingdeath",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/adyingdeath/meatproxy.git"
24
+ },
25
+ "homepage": "https://github.com/adyingdeath/meatproxy",
26
+ "bugs": {
27
+ "url": "https://github.com/adyingdeath/meatproxy/issues"
28
+ },
29
+ "type": "module",
30
+ "bin": {
31
+ "mtp": "./dist/index.js"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.12.0"
38
+ },
39
+ "dependencies": {
40
+ "chalk": "^6.0.1",
41
+ "clipboardy": "^5.3.2",
42
+ "commander": "^15.0.0",
43
+ "diff": "^9.0.0",
44
+ "terminal-size": "^4.0.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^26.6.5",
48
+ "tsup": "^9.0.0",
49
+ "tsx": "^4.23.15",
50
+ "typescript": "^7.0.2"
51
+ }
52
+ }