@rubytech/create-maxy-code 0.1.593 → 0.1.594
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 +2 -2
- package/payload/platform/package.json +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -3
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +5 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +2 -0
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/telegram-guide.md +3 -1
- package/payload/platform/scripts/__tests__/agent-identity-reconcile.test.sh +110 -0
- package/payload/platform/scripts/__tests__/check-orphan-dist.test.sh +87 -0
- package/payload/platform/scripts/check-orphan-dist.mjs +143 -0
- package/payload/platform/scripts/lib/agent-identity-reconcile.sh +122 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +8 -26
- package/payload/platform/scripts/setup-account.sh +15 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +5 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +42 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
- package/payload/platform/templates/agents/public/IDENTITY.md +3 -3
- package/payload/platform/lib/anthropic-key/dist/index.d.ts +0 -22
- package/payload/platform/lib/anthropic-key/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/anthropic-key/dist/index.js +0 -232
- package/payload/platform/lib/anthropic-key/dist/index.js.map +0 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +0 -37
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +0 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.js +0 -101
- package/payload/platform/lib/graph-search/dist/query-expansion.js.map +0 -1
- package/payload/platform/lib/oauth-llm/dist/index.d.ts +0 -116
- package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/oauth-llm/dist/index.js +0 -386
- package/payload/platform/lib/oauth-llm/dist/index.js.map +0 -1
- package/payload/platform/lib/screening-patterns/dist/index.d.ts +0 -29
- package/payload/platform/lib/screening-patterns/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/screening-patterns/dist/index.js +0 -48
- package/payload/platform/lib/screening-patterns/dist/index.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts +0 -2
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js +0 -34
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts +0 -39
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js +0 -249
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts +0 -6
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js +0 -32
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +0 -283
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +0 -1155
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +0 -90
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +0 -551
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts +0 -17
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js +0 -186
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts +0 -29
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js +0 -105
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +0 -38
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +0 -817
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +0 -30
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +0 -215
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts +0 -19
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js +0 -151
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts +0 -6
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js +0 -89
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js +0 -90
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +0 -226
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +0 -60
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +0 -169
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +0 -248
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +0 -824
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +0 -63
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +0 -210
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +0 -35
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +0 -28
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js +0 -68
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js +0 -88
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js +0 -58
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js +0 -64
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js +0 -70
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js +0 -48
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js +0 -74
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js +0 -47
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js +0 -65
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js +0 -43
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js +0 -65
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js +0 -49
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +0 -79
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js +0 -329
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +0 -81
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +0 -34
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +0 -62
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts +0 -61
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js +0 -102
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts +0 -35
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js +0 -73
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +0 -29
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +0 -22
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +0 -30
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +0 -130
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +0 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +0 -65
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +0 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +0 -262
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js +0 -100
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js +0 -22
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts +0 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js +0 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.d.ts +0 -2
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.d.ts.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.js +0 -196
- package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.js.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts +0 -70
- package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js +0 -247
- package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +0 -21
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +0 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +0 -40
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +0 -1
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts +0 -8
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts.map +0 -1
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js +0 -83
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js.map +0 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts +0 -151
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts.map +0 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js +0 -299
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts +0 -39
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.js +0 -122
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts +0 -39
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js +0 -133
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts +0 -22
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.js +0 -50
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts +0 -30
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js +0 -204
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts +0 -80
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +0 -306
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +0 -49
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.js +0 -52
- package/payload/platform/services/claude-session-manager/dist/session-store.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts +0 -83
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js +0 -345
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js.map +0 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +0 -104
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +0 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.js +0 -408
- package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +0 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +0 -104
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +0 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +0 -393
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +0 -1
|
@@ -1,1155 +0,0 @@
|
|
|
1
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, openSync, unlinkSync, copyFileSync, statSync, } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { Resolver } from "node:dns/promises";
|
|
6
|
-
let cachedBrand = null;
|
|
7
|
-
export function loadBrand() {
|
|
8
|
-
if (cachedBrand)
|
|
9
|
-
return cachedBrand;
|
|
10
|
-
const platformRoot = process.env.PLATFORM_ROOT;
|
|
11
|
-
if (!platformRoot) {
|
|
12
|
-
throw new Error("PLATFORM_ROOT env var is not set — cannot determine brand configuration");
|
|
13
|
-
}
|
|
14
|
-
const brandPath = join(platformRoot, "config", "brand.json");
|
|
15
|
-
if (!existsSync(brandPath)) {
|
|
16
|
-
throw new Error(`brand.json not found at ${brandPath} — PLATFORM_ROOT may be incorrect`);
|
|
17
|
-
}
|
|
18
|
-
const parsed = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
19
|
-
cachedBrand = {
|
|
20
|
-
configDir: parsed.configDir ?? ".maxy",
|
|
21
|
-
productName: parsed.productName ?? "Maxy",
|
|
22
|
-
vncDisplay: typeof parsed.vncDisplay === "number" ? parsed.vncDisplay : 99,
|
|
23
|
-
};
|
|
24
|
-
return cachedBrand;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Test-only: reset the cached brand so a subsequent loadBrand() re-reads
|
|
28
|
-
* the manifest. Production code never calls this.
|
|
29
|
-
*/
|
|
30
|
-
export function _resetBrandCache() {
|
|
31
|
-
cachedBrand = null;
|
|
32
|
-
}
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Binary detection
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
let cachedBinaryPath = null;
|
|
37
|
-
const SEARCH_PATHS = [
|
|
38
|
-
"/usr/local/bin/cloudflared",
|
|
39
|
-
"/usr/bin/cloudflared",
|
|
40
|
-
join(homedir(), ".local/bin/cloudflared"),
|
|
41
|
-
];
|
|
42
|
-
export function findBinary() {
|
|
43
|
-
if (cachedBinaryPath)
|
|
44
|
-
return cachedBinaryPath;
|
|
45
|
-
try {
|
|
46
|
-
cachedBinaryPath = execFileSync("which", ["cloudflared"], {
|
|
47
|
-
encoding: "utf-8",
|
|
48
|
-
timeout: 5000,
|
|
49
|
-
}).trim();
|
|
50
|
-
return cachedBinaryPath;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// not on PATH
|
|
54
|
-
}
|
|
55
|
-
for (const p of SEARCH_PATHS) {
|
|
56
|
-
if (existsSync(p)) {
|
|
57
|
-
cachedBinaryPath = p;
|
|
58
|
-
return p;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
export function isInstalled() {
|
|
64
|
-
return findBinary() !== null;
|
|
65
|
-
}
|
|
66
|
-
export function version() {
|
|
67
|
-
const bin = findBinary();
|
|
68
|
-
if (!bin)
|
|
69
|
-
return null;
|
|
70
|
-
try {
|
|
71
|
-
const out = execFileSync(bin, ["--version"], {
|
|
72
|
-
encoding: "utf-8",
|
|
73
|
-
timeout: 5000,
|
|
74
|
-
});
|
|
75
|
-
const match = out.match(/cloudflared version (\S+)/);
|
|
76
|
-
return match?.[1] ?? out.trim();
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function bindingDir() {
|
|
83
|
-
return join(homedir(), loadBrand().configDir, "cloudflare");
|
|
84
|
-
}
|
|
85
|
-
function bindingFile() {
|
|
86
|
-
return join(bindingDir(), "account-binding.json");
|
|
87
|
-
}
|
|
88
|
-
export function readAccountBinding() {
|
|
89
|
-
try {
|
|
90
|
-
const path = bindingFile();
|
|
91
|
-
if (!existsSync(path))
|
|
92
|
-
return null;
|
|
93
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
94
|
-
if (typeof parsed?.accountId !== "string" || typeof parsed?.boundAt !== "string") {
|
|
95
|
-
console.error(`[cloudflare:binding] malformed account-binding.json at ${path} — treating as absent`);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
return { accountId: parsed.accountId, boundAt: parsed.boundAt };
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
console.error(`[cloudflare:binding] failed to read ${bindingFile()}: ${err}`);
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* @internal — production code MUST go through `materializeBinding` so the
|
|
107
|
-
* source-of-truth lifecycle (fresh-login vs migration) is preserved and
|
|
108
|
-
* logged. Exported only so unit tests can pre-seed bindings without
|
|
109
|
-
* re-implementing the file format.
|
|
110
|
-
*/
|
|
111
|
-
export function writeAccountBinding(accountId) {
|
|
112
|
-
const dir = bindingDir();
|
|
113
|
-
mkdirSync(dir, { recursive: true });
|
|
114
|
-
const binding = { accountId, boundAt: new Date().toISOString() };
|
|
115
|
-
writeFileSync(bindingFile(), JSON.stringify(binding, null, 2), { mode: 0o600 });
|
|
116
|
-
return binding;
|
|
117
|
-
}
|
|
118
|
-
function resetAccountBinding() {
|
|
119
|
-
const path = bindingFile();
|
|
120
|
-
try {
|
|
121
|
-
unlinkSync(path);
|
|
122
|
-
console.error(`[cloudflare:binding] reset ${path}`);
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
const code = err.code;
|
|
127
|
-
if (code === "ENOENT")
|
|
128
|
-
return false;
|
|
129
|
-
console.error(`[cloudflare:binding] failed to reset ${path}: ${err}`);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
export function logRefuse(detail) {
|
|
134
|
-
const brand = (() => {
|
|
135
|
-
try {
|
|
136
|
-
return loadBrand();
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
})();
|
|
142
|
-
const f = detail.fields ?? {};
|
|
143
|
-
const fields = {
|
|
144
|
-
reason: detail.reason,
|
|
145
|
-
brand: brand?.productName ?? "unknown",
|
|
146
|
-
boundAccountId: f.boundAccountId ?? null,
|
|
147
|
-
certAccountId: f.certAccountId ?? null,
|
|
148
|
-
requestedDomain: f.requestedDomain ?? null,
|
|
149
|
-
requestedHostname: f.requestedHostname ?? null,
|
|
150
|
-
actualFqdn: f.actualFqdn ?? null,
|
|
151
|
-
tunnelId: f.tunnelId ?? null,
|
|
152
|
-
};
|
|
153
|
-
console.error(`[cloudflare:refuse] ${JSON.stringify(fields)}`);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* The only recovery instruction — every refusal that involves the wrong
|
|
157
|
-
* Cloudflare account points here. Single source of truth so the four
|
|
158
|
-
* surfaces that quote it (tunnel-status, tunnel-add-hostname, personal
|
|
159
|
-
* assistant template, setup-tunnel skill) cannot drift.
|
|
160
|
-
*/
|
|
161
|
-
export const DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION = "Open Cloudflare in your browser. Look at the name in the top-left — " +
|
|
162
|
-
"that is the Cloudflare account this laptop will talk to. If it is not " +
|
|
163
|
-
"the account that owns the domain you want to use, switch accounts using " +
|
|
164
|
-
"the top-left dropdown. Once the correct account is showing, tell me and " +
|
|
165
|
-
"I will start the sign-in again.";
|
|
166
|
-
export function recoveryMessage() {
|
|
167
|
-
return ("Recovery: confirm you are signed into the correct Cloudflare account " +
|
|
168
|
-
"in your browser, then ask me to run tunnel-login. If you recently switched " +
|
|
169
|
-
"accounts, ask me to run tunnel-login with force=true so the current sign-in " +
|
|
170
|
-
"is cleared before the new one starts.");
|
|
171
|
-
}
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
// Reset auth — unlinks cert.pem (both paths) and the account binding
|
|
174
|
-
//
|
|
175
|
-
// Kill order matters: terminate any in-flight `cloudflared tunnel login`
|
|
176
|
-
// process BEFORE deleting cert state, otherwise its browser handshake can
|
|
177
|
-
// complete between our unlink and return, writing a fresh cert.pem to the
|
|
178
|
-
// legacy path that findCert() would silently re-import on the next read.
|
|
179
|
-
// The kill-then-unlink ordering preserves the same race-close pattern.
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
export function resetAuth() {
|
|
182
|
-
const result = { deletedCert: false, deletedBinding: false };
|
|
183
|
-
const loginState = readLoginState();
|
|
184
|
-
if (loginState?.pid && isProcessAlive(loginState.pid)) {
|
|
185
|
-
try {
|
|
186
|
-
process.kill(loginState.pid, "SIGTERM");
|
|
187
|
-
console.error(`[tunnel-login] killed stale login process PID ${loginState.pid}`);
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
// Process may have exited between check and kill
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
clearLoginState();
|
|
194
|
-
const cp = certPath();
|
|
195
|
-
try {
|
|
196
|
-
unlinkSync(cp);
|
|
197
|
-
result.deletedCert = true;
|
|
198
|
-
console.error(`[tunnel-login] deleted: ${cp}`);
|
|
199
|
-
}
|
|
200
|
-
catch (err) {
|
|
201
|
-
const code = err.code;
|
|
202
|
-
if (code === "ENOENT") {
|
|
203
|
-
// File already gone — no-op
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
console.error(`[tunnel-login] failed to delete cert.pem ${cp}: ${err}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const legacyCp = legacyCertPath();
|
|
210
|
-
try {
|
|
211
|
-
unlinkSync(legacyCp);
|
|
212
|
-
console.error(`[tunnel-login] deleted legacy cert.pem: ${legacyCp}`);
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
const code = err.code;
|
|
216
|
-
if (code === "ENOENT") {
|
|
217
|
-
console.error(`[tunnel-login] legacy cert.pem absent: ${legacyCp}`);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
console.error(`[tunnel-login] failed to delete legacy cert.pem ${legacyCp}: ${err}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
result.deletedBinding = resetAccountBinding();
|
|
224
|
-
return result;
|
|
225
|
-
}
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// cert.pem — written by `cloudflared tunnel login`
|
|
228
|
-
//
|
|
229
|
-
// Stored under ~/{configDir}/cloudflared/ so each brand has its own cert.
|
|
230
|
-
// Function (not const) because loadBrand() requires PLATFORM_ROOT at runtime.
|
|
231
|
-
// ---------------------------------------------------------------------------
|
|
232
|
-
function certPath() {
|
|
233
|
-
return join(homedir(), loadBrand().configDir, "cloudflared/cert.pem");
|
|
234
|
-
}
|
|
235
|
-
// cloudflared tunnel login always writes here regardless of --origincert.
|
|
236
|
-
// Same literal lives in the installer's cloudflared migration block.
|
|
237
|
-
function legacyCertPath() {
|
|
238
|
-
return join(homedir(), ".cloudflared", "cert.pem");
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Find cert.pem, checking the brand-specific path first, then cloudflared's
|
|
242
|
-
* default location. cloudflared tunnel login ALWAYS writes to
|
|
243
|
-
* ~/.cloudflared/cert.pem regardless of --origincert, so we must check
|
|
244
|
-
* there as a fallback.
|
|
245
|
-
*
|
|
246
|
-
* Move-on-read: when the cert is found at the legacy path, copy it to the
|
|
247
|
-
* brand path AND unlink the legacy file. Without the unlink, the legacy
|
|
248
|
-
* file would survive `tunnel-login force=true` (which only deletes brand
|
|
249
|
-
* paths) and silently re-import on the next read — defeating the recovery
|
|
250
|
-
* path operators rely on after switching Cloudflare accounts.
|
|
251
|
-
*/
|
|
252
|
-
function findCert() {
|
|
253
|
-
const brandPath = certPath();
|
|
254
|
-
if (existsSync(brandPath))
|
|
255
|
-
return brandPath;
|
|
256
|
-
const defaultPath = legacyCertPath();
|
|
257
|
-
if (existsSync(defaultPath)) {
|
|
258
|
-
const dir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
259
|
-
mkdirSync(dir, { recursive: true });
|
|
260
|
-
copyFileSync(defaultPath, brandPath);
|
|
261
|
-
try {
|
|
262
|
-
unlinkSync(defaultPath);
|
|
263
|
-
console.error(`[tunnel-login] cert.pem migrated from ${defaultPath} to ${brandPath}`);
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
console.error(`[tunnel-login] WARNING: cert.pem migrated to ${brandPath} but legacy ${defaultPath} could not be removed: ${err}`);
|
|
267
|
-
}
|
|
268
|
-
return brandPath;
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
export function hasCert() {
|
|
273
|
-
return findCert() !== null;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Parse the ARGO TUNNEL TOKEN PEM block from cert.pem to extract the
|
|
277
|
-
* AccountTag. Only the account ID is returned — nothing else in cert.pem
|
|
278
|
-
* is used by this codebase. The cert itself stays on disk; `cloudflared`
|
|
279
|
-
* shell-outs read it via `--origincert` for tunnel CRUD and DNS routing.
|
|
280
|
-
*/
|
|
281
|
-
export function parseCertPem() {
|
|
282
|
-
try {
|
|
283
|
-
const cert = findCert();
|
|
284
|
-
if (!cert)
|
|
285
|
-
return null;
|
|
286
|
-
const pem = readFileSync(cert, "utf-8");
|
|
287
|
-
const beginMarker = "-----BEGIN ARGO TUNNEL TOKEN-----";
|
|
288
|
-
const endMarker = "-----END ARGO TUNNEL TOKEN-----";
|
|
289
|
-
const beginIdx = pem.indexOf(beginMarker);
|
|
290
|
-
const endIdx = pem.indexOf(endMarker);
|
|
291
|
-
if (beginIdx === -1 || endIdx === -1)
|
|
292
|
-
return null;
|
|
293
|
-
const b64 = pem
|
|
294
|
-
.slice(beginIdx + beginMarker.length, endIdx)
|
|
295
|
-
.replace(/\s+/g, "");
|
|
296
|
-
const json = Buffer.from(b64, "base64").toString("utf-8");
|
|
297
|
-
const data = JSON.parse(json);
|
|
298
|
-
const accountId = data.AccountTag ?? data.accountTag ?? data.AccountID ?? data.accountID ?? data.accountId;
|
|
299
|
-
if (!accountId)
|
|
300
|
-
return null;
|
|
301
|
-
return { accountId };
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
/** Wraps a structured RefusalDetail so call sites can branch on `reason`. */
|
|
308
|
-
export class CloudflareRefusalError extends Error {
|
|
309
|
-
refusal;
|
|
310
|
-
constructor(detail) {
|
|
311
|
-
super(detail.message);
|
|
312
|
-
this.name = "CloudflareRefusalError";
|
|
313
|
-
this.refusal = detail;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
export function validateAuth() {
|
|
317
|
-
const creds = parseCertPem();
|
|
318
|
-
const binding = readAccountBinding();
|
|
319
|
-
return {
|
|
320
|
-
hasCert: !!creds,
|
|
321
|
-
hasBinding: !!binding,
|
|
322
|
-
bound: !!(creds && binding && creds.accountId === binding.accountId),
|
|
323
|
-
certAccountId: creds?.accountId ?? null,
|
|
324
|
-
boundAccountId: binding?.accountId ?? null,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Establish the device-local account binding from cert.pem. Used by:
|
|
329
|
-
* (1) tunnel-login fresh-success path — first time creds are derived.
|
|
330
|
-
* (2) migration path — when cert.pem exists from a prior install but no
|
|
331
|
-
* binding has yet been written (silent materialization, since the
|
|
332
|
-
* cert was already trust-established by the operator's prior login).
|
|
333
|
-
*/
|
|
334
|
-
export function materializeBinding(source) {
|
|
335
|
-
const creds = parseCertPem();
|
|
336
|
-
if (!creds) {
|
|
337
|
-
throw new Error("Cannot materialize binding — cert.pem absent or unparseable");
|
|
338
|
-
}
|
|
339
|
-
const binding = writeAccountBinding(creds.accountId);
|
|
340
|
-
console.error(`[cloudflare:binding-materialized] source=${source} accountId=${creds.accountId} boundAt=${binding.boundAt}`);
|
|
341
|
-
return binding;
|
|
342
|
-
}
|
|
343
|
-
// Alias-domains read/write lived here historically; the MCP tool that wrote
|
|
344
|
-
// it was removed, and the writer moved to
|
|
345
|
-
// platform/ui/app/lib/alias-domains.ts so the reader (server/index.ts
|
|
346
|
-
// watchFile loop) and the writer share one process. The file on disk
|
|
347
|
-
// (~/{configDir}/alias-domains.json) is still the interchange format.
|
|
348
|
-
// ---------------------------------------------------------------------------
|
|
349
|
-
// CLI-based tunnel operations
|
|
350
|
-
//
|
|
351
|
-
// `cloudflared` CLI uses cert.pem directly for tunnel CRUD and DNS routing.
|
|
352
|
-
// Shelling out to the CLI is architecturally indistinguishable from the
|
|
353
|
-
// operator clicking the same action in the dashboard — both use the
|
|
354
|
-
// operator's OAuth-bound cert, nothing more.
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
|
-
// Name convention: `{product}-{device}` — e.g. "maxy-laptop". Encodes product identity and physical location in one label.
|
|
357
|
-
export function createTunnelCli(name) {
|
|
358
|
-
const bin = findBinary();
|
|
359
|
-
if (!bin)
|
|
360
|
-
throw new Error("cloudflared is not installed");
|
|
361
|
-
const cert = findCert();
|
|
362
|
-
if (!cert)
|
|
363
|
-
throw new Error("No cert.pem found — run tunnel-login first");
|
|
364
|
-
// Check for existing tunnel with this name first
|
|
365
|
-
try {
|
|
366
|
-
const listOutput = execFileSync(bin, ["tunnel", "--origincert", cert, "list", "-o", "json"], { encoding: "utf-8", timeout: 30000 });
|
|
367
|
-
const tunnels = JSON.parse(listOutput);
|
|
368
|
-
const existing = tunnels.find((t) => t.name === name && !t.deleted_at);
|
|
369
|
-
if (existing) {
|
|
370
|
-
console.error(`[tunnel-create] CLI: tunnel "${name}" already exists (${existing.id})`);
|
|
371
|
-
const brandCredPath = join(homedir(), loadBrand().configDir, "cloudflared", `${existing.id}.json`);
|
|
372
|
-
const defaultCredPath = join(homedir(), ".cloudflared", `${existing.id}.json`);
|
|
373
|
-
if (!existsSync(brandCredPath) && existsSync(defaultCredPath)) {
|
|
374
|
-
const dir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
375
|
-
mkdirSync(dir, { recursive: true });
|
|
376
|
-
copyFileSync(defaultCredPath, brandCredPath);
|
|
377
|
-
console.error(`[tunnel-create] credentials copied to ${brandCredPath}`);
|
|
378
|
-
}
|
|
379
|
-
return {
|
|
380
|
-
tunnelId: existing.id,
|
|
381
|
-
tunnelName: name,
|
|
382
|
-
credentialsPath: brandCredPath,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
// list failed — proceed to create
|
|
388
|
-
}
|
|
389
|
-
const output = execFileSync(bin, ["tunnel", "--origincert", cert, "create", name], { encoding: "utf-8", timeout: 30000 });
|
|
390
|
-
console.error(`[tunnel-create] CLI: ${output.trim()}`);
|
|
391
|
-
// Parse tunnel ID from output: "Created tunnel <name> with id <uuid>"
|
|
392
|
-
const idMatch = output.match(/with id ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
393
|
-
if (!idMatch) {
|
|
394
|
-
throw new Error(`cloudflared tunnel create succeeded but could not parse tunnel ID from output: ${output.trim()}`);
|
|
395
|
-
}
|
|
396
|
-
const tunnelId = idMatch[1];
|
|
397
|
-
// cloudflared writes credentials to ~/.cloudflared/<id>.json — copy to brand path
|
|
398
|
-
const defaultCredPath = join(homedir(), ".cloudflared", `${tunnelId}.json`);
|
|
399
|
-
const brandDir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
400
|
-
mkdirSync(brandDir, { recursive: true });
|
|
401
|
-
const brandCredPath = join(brandDir, `${tunnelId}.json`);
|
|
402
|
-
if (existsSync(defaultCredPath)) {
|
|
403
|
-
copyFileSync(defaultCredPath, brandCredPath);
|
|
404
|
-
console.error(`[tunnel-create] credentials copied to ${brandCredPath}`);
|
|
405
|
-
}
|
|
406
|
-
return { tunnelId, tunnelName: name, credentialsPath: brandCredPath };
|
|
407
|
-
}
|
|
408
|
-
export function getTunnelTokenCli(tunnelId) {
|
|
409
|
-
const bin = findBinary();
|
|
410
|
-
if (!bin)
|
|
411
|
-
throw new Error("cloudflared is not installed");
|
|
412
|
-
const cert = findCert();
|
|
413
|
-
if (!cert)
|
|
414
|
-
throw new Error("No cert.pem found — run tunnel-login first");
|
|
415
|
-
const output = execFileSync(bin, ["tunnel", "--origincert", cert, "token", tunnelId], { encoding: "utf-8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
416
|
-
const token = output.trim();
|
|
417
|
-
if (!token) {
|
|
418
|
-
throw new Error("cloudflared tunnel token returned empty output");
|
|
419
|
-
}
|
|
420
|
-
console.error(`[tunnel-token] CLI: obtained connector token for tunnel ${tunnelId}`);
|
|
421
|
-
return token;
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Write a local config.yml for cloudflared tunnel run.
|
|
425
|
-
* This is the ONLY tunnel startup configuration — no remote config, no API.
|
|
426
|
-
* hostnames: the actual FQDNs to route (e.g. ["joel.maxy.bot", "shop.maxy.bot"]).
|
|
427
|
-
*/
|
|
428
|
-
export function writeLocalConfig(tunnelId, credentialsPath, hostnames, port) {
|
|
429
|
-
const dir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
430
|
-
mkdirSync(dir, { recursive: true });
|
|
431
|
-
const configPath = join(dir, "config.yml");
|
|
432
|
-
const ingressLines = hostnames.flatMap((h) => [
|
|
433
|
-
` - hostname: ${h}`,
|
|
434
|
-
` service: http://localhost:${port}`,
|
|
435
|
-
]);
|
|
436
|
-
const yaml = [
|
|
437
|
-
`tunnel: ${tunnelId}`,
|
|
438
|
-
`credentials-file: ${credentialsPath}`,
|
|
439
|
-
`ingress:`,
|
|
440
|
-
...ingressLines,
|
|
441
|
-
` - service: http_status:404`,
|
|
442
|
-
"",
|
|
443
|
-
].join("\n");
|
|
444
|
-
writeFileSync(configPath, yaml, "utf-8");
|
|
445
|
-
console.error(`[tunnel-create] config written to ${configPath} hostnames=${JSON.stringify(hostnames)}`);
|
|
446
|
-
return configPath;
|
|
447
|
-
}
|
|
448
|
-
// ---------------------------------------------------------------------------
|
|
449
|
-
// Parse configured hostnames from config.yml
|
|
450
|
-
//
|
|
451
|
-
// `tunnel-status`'s end-to-end probe reads the hostnames from config.yml
|
|
452
|
-
// rather than from tunnel.state — config.yml is the authoritative source
|
|
453
|
-
// for what `cloudflared` is actually ingressing. State-file hostnames may
|
|
454
|
-
// drift from on-disk config; the running tunnel only honours config.yml.
|
|
455
|
-
// ---------------------------------------------------------------------------
|
|
456
|
-
export function parseConfiguredHostnames(configYmlPath) {
|
|
457
|
-
try {
|
|
458
|
-
if (!existsSync(configYmlPath))
|
|
459
|
-
return [];
|
|
460
|
-
const yaml = readFileSync(configYmlPath, "utf-8");
|
|
461
|
-
const hostnames = [];
|
|
462
|
-
for (const line of yaml.split("\n")) {
|
|
463
|
-
const m = line.match(/^\s*-\s*hostname:\s*(\S+)\s*$/);
|
|
464
|
-
if (m)
|
|
465
|
-
hostnames.push(m[1]);
|
|
466
|
-
}
|
|
467
|
-
return hostnames;
|
|
468
|
-
}
|
|
469
|
-
catch (err) {
|
|
470
|
-
console.error(`[tunnel-status] failed to parse ${configYmlPath}: ${err}`);
|
|
471
|
-
return [];
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
// ---------------------------------------------------------------------------
|
|
475
|
-
// Nameserver pre-flight — zone-parent routability without any API
|
|
476
|
-
//
|
|
477
|
-
// Before shelling `cloudflared tunnel route dns`, confirm the hostname's
|
|
478
|
-
// registrable parent has its NS records pointing at Cloudflare. When they
|
|
479
|
-
// don't, `cloudflared` silently falls through to whichever zone the cert's
|
|
480
|
-
// account does own and creates a malformed CNAME (the historical
|
|
481
|
-
// `admin.maxy.bot.joelsmalley.xyz` failure mode). Refusing pre-flight is
|
|
482
|
-
// the only way to prevent that drift from inside this codebase.
|
|
483
|
-
//
|
|
484
|
-
// Heuristic for registrable parent: take the last two labels. This fails
|
|
485
|
-
// for multi-label public suffixes (e.g. `.co.uk`, `.com.au`) — not worth
|
|
486
|
-
// correctness here because Cloudflare's own sign-up flow rejects those
|
|
487
|
-
// at zone-add time, so the NS lookup will also fail for them, giving the
|
|
488
|
-
// same "not on Cloudflare yet" message.
|
|
489
|
-
// ---------------------------------------------------------------------------
|
|
490
|
-
function registrableParent(hostname) {
|
|
491
|
-
const labels = hostname.toLowerCase().split(".");
|
|
492
|
-
if (labels.length <= 2)
|
|
493
|
-
return hostname.toLowerCase();
|
|
494
|
-
return labels.slice(-2).join(".");
|
|
495
|
-
}
|
|
496
|
-
export async function checkZoneParentOnCloudflare(hostname) {
|
|
497
|
-
const zoneParent = registrableParent(hostname);
|
|
498
|
-
const resolver = new Resolver();
|
|
499
|
-
resolver.setServers(["1.1.1.1", "8.8.8.8"]);
|
|
500
|
-
try {
|
|
501
|
-
const ns = await resolver.resolveNs(zoneParent);
|
|
502
|
-
const onCloudflare = ns.some((n) => /\.ns\.cloudflare\.com\.?$/i.test(n));
|
|
503
|
-
return { zoneParent, nameservers: ns, onCloudflare };
|
|
504
|
-
}
|
|
505
|
-
catch (err) {
|
|
506
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
507
|
-
return { zoneParent, nameservers: [], onCloudflare: false, error: msg };
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// ---------------------------------------------------------------------------
|
|
511
|
-
// CLI-based DNS routing — shells `cloudflared tunnel route dns` with defence
|
|
512
|
-
// in depth against the known malformed-CNAME fall-through.
|
|
513
|
-
//
|
|
514
|
-
// (1) Pre-flight: zone-parent NS records must point to Cloudflare.
|
|
515
|
-
// Refusal reason `hostname-zone-not-routable` tells the operator
|
|
516
|
-
// to add the domain in the dashboard before coming back.
|
|
517
|
-
// (2) Post-flight: the FQDN `cloudflared` wrote must equal the requested
|
|
518
|
-
// hostname. A mismatch means `cloudflared` routed to a zone owned by
|
|
519
|
-
// the currently-bound account instead of the intended zone — usually
|
|
520
|
-
// because the device signed into the wrong Cloudflare account.
|
|
521
|
-
// Refusal reason `post-flight-fqdn-mismatch` tells the operator to
|
|
522
|
-
// check the dashboard account name and re-login if wrong.
|
|
523
|
-
//
|
|
524
|
-
// Authentication pre-condition is just "cert.pem + binding + they agree" —
|
|
525
|
-
// enforced by validateAuth().bound. No API client is constructed anywhere.
|
|
526
|
-
// ---------------------------------------------------------------------------
|
|
527
|
-
export async function routeDnsCli(tunnelId, hostname) {
|
|
528
|
-
const bin = findBinary();
|
|
529
|
-
if (!bin)
|
|
530
|
-
throw new Error("cloudflared is not installed");
|
|
531
|
-
const auth = validateAuth();
|
|
532
|
-
if (!auth.bound) {
|
|
533
|
-
const reason = auth.hasCert && auth.hasBinding
|
|
534
|
-
? "account-drift"
|
|
535
|
-
: "unbound-device";
|
|
536
|
-
const detail = {
|
|
537
|
-
reason,
|
|
538
|
-
message: `This laptop is not signed into Cloudflare yet${auth.hasCert ? " (sign-in has drifted since it was set up)" : ""}. ` +
|
|
539
|
-
`${DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION}`,
|
|
540
|
-
fields: {
|
|
541
|
-
boundAccountId: auth.boundAccountId,
|
|
542
|
-
certAccountId: auth.certAccountId,
|
|
543
|
-
requestedHostname: hostname,
|
|
544
|
-
},
|
|
545
|
-
};
|
|
546
|
-
logRefuse(detail);
|
|
547
|
-
throw new CloudflareRefusalError(detail);
|
|
548
|
-
}
|
|
549
|
-
// (1) Pre-flight zone-parent routability.
|
|
550
|
-
console.error(`[cloudflare:tunnel-add-hostname:preflight] hostname=${hostname}`);
|
|
551
|
-
const preflight = await checkZoneParentOnCloudflare(hostname);
|
|
552
|
-
if (!preflight.onCloudflare) {
|
|
553
|
-
const detail = {
|
|
554
|
-
reason: "hostname-zone-not-routable",
|
|
555
|
-
message: `The domain ${preflight.zoneParent} is not on Cloudflare yet — this laptop cannot ` +
|
|
556
|
-
`reach it. Open Cloudflare in your browser and add ${preflight.zoneParent} under the ` +
|
|
557
|
-
`Cloudflare account you want to use (Websites → Add a site). When the domain shows as ` +
|
|
558
|
-
`Active, tell me and I will add the address again.`,
|
|
559
|
-
fields: {
|
|
560
|
-
requestedHostname: hostname,
|
|
561
|
-
requestedDomain: preflight.zoneParent,
|
|
562
|
-
},
|
|
563
|
-
};
|
|
564
|
-
logRefuse(detail);
|
|
565
|
-
throw new CloudflareRefusalError(detail);
|
|
566
|
-
}
|
|
567
|
-
const cert = findCert();
|
|
568
|
-
if (!cert)
|
|
569
|
-
throw new Error("No cert.pem found — validateAuth() should have refused first");
|
|
570
|
-
let output;
|
|
571
|
-
try {
|
|
572
|
-
output = execFileSync(bin, ["tunnel", "--origincert", cert, "route", "dns", "--overwrite-dns", tunnelId, hostname], { encoding: "utf-8", timeout: 30000 });
|
|
573
|
-
}
|
|
574
|
-
catch (err) {
|
|
575
|
-
const errObj = err;
|
|
576
|
-
const rawStderr = errObj.stderr;
|
|
577
|
-
const stderrMsg = typeof rawStderr === "string"
|
|
578
|
-
? rawStderr
|
|
579
|
-
: Buffer.isBuffer(rawStderr) ? rawStderr.toString("utf-8") : "";
|
|
580
|
-
const msg = stderrMsg || errObj.message || String(err);
|
|
581
|
-
if (msg.includes("already exists")) {
|
|
582
|
-
console.error(`[tunnel-route-dns] WARNING: ${hostname} "already exists" despite --overwrite-dns: ${msg}`);
|
|
583
|
-
return { created: false, output: msg, fqdn: hostname };
|
|
584
|
-
}
|
|
585
|
-
throw new Error(`cloudflared tunnel route dns failed: ${msg}`);
|
|
586
|
-
}
|
|
587
|
-
// (2) Post-flight FQDN assertion.
|
|
588
|
-
const fqdnMatch = output.match(/Added CNAME\s+(\S+?)\s+which will route/);
|
|
589
|
-
if (!fqdnMatch) {
|
|
590
|
-
console.error(`[tunnel-route-dns] WARNING: could not parse CNAME FQDN from cloudflared output — format may have changed. Output: ${output.trim()}`);
|
|
591
|
-
return { created: true, output: output.trim(), fqdn: hostname };
|
|
592
|
-
}
|
|
593
|
-
const actualFqdn = fqdnMatch[1];
|
|
594
|
-
if (actualFqdn !== hostname) {
|
|
595
|
-
// cloudflared wrote the address under a different domain than requested.
|
|
596
|
-
// That means the laptop is signed into a Cloudflare account that does
|
|
597
|
-
// not own the target domain, and `cloudflared` fell through to whichever
|
|
598
|
-
// domain the signed-in account does own. The agent cannot clean this up
|
|
599
|
-
// (no API access by design) — the operator cleans it up in the dashboard.
|
|
600
|
-
console.error(`[cloudflare:post-flight-mismatch] ${JSON.stringify({
|
|
601
|
-
requestedHostname: hostname,
|
|
602
|
-
actualFqdn,
|
|
603
|
-
tunnelId,
|
|
604
|
-
boundAccountId: auth.boundAccountId,
|
|
605
|
-
})}`);
|
|
606
|
-
const detail = {
|
|
607
|
-
reason: "post-flight-fqdn-mismatch",
|
|
608
|
-
message: `Cloudflare created the address under ${actualFqdn} instead of the requested ${hostname}. ` +
|
|
609
|
-
`This laptop is signed into a Cloudflare account that does not own ${registrableParent(hostname)}. ` +
|
|
610
|
-
`Two things to do: (a) open Cloudflare in your browser, sign into the account that does own ` +
|
|
611
|
-
`${registrableParent(hostname)}, and tell me so I can re-login; (b) in the same browser, delete the ` +
|
|
612
|
-
`stray record at ${actualFqdn} from the other account so it does not confuse anything.`,
|
|
613
|
-
fields: {
|
|
614
|
-
requestedHostname: hostname,
|
|
615
|
-
actualFqdn,
|
|
616
|
-
tunnelId,
|
|
617
|
-
boundAccountId: auth.boundAccountId,
|
|
618
|
-
},
|
|
619
|
-
};
|
|
620
|
-
logRefuse(detail);
|
|
621
|
-
throw new CloudflareRefusalError(detail);
|
|
622
|
-
}
|
|
623
|
-
console.error(`[tunnel-route-dns] CLI: routed ${hostname} → tunnel ${tunnelId} (overwrite) fqdn=${actualFqdn}`);
|
|
624
|
-
return { created: true, output: output.trim(), fqdn: actualFqdn };
|
|
625
|
-
}
|
|
626
|
-
// ---------------------------------------------------------------------------
|
|
627
|
-
// tunnel login — spawn `cloudflared tunnel login` and capture the auth URL
|
|
628
|
-
// ---------------------------------------------------------------------------
|
|
629
|
-
function loginStatePath() {
|
|
630
|
-
return join(homedir(), loadBrand().configDir, "cloudflared/login.state");
|
|
631
|
-
}
|
|
632
|
-
function readLoginState() {
|
|
633
|
-
try {
|
|
634
|
-
const p = loginStatePath();
|
|
635
|
-
if (!existsSync(p))
|
|
636
|
-
return null;
|
|
637
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
638
|
-
}
|
|
639
|
-
catch {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
function writeLoginState(state) {
|
|
644
|
-
const dir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
645
|
-
mkdirSync(dir, { recursive: true });
|
|
646
|
-
writeFileSync(loginStatePath(), JSON.stringify(state), "utf-8");
|
|
647
|
-
}
|
|
648
|
-
function clearLoginState() {
|
|
649
|
-
try {
|
|
650
|
-
const p = loginStatePath();
|
|
651
|
-
if (existsSync(p)) {
|
|
652
|
-
writeFileSync(p, "", "utf-8");
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
catch {
|
|
656
|
-
// best-effort
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Fingerprint emitted by `cloudflared tunnel login` into its log when its
|
|
661
|
-
* browser-launch subcommand cannot reach a display. A plain substring — the
|
|
662
|
-
* cloudflared log format is stable and regex gymnastics would hide the
|
|
663
|
-
* failure mode, not detect it.
|
|
664
|
-
*
|
|
665
|
-
* Critical semantic distinction: this marker is a *courtesy*
|
|
666
|
-
* warning — cloudflared attempted `xdg-open` against the argotunnel auth
|
|
667
|
-
* URL, failed, wrote the marker, and then CONTINUED RUNNING. Its local
|
|
668
|
-
* OAuth-callback listener is still alive; a user who opens the URL in any
|
|
669
|
-
* reachable browser can still complete sign-in and cert.pem will still
|
|
670
|
-
* land. Treating the marker as terminal loops the restart path
|
|
671
|
-
* hermetically because the respawn will re-emit the same marker the first
|
|
672
|
-
* time it tries to open a browser. `getLoginStatus` therefore only treats
|
|
673
|
-
* the marker as *advisory* when the PID is alive — liveness + cert are
|
|
674
|
-
* the source of truth for terminal-vs-live, not log content.
|
|
675
|
-
*/
|
|
676
|
-
const BROWSER_LAUNCH_FAILED_MARKER = "Failed to fetch resource";
|
|
677
|
-
function parseLoginLogForBrowserLaunchFailure(logPath, logOffsetBytes) {
|
|
678
|
-
try {
|
|
679
|
-
if (!existsSync(logPath))
|
|
680
|
-
return false;
|
|
681
|
-
// Read as Buffer so the byte offset is honoured exactly — logs are
|
|
682
|
-
// mostly ASCII but using a byte-level slice protects against any
|
|
683
|
-
// future multi-byte content producing a character-vs-byte drift.
|
|
684
|
-
const buf = readFileSync(logPath);
|
|
685
|
-
const sliced = buf.length > logOffsetBytes ? buf.subarray(logOffsetBytes) : Buffer.alloc(0);
|
|
686
|
-
const content = sliced.toString("utf-8");
|
|
687
|
-
return content.includes(BROWSER_LAUNCH_FAILED_MARKER);
|
|
688
|
-
}
|
|
689
|
-
catch (err) {
|
|
690
|
-
console.error(`[cloudflare:tunnel-login:log-parse-error] path=${logPath} err=${err instanceof Error ? err.message : String(err)}`);
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
export function getLoginStatus() {
|
|
695
|
-
const loginState = readLoginState();
|
|
696
|
-
if (!loginState || !loginState.pid) {
|
|
697
|
-
return { state: "idle" };
|
|
698
|
-
}
|
|
699
|
-
const alive = isProcessAlive(loginState.pid);
|
|
700
|
-
const browserLaunchFailed = parseLoginLogForBrowserLaunchFailure(loginState.logPath, loginState.logOffset ?? 0);
|
|
701
|
-
if (alive) {
|
|
702
|
-
// The OAuth-callback listener is still running. The marker is advisory
|
|
703
|
-
// only — the login flow can still complete if a human opens the URL.
|
|
704
|
-
return { state: "in-progress", loginState, browserLaunchFailed };
|
|
705
|
-
}
|
|
706
|
-
// PID is dead. Cert presence decides terminal state vs TOCTOU-idle.
|
|
707
|
-
if (findCert() !== null) {
|
|
708
|
-
// Upstream (tunnel-login handler) checks cert before calling this, so
|
|
709
|
-
// this branch is only reachable on a TOCTOU race where cert appeared
|
|
710
|
-
// between the top-level check and here. Treat as idle so the handler
|
|
711
|
-
// re-examines from the top on the next call.
|
|
712
|
-
return { state: "idle" };
|
|
713
|
-
}
|
|
714
|
-
// Terminal failure. Reason encodes whether the courtesy marker was
|
|
715
|
-
// emitted during the process's lifetime — useful for post-mortem grep
|
|
716
|
-
// ("did cloudflared try and fail to open the browser before dying?").
|
|
717
|
-
return {
|
|
718
|
-
state: "failed",
|
|
719
|
-
loginState,
|
|
720
|
-
reason: browserLaunchFailed
|
|
721
|
-
? "login-process-exited-with-marker"
|
|
722
|
-
: "login-process-exited-without-cert",
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Resolve the spawn env for `cloudflared tunnel login`. `cloudflared`'s
|
|
727
|
-
* first action after printing the auth URL is `xdg-open` against that URL
|
|
728
|
-
* so the operator's browser jumps straight to the Authorize screen. The
|
|
729
|
-
* MCP server's spawn context does not inherit DISPLAY (the chain platform
|
|
730
|
-
* → claude-agent → MCP server drops it along the way), so `xdg-open` has
|
|
731
|
-
* no X display to reach and the courtesy-fetch-resource marker lands in
|
|
732
|
-
* the login log every time.
|
|
733
|
-
*
|
|
734
|
-
* When this brand's Xtigervnc Unix socket is live we plumb DISPLAY
|
|
735
|
-
* explicitly so the browser launch has a chance of succeeding. The display
|
|
736
|
-
* number is per-brand: Maxy=:99, Real Agent=:100. Xtigervnc
|
|
737
|
-
* runs `-SecurityTypes None` locally, so XAUTHORITY is not required to
|
|
738
|
-
* connect. When this brand's VNC socket is absent (headless /
|
|
739
|
-
* not-yet-booted / native-mode device) we return null so the caller
|
|
740
|
-
* spawns with the default inherited env, and the new classification
|
|
741
|
-
* (marker alone is non-terminal) keeps the session alive via the
|
|
742
|
-
* advisory path.
|
|
743
|
-
*/
|
|
744
|
-
function vncSpawnEnv() {
|
|
745
|
-
const display = `:${loadBrand().vncDisplay}`;
|
|
746
|
-
const socket = `/tmp/.X11-unix/X${display.slice(1)}`;
|
|
747
|
-
if (!existsSync(socket))
|
|
748
|
-
return null;
|
|
749
|
-
return {
|
|
750
|
-
env: { ...process.env, DISPLAY: display },
|
|
751
|
-
display,
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
export function tunnelLogin() {
|
|
755
|
-
const bin = findBinary();
|
|
756
|
-
if (!bin)
|
|
757
|
-
throw new Error("cloudflared is not installed");
|
|
758
|
-
const status = getLoginStatus();
|
|
759
|
-
if (status.state === "in-progress") {
|
|
760
|
-
console.error(`[cloudflare:tunnel-login:waiting] pid=${status.loginState.pid} authUrl=${status.loginState.authUrl}`);
|
|
761
|
-
return Promise.resolve({ authUrl: status.loginState.authUrl, alreadyRunning: true });
|
|
762
|
-
}
|
|
763
|
-
const logDir = join(homedir(), loadBrand().configDir, "logs");
|
|
764
|
-
mkdirSync(logDir, { recursive: true });
|
|
765
|
-
const logPath = join(logDir, "cloudflared-login.log");
|
|
766
|
-
const logFd = openSync(logPath, "a");
|
|
767
|
-
// Record the log's byte size before the spawn writes anything. This is
|
|
768
|
-
// the watermark for any marker parsing: every subsequent call slices
|
|
769
|
-
// the log from this offset forward so historical markers from prior
|
|
770
|
-
// attempts cannot be re-attributed to the new process.
|
|
771
|
-
// narrowed the marker's role from terminal signal to advisory, so the
|
|
772
|
-
// watermark is no longer load-bearing for loop prevention — but it is
|
|
773
|
-
// still correct for attribution: a new process's courtesy-marker
|
|
774
|
-
// advisory should not count log content written by its predecessor.
|
|
775
|
-
const logOffset = existsSync(logPath) ? statSync(logPath).size : 0;
|
|
776
|
-
const certDir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
777
|
-
mkdirSync(certDir, { recursive: true });
|
|
778
|
-
const originCert = join(certDir, "cert.pem");
|
|
779
|
-
const vncEnv = vncSpawnEnv();
|
|
780
|
-
const displayField = vncEnv ? vncEnv.display : "inherit";
|
|
781
|
-
console.error(`[cloudflare:tunnel-login:spawn] log=${logPath} origincert=${originCert} ` +
|
|
782
|
-
`log-offset=${logOffset} display=${displayField}`);
|
|
783
|
-
const child = spawn(bin, ["tunnel", "--origincert", originCert, "login"], {
|
|
784
|
-
stdio: ["ignore", logFd, logFd],
|
|
785
|
-
detached: true,
|
|
786
|
-
env: vncEnv?.env,
|
|
787
|
-
});
|
|
788
|
-
child.unref();
|
|
789
|
-
if (!child.pid) {
|
|
790
|
-
throw new Error("Failed to spawn cloudflared tunnel login — no PID returned");
|
|
791
|
-
}
|
|
792
|
-
console.error(`[cloudflare:tunnel-login:spawn] pid=${child.pid}`);
|
|
793
|
-
const urlPattern = /https:\/\/dash\.cloudflare\.com[^\s]*/;
|
|
794
|
-
return new Promise((resolve, reject) => {
|
|
795
|
-
let elapsed = 0;
|
|
796
|
-
const interval = 500;
|
|
797
|
-
const timeout = 15000;
|
|
798
|
-
const poller = setInterval(() => {
|
|
799
|
-
elapsed += interval;
|
|
800
|
-
if (!isProcessAlive(child.pid)) {
|
|
801
|
-
clearInterval(poller);
|
|
802
|
-
console.error(`[cloudflare:tunnel-login:failed] reason=process-exited-before-auth-url pid=${child.pid}`);
|
|
803
|
-
clearLoginState();
|
|
804
|
-
try {
|
|
805
|
-
const output = readFileSync(logPath, "utf-8").slice(-500);
|
|
806
|
-
reject(new Error(`cloudflared exited before producing an auth URL. Last output: ${output}`));
|
|
807
|
-
}
|
|
808
|
-
catch {
|
|
809
|
-
reject(new Error("cloudflared exited before producing an auth URL"));
|
|
810
|
-
}
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
try {
|
|
814
|
-
const content = readFileSync(logPath, "utf-8");
|
|
815
|
-
const match = content.match(urlPattern);
|
|
816
|
-
if (match) {
|
|
817
|
-
clearInterval(poller);
|
|
818
|
-
const authUrl = match[0];
|
|
819
|
-
console.error(`[cloudflare:tunnel-login:waiting] pid=${child.pid} authUrl=${authUrl}`);
|
|
820
|
-
writeLoginState({
|
|
821
|
-
pid: child.pid,
|
|
822
|
-
startedAt: Date.now(),
|
|
823
|
-
authUrl,
|
|
824
|
-
logPath,
|
|
825
|
-
logOffset,
|
|
826
|
-
});
|
|
827
|
-
resolve({ authUrl });
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
catch {
|
|
832
|
-
// log file may not be flushed yet — retry
|
|
833
|
-
}
|
|
834
|
-
if (elapsed >= timeout) {
|
|
835
|
-
clearInterval(poller);
|
|
836
|
-
console.error(`[cloudflare:tunnel-login:failed] reason=auth-url-timeout pid=${child.pid} timeout-ms=${timeout}`);
|
|
837
|
-
try {
|
|
838
|
-
process.kill(child.pid, "SIGTERM");
|
|
839
|
-
}
|
|
840
|
-
catch { /* already gone */ }
|
|
841
|
-
clearLoginState();
|
|
842
|
-
try {
|
|
843
|
-
const output = readFileSync(logPath, "utf-8").slice(-500);
|
|
844
|
-
reject(new Error(`cloudflared did not produce an auth URL within ${timeout / 1000} seconds. Output: ${output}`));
|
|
845
|
-
}
|
|
846
|
-
catch {
|
|
847
|
-
reject(new Error(`cloudflared did not produce an auth URL within ${timeout / 1000} seconds`));
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}, interval);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
// ---------------------------------------------------------------------------
|
|
854
|
-
// Process management — detached, survives MCP server exit
|
|
855
|
-
// ---------------------------------------------------------------------------
|
|
856
|
-
function statePath() {
|
|
857
|
-
return join(homedir(), loadBrand().configDir, "cloudflared/tunnel.state");
|
|
858
|
-
}
|
|
859
|
-
function readState() {
|
|
860
|
-
try {
|
|
861
|
-
const p = statePath();
|
|
862
|
-
if (!existsSync(p))
|
|
863
|
-
return null;
|
|
864
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
865
|
-
}
|
|
866
|
-
catch {
|
|
867
|
-
return null;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
function writeState(state) {
|
|
871
|
-
const dir = join(homedir(), loadBrand().configDir, "cloudflared");
|
|
872
|
-
mkdirSync(dir, { recursive: true });
|
|
873
|
-
writeFileSync(statePath(), JSON.stringify(state), "utf-8");
|
|
874
|
-
}
|
|
875
|
-
function isProcessAlive(pid) {
|
|
876
|
-
if (pid === null || pid <= 0)
|
|
877
|
-
return false;
|
|
878
|
-
try {
|
|
879
|
-
process.kill(pid, 0);
|
|
880
|
-
return true;
|
|
881
|
-
}
|
|
882
|
-
catch {
|
|
883
|
-
return false;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
export function getPersistedTunnelId() {
|
|
887
|
-
return readState()?.tunnelId ?? null;
|
|
888
|
-
}
|
|
889
|
-
export function getPersistedDomain() {
|
|
890
|
-
return readState()?.domain ?? null;
|
|
891
|
-
}
|
|
892
|
-
export function getPersistedState() {
|
|
893
|
-
return readState();
|
|
894
|
-
}
|
|
895
|
-
export function saveTunnelIdentity(params) {
|
|
896
|
-
writeState({
|
|
897
|
-
tunnelId: params.tunnelId,
|
|
898
|
-
tunnelName: params.tunnelName,
|
|
899
|
-
domain: params.domain,
|
|
900
|
-
configPath: params.configPath,
|
|
901
|
-
credentialsPath: params.credentialsPath,
|
|
902
|
-
adminHostname: params.adminHostname,
|
|
903
|
-
publicHostname: params.publicHostname,
|
|
904
|
-
pid: null,
|
|
905
|
-
startedAt: null,
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Return the actual hostnames from persisted state (or derive from domain
|
|
910
|
-
* for pre-521 state files).
|
|
911
|
-
*/
|
|
912
|
-
export function getPersistedHostnames() {
|
|
913
|
-
const state = readState();
|
|
914
|
-
if (!state)
|
|
915
|
-
return [];
|
|
916
|
-
if (state.adminHostname) {
|
|
917
|
-
return state.publicHostname
|
|
918
|
-
? [state.adminHostname, state.publicHostname]
|
|
919
|
-
: [state.adminHostname];
|
|
920
|
-
}
|
|
921
|
-
if (state.domain) {
|
|
922
|
-
console.error(`[tunnel] backward-compat: deriving hostnames from domain=${state.domain} (no adminHostname in state)`);
|
|
923
|
-
return [`admin.${state.domain}`, `public.${state.domain}`];
|
|
924
|
-
}
|
|
925
|
-
return [];
|
|
926
|
-
}
|
|
927
|
-
export async function probeHostname(hostname, expectedTunnelId) {
|
|
928
|
-
const resolver = new Resolver();
|
|
929
|
-
resolver.setServers(["1.1.1.1", "8.8.8.8"]);
|
|
930
|
-
let cname = null;
|
|
931
|
-
try {
|
|
932
|
-
const cnames = await resolver.resolveCname(hostname);
|
|
933
|
-
cname = cnames[0] ?? null;
|
|
934
|
-
}
|
|
935
|
-
catch (err) {
|
|
936
|
-
const code = err.code;
|
|
937
|
-
if (code === "ENODATA") {
|
|
938
|
-
// Proxied records can flatten — try A records as a secondary signal
|
|
939
|
-
// that the name exists, but without a CNAME we cannot confirm this
|
|
940
|
-
// tunnel. Treat as cname-points-elsewhere (name exists but does not
|
|
941
|
-
// point here) only when an A record is present; otherwise dns-missing.
|
|
942
|
-
try {
|
|
943
|
-
await resolver.resolve4(hostname);
|
|
944
|
-
cname = null; // flattened — still need edge probe to confirm
|
|
945
|
-
}
|
|
946
|
-
catch {
|
|
947
|
-
return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
else if (code === "ENOTFOUND" || code === "ESERVFAIL") {
|
|
951
|
-
return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
|
|
952
|
-
}
|
|
953
|
-
else {
|
|
954
|
-
console.error(`[cloudflare:tunnel-status:probe] unexpected DNS error for ${hostname}: ${err}`);
|
|
955
|
-
return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
if (expectedTunnelId && cname) {
|
|
959
|
-
const cnameLc = cname.toLowerCase().replace(/\.$/, "");
|
|
960
|
-
const expected = `${expectedTunnelId}.cfargotunnel.com`.toLowerCase();
|
|
961
|
-
if (cnameLc !== expected) {
|
|
962
|
-
return { hostname, resolves: true, cname, httpStatus: null, failureMode: "cname-points-elsewhere" };
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
// HTTPS probe — any response proves edge routing is wired up.
|
|
966
|
-
let httpStatus = null;
|
|
967
|
-
try {
|
|
968
|
-
const controller = new AbortController();
|
|
969
|
-
const to = setTimeout(() => controller.abort(), 10000);
|
|
970
|
-
try {
|
|
971
|
-
const res = await fetch(`https://${hostname}/`, {
|
|
972
|
-
method: "GET",
|
|
973
|
-
redirect: "manual",
|
|
974
|
-
signal: controller.signal,
|
|
975
|
-
});
|
|
976
|
-
httpStatus = res.status;
|
|
977
|
-
}
|
|
978
|
-
finally {
|
|
979
|
-
clearTimeout(to);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
catch (err) {
|
|
983
|
-
console.error(`[cloudflare:tunnel-status:probe] HTTPS probe failed for ${hostname}: ${err instanceof Error ? err.message : String(err)}`);
|
|
984
|
-
return { hostname, resolves: true, cname, httpStatus: null, failureMode: "edge-unreachable" };
|
|
985
|
-
}
|
|
986
|
-
// Cloudflare edge error page status codes that indicate the tunnel is
|
|
987
|
-
// either not connected (530) or the hostname is not served by any tunnel
|
|
988
|
-
// (1033/1016 surface as 530 or 502 at the HTTP layer; Cloudflare sometimes
|
|
989
|
-
// uses 502/503 for unreachable origins). Any 5xx from the edge with
|
|
990
|
-
// missing CNAME match means the tunnel-edge wiring is wrong.
|
|
991
|
-
if (httpStatus >= 500 && httpStatus < 600) {
|
|
992
|
-
return { hostname, resolves: true, cname, httpStatus, failureMode: "tunnel-not-matched" };
|
|
993
|
-
}
|
|
994
|
-
return { hostname, resolves: true, cname, httpStatus, failureMode: "ok" };
|
|
995
|
-
}
|
|
996
|
-
export async function getStatus(domain) {
|
|
997
|
-
const auth = validateAuth();
|
|
998
|
-
const state = readState();
|
|
999
|
-
const running = state !== null && isProcessAlive(state.pid);
|
|
1000
|
-
const effectiveDomain = domain ?? state?.domain ?? null;
|
|
1001
|
-
const persistedHostnames = getPersistedHostnames();
|
|
1002
|
-
const configuredHostnames = state?.configPath
|
|
1003
|
-
? parseConfiguredHostnames(state.configPath)
|
|
1004
|
-
: [];
|
|
1005
|
-
// Probe the authoritative set (config.yml). If config.yml is absent, fall
|
|
1006
|
-
// back to persisted hostnames so partial-setup states still surface.
|
|
1007
|
-
const hostnamesToProbe = configuredHostnames.length > 0
|
|
1008
|
-
? configuredHostnames
|
|
1009
|
-
: persistedHostnames;
|
|
1010
|
-
let probes = [];
|
|
1011
|
-
let boundAccountOwnsHostnames = false;
|
|
1012
|
-
let healthy = false;
|
|
1013
|
-
let unhealthyReason = null;
|
|
1014
|
-
if (!running || !state?.tunnelId || hostnamesToProbe.length === 0) {
|
|
1015
|
-
unhealthyReason = !running
|
|
1016
|
-
? "not-running"
|
|
1017
|
-
: "no-tunnel-configured";
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
probes = await Promise.all(hostnamesToProbe.map((h) => probeHostname(h, state.tunnelId)));
|
|
1021
|
-
// A probe reaching the edge with a non-5xx response proves the signed-in
|
|
1022
|
-
// account owns the domain: the edge only routes to this tunnel's origin
|
|
1023
|
-
// when the domain's DNS on this account points at this tunnel. The CNAME
|
|
1024
|
-
// check inside probeHostname already short-circuits the "points to a
|
|
1025
|
-
// different tunnel" case to `cname-points-elsewhere` — so here we trust
|
|
1026
|
-
// `ok`. We do not gate on `p.cname !== null` because Cloudflare can flatten
|
|
1027
|
-
// CNAMEs (zone-apex records, some proxy configurations) — in that case
|
|
1028
|
-
// cname is null but the HTTPS response is still authoritative.
|
|
1029
|
-
boundAccountOwnsHostnames = probes.some((p) => p.failureMode === "ok");
|
|
1030
|
-
healthy = probes.every((p) => p.failureMode === "ok");
|
|
1031
|
-
if (!healthy) {
|
|
1032
|
-
// Distinguishing "wrong account" from "right account, tunnel broken":
|
|
1033
|
-
// a probe that resolved DNS at all is evidence the domain lives on
|
|
1034
|
-
// some Cloudflare account (the NS is Cloudflare's). A probe that
|
|
1035
|
-
// resolved AND doesn't point elsewhere is evidence this laptop's
|
|
1036
|
-
// account owns it — any other failure mode (edge-unreachable,
|
|
1037
|
-
// tunnel-not-matched, or a healthy flattened probe) indicates the
|
|
1038
|
-
// DNS points at *something* on the signed-in account, just not
|
|
1039
|
-
// working. Only when every probe is `dns-missing` or
|
|
1040
|
-
// `cname-points-elsewhere` do we conclude the bound account does
|
|
1041
|
-
// not own the hostnames.
|
|
1042
|
-
const anyCnamePointsHere = probes.some((p) => p.resolves &&
|
|
1043
|
-
p.failureMode !== "cname-points-elsewhere" &&
|
|
1044
|
-
p.failureMode !== "dns-missing");
|
|
1045
|
-
unhealthyReason = anyCnamePointsHere
|
|
1046
|
-
? "hostname-probes-failed"
|
|
1047
|
-
: "bound-account-does-not-own-hostname";
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (unhealthyReason === "bound-account-does-not-own-hostname") {
|
|
1051
|
-
// Surface the structured refusal reason — same channel the
|
|
1052
|
-
// agent-facing text uses, but keyed on the enum.
|
|
1053
|
-
logRefuse({
|
|
1054
|
-
reason: "bound-account-does-not-own-hostname",
|
|
1055
|
-
message: "tunnel-status probe: no configured hostname's DNS lands on this tunnel. " +
|
|
1056
|
-
`${DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION}`,
|
|
1057
|
-
fields: {
|
|
1058
|
-
boundAccountId: auth.boundAccountId,
|
|
1059
|
-
certAccountId: auth.certAccountId,
|
|
1060
|
-
tunnelId: state?.tunnelId ?? undefined,
|
|
1061
|
-
},
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
// Single-line branch-selection observability. The handler's trailing text
|
|
1065
|
-
// is driven off unhealthyReason + per-hostname failureMode; logging the
|
|
1066
|
-
// enum here means the branch taken is post-diagnosable from MCP stderr
|
|
1067
|
-
// without having to parse the returned JSON.
|
|
1068
|
-
console.error(`[cloudflare:tunnel-status:result] reason=${unhealthyReason ?? "healthy"} ` +
|
|
1069
|
-
`boundAccountOwnsHostnames=${boundAccountOwnsHostnames} ` +
|
|
1070
|
-
`probes=${probes.map((p) => `${p.hostname}:${p.failureMode}`).join(",") || "none"}`);
|
|
1071
|
-
return {
|
|
1072
|
-
installed: isInstalled(),
|
|
1073
|
-
version: version(),
|
|
1074
|
-
hasCert: auth.hasCert,
|
|
1075
|
-
hasBinding: auth.hasBinding,
|
|
1076
|
-
bound: auth.bound,
|
|
1077
|
-
certAccountId: auth.certAccountId,
|
|
1078
|
-
boundAccountId: auth.boundAccountId,
|
|
1079
|
-
running,
|
|
1080
|
-
pid: running ? state.pid : null,
|
|
1081
|
-
tunnelId: state?.tunnelId ?? null,
|
|
1082
|
-
domain: effectiveDomain,
|
|
1083
|
-
configuredHostnames,
|
|
1084
|
-
persistedHostnames,
|
|
1085
|
-
adminHostname: state?.adminHostname ?? (effectiveDomain ? `admin.${effectiveDomain}` : null),
|
|
1086
|
-
publicHostname: state?.publicHostname ?? (effectiveDomain ? `public.${effectiveDomain}` : null),
|
|
1087
|
-
upSince: running ? state.startedAt : null,
|
|
1088
|
-
probes,
|
|
1089
|
-
boundAccountOwnsHostnames,
|
|
1090
|
-
healthy,
|
|
1091
|
-
unhealthyReason,
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
export function startTunnel(params) {
|
|
1095
|
-
const bin = findBinary();
|
|
1096
|
-
if (!bin)
|
|
1097
|
-
throw new Error("cloudflared is not installed");
|
|
1098
|
-
const cert = findCert();
|
|
1099
|
-
if (!cert)
|
|
1100
|
-
throw new Error("No cert.pem found — run tunnel-login first");
|
|
1101
|
-
const state = readState();
|
|
1102
|
-
if (state && isProcessAlive(state.pid)) {
|
|
1103
|
-
throw new Error(`Tunnel is already running (PID ${state.pid})`);
|
|
1104
|
-
}
|
|
1105
|
-
const logDir = join(homedir(), loadBrand().configDir, "logs");
|
|
1106
|
-
mkdirSync(logDir, { recursive: true });
|
|
1107
|
-
const logPath = join(logDir, "cloudflared.log");
|
|
1108
|
-
const logFd = openSync(logPath, "a");
|
|
1109
|
-
const child = spawn(bin, ["--origincert", cert, "--config", params.configPath, "tunnel", "run"], {
|
|
1110
|
-
stdio: ["ignore", logFd, logFd],
|
|
1111
|
-
detached: true,
|
|
1112
|
-
});
|
|
1113
|
-
child.unref();
|
|
1114
|
-
if (!child.pid)
|
|
1115
|
-
throw new Error("Failed to spawn tunnel process");
|
|
1116
|
-
console.error(`[tunnel-enable] started PID ${child.pid} with config ${params.configPath}`);
|
|
1117
|
-
const existingState = readState();
|
|
1118
|
-
writeState({
|
|
1119
|
-
pid: child.pid,
|
|
1120
|
-
tunnelId: params.tunnelId,
|
|
1121
|
-
tunnelName: params.tunnelName,
|
|
1122
|
-
domain: params.domain,
|
|
1123
|
-
configPath: params.configPath,
|
|
1124
|
-
credentialsPath: params.credentialsPath,
|
|
1125
|
-
adminHostname: existingState?.adminHostname ?? null,
|
|
1126
|
-
publicHostname: existingState?.publicHostname ?? null,
|
|
1127
|
-
startedAt: Date.now(),
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
export function stopTunnel() {
|
|
1131
|
-
const state = readState();
|
|
1132
|
-
if (!state)
|
|
1133
|
-
return;
|
|
1134
|
-
const pid = state.pid;
|
|
1135
|
-
if (pid !== null && isProcessAlive(pid)) {
|
|
1136
|
-
try {
|
|
1137
|
-
process.kill(pid, "SIGTERM");
|
|
1138
|
-
}
|
|
1139
|
-
catch {
|
|
1140
|
-
// process may have exited between check and kill
|
|
1141
|
-
}
|
|
1142
|
-
setTimeout(() => {
|
|
1143
|
-
if (isProcessAlive(pid)) {
|
|
1144
|
-
try {
|
|
1145
|
-
process.kill(pid, "SIGKILL");
|
|
1146
|
-
}
|
|
1147
|
-
catch {
|
|
1148
|
-
/* already gone */
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}, 5000);
|
|
1152
|
-
}
|
|
1153
|
-
writeState({ ...state, pid: null, startedAt: null });
|
|
1154
|
-
}
|
|
1155
|
-
//# sourceMappingURL=cloudflared.js.map
|