clawmini 0.0.1 → 0.0.3

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.
Files changed (103) hide show
  1. package/.github/workflows/ci.yml +59 -0
  2. package/README.md +61 -76
  3. package/dist/adapter-discord/index.d.mts.map +1 -1
  4. package/dist/adapter-discord/index.mjs +13 -4
  5. package/dist/adapter-discord/index.mjs.map +1 -1
  6. package/dist/cli/index.mjs +8 -6
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/lite.mjs +64 -10
  9. package/dist/cli/lite.mjs.map +1 -1
  10. package/dist/daemon/index.mjs +732 -251
  11. package/dist/daemon/index.mjs.map +1 -1
  12. package/dist/{fetch-BjZVyU3Z.mjs → fetch-Cn1XNyiO.mjs} +1 -1
  13. package/dist/{fetch-BjZVyU3Z.mjs.map → fetch-Cn1XNyiO.mjs.map} +1 -1
  14. package/dist/lite-oSYSvaOr.mjs +164 -0
  15. package/dist/lite-oSYSvaOr.mjs.map +1 -0
  16. package/dist/web/_app/immutable/chunks/{COekwvP2.js → 8YNcRyEk.js} +1 -1
  17. package/dist/web/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
  18. package/dist/web/_app/immutable/entry/{app.B-vZe7PN.js → app.DO5eYwVz.js} +2 -2
  19. package/dist/web/_app/immutable/entry/start.D48mVn1m.js +1 -0
  20. package/dist/web/_app/immutable/nodes/{0.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
  21. package/dist/web/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
  22. package/dist/web/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
  23. package/dist/web/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
  24. package/dist/web/_app/immutable/nodes/{5.BJl7oM3b.js → 5.BpJUN6QH.js} +1 -1
  25. package/dist/web/_app/version.json +1 -1
  26. package/dist/web/index.html +6 -6
  27. package/dist/{workspace-CSgfo_2J.mjs → workspace-DjoNjhW0.mjs} +21 -40
  28. package/dist/workspace-DjoNjhW0.mjs.map +1 -0
  29. package/docs/15_lite_fetch_pending/development_log.md +31 -0
  30. package/docs/15_lite_fetch_pending/notes.md +48 -0
  31. package/docs/15_lite_fetch_pending/prd.md +39 -0
  32. package/docs/15_lite_fetch_pending/questions.md +3 -0
  33. package/docs/15_lite_fetch_pending/tickets.md +42 -0
  34. package/docs/CHECKS.md +2 -2
  35. package/docs/CLI_REFERENCE.md +35 -0
  36. package/docs/guides/sandbox_policies.md +12 -5
  37. package/eslint.config.js +12 -0
  38. package/package.json +3 -2
  39. package/src/adapter-discord/client.ts +1 -1
  40. package/src/adapter-discord/index.ts +22 -5
  41. package/src/cli/client.ts +8 -3
  42. package/src/cli/e2e/adapter-discord.test.ts +2 -2
  43. package/src/cli/e2e/daemon.test.ts +2 -1
  44. package/src/cli/e2e/export-lite-func.test.ts +41 -13
  45. package/src/cli/e2e/fallbacks.test.ts +4 -0
  46. package/src/cli/lite.ts +24 -6
  47. package/src/daemon/api/agent-router.ts +191 -0
  48. package/src/daemon/{router.test.ts → api/index.test.ts} +101 -34
  49. package/src/daemon/api/index.ts +4 -0
  50. package/src/daemon/{router-policy-request.test.ts → api/policy-request.test.ts} +27 -13
  51. package/src/daemon/api/router-utils.ts +159 -0
  52. package/src/daemon/api/trpc.ts +30 -0
  53. package/src/daemon/api/user-router.ts +221 -0
  54. package/src/daemon/index.ts +3 -3
  55. package/src/daemon/message-interruption.test.ts +17 -10
  56. package/src/daemon/message-typing.test.ts +1 -1
  57. package/src/daemon/message.ts +260 -239
  58. package/src/daemon/observation.test.ts +1 -1
  59. package/src/daemon/queue.test.ts +28 -0
  60. package/src/daemon/queue.ts +30 -15
  61. package/src/daemon/request-store.test.ts +4 -4
  62. package/src/daemon/request-store.ts +3 -1
  63. package/src/shared/workspace.ts +4 -5
  64. package/templates/debug/settings.json +5 -0
  65. package/templates/environments/macos/env.json +1 -1
  66. package/templates/environments/macos-proxy/env.json +1 -1
  67. package/templates/gemini-claw/.gemini/hooks/insert-pending.sh +9 -0
  68. package/templates/gemini-claw/.gemini/settings.json +14 -1
  69. package/templates/gemini-claw/.gemini/system.md +2 -0
  70. package/web/.svelte-kit/ambient.d.ts +2 -6
  71. package/web/.svelte-kit/generated/server/internal.js +1 -1
  72. package/web/.svelte-kit/output/client/.vite/manifest.json +29 -29
  73. package/web/.svelte-kit/output/client/_app/immutable/chunks/{COekwvP2.js → 8YNcRyEk.js} +1 -1
  74. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
  75. package/web/.svelte-kit/output/client/_app/immutable/entry/{app.B-vZe7PN.js → app.DO5eYwVz.js} +2 -2
  76. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +1 -0
  77. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
  78. package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
  79. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
  80. package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
  81. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BJl7oM3b.js → 5.BpJUN6QH.js} +1 -1
  82. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  83. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  84. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  85. package/web/.svelte-kit/output/server/manifest.js +1 -1
  86. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  87. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  88. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  89. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  90. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  91. package/dist/chats-DKgTeU7i.mjs +0 -91
  92. package/dist/chats-DKgTeU7i.mjs.map +0 -1
  93. package/dist/chats-Zd_HXDHx.mjs +0 -29
  94. package/dist/chats-Zd_HXDHx.mjs.map +0 -1
  95. package/dist/fs-B5wW0oaH.mjs +0 -14
  96. package/dist/fs-B5wW0oaH.mjs.map +0 -1
  97. package/dist/lite-Dl7WXyaH.mjs +0 -80
  98. package/dist/lite-Dl7WXyaH.mjs.map +0 -1
  99. package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
  100. package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +0 -1
  101. package/dist/workspace-CSgfo_2J.mjs.map +0 -1
  102. package/src/daemon/router.ts +0 -510
  103. package/web/.svelte-kit/output/client/_app/immutable/entry/start.oP1AgKhs.js +0 -1
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ - master
12
+
13
+ jobs:
14
+ static-analysis:
15
+ name: Format, Lint & Check
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: 22
25
+ cache: 'npm'
26
+
27
+ - name: Install dependencies
28
+ run: npm ci
29
+
30
+ - name: Run Prettier check
31
+ run: npm run format:check
32
+
33
+ - name: Run ESLint
34
+ run: npm run lint
35
+
36
+ - name: Run Type Check
37
+ run: npm run check
38
+
39
+ test:
40
+ name: Run Tests (Unit & E2E)
41
+ runs-on: ubuntu-latest
42
+ steps:
43
+ - name: Checkout repository
44
+ uses: actions/checkout@v4
45
+
46
+ - name: Setup Node.js
47
+ uses: actions/setup-node@v4
48
+ with:
49
+ node-version: 22
50
+ cache: 'npm'
51
+
52
+ - name: Install dependencies
53
+ run: npm ci
54
+
55
+ - name: Install Playwright Browsers
56
+ run: npx playwright install --with-deps
57
+
58
+ - name: Run Tests
59
+ run: npm run test
package/README.md CHANGED
@@ -1,98 +1,93 @@
1
1
  # Clawmini
2
2
 
3
- Clawmini is an orchestration layer for command-line AI agents, providing a unified chat experience that can span multiple conversations and multiple agents. Ultimately, it aims to deliver a personal assistant experience built entirely on top of your local tools and agents.
3
+ **The secure, local-first orchestrator for your AI agents.**
4
4
 
5
- ## Features
5
+ Clawmini gives you the power of a proactive personal AI assistant, without compromising your system's security. Unlike other local agents that run with unrestricted access to your machine, Clawmini is built from the ground up with **sandboxing**, **human-in-the-loop approvals**, and **strict network isolation**.
6
6
 
7
- - **Persistent, Multi-Agent Chat Sessions:** Maintain separate chats for different tasks, allowing you to converse with multiple agents across multiple conversations.
8
- - **Safe Concurrency:** Automatically manages state and handles race conditions, queuing background commands safely to prevent file lock issues.
9
- - **Built-in & Bring-Your-Own UI:** Includes a fast, beautifully designed SvelteKit Web UI to visually manage agents, chats, and monitor real-time execution. Alternatively, easily build and connect your own interfaces to its local API.
10
- - **Agent Templates:** Quickly scaffold new agents using built-in or custom templates (e.g. `gemini`), automatically merging configuration settings.
11
- - **Routers & Middleware Pipeline:** Process user messages through an extensible pipeline to dynamically alter content, target specific agents or sessions, and expand slash commands before they reach an agent.
12
- - **Local File System Storage:** Everything is stored completely locally in `.clawmini/` within your workspace as transparent JSON/JSONL files. No cloud syncing required.
13
- - **Sandboxed Agent API:** The daemon can optionally expose a secure HTTP server configured via the `api` setting in `.clawmini/settings.json`, allowing remote or containerized access. Give agents a minimal zero-dependency standalone client exported via `clawmini export-lite`. It securely authenticates with the Agent API using dynamically generated HMAC tokens (`CLAW_API_TOKEN`) to allow sandboxed agents to log messages and manage cron jobs without needing direct access to the host's Unix socket.
14
- - **Proactivity:** Incoming messages or events from various external sources can be proactively routed back to the user or directly to the agent for autonomous handling.
7
+ Bring your favorite CLI agents (Gemini CLI, Claude Code, OpenAI Codex, etc.), and Clawmini provides the memory, the chat support, and the security boundaries.
15
8
 
16
- ### Coming Soon
9
+ ## Why Clawmini?
17
10
 
18
- - **Human Approval Requests:** When an agent needs permission to execute a sensitive action or requires input, it will pause and ask the user for approval via a dedicated UI or dashboard.
11
+ 🛡️ **Zero-Trust Security by Default**
12
+ Run agents safely. Clawmini uses built-in sandboxing (macOS seatbelt or containerized environments) to restrict what your AI can touch. Sensitive actions require your explicit approval before execution.
19
13
 
20
- ## Quick Start
14
+ 🧠 **Persistent Memory & Proactivity**
15
+ Your agents don't just react; they act. Clawmini allows agents to schedule recurring tasks (cron jobs), maintain long-term context across sessions, and proactively notify you of updates or route messages from external sources.
21
16
 
