agentclick 0.1.0 → 0.2.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
@@ -1,145 +1,98 @@
1
1
  # AgentClick
2
2
 
3
- **Rich web UI for AI agent interactions click to edit, human-in-the-loop, preference learning.**
3
+ AI agents fail silently and take irreversible actions. AgentClick puts a human review step between your agent and the world.
4
4
 
5
- [![GitHub stars](https://img.shields.io/github/stars/agentlayer-io/AgentClick?style=flat-square)](https://github.com/agentlayer-io/AgentClick/stargazers)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
7
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/agentlayer-io/AgentClick/pulls)
5
+ [![npm version](https://img.shields.io/npm/v/agentclick)](https://www.npmjs.com/package/agentclick)
6
+ [![license](https://img.shields.io/npm/l/agentclick)](LICENSE)
7
+ [![npm downloads](https://img.shields.io/npm/dm/agentclick)](https://www.npmjs.com/package/agentclick)
8
8
 
9
9
  ---
10
10
 
11
- ## The Problem
11
+ ## Why AgentClick
12
12
 
13
- Every OpenClaw user interacts with their agent through text chat (WhatsApp / Telegram). Text is a degraded interface:
14
-
15
- - You can't click a paragraph and say "rewrite this"
16
- - You can't drag steps to reorder them
17
- - Every correction requires typing out instructions again
18
- - The agent never remembers your preferences
19
-
20
- ## The Solution
21
-
22
- When your agent finishes a task that needs your input, it opens a browser page — a purpose-built interaction UI. You click, choose, drag. No typing.
23
-
24
- ```
25
- Agent finishes email draft
26
- → Browser opens automatically
27
- → You click paragraph → choose: Delete / Rewrite / Keep
28
- → You confirm
29
- → Agent continues, remembers your choices for next time
30
- ```
31
-
32
- Every interaction teaches the agent your preferences. The more you use it, the less you need to explain.
33
-
34
- ---
35
-
36
- ## Current Status
37
-
38
- This project is already in a working prototype stage and supports multiple review flows end-to-end.
39
-
40
- Implemented:
41
-
42
- - Email review UI (legacy single-column + v2 inbox + draft layout)
43
- - Action approval UI (approve/reject + note)
44
- - Code/shell command review UI
45
- - Session history homepage with recent sessions
46
- - SQLite session persistence (`~/.openclaw/clawui-sessions.db`)
47
- - Long-poll wait endpoint for agent integration (`/api/sessions/:id/wait`)
48
- - Preference learning from paragraph deletions (writes rules to `MEMORY.md`)
49
- - Keyboard shortcuts (`Cmd/Ctrl+Enter` submit, `Escape` handling)
50
- - Browser auto-open on session creation
13
+ - **Not just approve/deny** -- edit the email subject, change the command, modify the payload before it sends.
14
+ - **Preference learning** -- delete a paragraph and tell AgentClick why. It writes the rule to disk so your agent never makes the same mistake again.
15
+ - **Framework-agnostic** -- works with OpenAI, Anthropic, LangChain, or any HTTP-capable agent. Just POST and long-poll.
51
16
 
52
17
  ---
53
18
 
54
19
  ## Quick Start
55
20
 
56
- ```bash
57
- git clone https://github.com/agentlayer-io/AgentClick.git
58
- cd AgentClick
59
- npm install
60
- npm run dev # dev mode: API on 3001, Vite UI on 5173
61
- ```
62
-
63
- ## Install (Global CLI)
64
-
65
21
  ```bash
66
22
  npm install -g agentclick
67
23
  agentclick
68
24
  ```
69
25
 
70
- This starts the AgentClick server on `http://localhost:3001` and serves the built web UI on the same port.
71
-
72
- Optional server config (defaults shown in `.env.example`):
26
+ Then test it with a mock session:
73
27
 
74
28
  ```bash
75
- PORT=3001
76
- OPENCLAW_WEBHOOK=http://localhost:18789/hooks/agent
29
+ curl -X POST http://localhost:3001/api/review \
30
+ -H "Content-Type: application/json" \
31
+ -d '{"type":"code_review","sessionKey":"test","payload":{"command":"rm -rf /tmp/old-cache","cwd":"/home/user","explanation":"Clean up stale cache directory","risk":"medium"}}'
77
32
  ```
78
33
 
79
- Create a local `.env` in the project root to override these values during development (server auto-loads it via `dotenv`).
34
+ A browser tab opens automatically. Review, approve or reject, and close the tab.
80
35
 
81
- Production-style local run (single port after build):
36
+ ---
82
37
 
83
- ```bash
84
- npm run build
85
- npm start # serves API + built web UI on localhost:3001
86
- ```
38
+ ## How It Works
87
39
 
88
- Deployment notes (reverse proxy, Docker/OpenClaw host mapping, env vars):
40
+ 1. **Agent POSTs structured data** to `http://localhost:3001/api/review` with a session key.
41
+ 2. **User reviews, edits, and approves** in the browser -- paragraph-level delete/rewrite for emails, approve/reject for commands and actions.
42
+ 3. **Agent receives the result** via long-poll (`GET /api/sessions/:id/wait`) and continues execution.
89
43
 
90
- - See `docs/deployment.md`
44
+ No WebSockets. No framework plugins. One HTTP endpoint in, one HTTP endpoint out.
91
45
 
92
- Copy the skill to your OpenClaw workspace:
46
+ ---
93
47
 
94
- ```bash
95
- cp -r skills/clawui-email ~/.openclaw/skills/
96
- ```
48
+ ## Comparison
97
49
 
98
- Restart OpenClaw. Ask it to write an email the review page will open automatically.
50
+ | Feature | AgentClick | AgentGate | LangGraph interrupt() | Vercel AI SDK |
51
+ |---|---|---|---|---|
52
+ | Pre-built review UI | Yes | No | No | No |
53
+ | Edit before approve | Yes | No | No | No |
54
+ | Preference learning | Yes | No | No | No |
55
+ | Framework-agnostic | Yes | Yes | LangGraph only | Vercel only |
56
+ | Self-hosted | Yes | Yes | Yes | Cloud |
99
57
 
100
58
  ---
101
59
 
102
- ## Project Structure
60
+ ## Session Types
103
61
 
104
- ```
105
- AgentClick/
106
- ├── packages/
107
- │ ├── server/ # Node.js + Express — receives agent data, handles callbacks
108
- │ └── web/ # React + Vite + Tailwind — the interaction UI
109
- ├── skills/
110
- │ └── clawui-email/
111
- │ └── SKILL.md # OpenClaw skill definition
112
- └── docs/
113
- └── research.md # Market & technical research notes
114
- ```
62
+ - **email_review** -- two-column inbox and draft editor. Users can delete paragraphs with reasons, request rewrites, toggle intent suggestions, and confirm or regenerate.
63
+ - **code_review** -- displays the shell command, working directory, affected files as a collapsible tree, and risk level. Approve or reject with an optional note.
64
+ - **action_approval** -- generic high-risk action gate. Shows action description, detail, and risk badge. Approve or reject with an optional note.
115
65
 
116
66
  ---
117
67
 
118
- ## Roadmap
68
+ ## API
119
69
 
120
- - [x] **M0** Email draft review (click to delete/rewrite paragraphs)
121
- - [x] **M1** — Preference learning (auto-save rules to MEMORY.md)
122
- - [x] **M2 (partial)** Agent integration loop (`/review` create session + `/wait` long-poll + callback)
123
- - [x] **Next** Unified serving (single port; no separate Vite port in production)
124
- - [x] **Next** Production serve polish (deployment docs / environment examples)
125
- - [ ] **Next** npm global package (`agentclick`)
126
- - [ ] **Next** — Remote mode UX + link delivery polish
127
- - [ ] **Later** — Agent task visualization (Mission Control view)
128
- - [ ] **Later** — Multi-framework support (beyond OpenClaw)
70
+ | Method | Endpoint | Description |
71
+ |---|---|---|
72
+ | POST | `/api/review` | Agent creates a review session. Returns `{ sessionId, url }`. Browser opens automatically. |
73
+ | GET | `/api/sessions/:id` | Fetch session data (payload, status, result). |
74
+ | GET | `/api/sessions/:id/wait` | Long-poll. Blocks up to 5 minutes until the user completes the review. |
75
+ | POST | `/api/sessions/:id/complete` | UI submits the user's decision. Triggers preference learning and agent callback. |
129
76
 
130
77
  ---
131
78
 
132
- ## Why Not ClawX?
79
+ ## Development
133
80
 
134
- [ClawX](https://github.com/ValueCell-ai/ClawX) is a desktop app for *managing* OpenClaw (installing skills, configuring channels, running the gateway). AgentClick is for *working with* your agent — reviewing its output, making decisions, teaching it your preferences. They're complementary.
135
-
136
- ---
81
+ ```bash
82
+ git clone https://github.com/agentlayer-io/AgentClick.git
83
+ cd AgentClick
84
+ npm install
85
+ npm run dev
86
+ ```
137
87
 
138
- ## Contributing
88
+ Server runs on `http://localhost:3001`, UI on `http://localhost:5173`.
139
89
 
140
- This is an early-stage open source project. All contributions welcome — UI components, new interaction patterns, OpenClaw integration improvements, documentation.
90
+ For production-style single-port serving:
141
91
 
142
- Open an issue to discuss before submitting large PRs.
92
+ ```bash
93
+ npm run build
94
+ npm start
95
+ ```
143
96
 
144
97
  ---
145
98
 
@@ -4,11 +4,39 @@ import { existsSync } from 'node:fs'
4
4
  import { dirname, join } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
  import { spawnSync } from 'node:child_process'
7
+ import net from 'node:net'
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url)
9
10
  const rootDir = dirname(dirname(__filename))
10
11
  const webDistIndex = join(rootDir, 'packages', 'web', 'dist', 'index.html')
11
12
  const serverDistEntry = join(rootDir, 'packages', 'server', 'dist', 'index.js')
13
+ const args = process.argv.slice(2)
14
+
15
+ function printHelp() {
16
+ console.log(`AgentClick CLI
17
+
18
+ Usage:
19
+ agentclick
20
+ agentclick --help
21
+
22
+ Options:
23
+ --help, -h Show this help message
24
+ `)
25
+ }
26
+
27
+ function parseArgs(argv) {
28
+ for (let i = 0; i < argv.length; i += 1) {
29
+ const arg = argv[i]
30
+ if (arg === '--help' || arg === '-h') {
31
+ printHelp()
32
+ process.exit(0)
33
+ }
34
+ console.error(`[agentclick] Unknown argument: ${arg}`)
35
+ console.error('[agentclick] Run "agentclick --help" for usage.')
36
+ process.exit(1)
37
+ }
38
+ return {}
39
+ }
12
40
 
13
41
  function run(command, args) {
14
42
  const result = spawnSync(command, args, {
@@ -25,6 +53,8 @@ function run(command, args) {
25
53
  }
26
54
  }
27
55
 
56
+ parseArgs(args)
57
+
28
58
  if (!existsSync(webDistIndex) || !existsSync(serverDistEntry)) {
29
59
  console.log('[agentclick] Build artifacts not found, running npm run build...')
30
60
  const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
@@ -36,4 +66,48 @@ if (!existsSync(serverDistEntry)) {
36
66
  process.exit(1)
37
67
  }
38
68
 
39
- run(process.execPath, [serverDistEntry])
69
+ async function canListen(port) {
70
+ return await new Promise(resolve => {
71
+ const server = net.createServer()
72
+ server.once('error', err => {
73
+ if ((err).code === 'EADDRINUSE') {
74
+ resolve(false)
75
+ return
76
+ }
77
+ console.error(`[agentclick] Port check failed for ${port}: ${err.message}`)
78
+ process.exit(1)
79
+ })
80
+ server.once('listening', () => {
81
+ server.close(() => resolve(true))
82
+ })
83
+ server.listen(port)
84
+ })
85
+ }
86
+
87
+ async function resolvePort() {
88
+ if (process.env.PORT) return process.env.PORT
89
+
90
+ let port = 3001
91
+ while (true) {
92
+ const available = await canListen(port)
93
+ if (available) return String(port)
94
+ console.log(`[agentclick] Port ${port} in use, trying ${port + 1}...`)
95
+ port += 1
96
+ }
97
+ }
98
+
99
+ const childEnv = { ...process.env }
100
+ childEnv.PORT = await resolvePort()
101
+
102
+ const result = spawnSync(process.execPath, [serverDistEntry], {
103
+ cwd: rootDir,
104
+ stdio: 'inherit',
105
+ env: childEnv,
106
+ })
107
+ if (result.error) {
108
+ console.error('[agentclick] Failed to start server:', result.error.message)
109
+ process.exit(1)
110
+ }
111
+ if (typeof result.status === 'number' && result.status !== 0) {
112
+ process.exit(result.status)
113
+ }
package/package.json CHANGED
@@ -1,6 +1,25 @@
1
1
  {
2
2
  "name": "agentclick",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
+ "description": "Human-in-the-loop approval UI for AI agents. Review, edit, and approve agent actions in your browser before they execute.",
5
+ "keywords": [
6
+ "ai-agent",
7
+ "human-in-the-loop",
8
+ "approval",
9
+ "hitl",
10
+ "llm",
11
+ "langchain",
12
+ "openai",
13
+ "agent-ui",
14
+ "agentic",
15
+ "ai-safety",
16
+ "human-oversight"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/agentlayer-io/AgentClick.git"
21
+ },
22
+ "homepage": "https://github.com/agentlayer-io/AgentClick",
4
23
  "bin": {
5
24
  "agentclick": "./bin/agentclick.mjs"
6
25
  },
@@ -11,8 +30,7 @@
11
30
  "packages/server/package.json",
12
31
  "packages/web/dist/",
13
32
  "packages/web/package.json",
14
- "README.md",
15
- "AGENTS.md"
33
+ "README.md"
16
34
  ],
17
35
  "workspaces": [
18
36
  "packages/*"
@@ -74,6 +74,35 @@ app.get('/api/sessions/:id', (req, res) => {
74
74
  return res.status(404).json({ error: 'Session not found' });
75
75
  res.json(session);
76
76
  });
77
+ // Mock summary endpoint for inbox items (UI integration first)
78
+ app.get('/api/sessions/:id/summary', (req, res) => {
79
+ const session = getSession(req.params.id);
80
+ if (!session)
81
+ return res.status(404).json({ error: 'Session not found' });
82
+ const payload = session.payload;
83
+ const inbox = payload?.inbox ?? [];
84
+ const emailId = req.query.emailId;
85
+ const email = inbox.find(item => item.id === emailId);
86
+ if (!email) {
87
+ return res.status(404).json({ error: 'Email not found in session' });
88
+ }
89
+ const preview = String(email.preview ?? '');
90
+ const summaryText = preview.length > 0
91
+ ? `This email appears to be about: ${preview}`
92
+ : 'No preview text is available for this email yet.';
93
+ const from = String(email.from ?? 'Unknown sender');
94
+ const category = String(email.category ?? 'Unknown');
95
+ res.json({
96
+ emailId,
97
+ summary: summaryText,
98
+ bullets: [
99
+ `From: ${from}`,
100
+ `Category: ${category}`,
101
+ 'Source: mock summary endpoint (replace with agent summary later)',
102
+ ],
103
+ confidence: 'mock',
104
+ });
105
+ });
77
106
  // Long-poll: agent calls this and blocks until user completes review (up to 5 min)
78
107
  app.get('/api/sessions/:id/wait', async (req, res) => {
79
108
  const TIMEOUT_MS = 5 * 60 * 1000;
@@ -154,6 +183,10 @@ function buildActionSummary(result) {
154
183
  if (result.regenerate) {
155
184
  lines.push('- User requested full regeneration.');
156
185
  }
186
+ const intents = (result.selectedIntents ?? []);
187
+ if (intents.length > 0) {
188
+ lines.push(`- Intent decisions: ${intents.map(i => `${i.id} → ${i.accepted ? 'accepted' : 'rejected'}`).join(', ')}`);
189
+ }
157
190
  return lines.join('\n');
158
191
  }
159
192
  if (SHOULD_SERVE_BUILT_WEB) {
@@ -83,6 +83,40 @@ app.get('/api/sessions/:id', (req, res) => {
83
83
  res.json(session)
84
84
  })
85
85
 
86
+ // Mock summary endpoint for inbox items (UI integration first)
87
+ app.get('/api/sessions/:id/summary', (req, res) => {
88
+ const session = getSession(req.params.id)
89
+ if (!session) return res.status(404).json({ error: 'Session not found' })
90
+
91
+ const payload = session.payload as Record<string, unknown> | undefined
92
+ const inbox = (payload?.inbox as Array<Record<string, unknown>> | undefined) ?? []
93
+ const emailId = req.query.emailId as string | undefined
94
+ const email = inbox.find(item => item.id === emailId)
95
+
96
+ if (!email) {
97
+ return res.status(404).json({ error: 'Email not found in session' })
98
+ }
99
+
100
+ const preview = String(email.preview ?? '')
101
+ const summaryText = preview.length > 0
102
+ ? `This email appears to be about: ${preview}`
103
+ : 'No preview text is available for this email yet.'
104
+
105
+ const from = String(email.from ?? 'Unknown sender')
106
+ const category = String(email.category ?? 'Unknown')
107
+
108
+ res.json({
109
+ emailId,
110
+ summary: summaryText,
111
+ bullets: [
112
+ `From: ${from}`,
113
+ `Category: ${category}`,
114
+ 'Source: mock summary endpoint (replace with agent summary later)',
115
+ ],
116
+ confidence: 'mock',
117
+ })
118
+ })
119
+
86
120
  // Long-poll: agent calls this and blocks until user completes review (up to 5 min)
87
121
  app.get('/api/sessions/:id/wait', async (req, res) => {
88
122
  const TIMEOUT_MS = 5 * 60 * 1000
@@ -172,6 +206,11 @@ function buildActionSummary(result: Record<string, unknown>): string {
172
206
  lines.push('- User requested full regeneration.')
173
207
  }
174
208
 
209
+ const intents = (result.selectedIntents ?? []) as Array<{ id: string; accepted: boolean }>
210
+ if (intents.length > 0) {
211
+ lines.push(`- Intent decisions: ${intents.map(i => `${i.id} → ${i.accepted ? 'accepted' : 'rejected'}`).join(', ')}`)
212
+ }
213
+
175
214
  return lines.join('\n')
176
215
  }
177
216