@rndmjoker/proton-mcp 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +405 -0
  3. package/dist/activity.d.ts +49 -0
  4. package/dist/activity.js +72 -0
  5. package/dist/activity.js.map +1 -0
  6. package/dist/bridge/certificate.d.ts +75 -0
  7. package/dist/bridge/certificate.js +176 -0
  8. package/dist/bridge/certificate.js.map +1 -0
  9. package/dist/bridge/connection.d.ts +99 -0
  10. package/dist/bridge/connection.js +244 -0
  11. package/dist/bridge/connection.js.map +1 -0
  12. package/dist/bridge/errors.d.ts +35 -0
  13. package/dist/bridge/errors.js +56 -0
  14. package/dist/bridge/errors.js.map +1 -0
  15. package/dist/config.d.ts +92 -0
  16. package/dist/config.js +142 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credentials/availability.d.ts +62 -0
  19. package/dist/credentials/availability.js +107 -0
  20. package/dist/credentials/availability.js.map +1 -0
  21. package/dist/credentials/encrypted-file.d.ts +50 -0
  22. package/dist/credentials/encrypted-file.js +156 -0
  23. package/dist/credentials/encrypted-file.js.map +1 -0
  24. package/dist/credentials/keyring.d.ts +48 -0
  25. package/dist/credentials/keyring.js +97 -0
  26. package/dist/credentials/keyring.js.map +1 -0
  27. package/dist/credentials/plain-file.d.ts +24 -0
  28. package/dist/credentials/plain-file.js +78 -0
  29. package/dist/credentials/plain-file.js.map +1 -0
  30. package/dist/credentials/probe-keyring.d.ts +11 -0
  31. package/dist/credentials/probe-keyring.js +57 -0
  32. package/dist/credentials/probe-keyring.js.map +1 -0
  33. package/dist/credentials/session.d.ts +15 -0
  34. package/dist/credentials/session.js +21 -0
  35. package/dist/credentials/session.js.map +1 -0
  36. package/dist/credentials/store.d.ts +80 -0
  37. package/dist/credentials/store.js +94 -0
  38. package/dist/credentials/store.js.map +1 -0
  39. package/dist/in-flight.d.ts +33 -0
  40. package/dist/in-flight.js +78 -0
  41. package/dist/in-flight.js.map +1 -0
  42. package/dist/mail/actions.d.ts +116 -0
  43. package/dist/mail/actions.js +265 -0
  44. package/dist/mail/actions.js.map +1 -0
  45. package/dist/mail/attachments.d.ts +39 -0
  46. package/dist/mail/attachments.js +86 -0
  47. package/dist/mail/attachments.js.map +1 -0
  48. package/dist/mail/compose.d.ts +264 -0
  49. package/dist/mail/compose.js +395 -0
  50. package/dist/mail/compose.js.map +1 -0
  51. package/dist/mail/drafts.d.ts +138 -0
  52. package/dist/mail/drafts.js +449 -0
  53. package/dist/mail/drafts.js.map +1 -0
  54. package/dist/mail/ids.d.ts +78 -0
  55. package/dist/mail/ids.js +135 -0
  56. package/dist/mail/ids.js.map +1 -0
  57. package/dist/mail/labels.d.ts +61 -0
  58. package/dist/mail/labels.js +139 -0
  59. package/dist/mail/labels.js.map +1 -0
  60. package/dist/mail/markup.d.ts +91 -0
  61. package/dist/mail/markup.js +265 -0
  62. package/dist/mail/markup.js.map +1 -0
  63. package/dist/mail/messages.d.ts +147 -0
  64. package/dist/mail/messages.js +219 -0
  65. package/dist/mail/messages.js.map +1 -0
  66. package/dist/mail/quote.d.ts +65 -0
  67. package/dist/mail/quote.js +157 -0
  68. package/dist/mail/quote.js.map +1 -0
  69. package/dist/mail/search.d.ts +66 -0
  70. package/dist/mail/search.js +107 -0
  71. package/dist/mail/search.js.map +1 -0
  72. package/dist/mail/send.d.ts +61 -0
  73. package/dist/mail/send.js +132 -0
  74. package/dist/mail/send.js.map +1 -0
  75. package/dist/mime/parse.d.ts +110 -0
  76. package/dist/mime/parse.js +194 -0
  77. package/dist/mime/parse.js.map +1 -0
  78. package/dist/server.d.ts +13 -0
  79. package/dist/server.js +387 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/session.d.ts +86 -0
  82. package/dist/session.js +220 -0
  83. package/dist/session.js.map +1 -0
  84. package/dist/settings.d.ts +52 -0
  85. package/dist/settings.js +80 -0
  86. package/dist/settings.js.map +1 -0
  87. package/dist/tools/actions.d.ts +21 -0
  88. package/dist/tools/actions.js +133 -0
  89. package/dist/tools/actions.js.map +1 -0
  90. package/dist/tools/capabilities.d.ts +52 -0
  91. package/dist/tools/capabilities.js +96 -0
  92. package/dist/tools/capabilities.js.map +1 -0
  93. package/dist/tools/confirm.d.ts +84 -0
  94. package/dist/tools/confirm.js +212 -0
  95. package/dist/tools/confirm.js.map +1 -0
  96. package/dist/tools/drafts.d.ts +21 -0
  97. package/dist/tools/drafts.js +199 -0
  98. package/dist/tools/drafts.js.map +1 -0
  99. package/dist/tools/failures.d.ts +46 -0
  100. package/dist/tools/failures.js +157 -0
  101. package/dist/tools/failures.js.map +1 -0
  102. package/dist/tools/format.d.ts +55 -0
  103. package/dist/tools/format.js +220 -0
  104. package/dist/tools/format.js.map +1 -0
  105. package/dist/tools/guidance.d.ts +38 -0
  106. package/dist/tools/guidance.js +226 -0
  107. package/dist/tools/guidance.js.map +1 -0
  108. package/dist/tools/interface.d.ts +28 -0
  109. package/dist/tools/interface.js +63 -0
  110. package/dist/tools/interface.js.map +1 -0
  111. package/dist/tools/labels.d.ts +21 -0
  112. package/dist/tools/labels.js +92 -0
  113. package/dist/tools/labels.js.map +1 -0
  114. package/dist/tools/list-folders.d.ts +14 -0
  115. package/dist/tools/list-folders.js +70 -0
  116. package/dist/tools/list-folders.js.map +1 -0
  117. package/dist/tools/messages.d.ts +9 -0
  118. package/dist/tools/messages.js +221 -0
  119. package/dist/tools/messages.js.map +1 -0
  120. package/dist/tools/send.d.ts +29 -0
  121. package/dist/tools/send.js +236 -0
  122. package/dist/tools/send.js.map +1 -0
  123. package/dist/tools/sign-in-required.d.ts +94 -0
  124. package/dist/tools/sign-in-required.js +168 -0
  125. package/dist/tools/sign-in-required.js.map +1 -0
  126. package/dist/web/layout.d.ts +75 -0
  127. package/dist/web/layout.js +590 -0
  128. package/dist/web/layout.js.map +1 -0
  129. package/dist/web/pages.d.ts +79 -0
  130. package/dist/web/pages.js +284 -0
  131. package/dist/web/pages.js.map +1 -0
  132. package/dist/web/sections.d.ts +105 -0
  133. package/dist/web/sections.js +207 -0
  134. package/dist/web/sections.js.map +1 -0
  135. package/dist/web/security.d.ts +122 -0
  136. package/dist/web/security.js +180 -0
  137. package/dist/web/security.js.map +1 -0
  138. package/dist/web/server.d.ts +153 -0
  139. package/dist/web/server.js +622 -0
  140. package/dist/web/server.js.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * What a tool does when nobody has signed in.
