poke-gate 0.2.2 → 0.3.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
@@ -28,6 +28,25 @@ Run Poke Gate on your Mac, then message Poke from iMessage, Telegram, or SMS to
28
28
  brew install f/tap/poke-gate
29
29
  ```
30
30
 
31
+ **Install via npx**
32
+
33
+ If you have Node.js installed, you can download and install the macOS app with a single command:
34
+
35
+ ```bash
36
+ npx poke-gate download-macos
37
+ ```
38
+
39
+ This downloads the latest DMG from GitHub Releases, installs the app to `/Applications`, and clears the quarantine flag automatically.
40
+
41
+ **Don't have Node.js?** Install it first:
42
+
43
+ ```bash
44
+ # Option 1: Homebrew
45
+ brew install node
46
+
47
+ # Option 2: Download from https://nodejs.org
48
+ ```
49
+
31
50
  **Manual download**
32
51
 
33
52
  Download the latest **Poke.macOS.Gate.dmg** from [Releases](https://github.com/f/poke-gate/releases), open it, and drag to Applications. Since the app is not notarized, you may need to run:
@@ -36,7 +55,9 @@ Download the latest **Poke.macOS.Gate.dmg** from [Releases](https://github.com/f
36
55
  xattr -cr /Applications/Poke\ macOS\ Gate.app
37
56
  ```
38
57
 
39
- **CLI** (no macOS app needed)
58
+ **CLI only** (no macOS app needed)
59
+
60
+ If you just want to run poke-gate from the terminal without the menu bar app:
40
61
 
41
62
  ```bash
42
63
  npx poke-gate
@@ -123,7 +144,9 @@ Hit **Run** in Xcode, or build from the command line:
123
144
 
124
145
  ## CLI usage
125
146
 
126
- If you prefer the command line over the macOS app:
147
+ The CLI requires [Node.js](https://nodejs.org) 18 or later. If you don't have it, install via `brew install node` or download from [nodejs.org](https://nodejs.org).
148
+
149
+ Start the gate:
127
150
 
128
151
  ```bash
129
152
  npx poke-gate
@@ -142,6 +165,12 @@ npx poke-gate --mode limited
142
165
  npx poke-gate --mode sandbox
