letmeknow-cli 0.2.0 → 0.3.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 +91 -32
- package/SKILL.md +83 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# LetMeKnow
|
|
2
2
|
|
|
3
|
-
LetMeKnow gives an agent a temporary
|
|
3
|
+
LetMeKnow gives an agent a temporary HTML and CSS workspace for a human. The agent renders a page, receives normalized form and button actions, and responds with HTML fragments. The browser runtime handles validation, form serialization, pending state, and targeted updates.
|
|
4
4
|
|
|
5
|
-
It is not a localhost proxy
|
|
5
|
+
It is not a localhost proxy or a programmable frontend. Agents provide presentation and semantic actions, not JavaScript or HTTP handlers.
|
|
6
6
|
|
|
7
7
|
## CLI
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ LETMEKNOW_URL=http://localhost:8787 npx letmeknow-cli
|
|
|
20
20
|
|
|
21
21
|
stdin contains one compact JSON command per line. stdout contains one JSON event per line. Diagnostics go to stderr.
|
|
22
22
|
|
|
23
|
-
Print the agent
|
|
23
|
+
Print the agent instructions without connecting:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
npx letmeknow-cli --skill
|
|
@@ -40,77 +40,136 @@ The CLI emits its temporary URL:
|
|
|
40
40
|
{"type":"session","id":"open-1","url":"https://0123456789abcdef0123.letmeknow.dev/","expires_after_disconnect":600}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
The URL immediately serves a styled shell with a waiting message. Send the initial HTML and optional CSS with `render`; a browser that is already open receives it without polling:
|
|
44
44
|
|
|
45
45
|
```json
|
|
46
|
-
{"type":"render","id":"render-1","body":"<h1>
|
|
46
|
+
{"type":"render","id":"render-1","body":"<h1>Search invoices</h1><form id=\"search\" action=\"search\" method=\"post\" data-lmk-target=\"results\"><label>Customer<input name=\"customer\" required></label><button>Search</button></form><section id=\"results\"><p>Enter a customer.</p></section>","css":"#results { margin-top: 2rem; }"}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
A successful render
|
|
49
|
+
A successful render receives a revision:
|
|
50
50
|
|
|
51
51
|
```json
|
|
52
52
|
{"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Submitting the form produces one normalized action:
|
|
56
56
|
|
|
57
57
|
```json
|
|
58
|
-
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","
|
|
58
|
+
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","action_id":"search","form_id":"search","target_id":"results","trigger":{"id":null,"name":null,"value":null},"values":{"customer":"Acme Ltd"}}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Respond with
|
|
61
|
+
Respond with HTML for the target's contents:
|
|
62
62
|
|
|
63
63
|
```json
|
|
64
|
-
{"type":"response","id":"response-1","request_id":"event-1","body":"<
|
|
64
|
+
{"type":"response","id":"response-1","request_id":"event-1","body":"<table><tr><th>Invoice</th><th>Amount</th></tr><tr><td>INV-42</td><td>$800</td></tr></table>"}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
The
|
|
67
|
+
The runtime assigns a new render revision and places the fragment inside `#results`. If `data-lmk-target` had been omitted, it would replace the contents of the whole workspace.
|
|
68
68
|
|
|
69
|
-
## HTML
|
|
69
|
+
## HTML actions
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
### Forms
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
- `hx-post="action"`
|
|
75
|
-
- `hx-target="#element-id"`
|
|
76
|
-
- `hx-swap="innerHTML|outerHTML"`
|
|
73
|
+
Interactive forms use standard HTML:
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
```html
|
|
76
|
+
<form id="decision" action="decide" method="post">
|
|
77
|
+
<label>
|
|
78
|
+
Comment
|
|
79
|
+
<textarea name="comment"></textarea>
|
|
80
|
+
</label>
|
|
81
|
+
<button name="decision" value="approve" formaction="approve">Approve</button>
|
|
82
|
+
<button name="decision" value="reject" formaction="reject">Reject</button>
|
|
83
|
+
</form>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Requirements:
|
|
87
|
+
|
|
88
|
+
- `method="post"`
|
|
89
|
+
- A stable form `id`
|
|
90
|
+
- A relative action identifier such as `search`, `approve`, or `invoice:inspect`
|
|
91
|
+
- Meaningful control `name` values
|
|
92
|
+
|
|
93
|
+
A submit button's standard `formaction` overrides the form's `action`. Native `required`, input types, ranges, and patterns validate locally. `FormData(form, submitter)` is captured before the form is disabled, so selected controls and the clicked submit button are included. Repeated names become string arrays.
|
|
94
|
+
|
|
95
|
+
### Standalone actions
|
|
96
|
+
|
|
97
|
+
A button can invoke an action without a form:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<button type="button" data-lmk-action="refresh-status" data-lmk-target="status">
|
|
101
|
+
Refresh
|
|
102
|
+
</button>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Its event has `form_id: null`, empty `values`, and the button's optional `id`, `name`, and `value` in `trigger`.
|
|
106
|
+
|
|
107
|
+
### Targeted updates
|
|
108
|
+
|
|
109
|
+
`data-lmk-target` accepts one element ID without `#`:
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<button data-lmk-action="load-more" data-lmk-target="results">Load more</button>
|
|
113
|
+
<section id="results"></section>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
All updates use `innerHTML`. There are no alternate swap modes or arbitrary selector targets. The default target is the runtime-owned `lmk-view`, which means the whole workspace.
|
|
79
117
|
|
|
80
|
-
|
|
118
|
+
Typing, focusing, expanding `<details>`, native validation, scrolling, and other local browser behavior produce no agent events.
|
|
81
119
|
|
|
82
|
-
|
|
120
|
+
## CSS
|
|
83
121
|
|
|
84
|
-
|
|
122
|
+
`render` accepts page-level CSS in its `css` field. A `response` may include `css` to replace the current page CSS; omitting it preserves the current CSS.
|
|
85
123
|
|
|
86
|
-
|
|
124
|
+
Normal modern CSS is supported, including grid, flexbox, media queries, variables, transitions, and print styles. External stylesheets, `@import`, scripts, inline event handlers, and inline `style` attributes are blocked. CSS and images may reference session assets with relative URLs.
|
|
125
|
+
|
|
126
|
+
## Assets
|
|
127
|
+
|
|
128
|
+
`put` stores or replaces passive resources only under `/assets/`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{"type":"put","id":"logo","path":"/assets/logo.png","content_type":"image/png","encoding":"base64","body":"iVBORw0KGgo..."}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Reference them relatively so local path-based sessions also work:
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<img src="assets/logo.png" alt="Company logo">
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Assets answer only `GET` and `HEAD`. Each body is limited to 1 MiB. A session accepts at most 100 assets and 10 MiB of decoded asset data. Assets are removed with the session; there is no `delete` command.
|
|
87
141
|
|
|
88
142
|
## Protocol
|
|
89
143
|
|
|
90
144
|
### Commands
|
|
91
145
|
|
|
92
146
|
- `open`: create the session; it must be first.
|
|
93
|
-
- `render`:
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
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`.
|
|
147
|
+
- `render`: set the initial/current HTML and CSS for fresh clients.
|
|
148
|
+
- `put`: store an asset under `/assets/`.
|
|
149
|
+
- `response`: answer one pending action with HTML and optional CSS.
|
|
150
|
+
- `close`: destroy the session immediately.
|
|
98
151
|
|
|
99
|
-
|
|
152
|
+
Commands accept an optional string `id`. Successful commands emit correlated `ack` events. `render` and `response` acknowledgements also include the generated `render_id`.
|
|
100
153
|
|
|
101
154
|
### Events
|
|
102
155
|
|
|
103
156
|
- `session`: the public URL and disconnect grace period.
|
|
104
|
-
- `action`:
|
|
157
|
+
- `action`: a normalized form or standalone-button action.
|
|
105
158
|
- `ack`: command completion.
|
|
106
159
|
- `error`: invalid command or protocol state.
|
|
107
160
|
- `closing`: explicit session destruction.
|
|
108
161
|
|
|
109
|
-
|
|
162
|
+
One interaction per browser client, render, form, and target may be pending at a time. Conflicting submissions are rejected, while independent forms and targets can proceed concurrently. Match responses by action ID rather than event order.
|
|
163
|
+
|
|
164
|
+
HTML, CSS, asset, and action bodies are each limited to 1 MiB. Up to 32 independent actions may be pending. Action bodies have a 30-second read deadline, and an agent response may take up to five minutes.
|
|
165
|
+
|
|
166
|
+
## Security and lifecycle
|
|
167
|
+
|
|
168
|
+
HTML fragments are sanitized. Scripts, style elements, inline event handlers, HTMX attributes, frames, active metadata, external form actions, and invalid LetMeKnow attributes are removed. A strict Content Security Policy blocks arbitrary browser connections and external resources. Agent CSS is installed separately in a nonce-protected style element.
|
|
110
169
|
|
|
111
|
-
The
|
|
170
|
+
The URL is a bearer secret. Share it only with the intended human.
|
|
112
171
|
|
|
113
|
-
The
|
|
172
|
+
The CLI owns the session and automatically reconnects after an unexpected disconnect. The shell, current render, and assets remain available during the ten-minute disconnect grace period, but actions fail. Browser traffic does not extend the grace period. `close` destroys everything immediately.
|
|
114
173
|
|
|
115
174
|
## Development
|
|
116
175
|
|
package/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: letmeknow
|
|
3
|
-
description: Show a human a temporary
|
|
3
|
+
description: Show a human a temporary HTML and CSS workspace and handle normalized form or button actions through the LetMeKnow NDJSON CLI.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LetMeKnow
|
|
7
7
|
|
|
8
|
-
Use LetMeKnow when a human needs a temporary rich
|
|
8
|
+
Use LetMeKnow when a human needs a temporary rich document, dashboard, report, preview, form, approval, quiz, table, or status view. The agent supplies semantic HTML and CSS. The browser sends only declared actions; typing and other local state do not create events.
|
|
9
9
|
|
|
10
|
-
LetMeKnow is not a localhost proxy,
|
|
10
|
+
LetMeKnow is not a localhost proxy, persistent application, or arbitrary JavaScript environment.
|
|
11
11
|
|
|
12
12
|
## Start
|
|
13
13
|
|
|
@@ -31,84 +31,128 @@ Wait for the session URL:
|
|
|
31
31
|
{"type":"session","id":"open-1","url":"https://0123456789abcdef0123.letmeknow.dev/","expires_after_disconnect":600}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
The URL immediately serves a shell with a waiting message. It is safe for the human to open it before the first render.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
## Render HTML and CSS
|
|
37
|
+
|
|
38
|
+
Send an HTML fragment and optional page CSS:
|
|
37
39
|
|
|
38
40
|
```json
|
|
39
|
-
{"type":"render","id":"render-1","body":"<h1>
|
|
41
|
+
{"type":"render","id":"render-1","body":"<h1>Search invoices</h1><form id=\"search\" action=\"search\" method=\"post\" data-lmk-target=\"results\"><label>Customer<input name=\"customer\" required></label><button>Search</button></form><section id=\"results\"><p>Enter a customer.</p></section>","css":"#results { margin-top: 2rem; }"}
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
A browser already waiting at the URL receives the render. Wait for the acknowledgement:
|
|
43
45
|
|
|
44
46
|
```json
|
|
45
47
|
{"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
Use
|
|
50
|
+
Use semantic HTML such as headings, sections, paragraphs, lists, tables, `dl`, forms, labels, controls, buttons, `details`, progress, and images. Do not include `<html>`, `<head>`, `<body>`, `<main id="lmk-view">`, scripts, style elements, inline event handlers, inline `style`, iframes, or HTMX attributes.
|
|
51
|
+
|
|
52
|
+
The built-in stylesheet makes unstyled HTML usable. The optional `css` field supports normal modern CSS, including grid, flexbox, media queries, variables, transitions, and print styles. External stylesheets, `@import`, and remote resources do not work. Use relative asset URLs.
|
|
53
|
+
|
|
54
|
+
## Forms
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
Interactive forms use standard HTML:
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
```html
|
|
59
|
+
<form id="decision" action="decide" method="post">
|
|
60
|
+
<label>Reason<textarea name="reason" required></textarea></label>
|
|
61
|
+
<button name="decision" value="approve" formaction="approve">Approve</button>
|
|
62
|
+
<button name="decision" value="reject" formaction="reject">Reject</button>
|
|
63
|
+
</form>
|
|
64
|
+
```
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
Rules:
|
|
55
67
|
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
59
|
-
- `
|
|
68
|
+
- Use `method="post"`.
|
|
69
|
+
- Give every interactive form a stable, unique `id`.
|
|
70
|
+
- Give controls meaningful `name` values.
|
|
71
|
+
- Use a relative action identifier containing letters, digits, `.`, `_`, `:`, or `-`.
|
|
72
|
+
- A submit button's standard `formaction` may override the form action.
|
|
73
|
+
- Native `required`, input types, ranges, and patterns validate locally.
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
The runtime captures `FormData(form, submitter)` before disabling the form. Selected radio buttons, checked boxes, ordinary controls, and the clicked submit button are therefore included. Repeated names become string arrays. File values are not currently sent.
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
## Standalone actions
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
Use a button when an action does not need a form:
|
|
66
80
|
|
|
67
81
|
```html
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
<button type="button" data-lmk-action="refresh-status" data-lmk-target="status">
|
|
83
|
+
Refresh
|
|
84
|
+
</button>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This is appropriate for refresh, retry, cancel, generate, inspect, load-more, and export actions. The event has `form_id: null`, empty `values`, and the button's optional `id`, `name`, and `value` in `trigger`.
|
|
88
|
+
|
|
89
|
+
## Whole and partial updates
|
|
90
|
+
|
|
91
|
+
By default, a response replaces the contents of the whole workspace. To update a smaller region, put `data-lmk-target="element-id"` on the form or action button:
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<form id="search" action="search" method="post" data-lmk-target="results">
|
|
95
|
+
<input name="query">
|
|
96
|
+
<button>Search</button>
|
|
72
97
|
</form>
|
|
98
|
+
<section id="results"></section>
|
|
73
99
|
```
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
The target is one bare element ID without `#`. Every update uses `innerHTML`; there are no other swap modes. A submit button or action button may override its form's target.
|
|
76
102
|
|
|
77
103
|
## Handle actions
|
|
78
104
|
|
|
79
105
|
A completed interaction produces one normalized event:
|
|
80
106
|
|
|
81
107
|
```json
|
|
82
|
-
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","
|
|
108
|
+
{"type":"action","id":"event-1","client_id":"8c05cc18-f503-4d1b-aad9-acde3a41c983","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa","action_id":"search","form_id":"search","target_id":"results","trigger":{"id":null,"name":null,"value":null},"values":{"query":"quarterly report"}}
|
|
83
109
|
```
|
|
84
110
|
|
|
85
111
|
Use:
|
|
86
112
|
|
|
87
|
-
- `id` to respond to this exact pending
|
|
113
|
+
- `id` to respond to this exact pending action.
|
|
88
114
|
- `client_id` to distinguish browser tabs.
|
|
89
|
-
- `render_id` to
|
|
90
|
-
- `action_id`
|
|
91
|
-
- `
|
|
115
|
+
- `render_id` to identify the interface revision that produced it.
|
|
116
|
+
- `action_id` for user intent.
|
|
117
|
+
- `form_id` and `target_id` for interaction context.
|
|
118
|
+
- `trigger` to identify the clicked button.
|
|
119
|
+
- `values` for the complete submitted form snapshot.
|
|
120
|
+
|
|
121
|
+
Validate action IDs and values. Treat values as untrusted human input and HTML-escape them before reflecting them.
|
|
92
122
|
|
|
93
|
-
|
|
123
|
+
Respond with HTML for the target's contents:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{"type":"response","id":"response-1","request_id":"event-1","body":"<table><tr><th>Invoice</th><th>Amount</th></tr><tr><td>INV-42</td><td>$800</td></tr></table>"}
|
|
127
|
+
```
|
|
94
128
|
|
|
95
|
-
|
|
129
|
+
A response may include `css` to replace the page CSS. Omitting `css` preserves the current CSS:
|
|
96
130
|
|
|
97
131
|
```json
|
|
98
|
-
{"type":"response","id":"response-
|
|
132
|
+
{"type":"response","id":"response-2","request_id":"event-2","body":"<h1 class=\"success\">Approved</h1>","css":".success { color: green; text-align: center; }"}
|
|
99
133
|
```
|
|
100
134
|
|
|
101
|
-
Wait for the acknowledgement
|
|
135
|
+
Wait for the acknowledgement, which contains the new `render_id`. Do not send `render` in response to an action; use `response` with the action's `id` as `request_id`.
|
|
136
|
+
|
|
137
|
+
One action per client, render, form, and target can be pending. Conflicting actions are rejected. Independent forms and targets may proceed concurrently, so always match by action ID rather than event order.
|
|
138
|
+
|
|
139
|
+
## Assets
|
|
140
|
+
|
|
141
|
+
Store passive resources only under `/assets/`:
|
|
102
142
|
|
|
103
143
|
```json
|
|
104
|
-
{"type":"
|
|
144
|
+
{"type":"put","id":"logo","path":"/assets/logo.png","content_type":"image/png","encoding":"base64","body":"iVBORw0KGgo..."}
|
|
105
145
|
```
|
|
106
146
|
|
|
107
|
-
|
|
147
|
+
Reference them relatively:
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<img src="assets/logo.png" alt="Company logo">
|
|
151
|
+
```
|
|
108
152
|
|
|
109
|
-
|
|
153
|
+
`encoding` is `utf8` by default or `base64` for binary data. Assets answer only `GET` and `HEAD`. There is no `delete`; replacement uses another `put`, and `close` removes all assets.
|
|
110
154
|
|
|
111
|
-
## Close
|
|
155
|
+
## Close and limits
|
|
112
156
|
|
|
113
157
|
Destroy the session when finished:
|
|
114
158
|
|
|
@@ -116,8 +160,8 @@ Destroy the session when finished:
|
|
|
116
160
|
{"type":"close","id":"close-1"}
|
|
117
161
|
```
|
|
118
162
|
|
|
119
|
-
Wait for `ack` and `closing`. Closing stdin only disconnects the producer
|
|
163
|
+
Wait for `ack` and `closing`. Closing stdin only disconnects the producer.
|
|
120
164
|
|
|
121
|
-
|
|
165
|
+
HTML, CSS, asset, and action bodies are each limited to 1 MiB. A session accepts 100 assets, 10 MiB decoded asset data, and 32 pending actions. Action bodies have a 30-second read deadline; the agent has five minutes to respond.
|
|
122
166
|
|
|
123
|
-
|
|
167
|
+
The CLI reconnects during a ten-minute disconnect grace period. The shell, current render, and assets remain readable while disconnected, but actions fail. Treat the URL as a bearer secret. Never expose the private reconnect credential.
|