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.
Files changed (3) hide show
  1. package/README.md +56 -54
  2. package/SKILL.md +67 -42
  3. 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. It is not a localhost proxy and does not read directories. A Node CLI carries newline-delimited JSON between stdin/stdout and a Cloudflare Durable Object over one persistent WebSocket.
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 skill file without opening a network connection:
23
+ Print the agent-facing instructions without opening a connection:
22
24
 
23
25
  ```bash
24
- npx letmeknow-cli --skill > SKILL.md
26
+ npx letmeknow-cli --skill
25
27
  ```
26
28
 
27
- Start a session:
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
- stdout returns the URL to share with the human:
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
- There is no initial bundle. Initial resources and later updates are the same `put` command:
43
+ Render the interface:
40
44
 
41
45
  ```json
42
- {"type":"put","id":"2","path":"/","content_type":"text/html; charset=utf-8","body":"<h1>Hello</h1>"}
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
- ## Protocol
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":"put","path":"/logo.png","content_type":"image/png","encoding":"base64","body":"iVBORw0KGgo..."}
52
+ {"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
62
53
  ```
63
54
 
64
- Delete a resource:
55
+ When the human clicks Approve, the CLI emits one normalized action:
65
56
 
66
57
  ```json
67
- {"type":"delete","id":"4","path":"/old.html"}
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
- Destroy everything immediately:
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":"close","id":"5"}
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
- ### Events: LetMeKnow to stdout
67
+ The response receives a new render revision in its acknowledgement.
77
68
 
78
- - `session` contains the public URL and disconnect grace period.
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
- Unknown paths are sent to the producer:
71
+ LetMeKnow renders standard semantic HTML with a built-in stylesheet. It recognizes only four HTMX-like attributes:
85
72
 
86
- ```json
87
- {"type":"request","id":"cf-request-id","method":"POST","path":"/answer","query":"step=2","headers":{"content-type":"application/x-www-form-urlencoded","hx-request":"true"},"encoding":"utf8","body":"answer=yes"}
88
- ```
73
+ - `hx-get="action"`
74
+ - `hx-post="action"`
75
+ - `hx-target="#element-id"`
76
+ - `hx-swap="innerHTML|outerHTML"`
89
77
 
90
- Answer with the request event's `id` as `request_id`:
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
- ```json
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
- Dynamic responses are not stored. Send a separate `put` to serve a path without involving the producer next time.
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
- Production sessions use isolated `*.letmeknow.dev` origins, so root-relative links, forms, and asset URLs work normally. Local sessions use `/s/<code>/`; use relative URLs there. Stored paths ignore the URL query when matching. Dynamic events contain pathname and query separately. Methods, forms, cookies, HTMX headers, SPA API requests, status, response headers, and text or binary bodies pass through generically. Browser request bodies use UTF-8 only for recognized textual media types; absent, unrecognized, or invalid UTF-8 bodies use base64. Request, response, and stored-resource bodies are bounded to 1 MiB and are non-streaming; a stalled dynamic browser request body times out after 30 seconds, and a producer response may take up to 5 minutes. Stored and dynamic responses default to `Cache-Control: no-store`; an explicit producer header overrides that default. A session accepts at most 100 stored resources (10 MiB decoded total) and 32 simultaneous dynamic requests.
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. The CLI receives an unguessable private reconnect credential in a private WebSocket message, keeps it off protocol stdout, and sends it as the WebSocket subprotocol on reconnect. The reconnect URL contains only the public session code. If the connection drops unexpectedly, it reconnects with that credential during the ten-minute grace period without requiring another `open` command; each connection attempt has a ten-second deadline, and a failed initial attempt exits nonzero while reconnect attempts continue within the grace period. Stored resources remain available for ten minutes, while unknown paths return `503`. Browser traffic does not extend the grace period. After the alarm fires all resources are deleted. A producer connection that never sends `open` is cleaned up after a short deadline. `close` deletes everything immediately.
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: Create a temporary browser surface for a human, serve static resources, and handle interactive HTTP requests through the LetMeKnow NDJSON CLI.
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 web page or interactive UI from an agent. It is not a localhost proxy and does not read files or directories.
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
- ## Start the CLI
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
- This connects to `https://letmeknow.dev` by default. To use another trusted deployment:
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
- Run the CLI as a long-lived child process. Write one compact JSON object per line to stdin, keep stdin open while the session is active, and read one JSON event per line from stdout. Read diagnostics from stderr separately. Do not mix stderr into the NDJSON stream.
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 `session` event and retain its URL:
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
- Store or replace exact pathnames with `put`. The following page submits to an unstored path so the agent can handle it dynamically:
34
+ ## Render semantic HTML
35
+
36
+ Send an HTML fragment with `render`:
41
37
 