143
166
  ```
144
167
 
168
+ Install or update the macOS app:
169
+
170
+ ```bash
171
+ npx poke-gate download-macos
172
+ ```
173
+
145
174
  Config is stored at `~/.config/poke-gate/config.json`.
146
175
 
147
176
  ## Agents
@@ -264,30 +293,6 @@ POKE_GATE_PERMISSION_MODE=limited npx poke-gate
264
293
 
265
294
  Only run Poke Gate on machines and networks you trust. Use `limited` or `sandbox` mode if you want tighter restrictions.
266
295
 
267
- ## Project structure
268
-
269
- ```
270
- clients/
271
- Poke macOS Gate/ macOS menu bar app (SwiftUI)
272
- bin/
273
- poke-gate.js CLI entry point with --mode flag
274
- src/
275
- app.js Startup: MCP server + tunnel + agent scheduler
276
- agents.js Agent discovery, scheduling, env loading, download
277
- mcp-server.js JSON-RPC MCP handler, tools, access policy, sandbox
278
- permission-service.js HMAC approval tokens, session whitelisting
279
- tunnel.js PokeTunnel wrapper
280
- test/
281
- mcp-server-access-policy.test.js
282
- mcp-server-loop-guard.test.js
283
- mcp-server-sandbox-command.test.js
284
- permission-service.test.js
285
- examples/
286
- agents/
287
- beeper.1h.js Example: Beeper message digest agent
288
- .env.beeper Example env file for beeper agent
289
- ```
290
-
291
296
  ## Credits
292
297
 
293
298
  - [Poke](https://poke.com) by [The Interaction Company of California](https://interaction.co)
package/bin/poke-gate.js CHANGED
@@ -39,6 +39,9 @@ async function main() {
39
39
  const prompt = promptIdx !== -1 ? args.slice(promptIdx + 1).join(" ") : args.slice(2).join(" ") || null;
40
40
  const { createAgent } = await import("../src/agent-create.js");
41
41
  await createAgent(prompt);
42
+ } else if (args[0] === "download-macos") {
43
+ const { downloadMacOSApp } = await import("../src/download-macos.js");
44
+ await downloadMacOSApp();
42
45
  } else {
43
46
  const mode = parseMode();
44
47
  if (mode) {
@@ -97,6 +97,10 @@ class GateService: ObservableObject {
97
97
  @Published var permissionMode: PermissionMode
98
98
  @Published var hasCompletedSetup: Bool
99
99
  @Published var systemPermissionStatuses: [SystemPermissionStatus] = []
100
+ @Published var availableUpdate: String? = nil
101
+ @Published var isUpdating = false
102
+ @Published var isCheckingForUpdate = false
103
+ @Published var updateCheckResult: String? = nil
100
104
 
101
105
  private var hasAutoStarted = false
102
106
  private var process: Process?
@@ -121,7 +125,9 @@ class GateService: ObservableObject {
121
125
  object: nil,
122
126
  queue: .main
123
127
  ) { [weak self] _ in
124
- self?.refreshSystemPermissions()
128
+ Task { @MainActor in
129
+ self?.refreshSystemPermissions()
130
+ }
125
131
  }
126
132
  }
127
133
 
@@ -302,6 +308,112 @@ class GateService: ObservableObject {
302
308
  if hasAPIKey {
303
309
  start()
304
310
  }
311
+ checkForUpdate(silent: true)
312
+ }
313
+
314
+ func checkForUpdate(silent: Bool = false) {
315
+ let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0"
316
+ isCheckingForUpdate = true
317
+ updateCheckResult = nil
318
+
319
+ Task.detached {
320
+ let start = ContinuousClock.now
321
+ var found = false
322
+
323
+ defer {
324
+ Task { @MainActor in
325
+ self.isCheckingForUpdate = false
326
+ if !found && !silent {
327
+ self.updateCheckResult = "You're on the latest version."
328
+ }
329
+ }
330
+ }
331
+
332
+ guard let url = URL(string: "https://registry.npmjs.org/poke-gate/latest") else { return }
333
+ do {
334
+ let (data, _) = try await URLSession.shared.data(from: url)
335
+ guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
336
+ let latestVersion = json["version"] as? String else { return }
337
+
338
+ let elapsed = ContinuousClock.now - start
339
+ if elapsed < .seconds(1) {
340
+ try await Task.sleep(for: .seconds(1) - elapsed)
341
+ }
342
+
343
+ if Self.isNewer(latestVersion, than: currentVersion) {
344
+ found = true
345
+ await MainActor.run {
346
+ self.availableUpdate = latestVersion
347
+ self.appendLog("Update available: v\(latestVersion) (current: v\(currentVersion))")
348
+ }
349
+ }
350
+ } catch {}
351
+ }
352
+ }
353
+
354
+ private nonisolated static func isNewer(_ remote: String, than local: String) -> Bool {
355
+ let r = remote.split(separator: ".").compactMap { Int($0) }
356
+ let l = local.split(separator: ".").compactMap { Int($0) }
357
+ for i in 0..<max(r.count, l.count) {
358
+ let rv = i < r.count ? r[i] : 0
359
+ let lv = i < l.count ? l[i] : 0
360
+ if rv > lv { return true }
361
+ if rv < lv { return false }
362
+ }
363
+ return false
364
+ }
365
+
366
+ func performUpdate() {
367
+ guard let version = availableUpdate else { return }
368
+ isUpdating = true
369
+ appendLog("Updating to v\(version)...")
370
+
371
+ stop()
372
+
373
+ let fullPath = shellPath()
374
+ let npxBin = findNpx()
375
+
376
+ let proc = Process()
377
+ let pipe = Pipe()
378
+ proc.executableURL = URL(fileURLWithPath: "/bin/zsh")
379
+ proc.arguments = ["-c", "\(npxBin) -y poke-gate@\(version) download-macos"]
380
+ proc.environment = ProcessInfo.processInfo.environment.merging(
381
+ ["PATH": fullPath],
382
+ uniquingKeysWith: { _, new in new }
383
+ )
384
+ proc.standardOutput = pipe
385
+ proc.standardError = pipe
386
+ proc.currentDirectoryURL = FileManager.default.homeDirectoryForCurrentUser
387
+
388
+ let handle = pipe.fileHandleForReading
389
+ handle.readabilityHandler = { [weak self] fh in
390
+ let data = fh.availableData
391
+ guard !data.isEmpty, let line = String(data: data, encoding: .utf8) else { return }
392
+ DispatchQueue.main.async {
393
+ for l in line.components(separatedBy: .newlines) where !l.isEmpty {
394
+ self?.appendLog(l)
395
+ }
396
+ }
397
+ }
398
+
399
+ proc.terminationHandler = { [weak self] proc in
400
+ DispatchQueue.main.async {
401
+ self?.isUpdating = false
402
+ if proc.terminationStatus == 0 {
403
+ self?.appendLog("Update complete. The app will relaunch.")
404
+ self?.availableUpdate = nil
405
+ } else {
406
+ self?.appendLog("Update failed (exit \(proc.terminationStatus)).")
407
+ }
408
+ }
409
+ }
410
+
411
+ do {
412
+ try proc.run()
413
+ } catch {
414
+ isUpdating = false
415
+ appendLog("Failed to start update: \(error.localizedDescription)")
416
+ }
305
417
  }
306
418
 
307
419
  func start() {
@@ -111,6 +111,9 @@ struct PopoverContent: View {
111
111
  SetupView(service: service)
112
112
  } else {
113
113
  VStack(spacing: 10) {
114
+ if service.availableUpdate != nil {
115
+ updateBanner
116
+ }
114
117
  statusSection
115
118
  recentActivitySection
116
119
  accessModeSection
@@ -128,6 +131,37 @@ struct PopoverContent: View {
128
131
  }
129
132
  }
130
133
 
134
+ private var updateBanner: some View {
135
+ HStack(spacing: 8) {
136
+ Image(systemName: "arrow.down.circle.fill")
137
+ .foregroundStyle(.blue)
138
+
139
+ VStack(alignment: .leading, spacing: 1) {
140
+ Text("Update available: v\(service.availableUpdate ?? "")")
141
+ .font(.caption)
142
+ .fontWeight(.semibold)
143
+ Text("A new version of Poke Gate is ready.")
144
+ .font(.caption2)
145
+ .foregroundStyle(.secondary)
146
+ }
147
+
148
+ Spacer()
149
+
150
+ if service.isUpdating {
151
+ ProgressView()
152
+ .controlSize(.small)
153
+ } else {
154
+ Button("Update") {
155
+ service.performUpdate()
156
+ }
157
+ .controlSize(.small)
158
+ .buttonStyle(.borderedProminent)
159
+ }
160
+ }
161
+ .padding(10)
162
+ .macPanelStyle(.neutral, cornerRadius: 10)
163
+ }
164
+
131
165
  private var statusSection: some View {
132
166
  VStack(alignment: .leading, spacing: 6) {
133
167
  HStack(spacing: 8) {
@@ -303,8 +337,10 @@ struct PopoverContent: View {
303
337
  .macPanelStyle(.neutral, cornerRadius: 12)
304
338
  }
305
339
 
340
+ @State private var refreshRotation: Double = 0
341
+
306
342
  private var footerSection: some View {
307
- HStack {
343
+ HStack(spacing: 6) {
308
344
  Button {
309
345
  NSApp.activate(ignoringOtherApps: true)
310
346
  openWindow(id: "about")
@@ -315,6 +351,28 @@ struct PopoverContent: View {
315
351
  }
316
352
  .buttonStyle(.plain)
317
353
 
354
+ Button {
355
+ service.checkForUpdate()
356
+ } label: {
357
+ Image(systemName: "arrow.triangle.2.circlepath")
358
+ .font(.system(size: 9))
359
+ .foregroundStyle(MacVisualStyle.sectionTitleColor)
360
+ .rotationEffect(.degrees(refreshRotation))
361
+ }
362
+ .buttonStyle(.plain)
363
+ .disabled(service.isCheckingForUpdate)
364
+ .onChange(of: service.isCheckingForUpdate) { _, checking in
365
+ if checking {
366
+ withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
367
+ refreshRotation = 360
368
+ }
369
+ } else {
370
+ withAnimation(.default) {
371
+ refreshRotation = 0
372
+ }
373
+ }
374
+ }
375
+
318
376
  Spacer()
319
377
 
320
378
  Text("Not affiliated with Poke")
@@ -322,6 +380,18 @@ struct PopoverContent: View {
322
380
  .foregroundStyle(MacVisualStyle.sectionTitleColor.opacity(0.7))
323
381
  }
324
382
  .padding(.horizontal, 6)
383
+ .onChange(of: service.updateCheckResult) { _, result in
384
+ guard let message = result else { return }
385
+ service.updateCheckResult = nil
386
+ DispatchQueue.main.async {
387
+ let alert = NSAlert()
388
+ alert.messageText = "Version Check"
389
+ alert.informativeText = message
390
+ alert.alertStyle = .informational
391
+ alert.addButton(withTitle: "OK")
392
+ alert.runModal()
393
+ }
394
+ }
325
395
  }
326
396
 
327
397
  private func sectionTitle(_ text: String) -> some View {
@@ -286,7 +286,7 @@
286
286
  "@executable_path/../Frameworks",
287
287
  );
288
288
  MACOSX_DEPLOYMENT_TARGET = 15.0;
289
- MARKETING_VERSION = 0.2.1;
289
+ MARKETING_VERSION = 0.2.2;
290
290
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
291
291
  PRODUCT_NAME = "$(TARGET_NAME)";
292
292
  REGISTER_APP_GROUPS = YES;
@@ -322,7 +322,7 @@
322
322
  "@executable_path/../Frameworks",
323
323
  );
324
324
  MACOSX_DEPLOYMENT_TARGET = 15.0;
325
- MARKETING_VERSION = 0.2.1;
325
+ MARKETING_VERSION = 0.2.2;
326
326
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
327
327
  PRODUCT_NAME = "$(TARGET_NAME)";
328
328
  REGISTER_APP_GROUPS = YES;
package/docs/cli.md CHANGED
@@ -82,6 +82,22 @@ npx poke-gate agent create --prompt "send me a daily git commit summary across a
82
82
  npx poke-gate agent create --prompt "track Spotify listening and log my music taste"
83
83
  ```
