@rubytech/create-maxy 1.0.799 → 1.0.801
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/dist/index.js +7 -1
- package/package.json +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +191 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +207 -0
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +12 -0
- package/payload/platform/plugins/cloudflare/scripts/_cdp-authorize-matcher.mjs +74 -0
- package/payload/platform/plugins/cloudflare/scripts/_cdp-authorize.mjs +60 -50
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +118 -22
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +2 -2
- package/payload/platform/plugins/whatsapp-import/bin/ingest.mjs +732 -0
- package/payload/platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh +102 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +49 -97
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +1 -1
- package/payload/platform/scripts/seed-neo4j.sh +24 -15
- package/payload/platform/templates/specialists/agents/database-operator.md +13 -3
- package/payload/server/public/assets/{admin-C0lKk6WM.js → admin-Sa301b8q.js} +6 -6
- package/payload/server/public/index.html +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-gate.test.sh +0 -166
- package/payload/platform/plugins/admin/hooks/archive-ingest-gate.sh +0 -147
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/conversation-and-messages.md +0 -99
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/insight-extraction.md +0 -121
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Drive the Cloudflare argotunnel
|
|
2
|
+
// Drive the Cloudflare argotunnel consent page to a deterministic terminal
|
|
3
|
+
// state via CDP WebSocket. Originally Task 588 (Authorize click from code,
|
|
4
|
+
// collapsing a 180 s human-latency window). Task 855 widens the contract to
|
|
5
|
+
// three states because the dashboard does not always render a button — once
|
|
6
|
+
// the bound account has authorized this device's zones, navigating the same
|
|
7
|
+
// (or a fresh) consent URL renders a Success modal verbatim, and the
|
|
8
|
+
// pre-855 helper conflated "no button visible yet" with "no button will
|
|
9
|
+
// ever appear" and exited error.
|
|
3
10
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// press the Authorize button. Pre-Task 588 the script then polled for
|
|
7
|
-
// cert.pem for up to 180 seconds, waiting for a human click in VNC. This
|
|
8
|
-
// helper does the click from code, collapsing the wait to ~1-3 seconds.
|
|
9
|
-
//
|
|
10
|
-
// Protocol: Chrome DevTools Protocol over WebSocket (https://chromedevtools.github.io/devtools-protocol/).
|
|
11
|
+
// Protocol: Chrome DevTools Protocol over WebSocket
|
|
12
|
+
// (https://chromedevtools.github.io/devtools-protocol/):
|
|
11
13
|
// 1. GET http://127.0.0.1:9222/json/list → find the target with matching id;
|
|
12
14
|
// extract webSocketDebuggerUrl.
|
|
13
15
|
// 2. Open WS, enable Page + Runtime domains.
|
|
14
16
|
// 3. Wait for Page.loadEventFired (or observe document.readyState==='complete').
|
|
15
|
-
// 4. Poll Runtime.evaluate every 200 ms for up to ~3 s,
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
17
|
+
// 4. Poll Runtime.evaluate every 200 ms for up to ~3 s, evaluating the
|
|
18
|
+
// tri-state matcher from _cdp-authorize-matcher.mjs. The matcher
|
|
19
|
+
// returns one of:
|
|
20
|
+
// - { kind:'button', descriptor } — Authorize/Connect button found,
|
|
21
|
+
// click() fired in-page; emit
|
|
22
|
+
// reason=clicked, exit 0.
|
|
23
|
+
// - { kind:'success' } — Success modal text detected
|
|
24
|
+
// ("Cloudflared has installed a
|
|
25
|
+
// certificate"); cert is bound to
|
|
26
|
+
// the account. Emit reason=
|
|
27
|
+
// cert-already-installed, exit 0.
|
|
28
|
+
// Caller's existing cert-poll picks
|
|
29
|
+
// up the cert (callback is
|
|
30
|
+
// idempotent on this navigation).
|
|
31
|
+
// - null — Neither anchor matched yet; loop.
|
|
32
|
+
// After BUTTON_POLL_TIMEOUT_MS with no terminal match, emit
|
|
33
|
+
// reason=authorize-button-not-found, exit 1.
|
|
19
34
|
//
|
|
20
35
|
// Exit codes:
|
|
21
|
-
// 0 - clicked
|
|
22
|
-
// 1 - authorize-button-not-found (
|
|
36
|
+
// 0 - terminal success (reason=clicked OR reason=cert-already-installed)
|
|
37
|
+
// 1 - authorize-button-not-found (loud failure — button AND modal absent)
|
|
23
38
|
// 2 - cdp-ws-unreachable or node-websocket-unavailable
|
|
24
39
|
// 3 - target-not-found (the target_id isn't in /json/list)
|
|
25
40
|
// 4 - click-evaluate-threw (Runtime.evaluate returned wasThrown=true)
|
|
26
41
|
// 5 - protocol-error (malformed CDP response)
|
|
27
42
|
//
|
|
28
|
-
//
|
|
29
|
-
// shape so setup-tunnel.sh's tee_subprocess picks it up into the stream log:
|
|
43
|
+
// Stdout contract (read by setup-tunnel.sh's awk-based reason parser):
|
|
30
44
|
// cdp-authorize result=<ok|error> reason=<…> elapsed_ms=<…> [detail=<…>]
|
|
45
|
+
// Exactly ONE such line per invocation; the wrapper takes the LAST
|
|
46
|
+
// `result=ok` line via awk so future debug-line additions cannot mis-route.
|
|
47
|
+
|
|
48
|
+
import { MATCH_EXPR } from './_cdp-authorize-matcher.mjs';
|
|
31
49
|
|
|
32
50
|
const CDP_HOST = '127.0.0.1';
|
|
33
51
|
const CDP_PORT = 9222;
|
|
@@ -211,35 +229,16 @@ try {
|
|
|
211
229
|
die(1, 'page-load-timeout', { detail: err.message, elapsed_ms: Date.now() - started });
|
|
212
230
|
}
|
|
213
231
|
|
|
214
|
-
// Poll
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
(()
|
|
220
|
-
const candidates = Array.from(document.querySelectorAll('button, input[type="submit"]'));
|
|
221
|
-
const match = candidates.find((el) => {
|
|
222
|
-
const text = (el.textContent ?? el.value ?? '').trim();
|
|
223
|
-
return /^(authorize|connect)$/i.test(text) && !el.disabled;
|
|
224
|
-
});
|
|
225
|
-
if (!match) return null;
|
|
226
|
-
const descriptor = {
|
|
227
|
-
tag: match.tagName.toLowerCase(),
|
|
228
|
-
text: (match.textContent ?? match.value ?? '').trim().slice(0, 40),
|
|
229
|
-
disabled: Boolean(match.disabled),
|
|
230
|
-
};
|
|
231
|
-
match.click();
|
|
232
|
-
return descriptor;
|
|
233
|
-
})()
|
|
234
|
-
`;
|
|
235
|
-
|
|
236
|
-
const clickDeadline = Date.now() + BUTTON_POLL_TIMEOUT_MS;
|
|
237
|
-
let clicked = null;
|
|
238
|
-
while (Date.now() < clickDeadline) {
|
|
232
|
+
// Poll the consent page until one of the three terminal states resolves.
|
|
233
|
+
// MATCH_EXPR is built from _cdp-authorize-matcher.mjs's findMatch so the
|
|
234
|
+
// JSDOM unit tests and the live CDP path execute literally identical logic.
|
|
235
|
+
const matchDeadline = Date.now() + BUTTON_POLL_TIMEOUT_MS;
|
|
236
|
+
let matched = null;
|
|
237
|
+
while (Date.now() < matchDeadline) {
|
|
239
238
|
let r;
|
|
240
239
|
try {
|
|
241
240
|
r = await send('Runtime.evaluate', {
|
|
242
|
-
expression:
|
|
241
|
+
expression: MATCH_EXPR,
|
|
243
242
|
returnByValue: true,
|
|
244
243
|
awaitPromise: false,
|
|
245
244
|
});
|
|
@@ -253,22 +252,33 @@ while (Date.now() < clickDeadline) {
|
|
|
253
252
|
});
|
|
254
253
|
}
|
|
255
254
|
const val = r?.result?.value;
|
|
256
|
-
if (val && typeof val === 'object') {
|
|
257
|
-
|
|
255
|
+
if (val && typeof val === 'object' && (val.kind === 'button' || val.kind === 'success')) {
|
|
256
|
+
matched = val;
|
|
258
257
|
break;
|
|
259
258
|
}
|
|
260
259
|
await new Promise((res) => setTimeout(res, BUTTON_POLL_INTERVAL_MS));
|
|
261
260
|
}
|
|
262
261
|
|
|
263
|
-
if (!
|
|
262
|
+
if (!matched) {
|
|
264
263
|
die(1, 'authorize-button-not-found', { elapsed_ms: Date.now() - started });
|
|
265
264
|
}
|
|
266
265
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
266
|
+
if (matched.kind === 'button') {
|
|
267
|
+
const d = matched.descriptor ?? {};
|
|
268
|
+
emit('ok', 'clicked', {
|
|
269
|
+
tag: d.tag,
|
|
270
|
+
text: d.text,
|
|
271
|
+
elapsed_ms: Date.now() - started,
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
// matched.kind === 'success' — Success modal text is on the page; the
|
|
275
|
+
// bound account already has a cert. Caller drops into the cert-poll loop;
|
|
276
|
+
// the in-flight cloudflared subprocess receives the idempotent callback
|
|
277
|
+
// and writes ~/.cloudflared/cert.pem.
|
|
278
|
+
emit('ok', 'cert-already-installed', {
|
|
279
|
+
elapsed_ms: Date.now() - started,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
272
282
|
|
|
273
283
|
try { ws.close(); } catch { /* best-effort */ }
|
|
274
284
|
process.exit(0);
|
|
@@ -60,22 +60,74 @@ CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
|
60
60
|
mkdir -p "${CFG_DIR}"
|
|
61
61
|
|
|
62
62
|
# --------------------------------------------------------------------------
|
|
63
|
-
# Step 1: OAuth login
|
|
63
|
+
# Step 1: OAuth login. Corresponds to runbook Step 1.
|
|
64
64
|
#
|
|
65
|
-
#
|
|
65
|
+
# Step 1 is a state machine over three observable variables — brand-scoped
|
|
66
|
+
# cert path, default cert path, and the helper's outcome (Task 855):
|
|
67
|
+
#
|
|
68
|
+
# ${CFG_DIR}/cert.pem present → Step 1 already done; skip.
|
|
69
|
+
# ${CFG_DIR}/cert.pem missing AND
|
|
70
|
+
# ~/.cloudflared/cert.pem present → pre-flight: a prior run
|
|
71
|
+
# reached the OAuth callback
|
|
72
|
+
# but failed before the mv;
|
|
73
|
+
# promote and skip the
|
|
74
|
+
# helper. Idempotent.
|
|
75
|
+
# both missing → spawn cloudflared, drive
|
|
76
|
+
# the consent page via CDP,
|
|
77
|
+
# poll for cert.pem, mv.
|
|
78
|
+
#
|
|
79
|
+
# Control flow when both certs are missing (Task 556 / 588 / 855):
|
|
66
80
|
# 1. CDP precheck on 127.0.0.1:9222 — loud failure if Chromium isn't up.
|
|
67
81
|
# 2. Spawn cloudflared with stdout+stderr teed line-by-line to
|
|
68
82
|
# $STREAM_LOG_PATH with prefix [script:setup-tunnel:cloudflared]
|
|
69
83
|
# (Task 605's chat-surface namespace — see _stream-log.sh header).
|
|
70
84
|
# 3. Extract the authorize URL with a tolerant regex as it streams.
|
|
71
85
|
# 4. Drive the VNC Chromium to that URL via CDP `PUT /json/new?<url>`.
|
|
72
|
-
# 5.
|
|
73
|
-
#
|
|
86
|
+
# 5. Run _cdp-authorize.mjs — tri-state matcher returns one of:
|
|
87
|
+
# reason=clicked → button matched + clicked
|
|
88
|
+
# reason=cert-already-installed → Success modal detected
|
|
89
|
+
# reason=authorize-button-not-found → loud failure, exit 1
|
|
90
|
+
# (a)+(b) drop into the cert-pem poll; (c) bubbles up.
|
|
91
|
+
# 6. Wait for ~/.cloudflared/cert.pem to land with bounded timeout.
|
|
92
|
+
# 7. Move cert.pem into the brand-scoped path.
|
|
74
93
|
#
|
|
75
|
-
# Every branch exits 1 loudly naming the
|
|
94
|
+
# Every failure branch exits 1 loudly naming the cause — no silent retries,
|
|
76
95
|
# no xdg-open race, no cloudflared-internal browser-spawn path.
|
|
77
96
|
# --------------------------------------------------------------------------
|
|
78
97
|
|
|
98
|
+
# Pre-flight cert-promotion (Task 855). When ${CFG_DIR}/cert.pem is missing
|
|
99
|
+
# but the OAuth-default ~/.cloudflared/cert.pem is present, a prior run
|
|
100
|
+
# reached the cloudflared callback but did not survive to the brand-scoped
|
|
101
|
+
# mv at the end of Step 1. The cert is bound to the account already; the
|
|
102
|
+
# only remediation is the move. Doing it before re-spawning cloudflared is
|
|
103
|
+
# what stops Step 1 from looping forever on a dashboard that has moved past
|
|
104
|
+
# a button-bearing state.
|
|
105
|
+
#
|
|
106
|
+
# The mv exit code is checked: if it fails (EACCES, ENOSPC, weird FS state),
|
|
107
|
+
# loud-fail with reason=cert-promote-failed instead of pretending Step 1
|
|
108
|
+
# succeeded — the next steps would die opaquely on the missing brand cert.
|
|
109
|
+
# stderr is captured into the phase_line so the operator sees the actual
|
|
110
|
+
# failure cause (mv exit-code 1 alone cannot disambiguate EACCES from ENOSPC).
|
|
111
|
+
if [ ! -f "${CFG_DIR}/cert.pem" ] && [ -f "${HOME}/.cloudflared/cert.pem" ]; then
|
|
112
|
+
MV_ERR="$(mktemp -t maxy-cert-promote-err.XXXXXX)"
|
|
113
|
+
if mv "${HOME}/.cloudflared/cert.pem" "${CFG_DIR}/cert.pem" 2>"${MV_ERR}"; then
|
|
114
|
+
rm -f "${MV_ERR}"
|
|
115
|
+
phase_line setup-tunnel step=oauth-login result=ok \
|
|
116
|
+
reason=cert-promoted-from-default-path waited="0s"
|
|
117
|
+
else
|
|
118
|
+
MV_RC=$?
|
|
119
|
+
MV_STDERR="$(tr '\n' ' ' < "${MV_ERR}" 2>/dev/null | head -c 200 || echo unavailable)"
|
|
120
|
+
rm -f "${MV_ERR}"
|
|
121
|
+
phase_line setup-tunnel step=oauth-login result=error \
|
|
122
|
+
reason=cert-promote-failed mv_rc="${MV_RC}" stderr="${MV_STDERR}" \
|
|
123
|
+
from="${HOME}/.cloudflared/cert.pem" to="${CFG_DIR}/cert.pem"
|
|
124
|
+
echo "ERROR: failed to promote ~/.cloudflared/cert.pem to ${CFG_DIR}/cert.pem (mv exit=${MV_RC})." >&2
|
|
125
|
+
echo " mv stderr: ${MV_STDERR}" >&2
|
|
126
|
+
echo " Step 1 cannot proceed without the cert in the brand-scoped path." >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
79
131
|
if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
80
132
|
phase_line setup-tunnel step=oauth-login cert_path="${CFG_DIR}/cert.pem" display="${DISPLAY:-:99}"
|
|
81
133
|
|
|
@@ -215,22 +267,61 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
215
267
|
echo "ERROR: _cdp-authorize.mjs helper missing or not executable at ${AUTH_HELPER}" >&2
|
|
216
268
|
exit 1
|
|
217
269
|
fi
|
|
218
|
-
#
|
|
219
|
-
# stream log under the setup-tunnel:cdp-click tag
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
270
|
+
# tee_subprocess_capture pipes the helper's `cdp-authorize result=…` line
|
|
271
|
+
# into the stream log under the setup-tunnel:cdp-click tag (live-tailable)
|
|
272
|
+
# AND through this shell's stdout so the wrapper can capture it for the
|
|
273
|
+
# tri-state reason discriminator (Task 855). The helper writes exactly
|
|
274
|
+
# one such line per invocation and exits; the shape is its contract:
|
|
275
|
+
# `cdp-authorize result=<ok|error> reason=<token> [k=v …]`.
|
|
276
|
+
# The wrapper's awk takes the LAST `cdp-authorize result=ok ` line so that
|
|
277
|
+
# any future emit() additions cannot mis-route the discriminator — the
|
|
278
|
+
# success line is always the last one before exit.
|
|
279
|
+
HELPER_OUT="$(mktemp -t maxy-cdp-authorize-out.XXXXXX)"
|
|
280
|
+
if tee_subprocess_capture setup-tunnel:cdp-click -- \
|
|
281
|
+
node "${AUTH_HELPER}" "${CDP_TARGET_ID}" \
|
|
282
|
+
> "${HELPER_OUT}"; then
|
|
283
|
+
HELPER_REASON="$(awk '/^cdp-authorize result=ok / {m=$0} END {if (m) {match(m, /reason=[a-z0-9-]+/); print substr(m, RSTART+7, RLENGTH-7)}}' "${HELPER_OUT}")"
|
|
284
|
+
rm -f "${HELPER_OUT}"
|
|
285
|
+
case "${HELPER_REASON}" in
|
|
286
|
+
clicked)
|
|
287
|
+
phase_line setup-tunnel step=browser-drive result=ok \
|
|
288
|
+
reason=clicked target_id="${CDP_TARGET_ID}"
|
|
289
|
+
;;
|
|
290
|
+
cert-already-installed)
|
|
291
|
+
# The dashboard rendered the Success modal — the bound account
|
|
292
|
+
# already has a cert. The OAuth callback URL was exercised by this
|
|
293
|
+
# navigation, so the in-flight cloudflared subprocess receives the
|
|
294
|
+
# idempotent callback and writes ~/.cloudflared/cert.pem; the poll
|
|
295
|
+
# below picks it up.
|
|
296
|
+
phase_line setup-tunnel step=browser-drive result=ok \
|
|
297
|
+
reason=cert-already-installed target_id="${CDP_TARGET_ID}"
|
|
298
|
+
;;
|
|
299
|
+
*)
|
|
300
|
+
# Helper exited 0 but emitted a reason this wrapper does not
|
|
301
|
+
# recognise — protocol drift between helper and wrapper. Loud-fail
|
|
302
|
+
# rather than silently fall through to the cert-poll, which would
|
|
303
|
+
# mask the cause if the cert never lands.
|
|
304
|
+
kill "${CF_PIPELINE_PID}" 2>/dev/null || true
|
|
305
|
+
phase_line setup-tunnel step=browser-drive result=error \
|
|
306
|
+
reason=helper-unknown-success-reason raw="${HELPER_REASON:-empty}"
|
|
307
|
+
echo "ERROR: CDP helper exited 0 with unrecognised reason: ${HELPER_REASON:-empty}" >&2
|
|
308
|
+
echo " Expected reason=clicked or reason=cert-already-installed." >&2
|
|
309
|
+
echo " Helper / wrapper protocol drift — investigate stream log." >&2
|
|
310
|
+
exit 1
|
|
311
|
+
;;
|
|
312
|
+
esac
|
|
225
313
|
else
|
|
226
314
|
CLICK_RC=$?
|
|
315
|
+
rm -f "${HELPER_OUT}"
|
|
227
316
|
kill "${CF_PIPELINE_PID}" 2>/dev/null || true
|
|
228
|
-
# Helper exit codes:
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
317
|
+
# Helper exit codes (mapped 1:1 to reason= so stream-log grep is precise;
|
|
318
|
+
# a catch-all reason would defeat the observability contract):
|
|
319
|
+
# 1 = authorize-button-not-found (loud — neither button nor success
|
|
320
|
+
# modal matched before BUTTON_POLL_TIMEOUT_MS; the form surfaces
|
|
321
|
+
# a verbatim quote of references/dashboard-guide.md "Authorise a
|
|
322
|
+
# new tunnel" so the operator can recover without SSH).
|
|
323
|
+
# 2 = cdp-ws-unreachable, 3 = target-not-found, 4 = click-evaluate-threw,
|
|
324
|
+
# 5 = protocol-error. All are final — no retry, no silent fallback.
|
|
234
325
|
case "${CLICK_RC}" in
|
|
235
326
|
1) CLICK_REASON=authorize-button-not-found ;;
|
|
236
327
|
2) CLICK_REASON=cdp-ws-unreachable ;;
|
|
@@ -242,8 +333,13 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
242
333
|
phase_line setup-tunnel step=browser-drive result=error \
|
|
243
334
|
reason="${CLICK_REASON}" click_rc="${CLICK_RC}"
|
|
244
335
|
echo "ERROR: CDP-driven Authorize click failed (helper exit=${CLICK_RC}, reason=${CLICK_REASON})." >&2
|
|
245
|
-
|
|
246
|
-
|
|
336
|
+
if [ "${CLICK_REASON}" = "authorize-button-not-found" ]; then
|
|
337
|
+
echo " The dashboard did not present an Authorize button or a Success modal." >&2
|
|
338
|
+
echo " The form will surface the dashboard click-path for manual recovery." >&2
|
|
339
|
+
else
|
|
340
|
+
echo " The CDP helper failed before reaching a terminal page state." >&2
|
|
341
|
+
echo " Re-run setup — or run ~/reset-tunnel.sh first if state is corrupt." >&2
|
|
342
|
+
fi
|
|
247
343
|
exit 1
|
|
248
344
|
fi
|
|
249
345
|
fi # end CDP-available branch (Task 664)
|
|
@@ -281,7 +377,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
281
377
|
echo "ERROR: Timed out after ${LOGIN_WAIT}s waiting for cert.pem to land." >&2
|
|
282
378
|
exit 1
|
|
283
379
|
fi
|
|
284
|
-
# Heartbeat every 2 s after the click. t=0 is the
|
|
380
|
+
# Heartbeat every 2 s after the click. t=0 is the browser-drive
|
|
285
381
|
# phase line above; the first heartbeat fires at t=2. Without this line
|
|
286
382
|
# the tailer sees silence for the full 1-20 s round-trip — the exact
|
|
287
383
|
# state Task 588 forbids.
|
|
@@ -294,7 +390,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
294
390
|
done
|
|
295
391
|
|
|
296
392
|
mv "${HOME}/.cloudflared/cert.pem" "${CFG_DIR}/cert.pem"
|
|
297
|
-
phase_line setup-tunnel step=oauth-login result=
|
|
393
|
+
phase_line setup-tunnel step=oauth-login result=ok \
|
|
298
394
|
path="${CFG_DIR}/cert.pem" waited="${LOGIN_WAIT}s"
|
|
299
395
|
fi
|
|
300
396
|
|
|
@@ -22,6 +22,8 @@ Any Cloudflare action outside these four surfaces is a discipline violation —
|
|
|
22
22
|
|
|
23
23
|
Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, and dispatches the `${BRAND}.service` restart to a transient `systemd-run` unit (Task 558) — all in one invocation. The restart fires a few seconds after the script exits so the script does not kill its own cgroup when invoked via the Bash tool; the chat UI receives a `server_shutdown` SSE frame and reconnects automatically. Post-restart hostname verification is out of scope for the script (connector is not up when the script exits) — verify via the next admin turn or manually with `curl -I https://<hostname>`. Apex hostnames cannot be routed by the CLI; when one is passed, the script prints an `ACTION REQUIRED` block naming the exact dashboard record to edit.
|
|
24
24
|
|
|
25
|
+
Step 1's OAuth flow is a state machine over three observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`), the OAuth-default cert path (`~/.cloudflared/cert.pem`), and the consent-page DOM via the CDP helper `_cdp-authorize.mjs` (Task 855). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the helper polls `dash.cloudflare.com/argotunnel` and resolves to one of three terminal outcomes — `reason=clicked` (button matched and clicked), `reason=cert-already-installed` (Success modal text detected — the bound account already has a cert, callback is idempotent so the in-flight cloudflared subprocess writes the cert anyway), or `reason=authorize-button-not-found` (neither anchor matched; loud failure, exit 1). The first two paths drop into the existing brand-scoped cert poll; the third surfaces a remediation card in the form so the operator can complete the consent click in their own browser without SSH or `~/reset-tunnel.sh`.
|
|
26
|
+
|
|
25
27
|
### How inputs reach the script
|
|
26
28
|
|
|
27
29
|
Inputs arrive through the `cloudflare-setup-form` component, not agent Q&A. The onboarding skill renders the form, the user submits admin/public/apex labels and the admin password in one action, and the `/api/admin/cloudflare/setup` endpoint runs (in this order):
|
|
@@ -50,6 +52,8 @@ The agent does not invoke the script directly during onboarding — the endpoint
|
|
|
50
52
|
|
|
51
53
|
The endpoint returns `{ ok: false, field: "script", message, output }` and the form surfaces the error inline. Relay the output to the user, name the exit code, and cite `references/reset-guide.md` for the next action. Offer to re-render the form after any manual steps the script's error output named. Do not attempt a second invocation outside the form, a Playwright-driven dashboard inspection, or an alternative `cloudflared` command sequence. The discipline rule below applies.
|
|
52
54
|
|
|
55
|
+
When the failure reason is `authorize-button-not-found` (Task 855), the form renders a verbatim quote of `references/dashboard-guide.md` § "Authorise a new tunnel (pick the right zone)" alongside a "Try again" button — the operator completes the consent click in their own browser, then re-submits the same form. No agent action is required; relay the form's chat acknowledgement when it arrives. Do not suggest `~/reset-tunnel.sh` for this reason — the cert path is intact and a fresh consent click is the only remediation needed.
|
|
56
|
+
|
|
53
57
|
---
|
|
54
58
|
|
|
55
59
|
## 2. Manual fallback — `references/manual-setup.md`
|
|
@@ -40,7 +40,7 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
40
40
|
| `waitlist` | Waitlist lifecycle — extract sign-ups from conversations, review | — |
|
|
41
41
|
| `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
|
|
42
42
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
43
|
-
| `whatsapp-import` | Import a WhatsApp `_chat.txt` export as a Conversation + chronologically-chained Messages
|
|
43
|
+
| `whatsapp-import` | Import a WhatsApp `_chat.txt` export as a Conversation + chronologically-chained Messages plus `:Observation` nodes for typed insights (mentions, tasks, preferences, observed relationships). Single Bash entry — `bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh <archive> --owner-element-id <id> --scope <admin\|public>` runs parse → archive-write → Haiku insight in one process. Distinct from the live `whatsapp` plugin which is a Baileys QR-pairing channel. | Database operator |
|
|
44
44
|
|
|
45
45
|
### Claude Official (marketplace)
|
|
46
46
|
|
|
@@ -14,7 +14,7 @@ Ingests a WhatsApp "Export Chat" archive (the `_chat.txt` file plus media attach
|
|
|
14
14
|
|
|
15
15
|
## When this applies
|
|
16
16
|
|
|
17
|
-
The admin agent delegates to `database-operator` when the operator drops a `_chat.txt` (or its containing folder) into chat. The specialist runs the skill's archive-owner
|
|
17
|
+
The admin agent delegates to `database-operator` when the operator drops a `_chat.txt` (or its containing folder) into chat. The specialist runs the skill's archive-owner confirmation flow before any line is written, then invokes the deterministic Bash entry (`bin/whatsapp-ingest.sh`) once: parse, archive-write (via `memoryArchiveWrite` in-process), and Haiku insight all run in one Node process — no MCP envelope between steps (Task 855).
|
|
18
18
|
|
|
19
19
|
## Accepted export shapes
|
|
20
20
|
|
|
@@ -28,6 +28,6 @@ WhatsApp's "Export Chat" emits `[DD/MM/YYYY, HH:MM:SS]` prefixes by default in m
|
|
|
28
28
|
|
|
29
29
|
## Relationship to other plugins
|
|
30
30
|
|
|
31
|
-
- **memory** — the underlying write surface
|
|
31
|
+
- **memory** — the underlying write surface imported in-process by `bin/ingest.mjs` (`memoryArchiveWrite` for bulk Conversation+Messages; direct Cypher `:Observation` writes for the insight pass). All writes carry `source='whatsapp'` + `createdByAgent='whatsapp-import'` provenance. The legacy `mcp__memory__whatsapp-export-parse` / `whatsapp-export-insight-write` MCP tools and the direct `memory-archive-write` MCP path with `archiveType=whatsapp-export` are blocked at the harness — the Bash entry is the only supported invocation surface (Task 855).
|
|
32
32
|
- **database-operator specialist** — owns execution. See [admin/IDENTITY.md](../../../platform/templates/agents/admin/IDENTITY.md) delegation clause and [database-operator.md](../../../platform/templates/specialists/agents/database-operator.md) per-source archive list.
|
|
33
33
|
- **linkedin-import** — sister plugin under the same pattern (LinkedIn Basic Data Export). Reading [linkedin-import/PLUGIN.md](../linkedin-import/PLUGIN.md) is the fastest way to understand the shape this plugin follows.
|