42
38
  ```json
43
- {"type":"put","id":"put-1","path":"/","content_type":"text/html; charset=utf-8","body":"<!doctype html><form method=\"post\" action=\"answer\"><label>Answer <input name=\"answer\"></label><button>Send</button></form>"}
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
- Use relative links, form actions, and asset URLs so pages also work on local deployments, whose session URL includes a path prefix. Wait for `{"type":"ack","id":"put-1"}` before relying on the update. `status` defaults to `200`, `encoding` to `utf8`, and `body` to an empty string. `headers` accepts string values or string arrays; `content_type` overrides `Content-Type`. For binary content, set `encoding` to `base64`. Paths must start with `/` and must not contain a query. Query strings do not affect stored-path matching.
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":"delete","id":"delete-1","path":"/old.html"}
45
+ {"type":"ack","id":"render-1","render_id":"b87438c2-4f1c-44bd-9875-6cc64370b8aa"}
52
46
  ```
53
47
 
54
- ## Handle dynamic requests
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
- A browser request whose path is not stored produces a `request` event:
50
+ HTML bodies are limited to 1 MiB.
57
51
 
58
- ```json
59
- {"type":"request","id":"cf-request-id","method":"POST","path":"/answer","query":"step=2","headers":{"content-type":"application/x-www-form-urlencoded"},"encoding":"utf8","body":"answer=yes"}
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
- Reply using the event's `id` as `request_id`:
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":"response","id":"response-1","request_id":"cf-request-id","status":200,"content_type":"text/html; charset=utf-8","body":"<strong>Accepted</strong>"}
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
- Wait for the correlated `ack`. Dynamic responses are not stored; use `put` if later requests should receive the same response without involving the agent. Treat request bodies and headers as untrusted input, and escape or validate values before placing them in HTML, headers, or commands.
85
+ Use:
69
86
 
70
- The stdout event types are `session`, `request`, `ack`, `error`, and `closing`. Handle `error` rather than assuming a command succeeded.
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
- ## Close and lifecycle
93
+ Treat all values as untrusted human input. File input values are not currently sent.
73
94
 
74
- Destroy the session and all content immediately when finished:
95
+ Respond with the next HTML fragment:
75
96
 
76
97
  ```json
77
- {"type":"close","id":"close-1"}
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 `ack` and `closing`, then let the process exit. Closing stdin or terminating the CLI only disconnects the producer; it does not replace an explicit `close`.
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
- Limits per session: 1 MiB per stored, request, or response body; 100 stored resources; 10 MiB decoded stored content in total; and 32 simultaneous dynamic requests. Bodies are non-streaming. Default responses include `Cache-Control: no-store` unless explicitly overridden.
103
+ ```json
104
+ {"type":"ack","id":"response-1","render_id":"56abe335-bd83-4a79-8106-341579815fa0"}
105
+ ```
85
106
 
86
- ## Security
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
- Treat the session URL as a bearer secret: anyone who has it can access the surface and submit requests. Share it only with the intended human, and do not put it in source control, public logs, issue trackers, or unrelated output. The CLI also receives a separate private reconnect credential over the WebSocket, keeps it off protocol stdout, and uses it automatically. Never expose, persist, or ask the human for that credential. Use only a trusted `LETMEKNOW_URL`, because the deployment receives all page content and browser traffic.
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
- ## Recommended skill output
111
+ ## Close
91
112
 
92
- The minimal package interface should be:
113
+ Destroy the session when finished:
93
114
 
94
- ```bash
95
- npx letmeknow-cli --skill
115
+ ```json
116
+ {"type":"close","id":"close-1"}
96
117
  ```
97
118
 
98
- It should print this exact `SKILL.md` byte-for-byte to stdout and exit successfully without opening a network connection.
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.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A temporary interactive web surface for agents.",
5
- "files": ["bin", "SKILL.md"],
5
+ "files": [
6
+ "bin",
7
+ "SKILL.md"
8
+ ],
6
9
  "type": "module",
7
10
  "bin": {
8
11
  "letmeknow": "bin/letmeknow.js"