84
84
 
85
+ ## Download the macOS app
86
+
87
+ ```bash
88
+ npx poke-gate download-macos
89
+ ```
90
+
91
+ Downloads and installs the Poke macOS Gate app from GitHub Releases. Matches the version of the npm package being run.
92
+
93
+ This command:
94
+ 1. Downloads the DMG from the matching GitHub release
95
+ 2. Mounts the DMG, copies the app to `/Applications`
96
+ 3. Clears the quarantine flag (`xattr -cr`)
97
+ 4. Launches the app
98
+
99
+ The macOS app also checks for updates automatically on startup and shows a banner when a new version is available.
100
+
85
101
  ## Install an agent
86
102
 
87
103
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-gate",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Expose your machine to your Poke AI assistant via MCP tunnel",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -146,8 +146,8 @@ function buildAccessModeMessage(mode) {
146
146
  default:
147
147
  return (
148
148
  "Access mode: Full. " +
149
- "You can run any shell command, read and write files, list directories, take screenshots, and check system info. " +
150
- "Use the tools directly whenever needed no approval required."
149
+ "You can run any shell command, read files, list directories, take screenshots, and check system info — no approval needed. " +
150
+ "Only destructive actions (deleting files, rm, write_file) require a one-time approval; after that, everything is auto-approved for the session."
151
151
  );
152
152
  }
