@vibelord/lanes 1.2.0 → 1.5.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/package.json +1 -1
- package/payload/lanes/README.md +7 -5
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +1091 -76
- package/payload/lanes/container/hooks/grok-cli-hooks.json +8 -0
- package/payload/lanes/container/hooks/grok-hooks.json +12 -0
- package/payload/lanes/container/hooks/kimi-hooks.toml +49 -0
- package/payload/lanes/container/lane +55 -8
- package/payload/lanes/container/lane-mcp +120 -22
- package/payload/lanes/container/lane-signal +86 -23
- package/payload/lanes/container/lane_core.py +524 -36
- package/payload/lanes/container/throttle-pulse +456 -0
- package/payload/lanes/docs/ARCHITECTURE.md +357 -34
- package/payload/lanes/docs/DESIGN.md +13 -3
- package/payload/lanes/docs/INSTALL.md +47 -10
- package/payload/lanes/docs/RUNBOOK.md +761 -12
- package/payload/lanes/docs/USERGUIDE.md +421 -46
- package/payload/lanes/holders.lock +5 -0
- package/payload/lanes/install/enable-docs.sh +78 -0
- package/payload/lanes/install/host-install.sh +39 -5
- package/payload/lanes/libexec/agent_state.py +604 -12
- package/payload/lanes/libexec/docs_store.py +995 -0
- package/payload/lanes/libexec/docsd.py +2010 -0
- package/payload/lanes/libexec/economy_poll.py +6 -2
- package/payload/lanes/libexec/harness_grok.py +742 -0
- package/payload/lanes/libexec/lanes_lib.py +143 -20
- package/payload/lanes/libexec/mdlite.py +575 -0
- package/payload/lanes/libexec/members.py +5 -0
- package/payload/lanes/libexec/notify.py +65 -5
- package/payload/lanes/libexec/sidebar.py +626 -25
- package/payload/lanes/libexec/statusd.py +530 -32
- package/payload/lanes/libexec/tokens.py +42 -9
- package/payload/lanes/libexec/update.py +171 -26
- package/payload/lanes/libexec/uploads.py +538 -0
- package/payload/lanes/libexec/users.py +31 -3
- package/payload/lanes/site/site.example.conf +4 -0
- package/payload/lanes/systemd/lanes-docsd.service +29 -0
- package/payload/lanes/web/Caddyfile +24 -1
- package/payload/lanes/web/README.md +86 -8
- package/payload/lanes/web/browser.js +2 -4
- package/payload/lanes/web/docs.css +52 -0
- package/payload/lanes/web/docs.js +36 -0
- package/payload/lanes/web/mobile.js +3 -2
- package/payload/lanes/web/upload.css +17 -0
- package/payload/lanes/web/upload.js +84 -0
- package/payload/manifest.json +5 -3
|
@@ -31,10 +31,17 @@ NEEDS_YOU set. The six-word `state` is never read: the agent status is the only
|
|
|
31
31
|
device_active).
|
|
32
32
|
* Payload: `<lane name>: <agent task> — <status>` and, on a second line, the client command that opens the lane
|
|
33
33
|
(`lanes-launch lane <id>`). No client registers a URL scheme for it, so the push carries a tappable action only
|
|
34
|
-
when `action_url` (a template over `{lane}`) is configured.
|
|
34
|
+
when `action_url` (a template over `{lane}`, and `{doc}` for a `published` event — `action_url()`) is configured.
|
|
35
35
|
* A failed send is one journal line (lane id and the failure class — never the topic or the bearer) and is not
|
|
36
36
|
retried; it never stops the service.
|
|
37
37
|
|
|
38
|
+
Published documents (plan W10 W4): the docs service's publish pick-up (`lanes-docsd`, docsd.Pickup), not this
|
|
39
|
+
service, calls `notify_published` once a document an agent published from a lane is stored — the same config, the
|
|
40
|
+
same desk-presence rule (device_active) and the same `send`, at most one push per lane per minute (docsd's
|
|
41
|
+
PUBLISHED_PUSH_S), best effort. Its payload is `{title: 'Published', message: '<lane name> published a document (<kind>,
|
|
42
|
+
<size>)\n/lanes-docs/d/<id>', priority: 2}` and, when `action_url` is set, the tap target with `{doc}` resolved —
|
|
43
|
+
never the document's title, name or content: the topic is the host's, and the document may be for one login only.
|
|
44
|
+
|
|
38
45
|
Enrolment requests (W8 A5, LNS-44): the same service also watches `STATE/enrol-requests/` (lanes-enrold's store;
|
|
39
46
|
read through `enrold.load_requests`, so a request announced is one `lanes enrol list` shows). A request that is
|
|
40
47
|
pending — not approved, denied or past `expires_at` — and has not been announced yet yields ONE push:
|
|
@@ -55,7 +62,9 @@ Config `~/.config/lanes/notify.conf` (`LANES_NOTIFY_CONF` overrides the path), `
|
|
|
55
62
|
`url` (ntfy server, http/https, required), `topic` (`[A-Za-z0-9_-]{1,64}`, required), `token_file` (a file holding
|
|
56
63
|
an ntfy access token, optional — sent as a bearer, never logged; an unreadable one disables sending rather than
|
|
57
64
|
sending unauthenticated), `quiet_when_device_active_s` (default 120; 0 never suppresses), `action_url` (optional
|
|
58
|
-
template, e.g. `https://lanes.example/?lane={lane}
|
|
65
|
+
template, e.g. `https://lanes.example/?lane={lane}`; plan W10 W7 adds `{doc}`, the page `/lanes-docs/d/<id>` of the
|
|
66
|
+
document a `published` push names — empty for a needs-you push; the docs service sends those, W4). Without a valid
|
|
67
|
+
`url` and `topic` the service only tracks
|
|
59
68
|
transitions and sends nothing. The config is re-read when notify.conf OR its `token_file` changes (inode, size,
|
|
60
69
|
mtime or ctime, so a token written, rotated, removed or chmod-ed after notify.conf counts), and on every tick while
|
|
61
70
|
sending is disabled, so a token that becomes readable switches the pushes on within one interval.
|
|
@@ -76,6 +85,7 @@ import urllib.request
|
|
|
76
85
|
from pathlib import Path
|
|
77
86
|
|
|
78
87
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
88
|
+
import docs_store # noqa: E402
|
|
79
89
|
import enrold as E # noqa: E402
|
|
80
90
|
import lanes_lib as L # noqa: E402
|
|
81
91
|
import tokens # noqa: E402
|
|
@@ -95,6 +105,8 @@ PRESENCE = '*.pid' # clients/ files only
|
|
|
95
105
|
SEEN = '.seen.json' # a sidebar's opened-lane record; desk devices only
|
|
96
106
|
# (a .pid of any age, devices/<device>.json not mobile)
|
|
97
107
|
# users/<login>.json written by a desk device counts too
|
|
108
|
+
PUBLISHED_TITLE = 'Published' # a document published from a lane (notify_published)
|
|
109
|
+
PUBLISHED_PRIORITY = 2
|
|
98
110
|
ENROL_DIR = 'enrol-requests' # STATE/<this>/<id>.json: lanes-enrold's requests
|
|
99
111
|
ENROL_SEEN = 'notify.enrol-seen.json' # STATE/<this>: the request ids already announced
|
|
100
112
|
ENROL_TITLE = 'Enrol request'
|
|
@@ -275,6 +287,18 @@ def device_active(clients_dir, window, now, devices_dir=None, users_dir=None, me
|
|
|
275
287
|
return any(within(at, window) for _login, at in marks)
|
|
276
288
|
|
|
277
289
|
|
|
290
|
+
def action_url(template, lane_id, doc_id=None):
|
|
291
|
+
"""notify.conf's `action_url` template made a push's tap target -> the URL, or '' without a template. `{lane}` is
|
|
292
|
+
the lane id; `{doc}` (plan W10 W7) the page of the document a `published` event is about, `/lanes-docs/d/<id>`
|
|
293
|
+
(docs_store.URL; a docs_store id, else nothing), and nothing for an event without one (a needs-you push) — so
|
|
294
|
+
`https://<host>:8443{doc}` opens the document. Both are percent-quoted."""
|
|
295
|
+
if not template:
|
|
296
|
+
return ''
|
|
297
|
+
doc = docs_store.URL.format(doc_id) if isinstance(doc_id, str) and docs_store.ID_RE.match(doc_id) else ''
|
|
298
|
+
return (template.replace('{lane}', urllib.parse.quote(lane_id, safe=''))
|
|
299
|
+
.replace('{doc}', urllib.parse.quote(doc, safe='/')))
|
|
300
|
+
|
|
301
|
+
|
|
278
302
|
def _note(lane_id, status, agent, names, config):
|
|
279
303
|
label = LABELS.get(status, status)
|
|
280
304
|
name = _text(names.get(lane_id) or lane_id)
|
|
@@ -282,14 +306,50 @@ def _note(lane_id, status, agent, names, config):
|
|
|
282
306
|
first = _line(f'{name}: {task} — {label}' if task else f'{name} — {label}')
|
|
283
307
|
note = {'lane': lane_id, 'status': status, 'title': label,
|
|
284
308
|
'message': f'{first}\nlanes-launch lane {lane_id}', 'priority': PRIORITY.get(status, 3)}
|
|
285
|
-
|
|
286
|
-
if
|
|
287
|
-
url = template.replace('{lane}', urllib.parse.quote(lane_id, safe=''))
|
|
309
|
+
url = action_url(config.get('action_url') or '', lane_id)
|
|
310
|
+
if url:
|
|
288
311
|
note['click'] = url
|
|
289
312
|
note['actions'] = [{'action': 'view', 'label': 'Open lane', 'url': url}]
|
|
290
313
|
return note
|
|
291
314
|
|
|
292
315
|
|
|
316
|
+
def _size(size):
|
|
317
|
+
if size < 1024:
|
|
318
|
+
return f'{size} B'
|
|
319
|
+
return f'{size / 1024:.1f} KiB' if size < 1024 * 1024 else f'{size / (1024 * 1024):.1f} MiB'
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def published_note(lane_id, meta, names, config):
|
|
323
|
+
"""The push for a document stored from lane `lane_id` (`meta` its docs_store meta): the lane's name, the kind and
|
|
324
|
+
the size, and the document's page (module docstring) — never its title, name or content."""
|
|
325
|
+
name = _text(names.get(lane_id) or lane_id)
|
|
326
|
+
first = _line(f"{name} published a document ({_text(meta.get('kind'))}, {_size(int(meta.get('bytes') or 0))})")
|
|
327
|
+
note = {'lane': lane_id, 'status': 'published', 'doc': meta['id'], 'title': PUBLISHED_TITLE,
|
|
328
|
+
'message': f"{first}\n{docs_store.URL.format(meta['id'])}", 'priority': PUBLISHED_PRIORITY}
|
|
329
|
+
url = action_url(config.get('action_url') or '', lane_id, meta['id'])
|
|
330
|
+
if url:
|
|
331
|
+
note['click'] = url
|
|
332
|
+
note['actions'] = [{'action': 'view', 'label': 'Open document', 'url': url}]
|
|
333
|
+
return note
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def notify_published(lane_id, meta, names=None, conf_path=None, state=None, now=None, opener=None):
|
|
337
|
+
"""Send the `published` push for a stored document (the docs service's pick-up calls it, W4) -> the note sent, or
|
|
338
|
+
None: sending disabled (no valid notify.conf at `conf_path`, else $LANES_NOTIFY_CONF, else the default), somebody
|
|
339
|
+
at a desk (device_active over `state`, default L.STATE), or the send failed (one journal line). Never raises for a
|
|
340
|
+
failed send; the caller rate-limits."""
|
|
341
|
+
config = read_config(conf_path or os.environ.get('LANES_NOTIFY_CONF') or DEFAULT_CONF)
|
|
342
|
+
if not config['enabled']:
|
|
343
|
+
return None
|
|
344
|
+
state = Path(state) if state is not None else L.STATE
|
|
345
|
+
now = time.time() if now is None else now
|
|
346
|
+
window = int(config.get('quiet_when_device_active_s', DEFAULT_QUIET) or 0)
|
|
347
|
+
if device_active(state / 'clients', window, now, state / 'devices'):
|
|
348
|
+
return None
|
|
349
|
+
note = published_note(lane_id, meta, names or {}, config)
|
|
350
|
+
return note if send(note, config, opener) else None
|
|
351
|
+
|
|
352
|
+
|
|
293
353
|
def notify_changes(prev_status, new_status, config, names=None, now=None, clients_dir=None, devices_dir=None):
|
|
294
354
|
"""The notifications for lanes whose agent status ENTERED needs-you between two status.json documents.
|
|
295
355
|
`prev_status` None = the first read: seed only, nothing is news. Empty when a desk device is present."""
|