linkshell-cli 0.2.101 → 0.2.103

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.
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { readdirSync, existsSync } from "node:fs";
3
- import { join, resolve } from "node:path";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
4
  import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
5
5
 
6
6
  type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
@@ -68,6 +68,29 @@ function isRealClaudeSessionId(value: string | undefined): value is string {
68
68
  return Boolean(value && !value.startsWith("agent-session-"));
69
69
  }
70
70
 
71
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
72
+ return value && typeof value === "object" && !Array.isArray(value)
73
+ ? value as Record<string, unknown>
74
+ : undefined;
75
+ }
76
+
77
+ function stringField(value: unknown, keys: string[]): string | undefined {
78
+ const record = asRecord(value);
79
+ if (!record) return undefined;
80
+ for (const key of keys) {
81
+ const candidate = record[key];
82
+ if (typeof candidate === "string" && candidate.trim()) return candidate;
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ function isInsideCwd(cwd: string, candidate: string): boolean {
88
+ const root = resolve(cwd);
89
+ const target = resolve(root, candidate);
90
+ const rel = relative(root, target);
91
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
92
+ }
93
+
71
94
  function outcomeFromPermissionResponse(value: unknown): "allow" | "deny" {
72
95
  const raw = value && typeof value === "object" ? value as Record<string, unknown> : {};
73
96
  if (raw.behavior === "allow") return "allow";
@@ -158,9 +181,18 @@ export class ClaudeSdkClient {
158
181
  abortController,
159
182
  canUseTool: async (toolName: string, toolInput: unknown) => {
160
183
  if (input.permissionMode === "full_access") return { behavior: "allow" };
184
+ if (["Read", "Glob", "Grep", "LS", "NotebookRead", "TodoRead"].includes(toolName)) {
185
+ return { behavior: "allow" };
186
+ }
161
187
  if (input.permissionMode === "read_only" && ["Write", "Edit", "MultiEdit", "Bash"].includes(toolName)) {
162
188
  return { behavior: "deny", message: "Read-only mode is active." };
163
189
  }
190
+ if (input.permissionMode === "workspace_write" && ["Write", "Edit", "MultiEdit"].includes(toolName)) {
191
+ const filePath = stringField(toolInput, ["file_path", "path", "notebook_path"]);
192
+ if (filePath && isInsideCwd(input.cwd ?? this.input.cwd, filePath)) {
193
+ return { behavior: "allow" };
194
+ }
195
+ }
164
196
  const requestId = id("claude-perm");
165
197
  const response = await this.input.onRequest("claude/requestApproval", {
166
198
  requestId,
@@ -248,6 +280,19 @@ export class ClaudeSdkClient {
248
280
  return { sessions };
249
281
  }
250
282
 
283
+ async listModels(): Promise<unknown> {
284
+ return {
285
+ defaultModel: "default",
286
+ models: [
287
+ { id: "sonnet", label: "Sonnet" },
288
+ { id: "opus", label: "Opus" },
289
+ { id: "haiku", label: "Haiku" },
290
+ { id: "sonnet[1m]", label: "Sonnet 1M" },
291
+ { id: "opusplan", label: "Opus Plan" },
292
+ ],
293
+ };
294
+ }
295
+
251
296
  stop(): void {
252
297
  this.cancel({});
253
298
  }
@@ -443,6 +443,19 @@ export class ClaudeStreamJsonClient {
443
443
  return { sessions };
444
444
  }
445
445
 
446
+ async listModels(): Promise<unknown> {
447
+ return {
448
+ defaultModel: "default",
449
+ models: [
450
+ { id: "sonnet", label: "Sonnet" },
451
+ { id: "opus", label: "Opus" },
452
+ { id: "haiku", label: "Haiku" },
453
+ { id: "sonnet[1m]", label: "Sonnet 1M" },
454
+ { id: "opusplan", label: "Opus Plan" },
455
+ ],
456
+ };
457
+ }
458
+
446
459
  stop(): void {
447
460
  if (this.child && !this.child.killed) {
448
461
  this.child.kill("SIGTERM");