153
153
  }
@@ -0,0 +1,133 @@
1
+ import https from "node:https";
2
+ import { createWriteStream, unlinkSync, readFileSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
4
+ import { tmpdir } from "node:os";
5
+ import { join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const APP_NAME = "Poke macOS Gate";
9
+ const DMG_ASSET = "Poke.macOS.Gate.dmg";
10
+ const INSTALL_PATH = `/Applications/${APP_NAME}.app`;
11
+ const REPO = "f/poke-gate";
12
+
13
+ function getPackageVersion() {
14
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
15
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
16
+ return pkg.version;
17
+ }
18
+
19
+ function httpGet(url) {
20
+ return new Promise((resolve, reject) => {
21
+ https.get(url, { headers: { "User-Agent": "poke-gate" } }, (res) => {
22
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
23
+ return httpGet(res.headers.location).then(resolve, reject);
24
+ }
25
+ resolve(res);
26
+ }).on("error", reject);
27
+ });
28
+ }
29
+
30
+ function downloadFile(url, dest, version) {
31
+ return new Promise((resolve, reject) => {
32
+ const follow = (followUrl) => {
33
+ https.get(followUrl, { headers: { "User-Agent": "poke-gate" } }, (res) => {
34
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
35
+ return follow(res.headers.location);
36
+ }
37
+ if (res.statusCode !== 200) {
38
+ reject(new Error(`Download failed: HTTP ${res.statusCode}`));
39
+ return;
40
+ }
41
+
42
+ const total = parseInt(res.headers["content-length"], 10) || 0;
43
+ let downloaded = 0;
44
+ const file = createWriteStream(dest);
45
+
46
+ res.on("data", (chunk) => {
47
+ downloaded += chunk.length;
48
+ if (total > 0) {
49
+ const pct = Math.round((downloaded / total) * 100);
50
+ const dlMB = (downloaded / 1024 / 1024).toFixed(1);
51
+ const totalMB = (total / 1024 / 1024).toFixed(1);
52
+ process.stdout.write(`\rDownloading ${APP_NAME} v${version}... ${pct}% (${dlMB}/${totalMB} MB)`);
53
+ }
54
+ });
55
+
56
+ res.pipe(file);
57
+
58
+ file.on("finish", () => {
59
+ file.close(() => {
60
+ console.log("");
61
+ resolve();
62
+ });
63
+ });
64
+
65
+ file.on("error", (err) => {
66
+ file.close();
67
+ reject(err);
68
+ });
69
+ }).on("error", reject);
70
+ };
71
+ follow(url);
72
+ });
73
+ }
74
+
75
+ function run(cmd) {
76
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
77
+ }
78
+
79
+ export async function downloadMacOSApp() {
80
+ const version = getPackageVersion();
81
+ const tag = `v${version}`;
82
+ const dmgUrl = `https://github.com/${REPO}/releases/download/${tag}/${DMG_ASSET}`;
83
+
84
+ console.log(`Poke Gate macOS installer (${tag})`);
85
+ console.log("");
86
+
87
+ const res = await httpGet(`https://api.github.com/repos/${REPO}/releases/tags/${tag}`);
88
+ const chunks = [];
89
+ for await (const chunk of res) chunks.push(chunk);
90
+ const release = JSON.parse(Buffer.concat(chunks).toString());
91
+
92
+ if (!release.assets || !release.assets.find((a) => a.name === DMG_ASSET)) {
93
+ console.error(`No DMG found for ${tag}. The release may not have finished building yet.`);
94
+ process.exit(1);
95
+ }
96
+
97
+ const dmgPath = join(tmpdir(), `poke-gate-${version}.dmg`);
98
+
99
+ try {
100
+ await downloadFile(dmgUrl, dmgPath, version);
101
+
102
+ console.log("Mounting DMG...");
103
+ const mountOutput = run(`hdiutil attach "${dmgPath}" -nobrowse`);
104
+ const mountMatch = mountOutput.match(/\/Volumes\/.+/);
105
+ if (!mountMatch) {
106
+ throw new Error("Failed to detect mount point from hdiutil output.");
107
+ }
108
+ const mountPoint = mountMatch[0].trim();
109
+ const appSource = `${mountPoint}/${APP_NAME}.app`;
110
+
111
+ console.log("Stopping running instances...");
112
+ try { run(`pkill -f "${APP_NAME}"`); } catch {}
113
+ await new Promise((r) => setTimeout(r, 1000));
114
+
115
+ console.log(`Installing to ${INSTALL_PATH}...`);
116
+ try { run(`rm -rf "${INSTALL_PATH}"`); } catch {}
117
+ run(`cp -R "${appSource}" "${INSTALL_PATH}"`);
118
+
119
+ console.log("Clearing quarantine...");
120
+ run(`xattr -cr "${INSTALL_PATH}"`);
121
+
122
+ console.log("Unmounting DMG...");
123
+ try { run(`hdiutil detach "${mountPoint}" -quiet`); } catch {}
124
+
125
+ console.log("");
126
+ console.log(`Poke macOS Gate v${version} installed successfully.`);
127
+ console.log("");
128
+ console.log("Launching...");
129
+ try { run(`open "${INSTALL_PATH}"`); } catch {}
130
+ } finally {
131
+ try { unlinkSync(dmgPath); } catch {}
132
+ }
133
+ }
package/src/mcp-server.js CHANGED
@@ -22,6 +22,15 @@ const runCommandLoopState = new Map();
22
22
 