22
- Assuming you have built and linked the package globally:
17
+ 💻 **Beautiful Web UI & Chat App Extensibility**
18
+ No need to stay in the terminal. Manage your agents and chat with them through a fast, built-in Web interface running entirely on `localhost`. Hook up Discord to chat on-the-go, or easily build your adapters against the local API.
23
19
 
24
- ```bash
25
- # Initialize a new .clawmini settings folder, create an agent named 'coder' with the 'gemini-cladding' template, and set it as the default chat
26
- clawmini init --agent coder --agent-template gemini-cladding
20
+ 🔌 **Bring Your Own Agent**
21
+ Clawmini isn't tied to one model. It orchestrates _any_ CLI-based agent, acting as the secure bridge between the AI and your filesystem.
27
22
 
28
- # Start the local web interface on http://localhost:8080
29
- clawmini web
30
- ```
23
+ ## Quick Start: Meet Jeeves
31
24
 
32
- ## Command Reference
25
+ Let's set up a secure, sandboxed agent named Jeeves using the Gemini CLI template.
33
26
 
34
- ### Initialization & Daemon
27
+ ```bash
28
+ # 1. Install globally
29
+ npm install -g clawmini
35
30
 
36
- - `clawmini init`: Initialize a new `.clawmini` configuration folder.
37
- - `clawmini up`: Start the local daemon server in the background.
38
- - `clawmini down`: Stop the local daemon server.
39
- - `clawmini export-lite [--out <path>] [--stdout]`: Export the standalone `clawmini-lite` client script for use in sandboxed environments.
31
+ # 2. Initialize a workspace and create your first sandboxed agent
32
+ # Note: For a more basic experience, you can use the 'gemini' template instead
33
+ mkdir my-workspace && cd my-workspace
34
+ clawmini init --agent jeeves --agent-template gemini-claw --environment macos
40
35
 
41
- ### Chat Management
36
+ # 3. Start the background daemon
37
+ clawmini up
42
38
 
43
- - `clawmini chats list`: Display all existing chats in the workspace.
44
- - `clawmini chats add <id>`: Create a new chat with the specified identifier.
45
- - `clawmini chats delete <id>`: Remove a chat and its associated history.
46
- - `clawmini chats set-default <id>`: Update the globally configured default chat for the workspace.
39
+ # 4. Open the Web UI to start chatting!
40
+ clawmini web
41
+ ```
47
42
 
48
- ### Messaging
43
+ **Try asking Jeeves:** _"Summarize the recent changes in my git repository."_ Jeeves will run securely in its sandbox, read the diffs, and report back.
49
44
 
50
- - `clawmini messages send <message> [--chat <id>] [--agent <name>]`: Send a message to a specific chat (defaults to the workspace default chat). Use `--agent` to assign a specific agent to handle the message.
51
- - `clawmini messages tail [-n NUM] [--json] [--chat <id>]`: Display the most recent messages and command logs in a chat.
45
+ **What's going on?** When you send a message, Clawmini looks at the chat+message and then launches Gemini CLI with your message in the `jeeves/` directory. The `jeeves/` directory is set up with OpenClaw-like files and system prompt since you used the `gemini-claw` template. And since we chose the `macos` environment, Gemini CLI will run in a built-in Seatbelt sandbox that prevents it from editing anything outside your workspace folder. We additionally give Gemini CLI ways to schedule reminders and recurring tasks, send files, and request permissions to run sensitive commands (see Permission Requests).
52
46
 
53
- ### Agents
47
+ ### Common Slash Commands
54
48
 
55
- - `clawmini agents list`: Display all existing agents.
56
- - `clawmini agents add <id> [-d, --directory <dir>] [-t, --template <name>] [-e, --env <KEY=VALUE>...]`: Create a new agent, optionally setting its working directory, applying a template, and environment variables.
57
- - `clawmini agents update <id> [-d, --directory <dir>] [-e, --env <KEY=VALUE>...]`: Update an existing agent's configuration.
58
- - `clawmini agents delete <id>`: Remove an agent.
49
+ You can use these built-in slash commands in your chat interfaces:
59
50
 
60
- ### Cron Jobs
51
+ - `/new`: Clear previous context and start a new conversation thread.
52
+ - `/stop`: Stop all running commands and drop any queued messages.
53
+ - `/interrupt [message]`: Interrupt the current thinking, batching together all queued messages and sending them immediately.
54
+ - `/pending`: View any pending permission requests from your agent.
55
+ - `/approve [id]`: Approve a specific pending agent request.
56
+ - `/reject [id]`: Reject a specific pending agent request.
61
57
 
62
- - `clawmini cron list [--chat <id>]`: Display all cron jobs configured for a chat.
63
- - `clawmini cron add <name> [--cron <expr> | --every <duration> | --at <iso-time>] [-m, --message <text>]`: Create a new scheduled job. Supports standard cron expressions, recurring intervals (e.g., `10m`), or one-off executions at a specific time.
64
- - `clawmini cron delete <name> [--chat <id>]`: Remove an existing scheduled job.
58
+ ### Guides & Integrations
65
59
 
66
- ### Web Interface
60
+ - [Discord Integration Setup](./docs/guides/discord_adapter_setup.md)
61
+ - [Configuring Permission Requests](./docs/guides/sandbox_policies.md)
67
62
 
68
- - `clawmini web [-p, --port <number>]`: Start the local web interface (default port: 8080).
63
+ ## How It Works
69
64
 
70
- ## Configuration
65
+ **User** ↔️ **Web UI / CLI** ↔️ **Daemon** ↔️ **Sandbox / Environment** ↔️ **Agent**
71
66
 
72
- ## Global settings
67
+ The daemon securely authenticates with the Agent API using dynamically generated HMAC tokens (`CLAW_API_TOKEN`), allowing sandboxed agents to operate safely without direct access to the host's Unix socket.
73
68
 
74
- **TODO**
69
+ ### Built-in Environments
75
70
 
