@neeter/server 0.7.0 → 0.8.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.
@@ -0,0 +1,10 @@
1
+ import type { HookCallbackMatcher } from "@anthropic-ai/claude-agent-sdk";
2
+ /**
3
+ * Creates a PreToolUse hook that blocks file operations outside a sandbox directory.
4
+ * Inspects `file_path` and `path` fields in tool input and blocks any resolved path
5
+ * that falls outside the given directory.
6
+ *
7
+ * @param sandboxDir - Absolute path to the sandbox directory (must already be resolved)
8
+ * @param resolvePath - Path resolver function (e.g. `path.resolve` from `node:path`)
9
+ */
10
+ export declare function createSandboxHook(sandboxDir: string, resolvePath: (...segments: string[]) => string): HookCallbackMatcher[];
package/dist/hooks.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Creates a PreToolUse hook that blocks file operations outside a sandbox directory.
3
+ * Inspects `file_path` and `path` fields in tool input and blocks any resolved path
4
+ * that falls outside the given directory.
5
+ *
6
+ * @param sandboxDir - Absolute path to the sandbox directory (must already be resolved)
7
+ * @param resolvePath - Path resolver function (e.g. `path.resolve` from `node:path`)
8
+ */
9
+ export function createSandboxHook(sandboxDir, resolvePath) {
10
+ const normalizedDir = resolvePath(sandboxDir);
11
+ return [
12
+ {
13
+ hooks: [
14
+ async (input) => {
15
+ if (input.hook_event_name !== "PreToolUse")
16
+ return {};
17
+ const toolInput = input.tool_input;
18
+ const filePath = (toolInput.file_path ?? toolInput.path);
19
+ if (!filePath)
20
+ return {};
21
+ const resolved = resolvePath(filePath);
22
+ if (resolved !== normalizedDir && !resolved.startsWith(`${normalizedDir}/`)) {
23
+ return { decision: "block", reason: "Access outside sandbox directory is not allowed" };
24
+ }
25
+ return {};
26
+ },
27
+ ],
28
+ },
29
+ ];
30
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { CustomEvent, SSEEvent } from "@neeter/types";
2
+ export { createSandboxHook } from "./hooks.js";
2
3
  export { PermissionGate } from "./permission-gate.js";
3
4
  export { PushChannel } from "./push-channel.js";
4
5
  export { createAgentRouter } from "./router.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { createSandboxHook } from "./hooks.js";
1
2
  export { PermissionGate } from "./permission-gate.js";
2
3
  export { PushChannel } from "./push-channel.js";
3
4
  export { createAgentRouter } from "./router.js";
package/dist/router.js CHANGED
@@ -63,5 +63,12 @@ export function createAgentRouter(config) {
63
63
  session.lastActivityAt = Date.now();
64
64
  return c.json({ ok: true });
65
65
  });
66
+ app.post(`${basePath}/sessions/:id/abort`, (c) => {
67
+ const session = sessions.get(c.req.param("id"));
68
+ if (!session)
69
+ return c.json({ error: "Session not found" }, 404);
70
+ session.abort();
71
+ return c.json({ ok: true });
72
+ });
66
73
  return app;
67
74
  }
package/dist/session.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { query } from "@anthropic-ai/claude-agent-sdk";
1
+ import { type HookCallbackMatcher, type HookEvent, query } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { PermissionGate } from "./permission-gate.js";
3
3
  type SDKMessage = ReturnType<typeof query> extends AsyncGenerator<infer T> ? T : never;
4
4
  export interface SessionInit<TCtx> {
@@ -8,7 +8,9 @@ export interface SessionInit<TCtx> {
8
8
  mcpServers?: Record<string, unknown>;
9
9
  tools?: unknown[];
10
10
  allowedTools?: string[];
11
+ disallowedTools?: string[];
11
12
  maxTurns?: number;
13
+ cwd?: string;
12
14
  permissionMode?: "default" | "acceptEdits" | "plan" | "bypassPermissions";
13
15
  thinking?: {
14
16
  type: "enabled";
@@ -16,6 +18,7 @@ export interface SessionInit<TCtx> {
16
18
  } | {
17
19
  type: "disabled";
18
20
  };
21
+ hooks?: Partial<Record<HookEvent, HookCallbackMatcher[]>>;
19
22
  }
20
23
  export interface Session<TCtx> {
21
24
  id: string;
package/dist/session.js CHANGED
@@ -1,4 +1,4 @@
1
- import { query } from "@anthropic-ai/claude-agent-sdk";
1
+ import { query, } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { PermissionGate } from "./permission-gate.js";
3
3
  import { PushChannel } from "./push-channel.js";
4
4
  function userMessage(content) {
@@ -74,6 +74,9 @@ export class SessionManager {
74
74
  abortController,
75
75
  ...(canUseTool ? { canUseTool } : {}),
76
76
  ...(init.thinking ? { thinking: init.thinking } : {}),
77
+ ...(init.cwd ? { cwd: init.cwd } : {}),
78
+ ...(init.disallowedTools ? { disallowedTools: init.disallowedTools } : {}),
79
+ ...(init.hooks ? { hooks: init.hooks } : {}),
77
80
  },
78
81
  });
79
82
  const session = {
@@ -208,6 +208,9 @@ export async function* streamSession(session, translator) {
208
208
  }
209
209
  }
210
210
  }
211
+ catch {
212
+ // Session was aborted or SDK threw — fall through to close the channel
213
+ }
211
214
  finally {
212
215
  unsubscribe();
213
216
  output.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neeter/server",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Hono server toolkit for building chat UIs on top of the Claude Agent SDK",
5
5
  "license": "MIT",
6
6
  "author": "Dan Leeper",
@@ -20,7 +20,7 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@neeter/types": "0.7.0"
23
+ "@neeter/types": "0.8.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@anthropic-ai/claude-agent-sdk": ">=0.2.0",
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "devDependencies": {
30
30
  "@anthropic-ai/claude-agent-sdk": "latest",
31
+ "@types/node": "^22.19.11",
31
32
  "hono": "^4.11.9"
32
33
  },
33
34
  "scripts": {