23
23
  const SAFE_TOOL_NAMES = new Set(["read_file", "read_image", "list_directory", "system_info", "network_speed"]);
24
24
 
25
+ const DESTRUCTIVE_COMMAND_PATTERNS = [
26
+ /\brm\b/i,
27
+ /\brmdir\b/i,
28
+ /\bunlink\b/i,
29
+ /\bmkfs\b/i,
30
+ /\bdiskutil\s+erase/i,
31
+ />\s*\//,
32
+ ];
33
+
25
34
  const LIMITED_RUN_COMMANDS = new Set([
26
35
  "curl", "yt-dlp", "youtube-dl",
27
36
  "ls", "pwd", "cat", "grep", "find", "head", "tail", "wc", "sed", "awk",
@@ -270,6 +279,15 @@ function hasDangerousPattern(commandText) {
270
279
  return DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(commandText));
271
280
  }
272
281
 
282
+ function isDestructiveInFullMode(name, cleanArgs) {
283
+ if (name === "write_file") return true;
284
+ if (name === "run_command") {
285
+ const cmd = typeof cleanArgs.command === "string" ? cleanArgs.command : "";
286
+ return DESTRUCTIVE_COMMAND_PATTERNS.some((p) => p.test(cmd));
287
+ }
288
+ return false;
289
+ }
290
+
273
291
  function validateRunCommandAgainstAllowlist(commandText, allowlist) {
274
292
  if (typeof commandText !== "string" || commandText.trim().length === 0) {
275
293
  return "Command is empty.";
@@ -565,7 +583,11 @@ function handleToolCall(name, args, context = {}) {
565
583
  return blocked;
566
584
  }
567
585
 
568
- if (PERMISSION_MODE !== "full" && permissionService.isRisky(name)) {
586
+ const needsApproval = PERMISSION_MODE === "full"
587
+ ? isDestructiveInFullMode(name, cleanArgs)
588
+ : permissionService.isRisky(name);
589
+
590
+ if (needsApproval) {
569
591
  const commandText = typeof cleanArgs.command === "string" ? cleanArgs.command : "";
570
592
  const alreadyAllowed = sessionAutoApproveAllRisky.has(sessionId) ||
571
593
  (commandText && permissionService.isAllowedBySessionPattern(sessionId, commandText));
@@ -581,12 +603,15 @@ function handleToolCall(name, args, context = {}) {
581
603
  return buildApprovalResponse(name, cleanArgs, approval);
582
604
  }
583
605
 
584
- if (name === "run_command" && args.remember_in_session === true && commandText) {
585
- permissionService.allowPatternForSession(sessionId, commandText);
586
- }
587
-
588
- if (args.remember_all_risky === true) {
606
+ if (PERMISSION_MODE === "full") {
589
607
  sessionAutoApproveAllRisky.add(sessionId);
608
+ } else {
609
+ if (name === "run_command" && args.remember_in_session === true && commandText) {
610
+ permissionService.allowPatternForSession(sessionId, commandText);
611
+ }
612
+ if (args.remember_all_risky === true) {
613
+ sessionAutoApproveAllRisky.add(sessionId);
614
+ }
590
615
  }
591
616
  }
592
617
  }