@method-ai-hq/sdk 0.0.0-stage → 0.14.3
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/LICENSE +21 -0
- package/README.md +23 -2
- package/dist/packages/contracts/src/identity.d.ts +16 -0
- package/dist/packages/contracts/src/identity.js +41 -0
- package/dist/packages/contracts/src/method-package.d.ts +71 -0
- package/dist/packages/contracts/src/method-package.js +22 -0
- package/dist/packages/method-document/src/conversion-report.d.ts +138 -0
- package/dist/packages/method-document/src/conversion-report.js +23 -0
- package/dist/packages/sdk/examples/daily-briefing/README.md +31 -0
- package/dist/packages/sdk/examples/daily-briefing/TASK.md +3 -0
- package/dist/packages/sdk/examples/daily-briefing/approved-report.md +138 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_artifacts.py +39 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_check_inputs.py +47 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_files.py +31 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_manifest.py +23 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_markdown.py +67 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_progress.py +18 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_render.py +71 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_sessions.py +37 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_times.py +158 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_validation.py +101 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_website.py +215 -0
- package/dist/packages/sdk/examples/daily-briefing/checks.json +19 -0
- package/dist/packages/sdk/examples/daily-briefing/daily-briefing.method +158 -0
- package/dist/packages/sdk/examples/daily-briefing/inputs.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/package-lock.json +10 -0
- package/dist/packages/sdk/examples/daily-briefing/package.json +4 -0
- package/dist/packages/sdk/examples/daily-briefing/pyproject.toml +8 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.css +35 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.js +42 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/context.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/documents/document-001.md +219 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/media.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/metadata/documents.json +39 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0001.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0002.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0003.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0004.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0005.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0006.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0007.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0008.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0009.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0010.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0011.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0012.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0013.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0014.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0015.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0016.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0017.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0018.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0019.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0020.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0021.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0022.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0023.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0024.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0025.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0026.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0027.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0028.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/sessions.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/timeline.jsonl +28 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/untimed.jsonl +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-output.zip +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-report.md +51 -0
- package/dist/packages/sdk/examples/daily-briefing/uv.lock +128 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked-LICENSE.md +44 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked.mjs +77 -0
- package/dist/packages/sdk/examples/files.json +126 -0
- package/dist/packages/sdk/examples/message-routing/README.md +23 -0
- package/dist/packages/sdk/examples/message-routing/TASK.md +5 -0
- package/dist/packages/sdk/examples/message-routing/choose-destination.mjs +10 -0
- package/dist/packages/sdk/examples/message-routing/create-ticket.mjs +15 -0
- package/dist/packages/sdk/examples/message-routing/inputs.json +1 -0
- package/dist/packages/sdk/examples/message-routing/message-routing.method +45 -0
- package/dist/packages/sdk/examples/message-routing/result.fixture.json +6 -0
- package/dist/packages/sdk/examples/message-routing/routing.mjs +29 -0
- package/dist/packages/sdk/examples/message-routing/routing.test.mjs +27 -0
- package/dist/packages/sdk/examples/message-routing/ticket.method +88 -0
- package/dist/packages/sdk/examples/message-routing/ticket.test.mjs +64 -0
- package/dist/packages/sdk/examples/notes-summary/README.md +23 -0
- package/dist/packages/sdk/examples/notes-summary/TASK.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/case.json +36 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/examples.json +16 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/files/0 +10 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/recording.json +195 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-05-standup.md +4 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-07-planning.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/notes-summary.method +54 -0
- package/dist/packages/sdk/examples/notes-summary/out/.keep +0 -0
- package/dist/packages/sdk/examples/notes-summary/read-notes.mjs +4 -0
- package/dist/packages/sdk/examples/notes-summary/save.mjs +7 -0
- package/dist/packages/sdk/examples/outbound-management/README.md +119 -0
- package/dist/packages/sdk/examples/outbound-management/TASK.md +5 -0
- package/dist/packages/sdk/examples/outbound-management/check_plan.py +16 -0
- package/dist/packages/sdk/examples/outbound-management/check_sources.py +14 -0
- package/dist/packages/sdk/examples/outbound-management/crm.py +153 -0
- package/dist/packages/sdk/examples/outbound-management/crm_test.py +141 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/README.md +15 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/crm.json +54 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-1/messages.json +29 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-2/messages.json +38 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/observations.json +12 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/plan.json +69 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/receipt.json +1 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/state.json +99 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/tasks.md +16 -0
- package/dist/packages/sdk/examples/outbound-management/inputs.json +4 -0
- package/dist/packages/sdk/examples/outbound-management/outbound.method +277 -0
- package/dist/packages/sdk/examples/outbound-management/read_crm.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/save_day.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/starter/crm.json +11 -0
- package/dist/packages/sdk/examples/social-briefing/README.md +41 -0
- package/dist/packages/sdk/examples/social-briefing/TASK.md +5 -0
- package/dist/packages/sdk/examples/social-briefing/inputs.json +5 -0
- package/dist/packages/sdk/examples/social-briefing/result.fixture.json +14 -0
- package/dist/packages/sdk/examples/social-briefing/social-briefing.method +110 -0
- package/dist/packages/sdk/examples/support-triage/README.md +77 -0
- package/dist/packages/sdk/examples/support-triage/TASK.md +3 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/case.json +40 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/recording.json +460 -0
- package/dist/packages/sdk/examples/support-triage/fake-helpdesk.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/helpdesk.mjs +33 -0
- package/dist/packages/sdk/examples/support-triage/inputs.json +1 -0
- package/dist/packages/sdk/examples/support-triage/read-ticket.mjs +8 -0
- package/dist/packages/sdk/examples/support-triage/route.mjs +4 -0
- package/dist/packages/sdk/examples/support-triage/sample-tickets.json +5 -0
- package/dist/packages/sdk/examples/support-triage/set-ticket.mjs +9 -0
- package/dist/packages/sdk/examples/support-triage/support-triage.method +165 -0
- package/dist/packages/sdk/examples/support-triage/triage.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/triage.test.mjs +68 -0
- package/dist/packages/sdk/observers/mail.delivery/fetch.mjs +87 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce-by-recipient.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delayed.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delivered.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/empty.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/other-message.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/judge.mjs +13 -0
- package/dist/packages/sdk/observers/mail.delivery/observer.json +14 -0
- package/dist/packages/sdk/package.json +232 -0
- package/dist/packages/sdk/src/authoring-example.d.ts +91 -0
- package/dist/packages/sdk/src/authoring-example.js +50 -0
- package/dist/packages/sdk/src/authoring-instructions.d.ts +20 -0
- package/dist/packages/sdk/src/authoring-instructions.js +151 -0
- package/dist/packages/sdk/src/authoring-prompt.d.ts +2 -0
- package/dist/packages/sdk/src/authoring-prompt.js +8 -0
- package/dist/packages/sdk/src/authoring.d.ts +10 -0
- package/dist/packages/sdk/src/authoring.js +287 -0
- package/dist/packages/sdk/src/bindings.d.ts +4 -0
- package/dist/packages/sdk/src/bindings.js +124 -0
- package/dist/packages/sdk/src/bridge.d.ts +2 -0
- package/dist/packages/sdk/src/bridge.js +23 -0
- package/dist/packages/sdk/src/browser-runtime/pyproject.toml +5 -0
- package/dist/packages/sdk/src/browser-runtime/tools.json +189 -0
- package/dist/packages/sdk/src/browser-runtime/uv.lock +3894 -0
- package/dist/packages/sdk/src/browser-service.py +127 -0
- package/dist/packages/sdk/src/browser.d.ts +38 -0
- package/dist/packages/sdk/src/browser.js +203 -0
- package/dist/packages/sdk/src/capabilities.d.ts +13 -0
- package/dist/packages/sdk/src/capabilities.js +68 -0
- package/dist/packages/sdk/src/classification-client.d.ts +4 -0
- package/dist/packages/sdk/src/classification-client.js +27 -0
- package/dist/packages/sdk/src/cloud.d.ts +23 -0
- package/dist/packages/sdk/src/cloud.js +43 -0
- package/dist/packages/sdk/src/computer-settings.d.ts +28 -0
- package/dist/packages/sdk/src/computer-settings.js +57 -0
- package/dist/packages/sdk/src/connect.d.ts +47 -0
- package/dist/packages/sdk/src/connect.js +203 -0
- package/dist/packages/sdk/src/current-inspection.d.ts +3 -0
- package/dist/packages/sdk/src/current-inspection.js +146 -0
- package/dist/packages/sdk/src/current-runtime.d.ts +14 -0
- package/dist/packages/sdk/src/current-runtime.js +167 -0
- package/dist/packages/sdk/src/doctor.d.ts +17 -0
- package/dist/packages/sdk/src/doctor.js +68 -0
- package/dist/packages/sdk/src/executable.d.ts +2 -0
- package/dist/packages/sdk/src/executable.js +22 -0
- package/dist/packages/sdk/src/explain-effects.d.ts +32 -0
- package/dist/packages/sdk/src/explain-effects.js +192 -0
- package/dist/packages/sdk/src/explain.d.ts +115 -0
- package/dist/packages/sdk/src/explain.js +441 -0
- package/dist/packages/sdk/src/files.d.ts +9 -0
- package/dist/packages/sdk/src/files.js +50 -0
- package/dist/packages/sdk/src/guard-keys.d.ts +5 -0
- package/dist/packages/sdk/src/guard-keys.js +42 -0
- package/dist/packages/sdk/src/hosted-models.d.ts +49 -0
- package/dist/packages/sdk/src/hosted-models.js +81 -0
- package/dist/packages/sdk/src/improve.d.ts +55 -0
- package/dist/packages/sdk/src/improve.js +159 -0
- package/dist/packages/sdk/src/index.d.ts +13 -0
- package/dist/packages/sdk/src/index.js +13 -0
- package/dist/packages/sdk/src/inspect.d.ts +6 -0
- package/dist/packages/sdk/src/inspect.js +17 -0
- package/dist/packages/sdk/src/link.d.ts +12 -0
- package/dist/packages/sdk/src/link.js +53 -0
- package/dist/packages/sdk/src/local-cli.d.ts +24 -0
- package/dist/packages/sdk/src/local-cli.js +146 -0
- package/dist/packages/sdk/src/local-setup.d.ts +15 -0
- package/dist/packages/sdk/src/local-setup.js +42 -0
- package/dist/packages/sdk/src/method-client.d.ts +30 -0
- package/dist/packages/sdk/src/method-client.js +250 -0
- package/dist/packages/sdk/src/method-files.d.ts +29 -0
- package/dist/packages/sdk/src/method-files.js +93 -0
- package/dist/packages/sdk/src/method-help.d.ts +19 -0
- package/dist/packages/sdk/src/method-help.js +404 -0
- package/dist/packages/sdk/src/method-issues.d.ts +86 -0
- package/dist/packages/sdk/src/method-issues.js +192 -0
- package/dist/packages/sdk/src/method-sync.d.ts +46 -0
- package/dist/packages/sdk/src/method-sync.js +293 -0
- package/dist/packages/sdk/src/method.d.ts +5 -0
- package/dist/packages/sdk/src/method.js +359 -0
- package/dist/packages/sdk/src/model-checks.d.ts +96 -0
- package/dist/packages/sdk/src/model-checks.js +181 -0
- package/dist/packages/sdk/src/outbox.d.ts +120 -0
- package/dist/packages/sdk/src/outbox.js +297 -0
- package/dist/packages/sdk/src/own-model-key.d.ts +22 -0
- package/dist/packages/sdk/src/own-model-key.js +76 -0
- package/dist/packages/sdk/src/prepare.d.ts +24 -0
- package/dist/packages/sdk/src/prepare.js +245 -0
- package/dist/packages/sdk/src/process.d.ts +15 -0
- package/dist/packages/sdk/src/process.js +107 -0
- package/dist/packages/sdk/src/production.d.ts +182 -0
- package/dist/packages/sdk/src/production.js +284 -0
- package/dist/packages/sdk/src/progress.d.ts +12 -0
- package/dist/packages/sdk/src/progress.js +48 -0
- package/dist/packages/sdk/src/proposals.d.ts +73 -0
- package/dist/packages/sdk/src/proposals.js +284 -0
- package/dist/packages/sdk/src/quality.d.ts +82 -0
- package/dist/packages/sdk/src/quality.js +294 -0
- package/dist/packages/sdk/src/result-files.d.ts +5 -0
- package/dist/packages/sdk/src/result-files.js +149 -0
- package/dist/packages/sdk/src/result-transfer.d.ts +21 -0
- package/dist/packages/sdk/src/result-transfer.js +37 -0
- package/dist/packages/sdk/src/run-data.d.ts +11 -0
- package/dist/packages/sdk/src/run-data.js +80 -0
- package/dist/packages/sdk/src/run-method.d.ts +2 -0
- package/dist/packages/sdk/src/run-method.js +2 -0
- package/dist/packages/sdk/src/run-saved.d.ts +5 -0
- package/dist/packages/sdk/src/run-saved.js +138 -0
- package/dist/packages/sdk/src/run-worker.d.ts +6 -0
- package/dist/packages/sdk/src/run-worker.js +131 -0
- package/dist/packages/sdk/src/secrets.d.ts +37 -0
- package/dist/packages/sdk/src/secrets.js +196 -0
- package/dist/packages/sdk/src/update.d.ts +5 -0
- package/dist/packages/sdk/src/update.js +39 -0
- package/dist/packages/sdk/src/versions.d.ts +58 -0
- package/dist/packages/sdk/src/versions.js +144 -0
- package/dist/packages/sdk/src/worker.d.ts +120 -0
- package/dist/packages/sdk/src/worker.js +443 -0
- package/dist/packages/workflow-language/src/execution.d.ts +1 -0
- package/dist/packages/workflow-language/src/execution.js +1 -0
- package/dist/packages/workflow-language/src/inspection.d.ts +990 -0
- package/dist/packages/workflow-language/src/inspection.js +130 -0
- package/dist/packages/workflow-language/src/result-files.d.ts +160 -0
- package/dist/packages/workflow-language/src/result-files.js +67 -0
- package/dist/packages/workflow-language/src/schema.d.ts +207 -0
- package/dist/packages/workflow-language/src/schema.js +56 -0
- package/dist/packages/workflow-language/src/validate.d.ts +11 -0
- package/dist/packages/workflow-language/src/validate.js +77 -0
- package/package.json +230 -4
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build the complete website from the writer's selected content.
|
|
3
|
+
|
|
4
|
+
Reads accepted prepared data and an Markdown briefing. Never rewrites either.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import html
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from urllib.parse import urlsplit
|
|
16
|
+
from briefing_markdown import parse
|
|
17
|
+
from briefing_sessions import social_sessions
|
|
18
|
+
from briefing_progress import progress
|
|
19
|
+
|
|
20
|
+
TEMPLATES=Path(__file__).resolve().parent/'reader'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load(path):return json.loads(path.read_text())
|
|
24
|
+
def optional(path):return load(path) if path.exists() else []
|
|
25
|
+
def h(value):return html.escape(str(value),quote=True)
|
|
26
|
+
def put(path,text):
|
|
27
|
+
path.parent.mkdir(parents=True,exist_ok=True);path.write_text(text);path.chmod(0o600)
|
|
28
|
+
def stamp(value):return dt.datetime.fromisoformat(value)
|
|
29
|
+
def clock(value):return stamp(value).strftime('%I:%M %p').lstrip('0')
|
|
30
|
+
def quotes(text):
|
|
31
|
+
result=[];depth=0;start=0
|
|
32
|
+
for i,char in enumerate(text):
|
|
33
|
+
if char=='“':
|
|
34
|
+
if depth==0:start=i+1
|
|
35
|
+
depth+=1
|
|
36
|
+
elif char=='”' and depth:
|
|
37
|
+
depth-=1
|
|
38
|
+
if depth==0:result.append(text[start:i])
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
def readable_time(e):
|
|
42
|
+
time=e['time']
|
|
43
|
+
return stamp(time['local']).strftime('%b %d · %I:%M %p').replace(' 0',' ') if time['local'] else time['day']
|
|
44
|
+
def host(url):return (urlsplit(url or '').hostname or '').removeprefix('www.').removeprefix('mobile.')
|
|
45
|
+
def safe_url(url):return url if urlsplit(url or '').scheme in ['http','https'] else ''
|
|
46
|
+
|
|
47
|
+
def render_markdown(texts,marked,source_urls=None):
|
|
48
|
+
# Original HTML is shown as source text. Links open HTTP(S), anchors, or local website pages.
|
|
49
|
+
code="""import {marked} from MARKED;
|
|
50
|
+
const escape=s=>s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"');
|
|
51
|
+
marked.use({renderer:{html(token){return escape(token.text)},link(token){let label=this.parser.parseInline(token.tokens);let href=token.href;if(href.startsWith('source:')){const ids=href.slice(7).split(',');return ids.map((id,i)=>{if(!Object.hasOwn(sources,id))throw Error('Unknown source record: '+id);return '<a target="_blank" rel="noopener noreferrer" href="'+escape(sources[id])+'">'+(i===0?label:'['+(i+1)+']')+'</a>'}).join(' ')}return /^(https?:|#|[a-zA-Z0-9_-]+[.]html(?:#|$))/.test(href)?'<a target="_blank" rel="noopener noreferrer" href="'+escape(href)+'">'+label+'</a>':label},image(token){return escape(token.text||'Image referenced in saved text')}}});
|
|
52
|
+
let chunks=[];for await(const c of process.stdin)chunks.push(c);const {texts:inputs,sources}=JSON.parse(Buffer.concat(chunks).toString());
|
|
53
|
+
process.stdout.write(JSON.stringify(Object.fromEntries(Object.entries(inputs).map(([key,value])=>[key,marked.parse(value)]))));
|
|
54
|
+
""".replace('MARKED',json.dumps(marked.resolve().as_uri()))
|
|
55
|
+
return json.loads(subprocess.run(['node','--input-type=module','-e',code],input=json.dumps({'texts':texts,'sources':source_urls or {}}),text=True,capture_output=True,check=True).stdout)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def convert_photo(original,target):
|
|
59
|
+
from PIL import Image, ImageOps
|
|
60
|
+
from pillow_heif import register_heif_opener
|
|
61
|
+
register_heif_opener()
|
|
62
|
+
with Image.open(original) as source:
|
|
63
|
+
preview=ImageOps.exif_transpose(source)
|
|
64
|
+
preview.thumbnail((1600,1600))
|
|
65
|
+
if preview.mode in ('RGBA','LA') or 'transparency' in preview.info:
|
|
66
|
+
rgba=preview.convert('RGBA');background=Image.new('RGB',rgba.size,'white');background.paste(rgba,mask=rgba.getchannel('A'));preview=background
|
|
67
|
+
preview.convert('RGB').save(target,format='JPEG',quality=85)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def build(prepared,briefing_file,out,marked,day=None,proposal=None):
|
|
71
|
+
proposal=proposal or parse(briefing_file.read_text(),day,marked)
|
|
72
|
+
dest=out
|
|
73
|
+
dest.mkdir(parents=True,exist_ok=True)
|
|
74
|
+
for name in ['reader.css','reader.js']:shutil.copyfile(TEMPLATES/name,dest/name)
|
|
75
|
+
raw={f.stem:load(f) for f in (prepared/'records').glob('*.json')}
|
|
76
|
+
records={cid:data['prepared'] for cid,data in raw.items()}
|
|
77
|
+
entries={r['entry_id']:r for name in ['timeline.jsonl','untimed.jsonl'] for r in map(json.loads,(prepared/name).read_text().splitlines())}
|
|
78
|
+
day=proposal['day']
|
|
79
|
+
title=proposal['title']
|
|
80
|
+
day_label=dt.date.fromisoformat(day).strftime('%b %d, %Y').replace(' 0',' ').upper()
|
|
81
|
+
source_ids={cid:r['source_record_ids'] for r in entries.values() for cid in r['source_record_ids']}
|
|
82
|
+
documents=load(prepared/'metadata/documents.json')
|
|
83
|
+
doc_by_record={cid:d for d in documents for cid in d['record_ids']}
|
|
84
|
+
source_ranges=proposal.get('source_ranges',{})
|
|
85
|
+
texts={cid:e['text'] for cid,e in records.items()}
|
|
86
|
+
for supplement in proposal.get('supplements',[]):texts[supplement['id']]=supplement['text']
|
|
87
|
+
for cid,ranges in source_ranges.items():
|
|
88
|
+
for name,(first,last) in ranges.items():texts[cid+':'+name]='\n'.join(texts[cid].splitlines()[first-1:last])
|
|
89
|
+
contexts=optional(prepared/'context.json')
|
|
90
|
+
for c in contexts:
|
|
91
|
+
data=load(prepared/c['source_json'])
|
|
92
|
+
for r in data['records']:texts['context-'+(r.get('message_id') or r['id'])]=r['text']
|
|
93
|
+
def document_page(doc):
|
|
94
|
+
return 'source-'+Path(doc['path']).stem+'.html'
|
|
95
|
+
source_urls={cid:document_page(doc)+'#'+cid for cid,doc in doc_by_record.items()}
|
|
96
|
+
source_urls.update({s['id']:s['id']+'.html' for s in proposal.get('supplements',[])})
|
|
97
|
+
rendered=render_markdown(texts,marked,source_urls)
|
|
98
|
+
media={}
|
|
99
|
+
for asset in optional(prepared/'media.json'):
|
|
100
|
+
original=prepared/asset['path'];name=Path(asset['path']).stem+'.jpg';target=dest/'media'/name;target.parent.mkdir(exist_ok=True)
|
|
101
|
+
if not target.exists():
|
|
102
|
+
convert_photo(original,target)
|
|
103
|
+
original_target=dest/asset['path'];original_target.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(original,original_target)
|
|
104
|
+
for cid in asset['records']:media[cid]={'png':'media/'+name,'original':asset['path']}
|
|
105
|
+
def photo(cid,caption):
|
|
106
|
+
asset=media[cid]
|
|
107
|
+
return f'<figure class="photo"><button data-photo="{h(asset["png"])}" aria-label="Enlarge photo"><img src="{h(asset["png"])}" alt="{h(caption)}" loading="lazy"></button><figcaption>{h(caption)} · <a href="{h(asset["original"])}">Original</a></figcaption></figure>'
|
|
108
|
+
groups=social_sessions(prepared)
|
|
109
|
+
browser_rows=[r for r in entries.values() if records[r['source_record_ids'][0]]['source'] in ['browser','google_takeout']]
|
|
110
|
+
navigation=' · '.join((['<a href="sessions.html">X and Instagram sessions</a>'] if groups else [])+(['<a href="browser.html">All browser visits</a>'] if browser_rows else []))
|
|
111
|
+
def page(title,body):
|
|
112
|
+
return f'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{h(title)}</title><link rel="stylesheet" href="reader.css"></head><body><nav><a href="index.html">{h(day_label)}</a><span>{navigation}</span></nav><main>{body}</main><dialog><button data-close-photo>Close</button><img alt=""></dialog><script src="reader.js"></script></body></html>'
|
|
113
|
+
for supplement in proposal.get('supplements',[]):
|
|
114
|
+
sid=supplement['id'];filename=sid+'.html'
|
|
115
|
+
put(dest/filename,page(supplement['title'],'<h1>'+h(supplement['title'])+'</h1><div class="source-text">'+rendered[sid]+'</div>'))
|
|
116
|
+
put(dest/'data'/f'{sid}.json',json.dumps({'id':sid,'html':rendered[sid],'time':'','date':'','speaker':'','basis':'','document':filename,'reading_label':'Full supporting document','ranges':{name:rendered[sid+':'+name] for name in source_ranges.get(sid,{})}},ensure_ascii=False))
|
|
117
|
+
def speaker(e):
|
|
118
|
+
role=e['actor_role']
|
|
119
|
+
if e['source']=='imessage':return 'User' if role=='user_written' else e['attributes'].get('sender') or 'Not supplied'
|
|
120
|
+
return {'user_written':'User','user':'User','assistant':'Assistant','other_person':'Other person'}.get(role,'')
|
|
121
|
+
def record_html(cid):
|
|
122
|
+
e=records[cid];meta=' · '.join(filter(None,[readable_time(e),speaker(e)]))
|
|
123
|
+
return f'<article class="record" id="{h(cid)}"><div class="record-head">{h(meta)} <a href="records/{h(cid)}.json">Source JSON</a></div><div class="source-text">{rendered[cid]}</div>'+ (photo(cid,'Saved photo') if cid in media else '')+'</article>'
|
|
124
|
+
for cid,e in records.items():
|
|
125
|
+
doc=doc_by_record[cid];docname=document_page(doc)
|
|
126
|
+
basis=('Saved with this activity' if e['text_basis']=='dated_google_activity_title' else 'Browser saved title') if e['source'] in ['browser','google_takeout'] else ''
|
|
127
|
+
if e.get('post_text_partial'):basis+=' · partial saved title'
|
|
128
|
+
data={'id':cid,'html':rendered[cid],'time':readable_time(e),'other_sources':[c for c in dict.fromkeys(source_ids.get(cid,[])) if c!=cid],'date':e['time']['day'],'speaker':speaker(e),'basis':basis,'document':docname+'#'+cid,'conversation':doc['kind']=='conversation','reading_label':'Read full conversation' if doc['kind']=='conversation' else 'Read full note' if e['source'] in ['roam','apple_notes'] else 'Read full document','ranges':{name:rendered[cid+':'+name] for name in source_ranges.get(cid,{})}}
|
|
129
|
+
put(dest/'data'/f'{cid}.json',json.dumps(data,ensure_ascii=False))
|
|
130
|
+
target=dest/'records'/f'{cid}.json';target.parent.mkdir(exist_ok=True);shutil.copyfile(prepared/'records'/f'{cid}.json',target)
|
|
131
|
+
for doc in documents:
|
|
132
|
+
body='<h1>'+h(doc['title'])+'</h1>'
|
|
133
|
+
body+=''.join(record_html(cid) for cid in doc['record_ids'])
|
|
134
|
+
put(dest/document_page(doc),page(doc['title'],body))
|
|
135
|
+
for c in contexts:
|
|
136
|
+
data=load(prepared/c['source_json']);body='<h1>'+h(c['title'])+'</h1><p class="small">Earlier conversation</p>'
|
|
137
|
+
for r in data['records']:
|
|
138
|
+
cid='context-'+(r.get('message_id') or r['id']);body+=f'<article id="{h(cid)}" class="record"><div class="record-head">{h(r.get("occurred_at") or r.get("day") or "Time not supplied")} · {h(r.get("role") or "")}</div><div class="source-text">{rendered[cid]}</div></article>'
|
|
139
|
+
put(dest/('context-'+c['conversation_id']+'.html'),page(c['title'],body))
|
|
140
|
+
def cite(ids,key,text='',label=None,range_name=None):
|
|
141
|
+
return f'<button class="{("source-link" if label else "cite")}" data-ids="{h(json.dumps(ids))}" data-quotes="{h(json.dumps(quotes(text)))}" data-panel="{h(key)}"'+(f' data-range="{h(range_name)}"' if range_name else '')+f' aria-expanded="false" aria-label="{h(label or "Read supporting sources")}">{h(label or ("↗" if key.startswith("event-") else key))}</button>'
|
|
142
|
+
def selected(row):
|
|
143
|
+
sources=[(cid,records[cid]) for cid in row['source_record_ids']]
|
|
144
|
+
dated=[(cid,e) for cid,e in sources if e['text_basis']=='dated_google_activity_title']
|
|
145
|
+
if dated:
|
|
146
|
+
titles={re.sub(r'^Visited\s+','',e['text']) for cid,e in dated}
|
|
147
|
+
if len(titles)>1:return None
|
|
148
|
+
cid,e=next(((cid,e) for cid,e in dated if not e['text'].startswith('Visited ')),dated[0])
|
|
149
|
+
else:cid,e=sources[0]
|
|
150
|
+
url=e['url'] or row.get('url') or ''
|
|
151
|
+
is_instagram=e['platform']=='Instagram'
|
|
152
|
+
if e['source']=='browser' and not dated:
|
|
153
|
+
route=urlsplit(url).path
|
|
154
|
+
if not re.search(r'/status/\d+|/(?:p|reel)/[^/]+',route):return None
|
|
155
|
+
text=e.get('post_text') or (e['text'] if is_instagram and not e['text'].startswith('http') else '')
|
|
156
|
+
text=html.unescape(text)
|
|
157
|
+
if not text.strip() or text.strip().lower() in ['instagram','instagram photos and videos','home / x','x','twitter']:return None
|
|
158
|
+
return {'id':row['entry_id'],'cid':cid,'text':text,'author':e.get('post_author') or '','url':url,'partial':e.get('post_text_partial',False),'time':row['time'],'source_ids':row['source_record_ids'],'platform':e['platform']}
|
|
159
|
+
def post_row(row):
|
|
160
|
+
sel=selected(row);e=records[row['source_record_ids'][0]];url=(sel or {}).get('url') or row.get('url') or e['url'] or ''
|
|
161
|
+
ids=row['source_record_ids'];key=row['entry_id']+'-full'
|
|
162
|
+
title=(sel or {}).get('text') or row.get('url') or row.get('title') or e['event_kind']
|
|
163
|
+
who=(sel or {}).get('author','');body=h(title)
|
|
164
|
+
link=safe_url(url)
|
|
165
|
+
if sel:body='“'+h(title)+'”'
|
|
166
|
+
if link:body=f'<a class="original" href="{h(link)}" target="_blank" rel="noopener noreferrer">'+body+'</a>'
|
|
167
|
+
if sel and sel['partial']:body+='<span class="partial" title="The saved post text is incomplete." aria-label="The saved post text is incomplete."> …</span>'
|
|
168
|
+
if who:body='<strong>'+h(who)+'</strong> '+body
|
|
169
|
+
time=clock(row['time']) if row['time'] else 'Untimed'
|
|
170
|
+
return f'<li class="evidence-host" data-entry="{h(row["entry_id"])}"><div class="post"><time datetime="{h(row["time"])}">{h(time)}</time><div class="post-text">'+body+' '+cite(ids,key,'“'+title+'”',label=None)+'</div></div></li>'
|
|
171
|
+
for g in groups:
|
|
172
|
+
g['full'].sort(key=lambda r:(stamp(r['time']),records[r['source_record_ids'][0]]['source_order'],r['entry_id']))
|
|
173
|
+
def session_html(g):
|
|
174
|
+
label=g['platform']+' '+clock(g['start'])
|
|
175
|
+
if g['start']!=g['end']:label+='–'+clock(g['end'])
|
|
176
|
+
label+=f" · {len(g['full'])} records"
|
|
177
|
+
return f'<section class="session" id="{h(g["id"])}"><h3>{h(label)}</h3><div class="session-preview"><ol class="records">'+''.join(post_row(r) for r in g['social'] if selected(r))+'</ol></div><details class="full-session"><summary>Show all</summary><ol class="records">'+''.join(post_row(r) for r in g['full'])+'</ol></details></section>'
|
|
178
|
+
put(dest/'sessions.html',page('X and Instagram sessions','<h1>X and Instagram sessions</h1>'+(''.join(session_html(g) for g in groups) or '<p>No saved social sessions for this day.</p>')))
|
|
179
|
+
if browser_rows: put(dest/'browser.html',page('All browser visits','<h1>All browser visits</h1><ol class="records" style="max-height:none">'+''.join(post_row(r) for r in browser_rows)+'</ol>'))
|
|
180
|
+
library='<h1>Full documents</h1><ul class="simple-list">'+''.join('<li><a href="'+h(document_page(d))+'">'+h(d['title'])+'</a></li>' for d in documents)+'</ul>'
|
|
181
|
+
if contexts:library+='<h2>Earlier conversations</h2><ul>'+''.join('<li><a href="context-'+c['conversation_id']+'.html">'+h(c['title'])+'</a></li>' for c in contexts)+'</ul>'
|
|
182
|
+
put(dest/'documents.html',page('Full documents',library))
|
|
183
|
+
story=''
|
|
184
|
+
sessions_by_id={g['id']:g for g in groups}
|
|
185
|
+
for n,block in enumerate(proposal['blocks'],1):
|
|
186
|
+
if block.get('session'):
|
|
187
|
+
session=sessions_by_id.get(block['session'])
|
|
188
|
+
if session is None:
|
|
189
|
+
warning='Session link '+block['session']+' was not found. Linking to all saved social sessions.'
|
|
190
|
+
print(warning,file=sys.stderr)
|
|
191
|
+
progress(warning)
|
|
192
|
+
story+='<p>This session link could not be found. <a href="sessions.html">View all saved social sessions</a>.</p>'
|
|
193
|
+
else:
|
|
194
|
+
story+=session_html(session)
|
|
195
|
+
continue
|
|
196
|
+
body=block['html']
|
|
197
|
+
if block['type']=='paragraph':body=body.replace('<p>', '<p class="story-p">', 1)
|
|
198
|
+
for i,citation in enumerate(proposal['citations']):
|
|
199
|
+
label=citation['label']
|
|
200
|
+
button=cite(citation['citations'],f'{n}-{i}',text=block['text'],label='Source',range_name=citation.get('source_range'))
|
|
201
|
+
button=button.replace('>Source</button>', '>'+label+'</button>')
|
|
202
|
+
body=body.replace(f'<!--briefing-citation-{i}-->',button)
|
|
203
|
+
for i,asset in enumerate(proposal['photos']):
|
|
204
|
+
body=body.replace(f'<!--briefing-photo-{i}-->',photo(asset['id'],asset['caption']))
|
|
205
|
+
story+=f'<div class="evidence-host" id="p{n}">'+body+'</div>'
|
|
206
|
+
story+='<footer><a href="documents.html">Full documents</a></footer>'
|
|
207
|
+
put(dest/'index.html',page(title,story))
|
|
208
|
+
put(dest/'display-groups.json',json.dumps([{'id':g['id'],'parent':g['parent'],'platform':g['platform'],'start':g['start'],'end':g['end'],'full':[r['entry_id'] for r in g['full']]} for g in groups],indent=2))
|
|
209
|
+
print(json.dumps({'output':str(out),'blocks':len(proposal['blocks']),'display_groups':len(groups),'source_records':len(records)}))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__=='__main__':
|
|
213
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
214
|
+
for name in ['prepared','briefing','out','marked']:parser.add_argument('--'+name,type=Path,required=True)
|
|
215
|
+
args=parser.parse_args();build(args.prepared,args.briefing,args.out,args.marked)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sample_output": {
|
|
3
|
+
"date": "2026-10-09",
|
|
4
|
+
"input": "sample/ (28 messages, fictional)",
|
|
5
|
+
"draft": "sample-report.md",
|
|
6
|
+
"made_by": "Written for this example from the sample records. The example's own scripts then ran on it: calculate_activity_times (briefing_times.py) made the time files, the write_briefing check (briefing_validation.py) passed, and render_and_save (briefing_render.py) built the website in sample-output.zip.",
|
|
7
|
+
"source_check": {
|
|
8
|
+
"status": "pass",
|
|
9
|
+
"reason": "Source links and supporting files are valid.",
|
|
10
|
+
"evidence": [
|
|
11
|
+
"29 citations checked.",
|
|
12
|
+
"2 supporting files checked."
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"report_sha256": "b37e90f1515c7a052f7b1cdb9188c7f0e070297194f2f1d3b00e37a662fef068",
|
|
16
|
+
"archive_sha256": "288f4428a1f13ce08a762f5e6e9e52ca50d1e5f528826aa12d9328a74b316d48"
|
|
17
|
+
},
|
|
18
|
+
"approved_report_sha256": "588d744b83c0ea53322c8c6d33b1eba50bfdc817b1ae5459118d9d409dce2e3c"
|
|
19
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
format: method/3.4
|
|
2
|
+
tools:
|
|
3
|
+
calculate_activity_times:
|
|
4
|
+
description: Calculate activity times and save time-plan.json and time-estimates.json in this run. Each successful call replaces both files, so supply the complete plan each time. Only the activities in the plan are calculated; choose the records for each. Before you count a message as work, check whether it is an automated prompt. Record in the plan why each record was selected or excluded. Gaps between records and media playback do not show continuous attention. Leave an activity unestimated when the records do not support an estimate.
|
|
5
|
+
in:
|
|
6
|
+
day:
|
|
7
|
+
type: text
|
|
8
|
+
description: Selected date in YYYY-MM-DD form.
|
|
9
|
+
timezone:
|
|
10
|
+
type: text
|
|
11
|
+
description: Selected named timezone.
|
|
12
|
+
plan:
|
|
13
|
+
type: text
|
|
14
|
+
description: "JSON with activities and not_estimated (short reasons for activities without an estimate). Each activity has id, label, reason, mode, and optional excluded: [{record_ids, reason}]. A field reference is {record_id, field}, where field is a dotted path in the prepared record, such as time.local, time.end, or attributes.msPlayed. Use points with points: [field references], gap_minutes, and optional compare_gaps. This counts first-to-last time in each group; a single point adds zero. Use intervals with intervals: [{start: field reference, end: field reference}]. Record connections between different endpoint records in the activity reason. Intervals are clipped to the day and overlaps count once. Use durations with durations: [field references] and unit: milliseconds, seconds, or minutes. Each selected duration counts once. Supply saved fields, not invented times or calculated totals."
|
|
15
|
+
out:
|
|
16
|
+
estimates:
|
|
17
|
+
type: text
|
|
18
|
+
description: Calculated durations and the records and assumptions used to calculate them.
|
|
19
|
+
files:
|
|
20
|
+
type: list
|
|
21
|
+
description: Supporting file references to return with the draft.
|
|
22
|
+
items:
|
|
23
|
+
type: file
|
|
24
|
+
run:
|
|
25
|
+
kind: run
|
|
26
|
+
runtime: python
|
|
27
|
+
entrypoint: briefing_times.py
|
|
28
|
+
effects: []
|
|
29
|
+
name: Write a daily briefing
|
|
30
|
+
goal: Turn one prepared day folder into a complete, cited daily briefing. Follow the approved example for writing and layout, and save the briefing with its website.
|
|
31
|
+
inputs:
|
|
32
|
+
day:
|
|
33
|
+
type: text
|
|
34
|
+
description: Date of the prepared records, in YYYY-MM-DD form.
|
|
35
|
+
timezone:
|
|
36
|
+
type: text
|
|
37
|
+
description: Timezone for those records, such as America/Chicago.
|
|
38
|
+
environment:
|
|
39
|
+
prepared_day:
|
|
40
|
+
type: files
|
|
41
|
+
description: Folder of prepared records for that day. Read-only.
|
|
42
|
+
steps:
|
|
43
|
+
open_inputs:
|
|
44
|
+
name: Check the input folders
|
|
45
|
+
in:
|
|
46
|
+
day: inputs.day
|
|
47
|
+
timezone: inputs.timezone
|
|
48
|
+
prepared_day: environment.prepared_day
|
|
49
|
+
do:
|
|
50
|
+
kind: run
|
|
51
|
+
runtime: python
|
|
52
|
+
entrypoint: briefing_check_inputs.py
|
|
53
|
+
out:
|
|
54
|
+
selected_day:
|
|
55
|
+
type: record
|
|
56
|
+
description: Prepared records, date, timezone, and source hash.
|
|
57
|
+
fields:
|
|
58
|
+
folder:
|
|
59
|
+
type: text
|
|
60
|
+
description: Absolute path to the prepared records.
|
|
61
|
+
day:
|
|
62
|
+
type: text
|
|
63
|
+
description: Selected date in YYYY-MM-DD form.
|
|
64
|
+
timezone:
|
|
65
|
+
type: text
|
|
66
|
+
description: Named timezone used to interpret this day.
|
|
67
|
+
start:
|
|
68
|
+
type: text
|
|
69
|
+
description: README path relative to the prepared folder.
|
|
70
|
+
source_digest:
|
|
71
|
+
type: text
|
|
72
|
+
description: Hash of the prepared files at the start of this run.
|
|
73
|
+
example:
|
|
74
|
+
type: record
|
|
75
|
+
description: The approved example and its complete text.
|
|
76
|
+
fields:
|
|
77
|
+
report:
|
|
78
|
+
type: text
|
|
79
|
+
description: Complete approved report.
|
|
80
|
+
session_links:
|
|
81
|
+
type: text
|
|
82
|
+
description: Links to saved social sessions for this day. A link on its own line displays that session in the story.
|
|
83
|
+
purpose: Checks the required record files, date, and timezone. Returns the prepared folder and its source hash, the complete approved report, and saved session links.
|
|
84
|
+
write_briefing:
|
|
85
|
+
name: Write the briefing
|
|
86
|
+
in:
|
|
87
|
+
selected_day: selected_day
|
|
88
|
+
example: example
|
|
89
|
+
session_links: session_links
|
|
90
|
+
do:
|
|
91
|
+
kind: agent
|
|
92
|
+
model: default
|
|
93
|
+
prompt: |
|
|
94
|
+
Write the story of {{selected_day.day}} from the records in {{selected_day.folder}}.
|
|
95
|
+
Follow the complete approved report in example.report for writing and detail.
|
|
96
|
+
Return Markdown with source links: [label](source:record-id) or [label](source:record-id#L10-L20).
|
|
97
|
+
Use calculate_activity_times for supported time estimates.
|
|
98
|
+
tools:
|
|
99
|
+
- calculate_activity_times
|
|
100
|
+
out:
|
|
101
|
+
draft:
|
|
102
|
+
type: text
|
|
103
|
+
description: The complete Markdown briefing.
|
|
104
|
+
supporting_files:
|
|
105
|
+
type: list
|
|
106
|
+
items:
|
|
107
|
+
type: file
|
|
108
|
+
description: File references returned by the calculator, or an empty list when none were made.
|
|
109
|
+
check:
|
|
110
|
+
kind: run
|
|
111
|
+
runtime: python
|
|
112
|
+
entrypoint: briefing_validation.py
|
|
113
|
+
reading:
|
|
114
|
+
check_name: Check source links
|
|
115
|
+
check: Checks source IDs, citation line ranges, and supporting file hashes, names, dates, and timezones. Returns pass when those references are valid.
|
|
116
|
+
render_and_save:
|
|
117
|
+
name: Render and save the briefing
|
|
118
|
+
in:
|
|
119
|
+
selected_day: selected_day
|
|
120
|
+
draft: draft
|
|
121
|
+
supporting_files: supporting_files
|
|
122
|
+
do:
|
|
123
|
+
kind: run
|
|
124
|
+
runtime: python
|
|
125
|
+
entrypoint: briefing_render.py
|
|
126
|
+
out:
|
|
127
|
+
saved_briefing:
|
|
128
|
+
type: file
|
|
129
|
+
description: Saved briefing receipt with its date, timezone, and file paths.
|
|
130
|
+
published_website:
|
|
131
|
+
type: file
|
|
132
|
+
description: Briefing website with source pages and supporting files.
|
|
133
|
+
format: method-website
|
|
134
|
+
purpose: Builds the briefing website and checks its local links and source hash. Saves the website and supporting files under their content hash, reuses an identical saved output, and returns the receipt and website manifest.
|
|
135
|
+
result:
|
|
136
|
+
briefing: saved_briefing
|
|
137
|
+
website: published_website
|
|
138
|
+
run_prompt: Run Write a daily briefing for the requested prepared day folder. Read the date and timezone from its records. When finished, open the briefing website and give me the run link.
|
|
139
|
+
files:
|
|
140
|
+
- briefing_files.py
|
|
141
|
+
- briefing_times.py
|
|
142
|
+
- briefing_sessions.py
|
|
143
|
+
- briefing_check_inputs.py
|
|
144
|
+
- briefing_artifacts.py
|
|
145
|
+
- briefing_manifest.py
|
|
146
|
+
- briefing_progress.py
|
|
147
|
+
- briefing_validation.py
|
|
148
|
+
- briefing_render.py
|
|
149
|
+
- briefing_website.py
|
|
150
|
+
- briefing_markdown.py
|
|
151
|
+
- reader/reader.css
|
|
152
|
+
- reader/reader.js
|
|
153
|
+
- package.json
|
|
154
|
+
- package-lock.json
|
|
155
|
+
- pyproject.toml
|
|
156
|
+
- uv.lock
|
|
157
|
+
- vendor/marked.mjs
|
|
158
|
+
- approved-report.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"day":"2026-05-11","timezone":"America/Chicago"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
:root{--paper:#faf8f3;--ink:#232b28;--muted:#626e66;--accent:#23614b;--line:#d8ded6}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:17px/1.7 Georgia,serif}a,button{color:var(--accent)}a{text-underline-offset:3px}button,summary{font:12px/1.5 'Helvetica Neue',sans-serif;cursor:pointer}button{border:0;background:none;padding:0}a:focus-visible,button:focus-visible,summary:focus-visible{outline:2px solid #bd7336;outline-offset:3px}nav{max-width:786px;margin:auto;display:flex;justify-content:space-between;gap:16px;padding:18px 28px;font:12px/1.5 'Helvetica Neue',sans-serif}nav a{text-decoration:none}main{max-width:786px;margin:40px auto;padding:0 28px}.mast{margin-bottom:30px}.eyebrow{text-transform:uppercase;letter-spacing:.13em;font:11px/1.5 'Helvetica Neue',sans-serif;color:var(--accent)}h1{font:normal clamp(38px,6vw,60px)/1.08 Georgia,serif;letter-spacing:-.04em;margin:15px 0 24px}h2{font:normal 27px/1.3 Georgia,serif;margin:40px 0 18px;border-top:1px solid var(--line);padding-top:18px}h3{font:600 13px/1.5 'Helvetica Neue',sans-serif;margin:0 0 7px}.story-p,.tldr{margin:0 0 22px}.tldr{font-size:18px}.cite{font:11px 'Helvetica Neue',sans-serif;vertical-align:super;position:relative;white-space:nowrap;margin-left:3px}.cite::before{content:'';position:absolute;inset:-9px -6px}.passage-button{display:inline-block;margin:0 0 18px}.evidence{font-size:15px;line-height:1.55;margin:4px 0 24px}.evidence[hidden]{display:none}.evidence .close{display:block;margin:0 0 14px}.record{margin:0 0 22px;scroll-margin-top:20px}.record-head{font:12px/1.5 'Helvetica Neue',sans-serif;color:var(--muted);margin-bottom:8px}.record-head a{font-size:11px;margin-left:10px}.source-text p{margin:0 0 12px}.source-text ul,.source-text ol{padding-left:22px}.source-text pre{white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.5 ui-monospace,monospace}.source-text code{font-size:.85em}.source-text a{overflow-wrap:anywhere}.source-text img{max-width:100%}mark{background:#f0e2ac;color:inherit;padding:0}.session{margin:22px 0 26px;scroll-margin-top:15px}.session h3 span{font-weight:400;color:var(--muted);margin-left:7px}.session-link{text-decoration:none}.post{display:grid;grid-template-columns:68px minmax(0,1fr);gap:10px;margin:0 0 3px;padding:0}.post time{font:11px/1.65 'Helvetica Neue',sans-serif;color:var(--muted);padding-top:2px;font-variant-numeric:tabular-nums}.post .post-text{font:15px/1.45 Georgia,serif;margin:0;overflow-wrap:anywhere}.post strong{font:600 12px/1.45 'Helvetica Neue',sans-serif}.post .cite{vertical-align:baseline}.post details{display:inline}.post summary{display:inline;margin-left:5px}.full-session{margin-top:7px}.full-session>summary{color:var(--accent);width:fit-content}.records{list-style:none;margin:10px 0;padding:0;max-height:65vh;overflow:auto;overscroll-behavior:contain}.records li{padding:0;border:0}.original{color:inherit;text-decoration:none}.original:hover{text-decoration:underline}.navigation-only{font:13px/1.5 'Helvetica Neue',sans-serif;color:var(--muted)}.partial{font:11px/1.5 'Helvetica Neue',sans-serif;color:var(--muted)}.photo{margin:12px 0 25px}.photo button{display:block}.photo img{display:block;width:190px;max-width:100%;height:auto}.photo figcaption{font:11px/1.5 'Helvetica Neue',sans-serif;color:var(--muted);margin-top:6px}dialog{border:0;background:var(--paper);padding:18px;max-width:95vw;max-height:95vh}dialog::backdrop{background:#000a}dialog img{display:block;max-width:86vw;max-height:82vh;width:auto;height:auto}dialog button{margin:0 0 8px}.simple-list{padding:0;list-style:none}.simple-list li{margin:0 0 10px}.small{font:12px/1.5 'Helvetica Neue',sans-serif;color:var(--muted)}footer{margin:40px 0 20px;font:12px/1.5 'Helvetica Neue',sans-serif}code{font-size:.85em}html{scroll-behavior:auto}@media(max-width:600px){main{margin:26px auto;padding:0 18px}nav{padding:15px 18px}.post{grid-template-columns:60px minmax(0,1fr);gap:7px}h1{font-size:42px}}
|
|
2
|
+
|
|
3
|
+
.track-list{margin-bottom:25px}.track-list>summary{color:var(--accent);width:fit-content}.track-list .simple-list{font-size:15px;line-height:1.5}.track-list .simple-list>li{margin:0 0 3px}
|
|
4
|
+
|
|
5
|
+
.evidence{margin:0 0 18px;font-size:15px;line-height:1.45}
|
|
6
|
+
.evidence-scroll{max-height:65vh;overflow:auto;overscroll-behavior:contain;scrollbar-width:thin}
|
|
7
|
+
.evidence-scroll:focus-visible{outline:2px solid #bd7336;outline-offset:2px}
|
|
8
|
+
.evidence .record{display:grid;grid-template-columns:68px minmax(0,1fr);gap:10px;margin:0 0 9px;scroll-margin:0}
|
|
9
|
+
.evidence .record>time{font:11px/1.65 'Helvetica Neue',sans-serif;color:var(--muted);padding-top:2px}
|
|
10
|
+
.evidence .record.untimed{display:block}
|
|
11
|
+
.evidence .record.untimed>time{display:none}
|
|
12
|
+
.evidence .record-head{display:flex;align-items:baseline;gap:7px;margin:0 0 3px;font:12px/1.45 'Helvetica Neue',sans-serif}
|
|
13
|
+
.evidence .record-head a{margin:0;text-decoration:none}
|
|
14
|
+
.evidence .source-text p{margin:0 0 6px}
|
|
15
|
+
.evidence .source-text ul,.evidence .source-text ol{margin:0 0 6px;padding-left:20px}
|
|
16
|
+
.evidence .source-text h1,.evidence .source-text h2,.evidence .source-text h3{font:bold 15px/1.45 Georgia,serif;letter-spacing:0;margin:8px 0 4px;padding:0;border:0}
|
|
17
|
+
.evidence .source-text pre{margin:5px 0}
|
|
18
|
+
.evidence .source-text>:last-child{margin-bottom:0}
|
|
19
|
+
.evidence-host:has(>.evidence:not([hidden]))>.story-p{margin-bottom:8px}
|
|
20
|
+
.evidence-host:has(>.evidence:not([hidden]))>.passage-button{margin-bottom:5px}
|
|
21
|
+
@media(max-width:600px){.evidence .record{grid-template-columns:60px minmax(0,1fr);gap:7px}}
|
|
22
|
+
|
|
23
|
+
.evidence .record-head{float:left;margin-right:6px}.evidence .source-text>p:first-child{display:inline}.evidence .record.untimed .record-head{float:none;margin-right:0}
|
|
24
|
+
|
|
25
|
+
.source-text table{border-collapse:collapse;margin:6px 0 10px;text-align:left}
|
|
26
|
+
.source-text th,.source-text td{padding:0 12px 2px 0;vertical-align:top}
|
|
27
|
+
.source-text th:last-child,.source-text td:last-child{padding-right:0}
|
|
28
|
+
|
|
29
|
+
.source-link{font:inherit;text-decoration:underline;text-underline-offset:3px;display:inline;margin:0;padding:0}.evidence-host{overflow-wrap:anywhere}.evidence-host>pre{white-space:pre-wrap}.evidence-host>table{display:block;overflow-x:auto;border-collapse:collapse}.evidence-host>table th,.evidence-host>table td{padding:4px 12px;text-align:left}
|
|
30
|
+
|
|
31
|
+
.session-preview{max-height:15rem;overflow:hidden;mask-image:linear-gradient(#000 75%,transparent)}
|
|
32
|
+
.session-preview .records{max-height:none;overflow:visible;margin-bottom:0}
|
|
33
|
+
.session:has(>.full-session[open])>.session-preview{display:none}
|
|
34
|
+
.evidence-host>table{overflow-wrap:normal;word-break:normal;max-width:100%}
|
|
35
|
+
.evidence-host>table th,.evidence-host>table td{min-width:9rem}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const cache=new Map();
|
|
2
|
+
async function record(id){if(!cache.has(id))cache.set(id,fetch(`data/${id}.json`).then(r=>{if(!r.ok)throw Error('Source could not be opened');return r.json()}));return cache.get(id)}
|
|
3
|
+
function highlight(root,quotes){for(const quote of quotes){if(!quote.trim())continue;const walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT);const nodes=[];let joined='',node;while(node=walker.nextNode()){if(node.parentElement.closest('mark'))continue;nodes.push({node,start:joined.length});joined+=node.textContent}const normalized=[];let text='';for(let i=0;i<joined.length;i++){if(/\s/.test(joined[i])){if(text.endsWith(' '))continue;text+=' '}else{text+=joined[i]}normalized.push(i)}const q=quote.replace(/\s+/g,' ');const at=text.indexOf(q);if(at<0)continue;const start=normalized[at],end=normalized[at+q.length-1]+1;for(const item of nodes.reverse()){let a=Math.max(0,start-item.start),b=Math.min(item.node.length,end-item.start);if(b<=a)continue;const range=document.createRange();range.setStart(item.node,a);range.setEnd(item.node,b);const mark=document.createElement('mark');range.surroundContents(mark)}}}
|
|
4
|
+
function sourceElement(data,range){
|
|
5
|
+
const article=document.createElement('article');article.className='record';article.id=data.id;
|
|
6
|
+
const writing=/^(roam|apple_notes)-/.test(data.id);
|
|
7
|
+
const time=document.createElement('time');time.textContent=(data.time||'').split(' · ').at(-1);
|
|
8
|
+
if(!/AM|PM/.test(time.textContent))time.textContent='';
|
|
9
|
+
if(!time.textContent)article.classList.add('untimed');
|
|
10
|
+
const body=document.createElement('div');body.className='record-body';
|
|
11
|
+
const head=document.createElement('div');head.className='record-head';
|
|
12
|
+
if(!writing&&data.speaker){const speaker=document.createElement('strong');speaker.textContent=data.speaker==='User'?'You':data.speaker==='Assistant'?(data.id.startsWith('codex-')?'Codex':'ChatGPT'):data.speaker;head.append(speaker)}
|
|
13
|
+
const link=document.createElement('a');link.href=data.document;link.target='_blank';link.rel='noopener';link.textContent=writing?'Full note ↗':'↗';link.title=[data.reading_label,data.basis].filter(Boolean).join(' · ');link.setAttribute('aria-label',data.reading_label);head.append(link);
|
|
14
|
+
const text=document.createElement('div');text.className='source-text';text.innerHTML=range?data.ranges[range]:data.html;
|
|
15
|
+
body.append(head,text);article.append(time,body);return article;
|
|
16
|
+
}
|
|
17
|
+
async function openEvidence(button){
|
|
18
|
+
const host=button.closest('.evidence-host');let panel=host.querySelector(':scope > .evidence');
|
|
19
|
+
if(!panel){panel=document.createElement('div');panel.className='evidence';host.append(panel)}
|
|
20
|
+
const isOpen=!panel.hidden&&panel.dataset.owner===button.dataset.panel;
|
|
21
|
+
host.querySelectorAll('[data-ids]').forEach(b=>{b.setAttribute('aria-expanded','false');if(b.dataset.readLabel)b.textContent=b.dataset.readLabel});
|
|
22
|
+
panel.dataset.owner=button.dataset.panel;button.setAttribute('aria-expanded',String(!isOpen));
|
|
23
|
+
if(isOpen){panel.hidden=true;return}
|
|
24
|
+
panel.hidden=false;const request=Symbol();panel.request=request;panel.replaceChildren();
|
|
25
|
+
const scroll=document.createElement('div');scroll.className='evidence-scroll';scroll.tabIndex=0;scroll.setAttribute('role','region');scroll.setAttribute('aria-label','Saved sources');
|
|
26
|
+
panel.append(scroll);
|
|
27
|
+
try{
|
|
28
|
+
const ids=JSON.parse(button.dataset.ids),quotes=JSON.parse(button.dataset.quotes||'[]');
|
|
29
|
+
for(const id of ids){
|
|
30
|
+
const data=await record(id);if(panel.request!==request||panel.hidden)return;
|
|
31
|
+
const el=sourceElement(data,button.dataset.range);scroll.append(el);highlight(el.querySelector('.source-text'),quotes);
|
|
32
|
+
const mark=el.querySelector('mark');
|
|
33
|
+
if(mark){const a=el.querySelector('.record-head a'),url=new URL(a.href);url.searchParams.set('quote',quotes.find(q=>el.querySelector('.source-text').textContent.includes(q))||mark.textContent);a.href=url}
|
|
34
|
+
}
|
|
35
|
+
const first=scroll.querySelector('mark');
|
|
36
|
+
if(first)scroll.scrollTop=Math.max(0,first.getBoundingClientRect().top-scroll.getBoundingClientRect().top-30);
|
|
37
|
+
}catch(error){if(panel.request===request&&!panel.hidden)scroll.append(document.createTextNode(error.message))}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
document.addEventListener('click',e=>{const button=e.target.closest('[data-ids]');if(button)openEvidence(button);const photo=e.target.closest('[data-photo]');if(photo){const dlg=document.querySelector('dialog');dlg.querySelector('img').src=photo.dataset.photo;dlg.querySelector('img').alt=photo.querySelector('img').alt;dlg.showModal()}});
|
|
41
|
+
document.querySelector('dialog')?.addEventListener('click',e=>{if(e.target.tagName==='DIALOG'||e.target.closest('[data-close-photo]'))e.currentTarget.close()});
|
|
42
|
+
function revealHash(){const id=decodeURIComponent(location.hash.slice(1));if(!id)return;const el=document.getElementById(id);if(el){if(el.classList.contains('session')){const details=el.querySelector('.full-session');if(details)details.open=true}const q=new URLSearchParams(location.search).get('quote');if(q)highlight(el,[q]);(el.querySelector('mark')||el).scrollIntoView({block:'start'})}}window.addEventListener('hashchange',revealHash);revealHash();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|