76
- ## Routers
71
+ - `cladding` (most secure): A container-based sandbox using [cladding](https://github.com/dstoc/cladding)
72
+ - `macos`: A macOS sandbox environment that restricts write-access to the workspace.
73
+ - `macos-proxy`: A more constrained macOS sandbox that limits network access to an allowlist.
77
74
 
78
- Clawmini provides an extensible pipeline for processing user messages before they reach an agent using **Routers**. By defining a sequence of routers in your `.clawmini/settings.json` (global) or per-chat settings, you can dynamically alter message content, target specific agents or sessions, inject environment variables, and add automated replies.
75
+ ### Extensible Pipeline (Routers)
79
76
 
80
- Built-in routers include:
77
+ Process user messages before they reach an agent. Dynamically alter content, target specific agents, or expand slash commands (e.g., `/new` to clear context, `/foo` to expand a command script).
81
78
 
82
- - `@clawmini/slash-new`: Creates a new session ID when a message starts with `/new`, effectively clearing the context window for the agent.
83
- - `@clawmini/slash-command`: Expands slash commands (e.g., `/foo`) with the contents of matching files in your `.clawmini/commands/` directory.
79
+ ## Next Steps: Build Autonomous Workflows
84
80
 
85
- You can also write custom shell script routers that accept the current state via `stdin` and output JSON to dynamically control the routing logic. See the [`RouterState` interface](src/daemon/routers/types.ts) for the exact input and output schema.
81
+ Once you're comfortable, Clawmini offers powerful tools for advanced users:
86
82
 
87
- ## Agent Templates
83
+ - **The `gemini-claw` Template:** Scaffold a complete autonomous assistant with built-in memory management (`MEMORY.md`), identity (`SOUL.md`), and heartbeat checks (`HEARTBEAT.md`) for proactive tasking.
84
+ - **`clawmini-lite`:** Deploy agents into heavily restricted containers. Export the minimal, zero-dependency client (`clawmini export-lite`) to securely authenticate with the daemon, allowing the agent to log actions and request permissions without host access.
88
85
 
89
- Clawmini provides built-in templates to help you quickly scaffold new agents with pre-configured settings and files. When you run `clawmini agents add <id> --template <name>`, it copies the template's files into the agent's working directory and merges any provided environment variables or directory options with the template's `settings.json`.
86
+ ---
90
87
 
91
- The currently available built-in templates are:
88
+ ## Documentation & References
92
89
 
93
- - `gemini`: A basic template configured to use the `gemini` CLI as the agent's backend.
94
- - `gemini-cladding`: A template configured to run the `gemini` CLI wrapped inside `cladding` for enhanced security.
95
- - `gemini-claw-cladding`: A comprehensive template that sets up an autonomous personal assistant workspace (OpenClaw). It includes the `gemini-cladding` setup plus a full suite of scaffolding files like `GEMINI.md`, `SOUL.md`, `MEMORY.md`, and `HEARTBEAT.md` to establish the agent's identity, memory, and proactive capabilities.
90
+ For a full list of commands for managing chats, messages, agents, background jobs, and environments, please see the [CLI Command Reference](./docs/CLI_REFERENCE.md).
96
91
 
97
92
  ## Development Setup
98
93
 
@@ -103,35 +98,25 @@ Clawmini is a monorepo consisting of a Node.js TypeScript CLI/Daemon and an embe
103
98
  - Node.js (v18+)
104
99
  - npm
105
100
 
106
- ### Setup
101
+ ### Setup & Scripts
107
102
 
108
103
  ```bash
109
- # Install dependencies for both the root CLI and the web workspace
104
+ # Install dependencies and build the project
110
105
  npm install
111
-
112
- # Build the CLI, Daemon, and statically compile the Web UI
113
106
  npm run build
114
- ```
115
-
116
- ### Development Scripts
117
107
 
118
- During development, you can run the following commands from the root:
119
-
120
- ```bash
121
- # Watch mode for the CLI
108
+ # Development Watch Modes
122
109
  npm run dev:cli
123
-
124
- # Watch mode for the Daemon
125
110
  npm run dev:daemon
126
111
 
127
- # Run formatting, linting, type-checking, and tests
112
+ # Linting & Testing
128
113
  npm run format
129
114
  npm run lint
130
115
  npm run check
131
116
  npm run test
132
117
  ```
133
118
 
134
- ## Architecture Notes
119
+ **Architecture Notes:**
135
120
 
136
121
  - **Separation of Concerns:** The daemon (`src/daemon`) acts as the stateful orchestrator and queue manager, while the CLI (`src/cli`) is simply a thin TRPC client connecting via a UNIX socket.
137
- - **Web UI:** The `web/` directory is a SvelteKit application built with `@sveltejs/adapter-static`. Running `npm run build` bundles the web UI into `dist/web`, which is then served statically by the `clawmini web` Node.js server. Real-time updates to the web UI are powered by Server-Sent Events (SSE) tailing the local `.clawmini/chats/:id/chat.jsonl` files.
122
+ - **Web UI:** The `web/` directory is a SvelteKit application built with `@sveltejs/adapter-static`. Running `npm run build` bundles the web UI into `dist/web`, which is served statically by the `clawmini web` Node.js server. Real-time updates use Server-Sent Events (SSE) tailing local `.clawmini/chats/:id/chat.jsonl` files.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/adapter-discord/index.ts"],"mappings":";iBASsB,IAAA,CAAA,GAAI,OAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/adapter-discord/index.ts"],"mappings":";iBAUsB,IAAA,CAAA,GAAI,OAAA"}
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { l as getSocketPath, o as getClawminiDir, u as getWorkspaceRoot } from "../workspace-CSgfo_2J.mjs";
3
- import { t as createUnixSocketFetch } from "../fetch-BjZVyU3Z.mjs";
2
+ import { l as getSocketPath, o as getClawminiDir, u as getWorkspaceRoot } from "../workspace-DjoNjhW0.mjs";
3
+ import { t as createUnixSocketFetch } from "../fetch-Cn1XNyiO.mjs";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import fs$1 from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { z } from "zod";
8
9
  import { createTRPCClient, httpLink, httpSubscriptionLink, splitLink } from "@trpc/client";
9
10
  import http from "node:http";
@@ -387,7 +388,6 @@ async function main() {
387
388
  console.log(`Received message from ${message.author.tag}: ${message.content}`);
388
389
  const downloadedFiles = [];
389
390
  if (message.attachments.size > 0) {
390
- const { getClawminiDir } = await import("../workspace-CSgfo_2J.mjs").then((n) => n._);
391
391
  const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp", "discord");
392
392
  await fs$1.mkdir(tmpDir, { recursive: true });
393
393
  const maxSizeMB = config.maxAttachmentSizeMB ?? 25;
@@ -446,7 +446,16 @@ async function main() {
446
446
  process.exit(1);
447
447
  }
448
448
  }
449
- main().catch((error) => {
449
+ if ((() => {
450
+ try {
451
+ if (typeof process === "undefined" || !process.argv || process.argv.length < 2) return false;
452
+ const argv1 = process.argv[1];
453
+ if (!argv1) return false;
454
+ return path.resolve(argv1) === path.resolve(fileURLToPath(import.meta.url));
455
+ } catch {
456
+ return false;
457
+ }
458
+ })()) main().catch((error) => {
450
459
  console.error("Unhandled error in Discord Adapter:", error);
451
460
  process.exit(1);
452
461
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fsPromises","fsPromises","fs"],"sources":["../../src/adapter-discord/config.ts","../../src/shared/event-source.ts","../../src/adapter-discord/client.ts","../../src/adapter-discord/state.ts","../../src/adapter-discord/forwarder.ts","../../src/adapter-discord/index.ts"],"sourcesContent":["import fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { getClawminiDir } from '../shared/workspace.js';\nimport fs from 'node:fs';\n\nexport const DiscordConfigSchema = z.looseObject({\n botToken: z.string().min(1, 'Discord Bot Token is required.'),\n authorizedUserId: z.string().min(1, 'Authorized Discord User ID is required.'),\n chatId: z.string().default('default'),\n maxAttachmentSizeMB: z.number().default(25).optional(),\n});\n\nexport type DiscordConfig = z.infer<typeof DiscordConfigSchema>;\n\nexport function getDiscordConfigPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'adapters', 'discord', 'config.json');\n}\n\nexport async function readDiscordConfig(startDir = process.cwd()): Promise<DiscordConfig | null> {\n const configPath = getDiscordConfigPath(startDir);\n try {\n const data = await fsPromises.readFile(configPath, 'utf-8');\n const parsed = JSON.parse(data);\n const result = DiscordConfigSchema.safeParse(parsed);\n if (!result.success) {\n console.error('Invalid Discord configuration:', result.error.format());\n return null;\n }\n return result.data;\n } catch {\n // Return null if file doesn't exist or is invalid JSON\n return null;\n }\n}\n\nexport async function initDiscordConfig(startDir = process.cwd()): Promise<void> {\n const configPath = getDiscordConfigPath(startDir);\n const configDir = path.dirname(configPath);\n\n await fsPromises.mkdir(configDir, { recursive: true });\n\n if (fs.existsSync(configPath)) {\n console.log(`Config file already exists at ${configPath}`);\n return;\n }\n\n const templateConfig = {\n botToken: 'YOUR_DISCORD_BOT_TOKEN',\n authorizedUserId: 'YOUR_DISCORD_USER_ID',\n chatId: 'default',\n };\n\n await fsPromises.writeFile(configPath, JSON.stringify(templateConfig, null, 2), 'utf-8');\n console.log(`Created template configuration file at ${configPath}`);\n console.log('Please update it with your actual Discord Bot Token and User ID.');\n}\n\nexport function isAuthorized(userId: string, authorizedUserId: string): boolean {\n return userId === authorizedUserId;\n}\n","import http from 'node:http';\n\nexport function createUnixSocketEventSource(socketPath: string) {\n return class UnixSocketEventSource {\n public readyState: number = 0; // CONNECTING\n public readonly CONNECTING = 0;\n public readonly OPEN = 1;\n public readonly CLOSED = 2;\n\n req: http.ClientRequest | null = null;\n listeners: Record<string, ((event: Record<string, unknown>) => void)[]> = {};\n\n constructor(url: string, init?: Record<string, unknown>) {\n const parsedUrl = new URL(url);\n\n const options: http.RequestOptions = {\n socketPath,\n path: parsedUrl.pathname + parsedUrl.search,\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n 'Cache-Control': 'no-cache',\n ...(init?.headers as Record<string, string> | undefined),\n },\n };\n\n this.req = http.request(options, (res) => {\n if (res.statusCode === 200) {\n this.readyState = this.OPEN;\n this.dispatchEvent({ type: 'open' });\n } else {\n this.readyState = this.CLOSED;\n this.dispatchEvent({\n type: 'error',\n message: `Unexpected status code: ${res.statusCode}`,\n });\n return;\n }\n\n let buffer = '';\n res.on('data', (chunk) => {\n buffer += chunk.toString('utf-8');\n const lines = buffer.split(/\\r?\\n\\r?\\n/);\n buffer = lines.pop() || '';\n\n for (const block of lines) {\n this.parseBlock(block);\n }\n });\n\n res.on('end', () => {\n if (buffer) this.parseBlock(buffer);\n this.readyState = this.CLOSED;\n this.dispatchEvent({ type: 'close' });\n });\n });\n\n this.req.on('error', (err) => {\n this.readyState = this.CLOSED;\n this.dispatchEvent({ type: 'error', error: err });\n });\n\n this.req.end();\n }\n\n parseBlock(block: string) {\n if (!block.trim()) return;\n\n const lines = block.split(/\\r?\\n/);\n let eventType = 'message';\n let data = '';\n let id = '';\n\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith('data: ')) {\n data += (data ? '\\n' : '') + line.slice(6);\n } else if (line.startsWith('id: ')) {\n id = line.slice(4).trim();\n }\n }\n\n if (data) {\n this.dispatchEvent({\n type: eventType,\n data,\n lastEventId: id,\n });\n }\n }\n\n public addEventListener(type: string, listener: (event: Record<string, unknown>) => void) {\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n }\n this.listeners[type].push(listener);\n }\n\n public removeEventListener(type: string, listener: (event: Record<string, unknown>) => void) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type].filter((l) => l !== listener);\n }\n\n dispatchEvent(event: Record<string, unknown>) {\n const type = event.type as string;\n if (this.listeners[type]) {\n for (const listener of this.listeners[type]) {\n listener(event);\n }\n }\n }\n\n public close() {\n this.readyState = this.CLOSED;\n if (this.req) {\n this.req.destroy();\n }\n }\n };\n}\n","import { createTRPCClient, httpLink, splitLink, httpSubscriptionLink } from '@trpc/client';\nimport type { AppRouter } from '../daemon/router.js';\nimport { getSocketPath } from '../shared/workspace.js';\nimport { createUnixSocketFetch } from '../shared/fetch.js';\nimport { createUnixSocketEventSource } from '../shared/event-source.js';\nimport fs from 'node:fs';\n\n/**\n * Creates a TRPC client that connects to the Clawmini daemon via a Unix socket.\n *\n * @param options - Configuration options for the client.\n * @returns A TRPC client instance for the AppRouter.\n */\nexport function getTRPCClient(options: { socketPath?: string } = {}) {\n const socketPath = options.socketPath ?? getSocketPath();\n\n if (!fs.existsSync(socketPath)) {\n throw new Error(`Daemon not running. Socket not found at ${socketPath}`);\n }\n\n const customFetch = createUnixSocketFetch(socketPath);\n const CustomEventSource = createUnixSocketEventSource(socketPath);\n\n return createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition(op) {\n return op.type === 'subscription';\n },\n true: httpSubscriptionLink({\n url: 'http://localhost',\n EventSource: CustomEventSource,\n }),\n false: httpLink({\n url: 'http://localhost',\n fetch: customFetch,\n }),\n }),\n ],\n });\n}\n","import fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { getClawminiDir } from '../shared/workspace.js';\n\nexport const DiscordStateSchema = z.object({\n lastSyncedMessageId: z.string().optional(),\n});\n\nexport type DiscordState = z.infer<typeof DiscordStateSchema>;\n\nexport function getDiscordStatePath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'adapters', 'discord', 'state.json');\n}\n\nexport async function readDiscordState(startDir = process.cwd()): Promise<DiscordState> {\n const statePath = getDiscordStatePath(startDir);\n try {\n const data = await fsPromises.readFile(statePath, 'utf-8');\n const parsed = JSON.parse(data);\n const result = DiscordStateSchema.safeParse(parsed);\n if (!result.success) {\n return { lastSyncedMessageId: undefined };\n }\n return result.data;\n } catch {\n // Return default state if file doesn't exist or is invalid JSON\n return { lastSyncedMessageId: undefined };\n }\n}\n\nexport async function writeDiscordState(\n state: DiscordState,\n startDir = process.cwd()\n): Promise<void> {\n const statePath = getDiscordStatePath(startDir);\n const dir = path.dirname(statePath);\n try {\n await fsPromises.mkdir(dir, { recursive: true });\n await fsPromises.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');\n } catch (err) {\n console.error(`Failed to write Discord state to ${statePath}:`, err);\n }\n}\n","import type { Client, MessageCreateOptions } from 'discord.js';\nimport path from 'node:path';\nimport type { getTRPCClient } from './client.js';\nimport { readDiscordState, writeDiscordState } from './state.js';\nimport type { ChatMessage, CommandLogMessage } from '../shared/chats.js';\nimport { getWorkspaceRoot } from '../shared/workspace.js';\n\nexport async function startDaemonToDiscordForwarder(\n client: Client,\n trpc: ReturnType<typeof getTRPCClient>,\n discordUserId: string,\n chatId: string = 'default',\n signal?: AbortSignal\n) {\n const state = await readDiscordState();\n let lastMessageId = state.lastSyncedMessageId;\n\n // 1. If we don't have a lastMessageId, get the most recent one from the daemon\n // to avoid sending the entire chat history on first run.\n if (!lastMessageId) {\n try {\n const messages = await trpc.getMessages.query({ chatId, limit: 1 });\n if (Array.isArray(messages) && messages.length > 0) {\n const lastMsg = messages[messages.length - 1];\n if (lastMsg) {\n lastMessageId = lastMsg.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId });\n }\n }\n } catch (error) {\n if (signal?.aborted) return;\n console.error('Failed to fetch initial messages from daemon:', error);\n }\n }\n\n console.log(\n `Starting daemon-to-discord forwarder for chat ${chatId}, lastMessageId: ${lastMessageId}`\n );\n\n let retryDelay = 1000;\n const maxRetryDelay = 30000;\n\n // 2. Start the observation loop using tRPC subscription\n return new Promise<void>((resolve) => {\n let subscription: { unsubscribe: () => void } | null = null;\n let messageQueue = Promise.resolve();\n\n const connect = () => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n subscription = trpc.waitForMessages.subscribe(\n { chatId, lastMessageId },\n {\n onData: (messages) => {\n retryDelay = 1000; // Reset retry delay on successful data\n\n if (!Array.isArray(messages) || messages.length === 0) {\n return;\n }\n\n // Queue processing to ensure sequential execution\n messageQueue = messageQueue.then(async () => {\n for (const rawMessage of messages) {\n if (signal?.aborted) break;\n\n const message = rawMessage as ChatMessage;\n\n // Only forward logs (agent responses, system messages)\n if (message.role === 'log') {\n const logMessage = message as CommandLogMessage;\n\n if (logMessage.level === 'verbose') {\n lastMessageId = logMessage.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n continue;\n }\n\n const hasContent = !!logMessage.content?.trim();\n const hasFiles = Array.isArray(logMessage.files) && logMessage.files.length > 0;\n\n // The daemon stores logMessage.files as paths relative to the WORKSPACE directory\n // (the directory containing .clawmini). We must resolve these against the current\n // workspace root so discord.js can successfully locate and read the files.\n let absoluteFiles: string[] = [];\n if (hasFiles) {\n const workspaceRoot = getWorkspaceRoot(process.cwd());\n absoluteFiles = logMessage.files!.map((f) => path.resolve(workspaceRoot, f));\n }\n\n if (!hasContent && !hasFiles) {\n lastMessageId = logMessage.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n continue;\n }\n\n try {\n const user = await client.users.fetch(discordUserId);\n const dm = await user.createDM();\n\n // Discord has a 2000 character limit for messages.\n if (hasContent && logMessage.content.length > 2000) {\n const chunks = chunkString(logMessage.content, 2000);\n for (let i = 0; i < chunks.length; i++) {\n if (signal?.aborted) break;\n const chunkOptions: MessageCreateOptions = { content: chunks[i] as string };\n if (i === chunks.length - 1 && hasFiles) {\n chunkOptions.files = absoluteFiles;\n }\n await dm.send(chunkOptions);\n }\n } else {\n const options: MessageCreateOptions = {};\n if (hasContent) {\n options.content = logMessage.content;\n }\n if (hasFiles) {\n options.files = absoluteFiles;\n }\n await dm.send(options);\n }\n } catch (error) {\n console.error(\n `Failed to send message to Discord user ${discordUserId}:`,\n error\n );\n // We don't advance lastMessageId if sending failed\n break;\n }\n }\n\n lastMessageId = message.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n }\n });\n },\n onError: (error) => {\n console.error(\n `Error in daemon-to-discord forwarder subscription. Retrying in ${retryDelay}ms.`,\n error\n );\n subscription?.unsubscribe();\n subscription = null;\n\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n setTimeout(() => {\n retryDelay = Math.min(retryDelay * 2, maxRetryDelay);\n connect();\n }, retryDelay);\n },\n onComplete: () => {\n subscription = null;\n if (!signal?.aborted) {\n setTimeout(() => connect(), retryDelay);\n } else {\n resolve();\n }\n },\n }\n );\n };\n\n let typingSubscription: { unsubscribe: () => void } | null = null;\n let typingRetryDelay = 1000;\n\n const connectTyping = () => {\n if (signal?.aborted) {\n return;\n }\n\n typingSubscription = trpc.waitForTyping.subscribe(\n { chatId },\n {\n onData: async (event) => {\n typingRetryDelay = 1000; // Reset retry delay on successful data\n if (!event) return;\n\n try {\n const user = await client.users.fetch(discordUserId);\n const dm = await user.createDM();\n await dm.sendTyping();\n } catch (error) {\n console.error(\n `Failed to send typing indicator to Discord user ${discordUserId}:`,\n error\n );\n }\n },\n onError: (error) => {\n console.error(\n `Error in daemon-to-discord typing forwarder subscription. Retrying in ${typingRetryDelay}ms.`,\n error\n );\n typingSubscription?.unsubscribe();\n typingSubscription = null;\n\n if (signal?.aborted) {\n return;\n }\n\n setTimeout(() => {\n typingRetryDelay = Math.min(typingRetryDelay * 2, maxRetryDelay);\n connectTyping();\n }, typingRetryDelay);\n },\n onComplete: () => {\n typingSubscription = null;\n if (!signal?.aborted) {\n setTimeout(() => connectTyping(), typingRetryDelay);\n }\n },\n }\n );\n };\n\n connect();\n connectTyping();\n\n signal?.addEventListener('abort', () => {\n subscription?.unsubscribe();\n typingSubscription?.unsubscribe();\n resolve();\n });\n });\n}\n\nfunction chunkString(str: string, size: number): string[] {\n const chunks: string[] = [];\n const chars = Array.from(str);\n for (let i = 0; i < chars.length; i += size) {\n chunks.push(chars.slice(i, i + size).join(''));\n }\n return chunks;\n}\n","#!/usr/bin/env node\n\nimport { Client, Events, GatewayIntentBits, Partials } from 'discord.js';\nimport { readDiscordConfig, isAuthorized, initDiscordConfig } from './config.js';\nimport { getTRPCClient } from './client.js';\nimport { startDaemonToDiscordForwarder } from './forwarder.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function main() {\n const args = process.argv.slice(2);\n\n if (args[0] === 'init') {\n await initDiscordConfig();\n return;\n }\n\n console.log('Discord Adapter starting...');\n\n const config = await readDiscordConfig();\n if (!config) {\n console.error(\n 'Failed to load Discord configuration. Please ensure .clawmini/adapters/discord/config.json exists and is valid.'\n );\n process.exit(1);\n }\n\n const trpc = getTRPCClient();\n\n const client = new Client({\n intents: [GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent],\n partials: [Partials.Channel],\n });\n\n client.once(Events.ClientReady, (readyClient) => {\n console.log(`Ready! Logged in as ${readyClient.user.tag}`);\n\n // Start forwarding from daemon to Discord\n startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, config.chatId).catch(\n (error) => {\n console.error('Error in daemon-to-discord forwarder:', error);\n }\n );\n });\n\n client.on(Events.MessageCreate, async (message) => {\n // Ignore messages from the bot itself\n if (message.author.id === client.user?.id) return;\n\n // Only handle DM messages\n if (message.guild) return;\n\n // Check if the user is authorized\n if (!isAuthorized(message.author.id, config.authorizedUserId)) {\n console.log(\n `Unauthorized message from ${message.author.tag} (${message.author.id}) ignored.`\n );\n return;\n }\n\n console.log(`Received message from ${message.author.tag}: ${message.content}`);\n\n const downloadedFiles: string[] = [];\n if (message.attachments.size > 0) {\n const { getClawminiDir } = await import('../shared/workspace.js');\n const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'discord');\n await fs.mkdir(tmpDir, { recursive: true });\n const maxSizeMB = config.maxAttachmentSizeMB ?? 25;\n const maxSizeBytes = maxSizeMB * 1024 * 1024;\n\n for (const attachment of message.attachments.values()) {\n if (attachment.size > maxSizeBytes) {\n console.warn(\n `Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`\n );\n await message.reply(\n `Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`\n );\n continue;\n }\n\n try {\n const res = await fetch(attachment.url);\n if (!res.ok) {\n console.error(`Failed to download attachment ${attachment.name}`);\n continue;\n }\n\n const uniqueName = `${Date.now()}-${attachment.name}`;\n const filePath = path.join(tmpDir, uniqueName);\n const arrayBuffer = await res.arrayBuffer();\n await fs.writeFile(filePath, Buffer.from(arrayBuffer));\n downloadedFiles.push(filePath);\n } catch (err) {\n console.error(`Error downloading attachment ${attachment.name}:`, err);\n }\n }\n }\n\n let finalContent = message.content;\n\n if (message.reference && message.reference.messageId) {\n try {\n const referencedMessage = await message.fetchReference();\n if (referencedMessage && referencedMessage.content) {\n const quotedContent = referencedMessage.content\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n finalContent = `${quotedContent}\\n${finalContent}`;\n }\n } catch (err) {\n console.error('Failed to fetch referenced message:', err);\n }\n }\n\n console.log(`Forwarding message to daemon: ${finalContent}`);\n try {\n await trpc.sendMessage.mutate({\n type: 'send-message',\n client: 'cli',\n data: {\n message: finalContent,\n chatId: config.chatId,\n files: downloadedFiles.length > 0 ? downloadedFiles : undefined,\n adapter: 'discord',\n noWait: true,\n },\n });\n console.log('Message forwarded to daemon successfully.');\n } catch (error) {\n console.error('Failed to forward message to daemon:', error);\n }\n });\n\n try {\n await client.login(config.botToken);\n } catch (error) {\n console.error('Failed to login to Discord:', error);\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error('Unhandled error in Discord Adapter:', error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;AAMA,MAAa,sBAAsB,EAAE,YAAY;CAC/C,UAAU,EAAE,QAAQ,CAAC,IAAI,GAAG,iCAAiC;CAC7D,kBAAkB,EAAE,QAAQ,CAAC,IAAI,GAAG,0CAA0C;CAC9E,QAAQ,EAAE,QAAQ,CAAC,QAAQ,UAAU;CACrC,qBAAqB,EAAE,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU;CACvD,CAAC;AAIF,SAAgB,qBAAqB,WAAW,QAAQ,KAAK,EAAU;AACrE,QAAO,KAAK,KAAK,eAAe,SAAS,EAAE,YAAY,WAAW,cAAc;;AAGlF,eAAsB,kBAAkB,WAAW,QAAQ,KAAK,EAAiC;CAC/F,MAAM,aAAa,qBAAqB,SAAS;AACjD,KAAI;EACF,MAAM,OAAO,MAAMA,KAAW,SAAS,YAAY,QAAQ;EAC3D,MAAM,SAAS,KAAK,MAAM,KAAK;EAC/B,MAAM,SAAS,oBAAoB,UAAU,OAAO;AACpD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAQ,MAAM,kCAAkC,OAAO,MAAM,QAAQ,CAAC;AACtE,UAAO;;AAET,SAAO,OAAO;SACR;AAEN,SAAO;;;AAIX,eAAsB,kBAAkB,WAAW,QAAQ,KAAK,EAAiB;CAC/E,MAAM,aAAa,qBAAqB,SAAS;CACjD,MAAM,YAAY,KAAK,QAAQ,WAAW;AAE1C,OAAMA,KAAW,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;AAEtD,KAAI,GAAG,WAAW,WAAW,EAAE;AAC7B,UAAQ,IAAI,iCAAiC,aAAa;AAC1D;;AASF,OAAMA,KAAW,UAAU,YAAY,KAAK,UANrB;EACrB,UAAU;EACV,kBAAkB;EAClB,QAAQ;EACT,EAEqE,MAAM,EAAE,EAAE,QAAQ;AACxF,SAAQ,IAAI,0CAA0C,aAAa;AACnE,SAAQ,IAAI,mEAAmE;;AAGjF,SAAgB,aAAa,QAAgB,kBAAmC;AAC9E,QAAO,WAAW;;;;;ACzDpB,SAAgB,4BAA4B,YAAoB;AAC9D,QAAO,MAAM,sBAAsB;EACjC,AAAO,aAAqB;EAC5B,AAAgB,aAAa;EAC7B,AAAgB,OAAO;EACvB,AAAgB,SAAS;EAEzB,MAAiC;EACjC,YAA0E,EAAE;EAE5E,YAAY,KAAa,MAAgC;GACvD,MAAM,YAAY,IAAI,IAAI,IAAI;GAE9B,MAAM,UAA+B;IACnC;IACA,MAAM,UAAU,WAAW,UAAU;IACrC,QAAQ;IACR,SAAS;KACP,QAAQ;KACR,iBAAiB;KACjB,GAAI,MAAM;KACX;IACF;AAED,QAAK,MAAM,KAAK,QAAQ,UAAU,QAAQ;AACxC,QAAI,IAAI,eAAe,KAAK;AAC1B,UAAK,aAAa,KAAK;AACvB,UAAK,cAAc,EAAE,MAAM,QAAQ,CAAC;WAC/B;AACL,UAAK,aAAa,KAAK;AACvB,UAAK,cAAc;MACjB,MAAM;MACN,SAAS,2BAA2B,IAAI;MACzC,CAAC;AACF;;IAGF,IAAI,SAAS;AACb,QAAI,GAAG,SAAS,UAAU;AACxB,eAAU,MAAM,SAAS,QAAQ;KACjC,MAAM,QAAQ,OAAO,MAAM,aAAa;AACxC,cAAS,MAAM,KAAK,IAAI;AAExB,UAAK,MAAM,SAAS,MAClB,MAAK,WAAW,MAAM;MAExB;AAEF,QAAI,GAAG,aAAa;AAClB,SAAI,OAAQ,MAAK,WAAW,OAAO;AACnC,UAAK,aAAa,KAAK;AACvB,UAAK,cAAc,EAAE,MAAM,SAAS,CAAC;MACrC;KACF;AAEF,QAAK,IAAI,GAAG,UAAU,QAAQ;AAC5B,SAAK,aAAa,KAAK;AACvB,SAAK,cAAc;KAAE,MAAM;KAAS,OAAO;KAAK,CAAC;KACjD;AAEF,QAAK,IAAI,KAAK;;EAGhB,WAAW,OAAe;AACxB,OAAI,CAAC,MAAM,MAAM,CAAE;GAEnB,MAAM,QAAQ,MAAM,MAAM,QAAQ;GAClC,IAAI,YAAY;GAChB,IAAI,OAAO;GACX,IAAI,KAAK;AAET,QAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,UAAU,CAC5B,aAAY,KAAK,MAAM,EAAE,CAAC,MAAM;YACvB,KAAK,WAAW,SAAS,CAClC,UAAS,OAAO,OAAO,MAAM,KAAK,MAAM,EAAE;YACjC,KAAK,WAAW,OAAO,CAChC,MAAK,KAAK,MAAM,EAAE,CAAC,MAAM;AAI7B,OAAI,KACF,MAAK,cAAc;IACjB,MAAM;IACN;IACA,aAAa;IACd,CAAC;;EAIN,AAAO,iBAAiB,MAAc,UAAoD;AACxF,OAAI,CAAC,KAAK,UAAU,MAClB,MAAK,UAAU,QAAQ,EAAE;AAE3B,QAAK,UAAU,MAAM,KAAK,SAAS;;EAGrC,AAAO,oBAAoB,MAAc,UAAoD;AAC3F,OAAI,CAAC,KAAK,UAAU,MAAO;AAC3B,QAAK,UAAU,QAAQ,KAAK,UAAU,MAAM,QAAQ,MAAM,MAAM,SAAS;;EAG3E,cAAc,OAAgC;GAC5C,MAAM,OAAO,MAAM;AACnB,OAAI,KAAK,UAAU,MACjB,MAAK,MAAM,YAAY,KAAK,UAAU,MACpC,UAAS,MAAM;;EAKrB,AAAO,QAAQ;AACb,QAAK,aAAa,KAAK;AACvB,OAAI,KAAK,IACP,MAAK,IAAI,SAAS;;;;;;;;;;;;;ACvG1B,SAAgB,cAAc,UAAmC,EAAE,EAAE;CACnE,MAAM,aAAa,QAAQ,cAAc,eAAe;AAExD,KAAI,CAAC,GAAG,WAAW,WAAW,CAC5B,OAAM,IAAI,MAAM,2CAA2C,aAAa;CAG1E,MAAM,cAAc,sBAAsB,WAAW;AAGrD,QAAO,iBAA4B,EACjC,OAAO,CACL,UAAU;EACR,UAAU,IAAI;AACZ,UAAO,GAAG,SAAS;;EAErB,MAAM,qBAAqB;GACzB,KAAK;GACL,aAVkB,4BAA4B,WAAW;GAW1D,CAAC;EACF,OAAO,SAAS;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACH,CAAC,CACH,EACF,CAAC;;;;;AClCJ,MAAa,qBAAqB,EAAE,OAAO,EACzC,qBAAqB,EAAE,QAAQ,CAAC,UAAU,EAC3C,CAAC;AAIF,SAAgB,oBAAoB,WAAW,QAAQ,KAAK,EAAU;AACpE,QAAO,KAAK,KAAK,eAAe,SAAS,EAAE,YAAY,WAAW,aAAa;;AAGjF,eAAsB,iBAAiB,WAAW,QAAQ,KAAK,EAAyB;CACtF,MAAM,YAAY,oBAAoB,SAAS;AAC/C,KAAI;EACF,MAAM,OAAO,MAAMC,KAAW,SAAS,WAAW,QAAQ;EAC1D,MAAM,SAAS,KAAK,MAAM,KAAK;EAC/B,MAAM,SAAS,mBAAmB,UAAU,OAAO;AACnD,MAAI,CAAC,OAAO,QACV,QAAO,EAAE,qBAAqB,QAAW;AAE3C,SAAO,OAAO;SACR;AAEN,SAAO,EAAE,qBAAqB,QAAW;;;AAI7C,eAAsB,kBACpB,OACA,WAAW,QAAQ,KAAK,EACT;CACf,MAAM,YAAY,oBAAoB,SAAS;CAC/C,MAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,KAAI;AACF,QAAMA,KAAW,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;AAChD,QAAMA,KAAW,UAAU,WAAW,KAAK,UAAU,OAAO,MAAM,EAAE,EAAE,QAAQ;UACvE,KAAK;AACZ,UAAQ,MAAM,oCAAoC,UAAU,IAAI,IAAI;;;;;;AClCxE,eAAsB,8BACpB,QACA,MACA,eACA,SAAiB,WACjB,QACA;CAEA,IAAI,iBADU,MAAM,kBAAkB,EACZ;AAI1B,KAAI,CAAC,cACH,KAAI;EACF,MAAM,WAAW,MAAM,KAAK,YAAY,MAAM;GAAE;GAAQ,OAAO;GAAG,CAAC;AACnE,MAAI,MAAM,QAAQ,SAAS,IAAI,SAAS,SAAS,GAAG;GAClD,MAAM,UAAU,SAAS,SAAS,SAAS;AAC3C,OAAI,SAAS;AACX,oBAAgB,QAAQ;AACxB,UAAM,kBAAkB,EAAE,qBAAqB,eAAe,CAAC;;;UAG5D,OAAO;AACd,MAAI,QAAQ,QAAS;AACrB,UAAQ,MAAM,iDAAiD,MAAM;;AAIzE,SAAQ,IACN,iDAAiD,OAAO,mBAAmB,gBAC5E;CAED,IAAI,aAAa;CACjB,MAAM,gBAAgB;AAGtB,QAAO,IAAI,SAAe,YAAY;EACpC,IAAI,eAAmD;EACvD,IAAI,eAAe,QAAQ,SAAS;EAEpC,MAAM,gBAAgB;AACpB,OAAI,QAAQ,SAAS;AACnB,aAAS;AACT;;AAGF,kBAAe,KAAK,gBAAgB,UAClC;IAAE;IAAQ;IAAe,EACzB;IACE,SAAS,aAAa;AACpB,kBAAa;AAEb,SAAI,CAAC,MAAM,QAAQ,SAAS,IAAI,SAAS,WAAW,EAClD;AAIF,oBAAe,aAAa,KAAK,YAAY;AAC3C,WAAK,MAAM,cAAc,UAAU;AACjC,WAAI,QAAQ,QAAS;OAErB,MAAM,UAAU;AAGhB,WAAI,QAAQ,SAAS,OAAO;QAC1B,MAAM,aAAa;AAEnB,YAAI,WAAW,UAAU,WAAW;AAClC,yBAAgB,WAAW;AAC3B,eAAM,kBAAkB,EAAE,qBAAqB,eAAe,CAAC,CAAC,MAC9D,QAAQ,MACT;AACD;;QAGF,MAAM,aAAa,CAAC,CAAC,WAAW,SAAS,MAAM;QAC/C,MAAM,WAAW,MAAM,QAAQ,WAAW,MAAM,IAAI,WAAW,MAAM,SAAS;QAK9E,IAAI,gBAA0B,EAAE;AAChC,YAAI,UAAU;SACZ,MAAM,gBAAgB,iBAAiB,QAAQ,KAAK,CAAC;AACrD,yBAAgB,WAAW,MAAO,KAAK,MAAM,KAAK,QAAQ,eAAe,EAAE,CAAC;;AAG9E,YAAI,CAAC,cAAc,CAAC,UAAU;AAC5B,yBAAgB,WAAW;AAC3B,eAAM,kBAAkB,EAAE,qBAAqB,eAAe,CAAC,CAAC,MAC9D,QAAQ,MACT;AACD;;AAGF,YAAI;SAEF,MAAM,KAAK,OADE,MAAM,OAAO,MAAM,MAAM,cAAc,EAC9B,UAAU;AAGhC,aAAI,cAAc,WAAW,QAAQ,SAAS,KAAM;UAClD,MAAM,SAAS,YAAY,WAAW,SAAS,IAAK;AACpD,eAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,eAAI,QAAQ,QAAS;WACrB,MAAM,eAAqC,EAAE,SAAS,OAAO,IAAc;AAC3E,eAAI,MAAM,OAAO,SAAS,KAAK,SAC7B,cAAa,QAAQ;AAEvB,iBAAM,GAAG,KAAK,aAAa;;gBAExB;UACL,MAAM,UAAgC,EAAE;AACxC,cAAI,WACF,SAAQ,UAAU,WAAW;AAE/B,cAAI,SACF,SAAQ,QAAQ;AAElB,gBAAM,GAAG,KAAK,QAAQ;;iBAEjB,OAAO;AACd,iBAAQ,MACN,0CAA0C,cAAc,IACxD,MACD;AAED;;;AAIJ,uBAAgB,QAAQ;AACxB,aAAM,kBAAkB,EAAE,qBAAqB,eAAe,CAAC,CAAC,MAC9D,QAAQ,MACT;;OAEH;;IAEJ,UAAU,UAAU;AAClB,aAAQ,MACN,kEAAkE,WAAW,MAC7E,MACD;AACD,mBAAc,aAAa;AAC3B,oBAAe;AAEf,SAAI,QAAQ,SAAS;AACnB,eAAS;AACT;;AAGF,sBAAiB;AACf,mBAAa,KAAK,IAAI,aAAa,GAAG,cAAc;AACpD,eAAS;QACR,WAAW;;IAEhB,kBAAkB;AAChB,oBAAe;AACf,SAAI,CAAC,QAAQ,QACX,kBAAiB,SAAS,EAAE,WAAW;SAEvC,UAAS;;IAGd,CACF;;EAGH,IAAI,qBAAyD;EAC7D,IAAI,mBAAmB;EAEvB,MAAM,sBAAsB;AAC1B,OAAI,QAAQ,QACV;AAGF,wBAAqB,KAAK,cAAc,UACtC,EAAE,QAAQ,EACV;IACE,QAAQ,OAAO,UAAU;AACvB,wBAAmB;AACnB,SAAI,CAAC,MAAO;AAEZ,SAAI;AAGF,aADW,OADE,MAAM,OAAO,MAAM,MAAM,cAAc,EAC9B,UAAU,EACvB,YAAY;cACd,OAAO;AACd,cAAQ,MACN,mDAAmD,cAAc,IACjE,MACD;;;IAGL,UAAU,UAAU;AAClB,aAAQ,MACN,yEAAyE,iBAAiB,MAC1F,MACD;AACD,yBAAoB,aAAa;AACjC,0BAAqB;AAErB,SAAI,QAAQ,QACV;AAGF,sBAAiB;AACf,yBAAmB,KAAK,IAAI,mBAAmB,GAAG,cAAc;AAChE,qBAAe;QACd,iBAAiB;;IAEtB,kBAAkB;AAChB,0BAAqB;AACrB,SAAI,CAAC,QAAQ,QACX,kBAAiB,eAAe,EAAE,iBAAiB;;IAGxD,CACF;;AAGH,WAAS;AACT,iBAAe;AAEf,UAAQ,iBAAiB,eAAe;AACtC,iBAAc,aAAa;AAC3B,uBAAoB,aAAa;AACjC,YAAS;IACT;GACF;;AAGJ,SAAS,YAAY,KAAa,MAAwB;CACxD,MAAM,SAAmB,EAAE;CAC3B,MAAM,QAAQ,MAAM,KAAK,IAAI;AAC7B,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KACrC,QAAO,KAAK,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC;AAEhD,QAAO;;;;;AC3OT,eAAsB,OAAO;AAG3B,KAFa,QAAQ,KAAK,MAAM,EAAE,CAEzB,OAAO,QAAQ;AACtB,QAAM,mBAAmB;AACzB;;AAGF,SAAQ,IAAI,8BAA8B;CAE1C,MAAM,SAAS,MAAM,mBAAmB;AACxC,KAAI,CAAC,QAAQ;AACX,UAAQ,MACN,kHACD;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,eAAe;CAE5B,MAAM,SAAS,IAAI,OAAO;EACxB,SAAS,CAAC,kBAAkB,gBAAgB,kBAAkB,eAAe;EAC7E,UAAU,CAAC,SAAS,QAAQ;EAC7B,CAAC;AAEF,QAAO,KAAK,OAAO,cAAc,gBAAgB;AAC/C,UAAQ,IAAI,uBAAuB,YAAY,KAAK,MAAM;AAG1D,gCAA8B,aAAa,MAAM,OAAO,kBAAkB,OAAO,OAAO,CAAC,OACtF,UAAU;AACT,WAAQ,MAAM,yCAAyC,MAAM;IAEhE;GACD;AAEF,QAAO,GAAG,OAAO,eAAe,OAAO,YAAY;AAEjD,MAAI,QAAQ,OAAO,OAAO,OAAO,MAAM,GAAI;AAG3C,MAAI,QAAQ,MAAO;AAGnB,MAAI,CAAC,aAAa,QAAQ,OAAO,IAAI,OAAO,iBAAiB,EAAE;AAC7D,WAAQ,IACN,6BAA6B,QAAQ,OAAO,IAAI,IAAI,QAAQ,OAAO,GAAG,YACvE;AACD;;AAGF,UAAQ,IAAI,yBAAyB,QAAQ,OAAO,IAAI,IAAI,QAAQ,UAAU;EAE9E,MAAM,kBAA4B,EAAE;AACpC,MAAI,QAAQ,YAAY,OAAO,GAAG;GAChC,MAAM,EAAE,mBAAmB,MAAM,OAAO;GACxC,MAAM,SAAS,KAAK,KAAK,eAAe,QAAQ,KAAK,CAAC,EAAE,OAAO,UAAU;AACzE,SAAMC,KAAG,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;GAC3C,MAAM,YAAY,OAAO,uBAAuB;GAChD,MAAM,eAAe,YAAY,OAAO;AAExC,QAAK,MAAM,cAAc,QAAQ,YAAY,QAAQ,EAAE;AACrD,QAAI,WAAW,OAAO,cAAc;AAClC,aAAQ,KACN,cAAc,WAAW,KAAK,uBAAuB,UAAU,gBAChE;AACD,WAAM,QAAQ,MACZ,uBAAuB,WAAW,KAAK,6BAA6B,UAAU,qBAC/E;AACD;;AAGF,QAAI;KACF,MAAM,MAAM,MAAM,MAAM,WAAW,IAAI;AACvC,SAAI,CAAC,IAAI,IAAI;AACX,cAAQ,MAAM,iCAAiC,WAAW,OAAO;AACjE;;KAGF,MAAM,aAAa,GAAG,KAAK,KAAK,CAAC,GAAG,WAAW;KAC/C,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW;KAC9C,MAAM,cAAc,MAAM,IAAI,aAAa;AAC3C,WAAMA,KAAG,UAAU,UAAU,OAAO,KAAK,YAAY,CAAC;AACtD,qBAAgB,KAAK,SAAS;aACvB,KAAK;AACZ,aAAQ,MAAM,gCAAgC,WAAW,KAAK,IAAI,IAAI;;;;EAK5E,IAAI,eAAe,QAAQ;AAE3B,MAAI,QAAQ,aAAa,QAAQ,UAAU,UACzC,KAAI;GACF,MAAM,oBAAoB,MAAM,QAAQ,gBAAgB;AACxD,OAAI,qBAAqB,kBAAkB,QAKzC,gBAAe,GAJO,kBAAkB,QACrC,MAAM,KAAK,CACX,KAAK,SAAS,KAAK,OAAO,CAC1B,KAAK,KAAK,CACmB,IAAI;WAE/B,KAAK;AACZ,WAAQ,MAAM,uCAAuC,IAAI;;AAI7D,UAAQ,IAAI,iCAAiC,eAAe;AAC5D,MAAI;AACF,SAAM,KAAK,YAAY,OAAO;IAC5B,MAAM;IACN,QAAQ;IACR,MAAM;KACJ,SAAS;KACT,QAAQ,OAAO;KACf,OAAO,gBAAgB,SAAS,IAAI,kBAAkB;KACtD,SAAS;KACT,QAAQ;KACT;IACF,CAAC;AACF,WAAQ,IAAI,4CAA4C;WACjD,OAAO;AACd,WAAQ,MAAM,wCAAwC,MAAM;;GAE9D;AAEF,KAAI;AACF,QAAM,OAAO,MAAM,OAAO,SAAS;UAC5B,OAAO;AACd,UAAQ,MAAM,+BAA+B,MAAM;AACnD,UAAQ,KAAK,EAAE;;;AAInB,MAAM,CAAC,OAAO,UAAU;AACtB,SAAQ,MAAM,uCAAuC,MAAM;AAC3D,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"index.mjs","names":["fsPromises","fsPromises","fs"],"sources":["../../src/adapter-discord/config.ts","../../src/shared/event-source.ts","../../src/adapter-discord/client.ts","../../src/adapter-discord/state.ts","../../src/adapter-discord/forwarder.ts","../../src/adapter-discord/index.ts"],"sourcesContent":["import fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { getClawminiDir } from '../shared/workspace.js';\nimport fs from 'node:fs';\n\nexport const DiscordConfigSchema = z.looseObject({\n botToken: z.string().min(1, 'Discord Bot Token is required.'),\n authorizedUserId: z.string().min(1, 'Authorized Discord User ID is required.'),\n chatId: z.string().default('default'),\n maxAttachmentSizeMB: z.number().default(25).optional(),\n});\n\nexport type DiscordConfig = z.infer<typeof DiscordConfigSchema>;\n\nexport function getDiscordConfigPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'adapters', 'discord', 'config.json');\n}\n\nexport async function readDiscordConfig(startDir = process.cwd()): Promise<DiscordConfig | null> {\n const configPath = getDiscordConfigPath(startDir);\n try {\n const data = await fsPromises.readFile(configPath, 'utf-8');\n const parsed = JSON.parse(data);\n const result = DiscordConfigSchema.safeParse(parsed);\n if (!result.success) {\n console.error('Invalid Discord configuration:', result.error.format());\n return null;\n }\n return result.data;\n } catch {\n // Return null if file doesn't exist or is invalid JSON\n return null;\n }\n}\n\nexport async function initDiscordConfig(startDir = process.cwd()): Promise<void> {\n const configPath = getDiscordConfigPath(startDir);\n const configDir = path.dirname(configPath);\n\n await fsPromises.mkdir(configDir, { recursive: true });\n\n if (fs.existsSync(configPath)) {\n console.log(`Config file already exists at ${configPath}`);\n return;\n }\n\n const templateConfig = {\n botToken: 'YOUR_DISCORD_BOT_TOKEN',\n authorizedUserId: 'YOUR_DISCORD_USER_ID',\n chatId: 'default',\n };\n\n await fsPromises.writeFile(configPath, JSON.stringify(templateConfig, null, 2), 'utf-8');\n console.log(`Created template configuration file at ${configPath}`);\n console.log('Please update it with your actual Discord Bot Token and User ID.');\n}\n\nexport function isAuthorized(userId: string, authorizedUserId: string): boolean {\n return userId === authorizedUserId;\n}\n","import http from 'node:http';\n\nexport function createUnixSocketEventSource(socketPath: string) {\n return class UnixSocketEventSource {\n public readyState: number = 0; // CONNECTING\n public readonly CONNECTING = 0;\n public readonly OPEN = 1;\n public readonly CLOSED = 2;\n\n req: http.ClientRequest | null = null;\n listeners: Record<string, ((event: Record<string, unknown>) => void)[]> = {};\n\n constructor(url: string, init?: Record<string, unknown>) {\n const parsedUrl = new URL(url);\n\n const options: http.RequestOptions = {\n socketPath,\n path: parsedUrl.pathname + parsedUrl.search,\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n 'Cache-Control': 'no-cache',\n ...(init?.headers as Record<string, string> | undefined),\n },\n };\n\n this.req = http.request(options, (res) => {\n if (res.statusCode === 200) {\n this.readyState = this.OPEN;\n this.dispatchEvent({ type: 'open' });\n } else {\n this.readyState = this.CLOSED;\n this.dispatchEvent({\n type: 'error',\n message: `Unexpected status code: ${res.statusCode}`,\n });\n return;\n }\n\n let buffer = '';\n res.on('data', (chunk) => {\n buffer += chunk.toString('utf-8');\n const lines = buffer.split(/\\r?\\n\\r?\\n/);\n buffer = lines.pop() || '';\n\n for (const block of lines) {\n this.parseBlock(block);\n }\n });\n\n res.on('end', () => {\n if (buffer) this.parseBlock(buffer);\n this.readyState = this.CLOSED;\n this.dispatchEvent({ type: 'close' });\n });\n });\n\n this.req.on('error', (err) => {\n this.readyState = this.CLOSED;\n this.dispatchEvent({ type: 'error', error: err });\n });\n\n this.req.end();\n }\n\n parseBlock(block: string) {\n if (!block.trim()) return;\n\n const lines = block.split(/\\r?\\n/);\n let eventType = 'message';\n let data = '';\n let id = '';\n\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith('data: ')) {\n data += (data ? '\\n' : '') + line.slice(6);\n } else if (line.startsWith('id: ')) {\n id = line.slice(4).trim();\n }\n }\n\n if (data) {\n this.dispatchEvent({\n type: eventType,\n data,\n lastEventId: id,\n });\n }\n }\n\n public addEventListener(type: string, listener: (event: Record<string, unknown>) => void) {\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n }\n this.listeners[type].push(listener);\n }\n\n public removeEventListener(type: string, listener: (event: Record<string, unknown>) => void) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type].filter((l) => l !== listener);\n }\n\n dispatchEvent(event: Record<string, unknown>) {\n const type = event.type as string;\n if (this.listeners[type]) {\n for (const listener of this.listeners[type]) {\n listener(event);\n }\n }\n }\n\n public close() {\n this.readyState = this.CLOSED;\n if (this.req) {\n this.req.destroy();\n }\n }\n };\n}\n","import { createTRPCClient, httpLink, splitLink, httpSubscriptionLink } from '@trpc/client';\nimport type { UserRouter as AppRouter } from '../daemon/api/index.js';\nimport { getSocketPath } from '../shared/workspace.js';\nimport { createUnixSocketFetch } from '../shared/fetch.js';\nimport { createUnixSocketEventSource } from '../shared/event-source.js';\nimport fs from 'node:fs';\n\n/**\n * Creates a TRPC client that connects to the Clawmini daemon via a Unix socket.\n *\n * @param options - Configuration options for the client.\n * @returns A TRPC client instance for the AppRouter.\n */\nexport function getTRPCClient(options: { socketPath?: string } = {}) {\n const socketPath = options.socketPath ?? getSocketPath();\n\n if (!fs.existsSync(socketPath)) {\n throw new Error(`Daemon not running. Socket not found at ${socketPath}`);\n }\n\n const customFetch = createUnixSocketFetch(socketPath);\n const CustomEventSource = createUnixSocketEventSource(socketPath);\n\n return createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition(op) {\n return op.type === 'subscription';\n },\n true: httpSubscriptionLink({\n url: 'http://localhost',\n EventSource: CustomEventSource,\n }),\n false: httpLink({\n url: 'http://localhost',\n fetch: customFetch,\n }),\n }),\n ],\n });\n}\n","import fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { getClawminiDir } from '../shared/workspace.js';\n\nexport const DiscordStateSchema = z.object({\n lastSyncedMessageId: z.string().optional(),\n});\n\nexport type DiscordState = z.infer<typeof DiscordStateSchema>;\n\nexport function getDiscordStatePath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'adapters', 'discord', 'state.json');\n}\n\nexport async function readDiscordState(startDir = process.cwd()): Promise<DiscordState> {\n const statePath = getDiscordStatePath(startDir);\n try {\n const data = await fsPromises.readFile(statePath, 'utf-8');\n const parsed = JSON.parse(data);\n const result = DiscordStateSchema.safeParse(parsed);\n if (!result.success) {\n return { lastSyncedMessageId: undefined };\n }\n return result.data;\n } catch {\n // Return default state if file doesn't exist or is invalid JSON\n return { lastSyncedMessageId: undefined };\n }\n}\n\nexport async function writeDiscordState(\n state: DiscordState,\n startDir = process.cwd()\n): Promise<void> {\n const statePath = getDiscordStatePath(startDir);\n const dir = path.dirname(statePath);\n try {\n await fsPromises.mkdir(dir, { recursive: true });\n await fsPromises.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');\n } catch (err) {\n console.error(`Failed to write Discord state to ${statePath}:`, err);\n }\n}\n","import type { Client, MessageCreateOptions } from 'discord.js';\nimport path from 'node:path';\nimport type { getTRPCClient } from './client.js';\nimport { readDiscordState, writeDiscordState } from './state.js';\nimport type { ChatMessage, CommandLogMessage } from '../shared/chats.js';\nimport { getWorkspaceRoot } from '../shared/workspace.js';\n\nexport async function startDaemonToDiscordForwarder(\n client: Client,\n trpc: ReturnType<typeof getTRPCClient>,\n discordUserId: string,\n chatId: string = 'default',\n signal?: AbortSignal\n) {\n const state = await readDiscordState();\n let lastMessageId = state.lastSyncedMessageId;\n\n // 1. If we don't have a lastMessageId, get the most recent one from the daemon\n // to avoid sending the entire chat history on first run.\n if (!lastMessageId) {\n try {\n const messages = await trpc.getMessages.query({ chatId, limit: 1 });\n if (Array.isArray(messages) && messages.length > 0) {\n const lastMsg = messages[messages.length - 1];\n if (lastMsg) {\n lastMessageId = lastMsg.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId });\n }\n }\n } catch (error) {\n if (signal?.aborted) return;\n console.error('Failed to fetch initial messages from daemon:', error);\n }\n }\n\n console.log(\n `Starting daemon-to-discord forwarder for chat ${chatId}, lastMessageId: ${lastMessageId}`\n );\n\n let retryDelay = 1000;\n const maxRetryDelay = 30000;\n\n // 2. Start the observation loop using tRPC subscription\n return new Promise<void>((resolve) => {\n let subscription: { unsubscribe: () => void } | null = null;\n let messageQueue = Promise.resolve();\n\n const connect = () => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n subscription = trpc.waitForMessages.subscribe(\n { chatId, lastMessageId },\n {\n onData: (messages) => {\n retryDelay = 1000; // Reset retry delay on successful data\n\n if (!Array.isArray(messages) || messages.length === 0) {\n return;\n }\n\n // Queue processing to ensure sequential execution\n messageQueue = messageQueue.then(async () => {\n for (const rawMessage of messages) {\n if (signal?.aborted) break;\n\n const message = rawMessage as ChatMessage;\n\n // Only forward logs (agent responses, system messages)\n if (message.role === 'log') {\n const logMessage = message as CommandLogMessage;\n\n if (logMessage.level === 'verbose') {\n lastMessageId = logMessage.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n continue;\n }\n\n const hasContent = !!logMessage.content?.trim();\n const hasFiles = Array.isArray(logMessage.files) && logMessage.files.length > 0;\n\n // The daemon stores logMessage.files as paths relative to the WORKSPACE directory\n // (the directory containing .clawmini). We must resolve these against the current\n // workspace root so discord.js can successfully locate and read the files.\n let absoluteFiles: string[] = [];\n if (hasFiles) {\n const workspaceRoot = getWorkspaceRoot(process.cwd());\n absoluteFiles = logMessage.files!.map((f) => path.resolve(workspaceRoot, f));\n }\n\n if (!hasContent && !hasFiles) {\n lastMessageId = logMessage.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n continue;\n }\n\n try {\n const user = await client.users.fetch(discordUserId);\n const dm = await user.createDM();\n\n // Discord has a 2000 character limit for messages.\n if (hasContent && logMessage.content.length > 2000) {\n const chunks = chunkString(logMessage.content, 2000);\n for (let i = 0; i < chunks.length; i++) {\n if (signal?.aborted) break;\n const chunkOptions: MessageCreateOptions = { content: chunks[i] as string };\n if (i === chunks.length - 1 && hasFiles) {\n chunkOptions.files = absoluteFiles;\n }\n await dm.send(chunkOptions);\n }\n } else {\n const options: MessageCreateOptions = {};\n if (hasContent) {\n options.content = logMessage.content;\n }\n if (hasFiles) {\n options.files = absoluteFiles;\n }\n await dm.send(options);\n }\n } catch (error) {\n console.error(\n `Failed to send message to Discord user ${discordUserId}:`,\n error\n );\n // We don't advance lastMessageId if sending failed\n break;\n }\n }\n\n lastMessageId = message.id;\n await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(\n console.error\n );\n }\n });\n },\n onError: (error) => {\n console.error(\n `Error in daemon-to-discord forwarder subscription. Retrying in ${retryDelay}ms.`,\n error\n );\n subscription?.unsubscribe();\n subscription = null;\n\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n setTimeout(() => {\n retryDelay = Math.min(retryDelay * 2, maxRetryDelay);\n connect();\n }, retryDelay);\n },\n onComplete: () => {\n subscription = null;\n if (!signal?.aborted) {\n setTimeout(() => connect(), retryDelay);\n } else {\n resolve();\n }\n },\n }\n );\n };\n\n let typingSubscription: { unsubscribe: () => void } | null = null;\n let typingRetryDelay = 1000;\n\n const connectTyping = () => {\n if (signal?.aborted) {\n return;\n }\n\n typingSubscription = trpc.waitForTyping.subscribe(\n { chatId },\n {\n onData: async (event) => {\n typingRetryDelay = 1000; // Reset retry delay on successful data\n if (!event) return;\n\n try {\n const user = await client.users.fetch(discordUserId);\n const dm = await user.createDM();\n await dm.sendTyping();\n } catch (error) {\n console.error(\n `Failed to send typing indicator to Discord user ${discordUserId}:`,\n error\n );\n }\n },\n onError: (error) => {\n console.error(\n `Error in daemon-to-discord typing forwarder subscription. Retrying in ${typingRetryDelay}ms.`,\n error\n );\n typingSubscription?.unsubscribe();\n typingSubscription = null;\n\n if (signal?.aborted) {\n return;\n }\n\n setTimeout(() => {\n typingRetryDelay = Math.min(typingRetryDelay * 2, maxRetryDelay);\n connectTyping();\n }, typingRetryDelay);\n },\n onComplete: () => {\n typingSubscription = null;\n if (!signal?.aborted) {\n setTimeout(() => connectTyping(), typingRetryDelay);\n }\n },\n }\n );\n };\n\n connect();\n connectTyping();\n\n signal?.addEventListener('abort', () => {\n subscription?.unsubscribe();\n typingSubscription?.unsubscribe();\n resolve();\n });\n });\n}\n\nfunction chunkString(str: string, size: number): string[] {\n const chunks: string[] = [];\n const chars = Array.from(str);\n for (let i = 0; i < chars.length; i += size) {\n chunks.push(chars.slice(i, i + size).join(''));\n }\n return chunks;\n}\n","#!/usr/bin/env node\n\nimport { Client, Events, GatewayIntentBits, Partials } from 'discord.js';\nimport { readDiscordConfig, isAuthorized, initDiscordConfig } from './config.js';\nimport { getTRPCClient } from './client.js';\nimport { startDaemonToDiscordForwarder } from './forwarder.js';\nimport { getClawminiDir } from '../shared/workspace.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function main() {\n const args = process.argv.slice(2);\n\n if (args[0] === 'init') {\n await initDiscordConfig();\n return;\n }\n\n console.log('Discord Adapter starting...');\n\n const config = await readDiscordConfig();\n if (!config) {\n console.error(\n 'Failed to load Discord configuration. Please ensure .clawmini/adapters/discord/config.json exists and is valid.'\n );\n process.exit(1);\n }\n\n const trpc = getTRPCClient();\n\n const client = new Client({\n intents: [GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent],\n partials: [Partials.Channel],\n });\n\n client.once(Events.ClientReady, (readyClient) => {\n console.log(`Ready! Logged in as ${readyClient.user.tag}`);\n\n // Start forwarding from daemon to Discord\n startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, config.chatId).catch(\n (error) => {\n console.error('Error in daemon-to-discord forwarder:', error);\n }\n );\n });\n\n client.on(Events.MessageCreate, async (message) => {\n // Ignore messages from the bot itself\n if (message.author.id === client.user?.id) return;\n\n // Only handle DM messages\n if (message.guild) return;\n\n // Check if the user is authorized\n if (!isAuthorized(message.author.id, config.authorizedUserId)) {\n console.log(\n `Unauthorized message from ${message.author.tag} (${message.author.id}) ignored.`\n );\n return;\n }\n\n console.log(`Received message from ${message.author.tag}: ${message.content}`);\n\n const downloadedFiles: string[] = [];\n if (message.attachments.size > 0) {\n const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'discord');\n await fs.mkdir(tmpDir, { recursive: true });\n const maxSizeMB = config.maxAttachmentSizeMB ?? 25;\n const maxSizeBytes = maxSizeMB * 1024 * 1024;\n\n for (const attachment of message.attachments.values()) {\n if (attachment.size > maxSizeBytes) {\n console.warn(\n `Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`\n );\n await message.reply(\n `Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`\n );\n continue;\n }\n\n try {\n const res = await fetch(attachment.url);\n if (!res.ok) {\n console.error(`Failed to download attachment ${attachment.name}`);\n continue;\n }\n\n const uniqueName = `${Date.now()}-${attachment.name}`;\n const filePath = path.join(tmpDir, uniqueName);\n const arrayBuffer = await res.arrayBuffer();\n await fs.writeFile(filePath, Buffer.from(arrayBuffer));\n downloadedFiles.push(filePath);\n } catch (err) {\n console.error(`Error downloading attachment ${attachment.name}:`, err);\n }\n }\n }\n\n let finalContent = message.content;\n\n if (message.reference && message.reference.messageId) {\n try {\n const referencedMessage = await message.fetchReference();\n if (referencedMessage && referencedMessage.content) {\n const quotedContent = referencedMessage.content\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n');\n finalContent = `${quotedContent}\\n${finalContent}`;\n }\n } catch (err) {\n console.error('Failed to fetch referenced message:', err);\n }\n }\n\n console.log(`Forwarding message to daemon: ${finalContent}`);\n try {\n await trpc.sendMessage.mutate({\n type: 'send-message',\n client: 'cli',\n data: {\n message: finalContent,\n chatId: config.chatId,\n files: downloadedFiles.length > 0 ? downloadedFiles : undefined,\n adapter: 'discord',\n noWait: true,\n },\n });\n console.log('Message forwarded to daemon successfully.');\n } catch (error) {\n console.error('Failed to forward message to daemon:', error);\n }\n });\n\n try {\n await client.login(config.botToken);\n } catch (error) {\n console.error('Failed to login to Discord:', error);\n process.exit(1);\n }\n}\n\nimport { fileURLToPath } from 'node:url';\n\nconst isMainModule = (() => {\n try {\n if (typeof process === 'undefined' || !process.argv || process.argv.length < 2) return false;\n const argv1 = process.argv[1];\n if (!argv1) return false;\n const p1 = path.resolve(argv1);\n const p2 = path.resolve(fileURLToPath(import.meta.url));\n return p1 === p2;\n } catch {\n return false;\n }\n})();\n\nif (isMainModule) {\n main().catch((error) => {\n console.error('Unhandled error in Discord Adapter:', error);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { a as getAgent, b as writeChatSettings, d as isValidAgentId, f as listAgents, g as readSettings, l as getSocketPath, m as readChatSettings, n as deleteAgent, o as getClawminiDir, r as enableEnvironment, t as applyTemplateToAgent, x as writeSettings, y as writeAgentSettings } from "../workspace-CSgfo_2J.mjs";
3
- import { n as pathIsInsideDir } from "../fs-B5wW0oaH.mjs";
4
- import { a as getChatsDir, c as isValidChatId, i as deleteChat, l as listChats, o as getDefaultChatId, r as createChat, s as getMessages, t as DEFAULT_CHAT_ID, u as setDefaultChatId } from "../chats-DKgTeU7i.mjs";
5
- import { t as createUnixSocketFetch } from "../fetch-BjZVyU3Z.mjs";
6
- import { i as writeLiteScript, r as getLiteScriptContent, t as exportLiteToAllEnvironments } from "../lite-Dl7WXyaH.mjs";
2
+ import { S as pathIsInsideDir, _ as readSettings, a as getAgent, b as writeChatSettings, d as isValidAgentId, f as listAgents, l as getSocketPath, m as readChatSettings, n as deleteAgent, o as getClawminiDir, r as enableEnvironment, t as applyTemplateToAgent, x as writeSettings, y as writeAgentSettings } from "../workspace-DjoNjhW0.mjs";
3
+ import { a as DEFAULT_CHAT_ID, c as deleteChat, d as getMessages, f as isValidChatId, i as writeLiteScript, l as getChatsDir, m as setDefaultChatId, p as listChats, r as getLiteScriptContent, s as createChat, t as exportLiteToAllEnvironments, u as getDefaultChatId } from "../lite-oSYSvaOr.mjs";
4
+ import { t as createUnixSocketFetch } from "../fetch-Cn1XNyiO.mjs";
7
5
  import { Command } from "commander";
8
6
  import fs from "node:fs";
9
7
  import path from "node:path";
@@ -56,6 +54,7 @@ const initCmd = new Command("init").description("Initialize a new .clawmini sett
56
54
  "@clawmini/slash-new",
57
55
  "@clawmini/slash-stop",
58
56
  "@clawmini/slash-interrupt",
57
+ "@clawmini/slash-policies",
59
58
  "@clawmini/slash-command"
60
59
  ],
61
60
  api: true
@@ -97,7 +96,10 @@ async function getDaemonClient(options = {}) {
97
96
  logFile
98
97
  ]
99
98
  }).unref();
100
- await new Promise((resolve) => setTimeout(resolve, 500));
99
+ for (let i = 0; i < 50; i++) {
100
+ await new Promise((resolve) => setTimeout(resolve, 100));
101
+ if (fs.existsSync(socketPath)) break;
102
+ }
101
103
  if (!fs.existsSync(socketPath)) throw new Error("Failed to start daemon.");
102
104
  }
103
105
  return createTRPCClient({ links: [httpLink({