3
+ *
4
+ * The point is narrow and worth stating plainly: a model told only that
5
+ * credentials are missing will ask the user to type their Bridge password into
6
+ * the chat, where it lands in a transcript. So the answer always names the local
7
+ * page instead, and says not to ask.
8
+ *
9
+ * Two ways of getting the user there:
10
+ *
11
+ * 1. **URL elicitation**, which the specification provides for exactly this
12
+ * case. The client shows the address, the user signs in, and the tool call
13
+ * is retried.
14
+ * 2. **A plain failure naming the address**, for clients that cannot elicit.
15
+ *
16
+ * ## Why this is a returned value and not a call
17
+ *
18
+ * The obvious-looking route, `ctx.mcpReq.elicitInput({mode:'url'})`, cannot work
19
+ * here. Protocol revision 2026-07-28 has no server-to-client request channel at
20
+ * all, and `serveStdio` marks its server instance as that era at construction.
21
+ * The push API therefore throws on every call this server will ever serve. That
22
+ * is why the earlier attempt at this never elicited anything and always fell
23
+ * back to the text.
24
+ *
25
+ * The replacement is a value the handler RETURNS: `inputRequired(...)` with an
26
+ * embedded URL elicitation. The client fulfils it and calls the tool again, so
27
+ * the retry is an ordinary second invocation rather than a loop inside the
28
+ * handler.
29
+ *
30
+ * The SDK's shim fulfils such a return on a 2025-era connection as well, and it
31
+ * does so under `serveStdio` too. An earlier note here said otherwise, on the
32
+ * strength of a measurement that must have been taken against a different
33
+ * serving mode; re-measured on 31.07.2026 against SDK 2.0.0, `serveStdio` pins
34
+ * one instance for the life of the connection, the returned request reaches the
35
+ * client as an `elicitation/create`, and the answer comes back. The text answer
36
+ * below is therefore the fallback for a client that declares nothing, not for
37
+ * every client. See clientCanShowUrl.
38
+ *
39
+ * ## No requestState
40
+ *
41
+ * `inputRequired` also accepts an opaque `requestState` that travels through the
42
+ * client and comes back. We do not use it. Whether someone is signed in is
43
+ * answered by asking the session, which is the truth; a claim carried back
44
+ * through the client would be attacker-controlled input that we would then have
45
+ * to integrity-protect for no gain. The embedded request alone satisfies the
46
+ * protocol's at-least-one rule.
47
+ */
48
+ import { inputRequired, inputResponse } from '@modelcontextprotocol/server';
49
+ import { canElicitUrl } from './capabilities.js';
50
+ /**
51
+ * The key our embedded elicitation is filed under.
52
+ *
53
+ * Also how a retry is recognised: a response under this key means the client has
54
+ * already taken the user to the page once.
55
+ */
56
+ export const SIGN_IN_KEY = 'protonMcpSignIn';
57
+ const MESSAGE = 'proton-mcp is not signed in to Proton Mail Bridge yet. Open the configuration page and enter ' +
58
+ 'the Proton address together with the Bridge password, which is the one the Bridge generates ' +
59
+ 'and not the Proton account password.';
60
+ /**
61
+ * The other reason a tool finds no credentials: they are on disk, encrypted.
62
+ *
63
+ * A separate message because the remedy is a different one, and the message
64
+ * above sends the user to the wrong place entirely. Nothing has to be looked up
65
+ * in the Bridge and no address has to be typed: the Bridge password is already
66
+ * stored, and the one thing missing is the master password that decrypts it,
67
+ * which is asked for once per start and never kept.
68
+ *
69
+ * Telling somebody to fetch their Bridge password when all they need is the
70
+ * password they chose themselves is the kind of wrong advice that ends with the
71
+ * encrypted file being thrown away.
72
+ */
73
+ const LOCKED_MESSAGE = 'proton-mcp has credentials stored, in an encrypted file that is not open yet. It needs the ' +
74
+ 'master password that was chosen when they were saved. Nothing else is missing: the Bridge ' +
75
+ 'password is already on disk, and no address has to be entered again.';
76
+ function messageFor(state) {
77
+ return state === 'locked' ? LOCKED_MESSAGE : MESSAGE;
78
+ }
79
+ /**
80
+ * Whether the client said it can show a URL.
81
+ *
82
+ * Asked in advance rather than attempted and caught, because there is nothing to
83
+ * catch: an embedded request the client never declared support for is refused by
84
+ * the SDK *after* the handler returns. The caller would get that refusal instead
85
+ * of an address, so an undeclared capability means the text answer.
86
+ *
87
+ * The paragraph that used to stand here claimed, as a measurement, that only the
88
+ * request's own envelope could be consulted and that a handshake declaration was
89
+ * unavailable under `serveStdio`. That was wrong, and it was wrong in the
90
+ * expensive direction: on this SDK the envelope does not exist at all, so the
91
+ * check answered no for every client and the elicitation never once fired. Where
92
+ * the declaration now comes from is in capabilities.ts.
93
+ */
94
+ export function clientCanShowUrl(ctx) {
95
+ return canElicitUrl(ctx);
96
+ }
97
+ /** The failure used when elicitation is unavailable, declined or fruitless. */
98
+ export function signInFailure(url, note, state = 'not-signed-in') {
99
+ const message = messageFor(state);
100
+ const head = note ? `${note}\n\n${message}` : message;
101
+ if (!url) {
102
+ // No page to send anyone to. For a locked file that is a dead end worth
103
+ // naming as one: the master password can only be entered there.
104
+ const text = state === 'locked'
105
+ ? `${head}\n\nThe configuration page is not running, so the file cannot be unlocked right ` +
106
+ 'now. It failed to start, most likely because its port was in use; setting ' +
107
+ 'PROTON_MCP_WEB_PORT to a free port and restarting is the way out. Do not ask for the ' +
108
+ 'master password in this conversation.'
109
+ : `${head}\n\nThe configuration page is not running. Set BRIDGE_USER and BRIDGE_PASS in ` +
110
+ 'the environment instead. Do not ask for the password in this conversation.';
111
+ return { content: [{ type: 'text', text }], isError: true };
112
+ }
113
+ const instruction = state === 'locked'
114
+ ? 'Tell the user to open it and enter their master password there. Do not ask for the ' +
115
+ 'master password in this conversation: it is the one thing that can decrypt the stored ' +
116
+ 'credentials, and it has no business in a transcript.'
117
+ : 'Tell the user to open it and sign in there. Do not ask for the password in this ' +
118
+ 'conversation.';
119
+ return {
120
+ content: [{ type: 'text', text: `${head}\n\nOpen ${url}\n\n${instruction}` }],
121
+ isError: true,
122
+ };
123
+ }
124
+ /**
125
+ * Asks the client to take the user to the sign-in page.
126
+ *
127
+ * Returned from the handler, not sent. See the note at the top of this file for
128
+ * why that is the only thing that can work here.
129
+ */
130
+ export function signInElicitation(url, state = 'not-signed-in') {
131
+ return inputRequired({
132
+ inputRequests: {
133
+ [SIGN_IN_KEY]: inputRequired.elicitUrl({ message: messageFor(state), url }),
134
+ },
135
+ });
136
+ }
137
+ /**
138
+ * Whether the client has already been through the sign-in page for this call.
139
+ *
140
+ * "accepted" means the user was shown the page and said they were done, so a
141
+ * still-missing sign-in is a real failure rather than a reason to ask again.
142
+ * Asking twice in a row would be a loop the user cannot escape.
143
+ */
144
+ export function priorAttempt(ctx) {
145
+ const responses = ctx?.mcpReq?.inputResponses;
146
+ if (!responses)
147
+ return 'none';
148
+ const view = inputResponse(responses, SIGN_IN_KEY);
149
+ if (view.kind === 'missing')
150
+ return 'none';
151
+ if (view.kind === 'elicit' && view.action === 'accept')
152
+ return 'accepted';
153
+ return 'refused';
154
+ }
155
+ /** The note explaining a retry that still found no credentials. */
156
+ export function retryNote(attempt, state = 'not-signed-in') {
157
+ if (attempt === 'accepted') {
158
+ if (state === 'locked') {
159
+ return 'The page was opened but the credentials are still locked, so the master password was not entered.';
160
+ }
161
+ return 'The sign-in page was opened but no credentials arrived, so the sign-in was not completed.';
162
+ }
163
+ if (attempt === 'refused') {
164
+ return 'The request to open the sign-in page was declined or cancelled.';
165
+ }
166
+ return undefined;
167
+ }
168
+ //# sourceMappingURL=sign-in-required.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-in-required.js","sourceRoot":"","sources":["../../src/tools/sign-in-required.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAA;AAE5C,MAAM,OAAO,GACX,+FAA+F;IAC/F,8FAA8F;IAC9F,sCAAsC,CAAA;AAExC;;;;;;;;;;;;GAYG;AACH,MAAM,cAAc,GAClB,6FAA6F;IAC7F,4FAA4F;IAC5F,sEAAsE,CAAA;AAKxE,SAAS,UAAU,CAAC,KAAyB;IAC3C,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAA;AACtD,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,OAAO,YAAY,CAAC,GAAG,CAAC,CAAA;AAC1B,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAC3B,GAAuB,EACvB,IAAa,EACb,KAAK,GAAuB,eAAe;IAE3C,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAA;IAErD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,wEAAwE;QACxE,gEAAgE;QAChE,MAAM,IAAI,GACR,KAAK,KAAK,QAAQ;YAChB,CAAC,CAAC,GAAG,IAAI,kFAAkF;gBACzF,4EAA4E;gBAC5E,uFAAuF;gBACvF,uCAAuC;YACzC,CAAC,CAAC,GAAG,IAAI,gFAAgF;gBACvF,4EAA4E,CAAA;QAClF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC7D,CAAC;IAED,MAAM,WAAW,GACf,KAAK,KAAK,QAAQ;QAChB,CAAC,CAAC,qFAAqF;YACrF,wFAAwF;YACxF,sDAAsD;QACxD,CAAC,CAAC,kFAAkF;YAClF,eAAe,CAAA;IAErB,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,YAAY,GAAG,OAAO,WAAW,EAAE,EAAE,CAAC;QAC7E,OAAO,EAAE,IAAI;KACd,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAW,EACX,KAAK,GAAuB,eAAe;IAE3C,OAAO,aAAa,CAAC;QACnB,aAAa,EAAE;YACb,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;SAC5E;KACF,CAAC,CAAA;AACJ,CAAC;AAKD;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,SAAS,GAAI,GAAqB,EAAE,MAAM,EAAE,cAAc,CAAA;IAChE,IAAI,CAAC,SAAS;QAAE,OAAO,MAAM,CAAA;IAC7B,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IAClD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAA;IACzE,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,SAAS,CACvB,OAAqB,EACrB,KAAK,GAAuB,eAAe;IAE3C,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvB,OAAO,mGAAmG,CAAA;QAC5G,CAAC;QACD,OAAO,2FAA2F,CAAA;IACpG,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,iEAAiE,CAAA;IAC1E,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The frame every page of the local interface sits in.
3
+ *
4
+ * Split out of pages.ts when the single status page became a set of sections:
5
+ * the styles, the navigation and the outer document are the same everywhere, and
6
+ * they were the larger half of that file.
7
+ *
8
+ * **There is no JavaScript here, and none anywhere in this interface.** The
9
+ * Content-Security-Policy forbids it, and that is not a limitation worked
10
+ * around: a page that cannot run script cannot be made to do anything by markup
11
+ * that finds its way in. So navigation is links, switches are form controls, and
12
+ * anything that looks interactive is CSS. See security.ts for the rest of the
13
+ * policy.
14
+ *
15
+ * The look follows Proton's own interfaces closely enough to feel related and
16
+ * uses none of their assets. No logo, no wordmark, no icon of theirs: those are
17
+ * trademarks, and every page says in as many words that this is not their
18
+ * project.
19
+ */
20
+ /** Escapes text for use in HTML. Applied to everything, without exception. */
21
+ export declare function escapeHtml(value: string): string;
22
+ /**
23
+ * The sections, in the order they appear.
24
+ *
25
+ * The top level is the service, which is why there is a group holding a single
26
+ * entry today: Proton Drive is meant to sit beside Proton Mail later, and a
27
+ * structure introduced at that point would move everything the user had learned.
28
+ */
29
+ export type Section = 'overview' | 'mailboxes' | 'bridge' | 'credentials' | 'activity'
30
+ /** Pages outside the navigation: sign-in, unlock, rejection. */
31
+ | 'none';
32
+ /**
33
+ * Whether the notice appears, and whether it can be sent away.
34
+ *
35
+ * It is read once and then it is furniture, so it can be dismissed and stays
36
+ * dismissed. What does not go away is the `unofficial` mark beside the name in
37
+ * the rail: the point of the notice is that nobody mistakes this for Proton's
38
+ * own software, and a mark that is always in view carries that better than a
39
+ * paragraph nobody reads twice.
40
+ *
41
+ * Dismissing is a form post rather than a click handler, because there is no
42
+ * JavaScript here, and it is remembered by the server rather than by the
43
+ * browser, because a setting the server holds survives a different browser.
44
+ */
45
+ export type DisclaimerState =
46
+ /** Shown, with the button. The token is bound to the dismissal path. */
47
+ {
48
+ csrf: string;
49
+ from: Section;
50
+ }
51
+ /** Shown without a button: no valid session to remember a dismissal against. */
52
+ | 'plain'
53
+ /** Dismissed earlier, so not shown at all. */
54
+ | 'dismissed';
55
+ export interface LayoutOptions {
56
+ title: string;
57
+ body: string;
58
+ /**
59
+ * Which entry is marked as current. `none` renders no navigation at all,
60
+ * which is what the sign-in and unlock pages want: there is nothing to reach
61
+ * yet, and a rail full of dead links would suggest otherwise.
62
+ */
63
+ section: Section;
64
+ /** Needed to build the links. Absent before anybody has signed in. */
65
+ token?: string;
66
+ /** Defaults to showing it without a button, the safe direction. */
67
+ disclaimer?: DisclaimerState;
68
+ }
69
+ export declare function layout(options: LayoutOptions): string;
70
+ /** Renders a duration the way a person would say it. */
71
+ export declare function formatDuration(ms: number): string;
72
+ /** An error banner, or nothing. */
73
+ export declare function errorBanner(message: string | undefined): string;
74
+ /** A success banner, or nothing. */
75
+ export declare function noticeBanner(message: string | undefined): string;