letmeknow-cli 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 +56 -54
- package/SKILL.md +67 -42
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# LetMeKnow
|
|
2
2
|
|
|
3
|
-
LetMeKnow gives an agent a temporary interactive web surface for a human.
|
|
3
|
+
LetMeKnow gives an agent a temporary, interactive web surface for a human. The agent sends semantic HTML, receives normalized user actions, and responds with HTML fragments. A constrained HTMX-like runtime handles forms, targets, swaps, pending state, and duplicate submissions in the browser.
|
|
4
|
+
|
|
5
|
+
It is not a localhost proxy, static file server, or arbitrary HTTP tunnel.
|
|
4
6
|
|
|
5
7
|
## CLI
|
|
6
8
|
|
|
@@ -18,88 +20,97 @@ LETMEKNOW_URL=http://localhost:8787 npx letmeknow-cli
|
|
|
18
20
|
|
|
19
21
|
stdin contains one compact JSON command per line. stdout contains one JSON event per line. Diagnostics go to stderr.
|
|
20
22
|
|
|
21
|
-
Print the agent-facing
|
|
23
|
+
Print the agent-facing instructions without opening a connection:
|
|
22
24
|
|
|
23
25
|
```bash
|
|
24
|
-
npx letmeknow-cli --skill
|
|
26
|
+
npx letmeknow-cli --skill
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
Open a session:
|
|
28
32
|
|
|
29
33
|
```json
|
|
30
|
-
{"type":"open","id":"1"}
|
|
34
|
+
{"type":"open","id":"open-1"}
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
The CLI emits its temporary URL:
|
|
34
38
|
|
|
35
39
|
```json
|
|
36
|
-
{"type":"session","id":"1","url":"https://0123456789abcdef0123.letmeknow.dev/","expires_after_disconnect":600}
|
|
40
|
+
{"type":"session","id":"open-1","url":"https://0123456789abcdef0123.letmeknow.dev/","expires_after_disconnect":600}
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Render the interface:
|
|
40
44
|
|
|
41
45
|
```json
|
|
42
|
-
{"type":"
|
|
43
|
-
{"type":"put","id":"3","path":"/app.js","content_type":"text/javascript","body":"document.body.append(' ready')"}
|
|
46
|
+
{"type":"render","id":"render-1","body":"<h1>Approve expense?</h1><dl><dt>Amount</dt><dd>$420</dd></dl><form id=\"decision\" hx-post=\"decide\"><label>Comment<textarea name=\"comment\" required></textarea></label><button hx-post=\"approve\" name=\"decision\" value=\"approve\">Approve</button><button hx-post=\"reject\" name=\"decision\" value=\"reject\">Reject</button></form>"}
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### Commands: stdin to LetMeKnow
|
|
49
|
-
|
|
50
|
-
- `open` creates the session and emits `session`.
|
|
51
|
-
- `put` stores or replaces an exact pathname.
|
|
52
|
-
- `delete` removes a stored pathname.
|
|
53
|
-
- `response` answers one pending browser request.
|
|
54
|
-
- `close` immediately destroys the session.
|
|
55
|
-
|
|
56
|
-
All commands accept an optional string `id`. Successful `put`, `delete`, `response`, and `close` commands emit a correlated `ack`.
|
|
57
|
-
|
|
58
|
-
A resource supports `status`, `headers`, `content_type`, `encoding`, and `body`. `status` defaults to `200`, `encoding` to `utf8`, and `body` to an empty string. Header values may be strings or string arrays; arrays preserve repeated headers such as `Set-Cookie`. `content_type` overrides any `Content-Type` header. Binary bodies use base64:
|
|
49
|
+
A successful render is assigned a revision:
|
|
59
50
|
|
|
60
51
|
```json
|
|
61
|
-
{"type":"
|
|
52
|
+
{"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
|
|
62
53
|
```
|
|
63
54
|
|
|
64
|
-
|
|
55
|
+
When the human clicks Approve, the CLI emits one normalized action:
|
|
65
56
|
|
|
66
57
|
```json
|
|
67
|
-
{"type":"
|
|
58
|
+
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","method":"POST","action_id":"approve","form_id":"decision","target":"#lmk-view","swap":"innerHTML","trigger":{"id":null,"name":"decision","value":"approve"},"values":{"comment":"Looks good","decision":"approve"}}
|
|
68
59
|
```
|
|
69
60
|
|
|
70
|
-
|
|
61
|
+
Respond with an HTML fragment. The default target is the whole view, so this replaces its contents:
|
|
71
62
|
|
|
72
63
|
```json
|
|
73
|
-
{"type":"
|
|
64
|
+
{"type":"response","id":"response-1","request_id":"event-1","body":"<h1>Approved</h1><p>The expense was approved.</p>"}
|
|
74
65
|
```
|
|
75
66
|
|
|
76
|
-
|
|
67
|
+
The response receives a new render revision in its acknowledgement.
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
- `request` describes a browser request that did not match a stored resource.
|
|
80
|
-
- `ack` confirms a command.
|
|
81
|
-
- `error` reports a command or protocol error.
|
|
82
|
-
- `closing` reports explicit destruction.
|
|
69
|
+
## HTML interaction profile
|
|
83
70
|
|
|
84
|
-
|
|
71
|
+
LetMeKnow renders standard semantic HTML with a built-in stylesheet. It recognizes only four HTMX-like attributes:
|
|
85
72
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
- `hx-get="action"`
|
|
74
|
+
- `hx-post="action"`
|
|
75
|
+
- `hx-target="#element-id"`
|
|
76
|
+
- `hx-swap="innerHTML|outerHTML"`
|
|
89
77
|
|
|
90
|
-
|
|
78
|
+
Action names contain letters, digits, `.`, `_`, `:`, or `-`. They are identifiers, not URLs. The default target is `#lmk-view` and the default swap is `innerHTML`.
|
|
91
79
|
|
|
92
|
-
|
|
93
|
-
{"type":"response","id":"6","request_id":"cf-request-id","status":200,"headers":{"content-type":"text/html; charset=utf-8","hx-trigger":"answered"},"body":"<strong>Accepted</strong>"}
|
|
94
|
-
```
|
|
80
|
+
Use ordinary HTML forms and named controls. On submission, LetMeKnow sends the complete form values and identifies the exact submit button through `trigger`. Repeated field names become string arrays. File values are not currently sent.
|
|
95
81
|
|
|
96
|
-
|
|
82
|
+
The browser handles typing, focus, native validation, and selection locally. These do not produce protocol traffic. Only a completed `hx-get` or `hx-post` interaction reaches the agent.
|
|
97
83
|
|
|
98
|
-
|
|
84
|
+
One interaction per browser client and form may be pending at a time. The runtime disables that form while waiting, and the service rejects duplicate submissions. Different forms and browser tabs remain independent. Every action includes the browser-tab `client_id` and the `render_id` that produced it.
|
|
85
|
+
|
|
86
|
+
Scripts, inline handlers, external styles, frames, external form submissions, and arbitrary browser connections are blocked by the page's Content Security Policy. Other HTMX attributes and extensions have no effect.
|
|
87
|
+
|
|
88
|
+
## Protocol
|
|
89
|
+
|
|
90
|
+
### Commands
|
|
91
|
+
|
|
92
|
+
- `open`: create the session; it must be first.
|
|
93
|
+
- `render`: replace the HTML shown on a fresh visit or refresh.
|
|
94
|
+
- `response`: answer one pending action with an HTML fragment.
|
|
95
|
+
- `close`: immediately destroy the session.
|
|
96
|
+
|
|
97
|
+
Commands accept an optional string `id`. Successful `render`, `response`, and `close` commands emit a correlated `ack`. `render` and `response` acknowledgements also contain the assigned `render_id`.
|
|
98
|
+
|
|
99
|
+
HTML bodies are UTF-8 strings limited to 1 MiB. Up to 32 interactions may be pending across independent clients and forms. Action request bodies have a 30-second read deadline, and an agent response may take up to five minutes.
|
|
100
|
+
|
|
101
|
+
### Events
|
|
102
|
+
|
|
103
|
+
- `session`: the public URL and disconnect grace period.
|
|
104
|
+
- `action`: one normalized user interaction.
|
|
105
|
+
- `ack`: command completion.
|
|
106
|
+
- `error`: invalid command or protocol state.
|
|
107
|
+
- `closing`: explicit session destruction.
|
|
99
108
|
|
|
100
109
|
## Lifecycle
|
|
101
110
|
|
|
102
|
-
The active CLI connection owns the session.
|
|
111
|
+
The active CLI connection owns the session. It receives a private reconnect credential and automatically reconnects after an unexpected disconnect. The credential never appears on protocol stdout.
|
|
112
|
+
|
|
113
|
+
The rendered interface remains visible during the ten-minute disconnect grace period, but interactions return `503`. Browser traffic does not extend the grace period. `close` destroys everything immediately.
|
|
103
114
|
|
|
104
115
|
## Development
|
|
105
116
|
|
|
@@ -107,14 +118,5 @@ The active CLI connection owns the session. The CLI receives an unguessable priv
|
|
|
107
118
|
npm install
|
|
108
119
|
npm run dev
|
|
109
120
|
npm test
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
The Worker uses one Durable Object per session. The object owns the producer WebSocket, stored resources, pending browser requests, and disconnect alarm. No D1 or R2 binding is required.
|
|
113
|
-
|
|
114
|
-
Deploy with:
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
121
|
npm run deploy
|
|
118
122
|
```
|
|
119
|
-
|
|
120
|
-
Production subdomain URLs require a proxied `*.letmeknow.dev` DNS record and a Worker route for `*.letmeknow.dev/*` in Cloudflare. The apex `letmeknow.dev` remains the control endpoint.
|
package/SKILL.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: letmeknow
|
|
3
|
-
description:
|
|
3
|
+
description: Show a human a temporary semantic HTML interface and handle normalized form actions through the LetMeKnow NDJSON CLI.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LetMeKnow
|
|
7
7
|
|
|
8
|
-
Use LetMeKnow when a human needs a temporary
|
|
8
|
+
Use LetMeKnow when a human needs a temporary rich interface, form, choice, approval, table, or status view. The agent sends HTML and receives only completed semantic actions; typing and other local browser state do not create events.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
LetMeKnow is not a localhost proxy, file server, or arbitrary HTTP tunnel. Do not send JavaScript, CSS, raw HTTP handlers, or application code.
|
|
11
|
+
|
|
12
|
+
## Start
|
|
11
13
|
|
|
12
14
|
Node.js 22 or newer is required.
|
|
13
15
|
|
|
@@ -15,84 +17,107 @@ Node.js 22 or newer is required.
|
|
|
15
17
|
npx letmeknow-cli
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
LETMEKNOW_URL=http://localhost:8787 npx letmeknow-cli
|
|
22
|
-
```
|
|
20
|
+
Run it as a long-lived child process. Write one compact JSON object per line to stdin, keep stdin open, and read one JSON event per line from stdout. Read stderr separately.
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## Open and publish static content
|
|
27
|
-
|
|
28
|
-
`open` must be the first command. String `id` values are optional; use them to correlate results.
|
|
22
|
+
Open first:
|
|
29
23
|
|
|
30
24
|
```json
|
|
31
25
|
{"type":"open","id":"open-1"}
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
Wait for the
|
|
28
|
+
Wait for the session URL:
|
|
35
29
|
|
|
36
30
|
```json
|
|
37
31
|
{"type":"session","id":"open-1","url":"https://0123456789abcdef0123.letmeknow.dev/","expires_after_disconnect":600}
|
|
38
32
|
```
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
## Render semantic HTML
|
|
35
|
+
|
|
36
|
+
Send an HTML fragment with `render`:
|
|
41
37
|
|
|
42
38
|
```json
|
|
43
|
-
{"type":"
|
|
39
|
+
{"type":"render","id":"render-1","body":"<h1>Approve expense?</h1><dl><dt>Amount</dt><dd>$420</dd></dl><form id=\"decision\" hx-post=\"decide\"><label>Comment<textarea name=\"comment\" required></textarea></label><button hx-post=\"approve\" name=\"decision\" value=\"approve\">Approve</button><button hx-post=\"reject\" name=\"decision\" value=\"reject\">Reject</button></form>"}
|
|
44
40
|
```
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Remove static content with:
|
|
42
|
+
Wait for its acknowledgement before directing the human to the URL:
|
|
49
43
|
|
|
50
44
|
```json
|
|
51
|
-
{"type":"
|
|
45
|
+
{"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
|
|
48
|
+
Use ordinary semantic HTML: headings, paragraphs, lists, tables, `dl`, forms, labels, inputs, buttons, progress, and images. A built-in stylesheet makes these presentable. Do not include `<html>`, `<head>`, `<body>`, `<main id="lmk-view">`, scripts, inline event handlers, stylesheets, iframes, or external forms.
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
HTML bodies are limited to 1 MiB.
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
## Declare interactions
|
|
53
|
+
|
|
54
|
+
Only these HTMX-like attributes are supported:
|
|
55
|
+
|
|
56
|
+
- `hx-get="action"`
|
|
57
|
+
- `hx-post="action"`
|
|
58
|
+
- `hx-target="#element-id"`
|
|
59
|
+
- `hx-swap="innerHTML"` or `hx-swap="outerHTML"`
|
|
60
|
+
|
|
61
|
+
Action values are identifiers, not URLs. Use letters, digits, `.`, `_`, `:`, and `-`. Other HTMX attributes and extensions do nothing.
|
|
62
|
+
|
|
63
|
+
The default target is `#lmk-view`, the runtime-owned whole-view container. The default swap is `innerHTML`. Usually omit both and respond with the complete next interface fragment.
|
|
64
|
+
|
|
65
|
+
Put `hx-get` or `hx-post` on a form, button, or link. A submit button's attribute overrides its form's attribute, allowing distinct actions:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<form id="decision" hx-post="decide">
|
|
69
|
+
<label>Reason<textarea name="reason" required></textarea></label>
|
|
70
|
+
<button hx-post="approve" name="decision" value="approve">Approve</button>
|
|
71
|
+
<button hx-post="reject" name="decision" value="reject">Reject</button>
|
|
72
|
+
</form>
|
|
60
73
|
```
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
Always give forms stable, unique `id` values and controls meaningful `name` values. Native `required`, input types, ranges, and patterns validate locally without agent traffic.
|
|
76
|
+
|
|
77
|
+
## Handle actions
|
|
78
|
+
|
|
79
|
+
A completed interaction produces one normalized event:
|
|
63
80
|
|
|
64
81
|
```json
|
|
65
|
-
{"type":"
|
|
82
|
+
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","method":"POST","action_id":"approve","form_id":"decision","target":"#lmk-view","swap":"innerHTML","trigger":{"id":null,"name":"decision","value":"approve"},"values":{"reason":"Looks correct","decision":"approve"}}
|
|
66
83
|
```
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
Use:
|
|
69
86
|
|
|
70
|
-
|
|
87
|
+
- `id` to respond to this exact pending interaction.
|
|
88
|
+
- `client_id` to distinguish browser tabs.
|
|
89
|
+
- `render_id` to know which interface produced the action.
|
|
90
|
+
- `action_id` and `trigger` to identify user intent.
|
|
91
|
+
- `values` for the complete form snapshot. Repeated names become string arrays.
|
|
71
92
|
|
|
72
|
-
|
|
93
|
+
Treat all values as untrusted human input. File input values are not currently sent.
|
|
73
94
|
|
|
74
|
-
|
|
95
|
+
Respond with the next HTML fragment:
|
|
75
96
|
|
|
76
97
|
```json
|
|
77
|
-
{"type":"
|
|
98
|
+
{"type":"response","id":"response-1","request_id":"event-1","body":"<h1>Approved</h1><p>The expense was approved.</p>"}
|
|
78
99
|
```
|
|
79
100
|
|
|
80
|
-
Wait for
|
|
81
|
-
|
|
82
|
-
The CLI owns the session and automatically reconnects after an unexpected disconnect. Each connection attempt has a 10-second deadline. Reconnection is possible only during the 10-minute disconnect grace period; stored resources remain available then, but unstored paths return `503`, and browser traffic does not extend the grace period. An initial producer connection must send `open` within 30 seconds. Dynamic request bodies have a 30-second read deadline, and a `response` may take at most 5 minutes.
|
|
101
|
+
Wait for the acknowledgement. It includes the new `render_id`:
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
```json
|
|
104
|
+
{"type":"ack","id":"response-1","render_id":"56abe335-bd83-4a79-8106-341579815fa0"}
|
|
105
|
+
```
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
Do not send a separate `render` in response to an action. `response` both resolves the pending browser request and updates that browser's interface. Use `render` only to replace the interface served on a fresh visit or refresh.
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
One action per client and form can be pending. The browser disables that form while waiting, and duplicate submissions are rejected. Different forms and clients can have independent pending actions. Respond using request IDs rather than assuming event order.
|
|
89
110
|
|
|
90
|
-
##
|
|
111
|
+
## Close
|
|
91
112
|
|
|
92
|
-
|
|
113
|
+
Destroy the session when finished:
|
|
93
114
|
|
|
94
|
-
```
|
|
95
|
-
|
|
115
|
+
```json
|
|
116
|
+
{"type":"close","id":"close-1"}
|
|
96
117
|
```
|
|
97
118
|
|
|
98
|
-
|
|
119
|
+
Wait for `ack` and `closing`. Closing stdin only disconnects the producer and does not replace explicit `close`.
|
|
120
|
+
|
|
121
|
+
The CLI automatically reconnects during a ten-minute disconnect grace period. The interface remains readable while disconnected, but interactions fail. Never expose the private reconnect credential. Treat the public session URL as a bearer secret and share it only with the intended human.
|
|
122
|
+
|
|
123
|
+
Limits: 1 MiB per HTML or action body, 32 pending interactions, 30 seconds to read an action body, and five minutes for the agent to respond.
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letmeknow-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A temporary interactive web surface for agents.",
|
|
5
|
-
"files": [
|
|
5
|
+
"files": [
|
|
6
|
+
"bin",
|
|
7
|
+
"SKILL.md"
|
|
8
|
+
],
|
|
6
9
|
"type": "module",
|
|
7
10
|
"bin": {
|
|
8
11
|
"letmeknow": "bin/letmeknow.js"
|