fluxmail 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +111 -0
- package/README.md +20 -4
- package/dist/accounts/gmailConnection.d.ts +17 -0
- package/dist/accounts/gmailConnection.d.ts.map +1 -0
- package/dist/accounts/gmailConnection.js +32 -0
- package/dist/accounts/gmailConnection.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +175 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -1
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -1
- package/dist/context.js.map +1 -1
- package/dist/http/app.d.ts +2 -0
- package/dist/http/app.d.ts.map +1 -1
- package/dist/http/app.js +96 -19
- package/dist/http/app.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/buildServer.d.ts +11 -3
- package/dist/mcp/buildServer.d.ts.map +1 -1
- package/dist/mcp/buildServer.js +333 -229
- package/dist/mcp/buildServer.js.map +1 -1
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +70 -0
- package/dist/permissions.js.map +1 -0
- package/dist/service/emailService.d.ts +1 -1
- package/dist/service/emailService.d.ts.map +1 -1
- package/dist/service/emailService.js +2 -2
- package/dist/service/emailService.js.map +1 -1
- package/dist/storage/apiKeys.d.ts +6 -1
- package/dist/storage/apiKeys.d.ts.map +1 -1
- package/dist/storage/apiKeys.js +56 -5
- package/dist/storage/apiKeys.js.map +1 -1
- package/dist/storage/db.d.ts +172 -0
- package/dist/storage/db.d.ts.map +1 -1
- package/dist/storage/db.js +35 -1
- package/dist/storage/db.js.map +1 -1
- package/dist/storage/gmailConnectionGrants.d.ts +28 -0
- package/dist/storage/gmailConnectionGrants.d.ts.map +1 -0
- package/dist/storage/gmailConnectionGrants.js +70 -0
- package/dist/storage/gmailConnectionGrants.js.map +1 -0
- package/dist/telemetry.d.ts +35 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +283 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +11 -7
package/LICENSE.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Fluxmail Proprietary License Agreement
|
|
2
|
+
|
|
3
|
+
Version 1.0, July 12, 2026
|
|
4
|
+
|
|
5
|
+
Copyright © 2026 Fluxmail. All rights reserved.
|
|
6
|
+
|
|
7
|
+
This Fluxmail Proprietary License Agreement (the “Agreement”) is between Fluxmail and the individual or entity accepting this Agreement (“you”). If you use the Software for an organization, you represent that you have authority to bind that organization, and “you” includes that organization.
|
|
8
|
+
|
|
9
|
+
By downloading, copying, installing, modifying, or using the Software, you accept this Agreement. If you do not accept it, do not use the Software.
|
|
10
|
+
|
|
11
|
+
## 1. Definitions
|
|
12
|
+
|
|
13
|
+
“Software” means the Fluxmail software distributed with this Agreement, including its source code, object code, documentation, and any Fluxmail updates or modifications provided under this Agreement. Software does not include Third-Party Software.
|
|
14
|
+
|
|
15
|
+
“License Entitlement” means the users, mailboxes, instances, features, usage limits, term, and other rights authorized by a license key, built-in plan, order form, subscription record, or other written authorization issued by Fluxmail.
|
|
16
|
+
|
|
17
|
+
“Production Use” means using the Software for live personal, business, organizational, customer-facing, or revenue-generating operations. It includes processing live mailbox data or making the Software available to users other than solely for development, testing, security review, or evaluation.
|
|
18
|
+
|
|
19
|
+
“Non-Production Use” means private development, testing, security review, or evaluation that does not process live operations or provide a service to end users.
|
|
20
|
+
|
|
21
|
+
“Third-Party Software” means software identified as third-party or open-source software, including dependencies distributed with or used by the Software.
|
|
22
|
+
|
|
23
|
+
“Competing Product” means a product or service whose primary purpose is to provide third parties with a self-hosted or hosted email integration server, email-agent interface, or email API offering substantially similar core functionality to the Software. It does not include software developed and used solely for your own internal operations.
|
|
24
|
+
|
|
25
|
+
## 2. License grant
|
|
26
|
+
|
|
27
|
+
Subject to this Agreement, Fluxmail grants you a limited, worldwide, non-exclusive, non-transferable, non-sublicensable license to:
|
|
28
|
+
|
|
29
|
+
1. view, audit, download, copy, compile, and run the Software for Non-Production Use;
|
|
30
|
+
2. make private modifications to the Software for your own use;
|
|
31
|
+
3. make reasonable backup and archival copies;
|
|
32
|
+
4. use GitHub’s standard public-repository features to view and fork the Software solely as needed to exercise the rights in items 1 through 3; and
|
|
33
|
+
5. use the Software in Production Use only within a valid License Entitlement.
|
|
34
|
+
|
|
35
|
+
A License Entitlement may be offered without charge. A zero-dollar or built-in entitlement remains subject to this Agreement and to its stated limits.
|
|
36
|
+
|
|
37
|
+
All rights not expressly granted in this Agreement are reserved by Fluxmail.
|
|
38
|
+
|
|
39
|
+
## 3. License Entitlements and technical controls
|
|
40
|
+
|
|
41
|
+
Your Production Use must stay within your License Entitlement. A License Entitlement applies only to the person, organization, and instances it covers. You may not share a license key outside that person or organization or use it on more instances than authorized.
|
|
42
|
+
|
|
43
|
+
You may not modify, remove, disable, circumvent, bypass, or interfere with any license-key validation, entitlement enforcement, usage metering, plan limitation, or other technical restriction in the Software. You may not enable or expose functionality protected by any of those controls except as authorized by your License Entitlement.
|
|
44
|
+
|
|
45
|
+
Modified versions and derivative works containing any part of the Software remain subject to this Agreement. Production Use of them requires a valid License Entitlement covering that use.
|
|
46
|
+
|
|
47
|
+
## 4. Restrictions
|
|
48
|
+
|
|
49
|
+
Unless Fluxmail gives you separate written permission, you may not:
|
|
50
|
+
|
|
51
|
+
1. distribute, publish, sell, sublicense, rent, lease, or otherwise provide the Software or a modified version to a third party;
|
|
52
|
+
2. publish or maintain a fork for distributing the Software, providing production-ready builds to others, avoiding License Entitlements, or operating a Competing Product;
|
|
53
|
+
3. offer the Software or its substantial features to third parties as a hosted, managed, time-sharing, or service-bureau product;
|
|
54
|
+
4. white-label the Software or represent it as your own product;
|
|
55
|
+
5. embed or incorporate the Software into a product or service distributed or provided to third parties;
|
|
56
|
+
6. use the Software or its source code to develop, train, support, or operate a Competing Product;
|
|
57
|
+
7. exceed or help another person exceed a License Entitlement;
|
|
58
|
+
8. remove, alter, or obscure copyright, license, attribution, or trademark notices; or
|
|
59
|
+
9. use Fluxmail names, logos, or trademarks except as allowed by applicable law or separate written permission.
|
|
60
|
+
|
|
61
|
+
These restrictions do not limit rights that applicable law does not allow Fluxmail to restrict.
|
|
62
|
+
|
|
63
|
+
## 5. Ownership and modifications
|
|
64
|
+
|
|
65
|
+
Fluxmail and its licensors retain all right, title, and interest in the Software, including all intellectual property rights. This Agreement does not transfer ownership of the Software to you.
|
|
66
|
+
|
|
67
|
+
As between you and Fluxmail, you retain ownership of code you write independently. Your ownership of a modification does not give you rights in the underlying Software or permission to distribute or commercially exploit a combined or derivative work.
|
|
68
|
+
|
|
69
|
+
## 6. Third-Party Software
|
|
70
|
+
|
|
71
|
+
Third-Party Software is licensed under its own terms, not this Agreement. If a third-party license conflicts with this Agreement for that Third-Party Software, the third-party license controls for that component. You are responsible for complying with applicable third-party terms.
|
|
72
|
+
|
|
73
|
+
## 7. Orders, subscriptions, and other agreements
|
|
74
|
+
|
|
75
|
+
Pricing, payment, renewal, cancellation, support, and service terms may be stated in an order form, subscription terms, or another agreement with Fluxmail. If that agreement conflicts with this Agreement, it controls for the specific subject and parties it covers.
|
|
76
|
+
|
|
77
|
+
Each release of the Software is governed by the license version distributed with that release. Fluxmail may distribute later releases under updated terms, but updated terms do not change the license for a release you already received unless you agree otherwise.
|
|
78
|
+
|
|
79
|
+
## 8. Term and termination
|
|
80
|
+
|
|
81
|
+
This Agreement continues until terminated. It terminates automatically if you materially breach it and do not cure the breach within 30 days after receiving notice from Fluxmail. A breach involving circumvention of technical controls, unauthorized distribution, or misuse of a license key may be terminated immediately if it cannot reasonably be cured.
|
|
82
|
+
|
|
83
|
+
When this Agreement terminates, you must stop using and delete all copies of the Software in your possession or control, except copies you must retain by law. Sections 4 through 12 survive termination. Termination does not affect rights or remedies that accrued before termination.
|
|
84
|
+
|
|
85
|
+
Expiration, cancellation, or downgrade of a License Entitlement may reduce or end your authorized Production Use without terminating this Agreement. Any grace period or restricted mode is determined by the applicable License Entitlement and the Software release you use.
|
|
86
|
+
|
|
87
|
+
## 9. Disclaimer of warranties
|
|
88
|
+
|
|
89
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED “AS IS” AND “AS AVAILABLE,” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. FLUXMAIL DISCLAIMS ALL EXPRESS, IMPLIED, AND STATUTORY WARRANTIES, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND THAT THE SOFTWARE WILL BE SECURE, UNINTERRUPTED, OR ERROR-FREE.
|
|
90
|
+
|
|
91
|
+
## 10. Limitation of liability
|
|
92
|
+
|
|
93
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, FLUXMAIL WILL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF DATA, REVENUE, PROFITS, GOODWILL, OR BUSINESS OPPORTUNITY, ARISING OUT OF OR RELATED TO THE SOFTWARE OR THIS AGREEMENT, EVEN IF FLUXMAIL WAS ADVISED THAT SUCH DAMAGES WERE POSSIBLE.
|
|
94
|
+
|
|
95
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, FLUXMAIL’S TOTAL LIABILITY ARISING OUT OF OR RELATED TO THE SOFTWARE OR THIS AGREEMENT WILL NOT EXCEED THE GREATER OF (A) THE AMOUNT YOU PAID FLUXMAIL FOR THE SOFTWARE DURING THE 12 MONTHS BEFORE THE EVENT GIVING RISE TO THE CLAIM OR (B) US$100.
|
|
96
|
+
|
|
97
|
+
Some jurisdictions do not allow certain warranty disclaimers or liability limitations. In those jurisdictions, these provisions apply only to the extent permitted by law.
|
|
98
|
+
|
|
99
|
+
## 11. Notices
|
|
100
|
+
|
|
101
|
+
Fluxmail may give you notice under this Agreement by email to any address you provided in connection with a License Entitlement, order, subscription, support request, or other communication with Fluxmail. An email notice is deemed received on the first business day after it is sent unless Fluxmail receives a delivery-failure message. You are responsible for keeping your contact information current.
|
|
102
|
+
|
|
103
|
+
## 12. General terms
|
|
104
|
+
|
|
105
|
+
You may not assign or transfer this Agreement or your License Entitlement without Fluxmail’s prior written consent. Fluxmail may assign this Agreement in connection with a merger, acquisition, reorganization, or sale of substantially all related assets.
|
|
106
|
+
|
|
107
|
+
If any provision of this Agreement is unenforceable, it will be enforced to the maximum extent permitted, and the remaining provisions will remain in effect. A waiver must be in writing and applies only to the specific instance stated. Headings are for convenience and do not affect interpretation.
|
|
108
|
+
|
|
109
|
+
This Agreement is governed by the laws of the State of New Jersey, excluding its conflict-of-law rules. Any dispute arising out of or relating to the Software or this Agreement must be brought exclusively in a state or federal court of competent jurisdiction located in New Jersey. You and Fluxmail consent to the personal jurisdiction of those courts. This paragraph does not limit rights that applicable law does not allow a party to waive.
|
|
110
|
+
|
|
111
|
+
This Agreement, together with any applicable License Entitlement and agreement described in Section 7, is the entire agreement between you and Fluxmail about the Software and replaces prior discussions or understandings about its licensing.
|
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# Fluxmail
|
|
2
2
|
|
|
3
|
-
Fluxmail is a self-hosted MCP server that connects AI agents to Gmail and IMAP/SMTP mailboxes. It gives MCP clients a consistent set of tools to read, search, draft, send, and organize email.
|
|
3
|
+
[Fluxmail](https://github.com/churichard/fluxmail-mcp) is a self-hosted MCP server that connects AI agents to Gmail and IMAP/SMTP mailboxes. It gives MCP clients a consistent set of tools to read, search, draft, send, and organize email.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Fluxmail requires Node.js 20 or later.
|
|
7
|
+
Fluxmail requires Node.js 20.20.x, or Node.js 22.22 or later.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g fluxmail
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
To run Fluxmail without installing it globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx fluxmail
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
## Connect Gmail
|
|
14
20
|
|
|
15
21
|
Create a Google Cloud OAuth client with the Gmail API enabled, then save its credentials:
|
|
@@ -20,7 +26,15 @@ fluxmail config set GOOGLE_CLIENT_SECRET <your-client-secret>
|
|
|
20
26
|
fluxmail accounts add gmail
|
|
21
27
|
```
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
For a local installation, the last command starts Google's consent flow in your browser.
|
|
30
|
+
|
|
31
|
+
For Docker or a remote server, set `FLUXMAIL_PUBLIC_URL` to the server's public HTTPS address and register `<FLUXMAIL_PUBLIC_URL>/auth/google/callback` as an authorized redirect URI in Google Cloud. Run the command on the server, then open the printed URL in your local browser:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
docker compose exec fluxmail fluxmail accounts add gmail
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Open the printed link, then select Continue with Google. The link expires after 10 minutes and works once. Fluxmail selects the flow from `FLUXMAIL_PUBLIC_URL`; `--local` and `--hosted` override that selection when needed.
|
|
24
38
|
|
|
25
39
|
## Connect IMAP
|
|
26
40
|
|
|
@@ -35,7 +49,7 @@ Fluxmail prompts for the password and stores it encrypted. Use `--imap-password-
|
|
|
35
49
|
|
|
36
50
|
## Connect an MCP client
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
For example, with Claude Code:
|
|
39
53
|
|
|
40
54
|
```bash
|
|
41
55
|
claude mcp add fluxmail -- fluxmail stdio
|
|
@@ -47,6 +61,8 @@ Or with Codex CLI:
|
|
|
47
61
|
codex mcp add fluxmail -- fluxmail stdio
|
|
48
62
|
```
|
|
49
63
|
|
|
64
|
+
To have your MCP client run Fluxmail without a global installation, replace `fluxmail stdio` with `npx -y fluxmail@latest stdio`. Replace `@latest` with an exact version to keep the client on that version.
|
|
65
|
+
|
|
50
66
|
Fluxmail also supports Streamable HTTP for Docker and remote deployments.
|
|
51
67
|
|
|
52
68
|
See the [setup guide](https://fluxmail.ai/docs/quickstart) for Google OAuth configuration, Docker deployment, supported MCP clients, environment variables, and the complete tool list.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FluxmailConfig } from '../config.js';
|
|
2
|
+
import type { FluxmailDb } from '../storage/db.js';
|
|
3
|
+
import { type GmailConnectionIntent } from '../storage/gmailConnectionGrants.js';
|
|
4
|
+
export type GmailConnectionMode = 'local' | 'hosted';
|
|
5
|
+
export declare function validateAccountConnectionFlags(provider: 'gmail' | 'imap', options: {
|
|
6
|
+
local?: boolean;
|
|
7
|
+
hosted?: boolean;
|
|
8
|
+
}): void;
|
|
9
|
+
export declare function selectGmailConnectionMode(config: FluxmailConfig, options: {
|
|
10
|
+
local?: boolean;
|
|
11
|
+
hosted?: boolean;
|
|
12
|
+
}): GmailConnectionMode;
|
|
13
|
+
export declare function prepareHostedGmailConnection(db: FluxmailDb, config: FluxmailConfig, intent: GmailConnectionIntent): {
|
|
14
|
+
connectionUrl: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=gmailConnection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmailConnection.d.ts","sourceRoot":"","sources":["../../src/accounts/gmailConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAA8B,KAAK,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAG7G,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAOrD,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,OAAO,GAAG,MAAM,EAC1B,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C,IAAI,CAIN;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C,mBAAmB,CAUrB;AAED,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,qBAAqB,GAC5B;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAO9C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EmailError } from '@fluxmail/core';
|
|
2
|
+
import { createGmailConnectionGrant } from '../storage/gmailConnectionGrants.js';
|
|
3
|
+
import { requireGoogleConfig } from './googleAuth.js';
|
|
4
|
+
function isLoopbackPublicUrl(publicUrl) {
|
|
5
|
+
const hostname = new URL(publicUrl).hostname;
|
|
6
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
|
|
7
|
+
}
|
|
8
|
+
export function validateAccountConnectionFlags(provider, options) {
|
|
9
|
+
if (provider === 'imap' && (options.local || options.hosted)) {
|
|
10
|
+
throw new EmailError('invalid_request', '--local and --hosted are only available for Gmail accounts.');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function selectGmailConnectionMode(config, options) {
|
|
14
|
+
if (options.local && options.hosted) {
|
|
15
|
+
throw new EmailError('invalid_request', '--local and --hosted cannot be used together.');
|
|
16
|
+
}
|
|
17
|
+
if (options.hosted && !config.publicUrlConfigured) {
|
|
18
|
+
throw new EmailError('invalid_request', '--hosted requires FLUXMAIL_PUBLIC_URL to be set.');
|
|
19
|
+
}
|
|
20
|
+
return options.hosted || (config.publicUrlConfigured && !options.local && !isLoopbackPublicUrl(config.publicUrl))
|
|
21
|
+
? 'hosted'
|
|
22
|
+
: 'local';
|
|
23
|
+
}
|
|
24
|
+
export function prepareHostedGmailConnection(db, config, intent) {
|
|
25
|
+
requireGoogleConfig(config);
|
|
26
|
+
const { token, expiresAt } = createGmailConnectionGrant(db, intent);
|
|
27
|
+
return {
|
|
28
|
+
connectionUrl: `${config.publicUrl}/auth/google/connect?token=${encodeURIComponent(token)}`,
|
|
29
|
+
expiresAt,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=gmailConnection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmailConnection.js","sourceRoot":"","sources":["../../src/accounts/gmailConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAE,0BAA0B,EAA8B,MAAM,qCAAqC,CAAC;AAC7G,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAItD,SAAS,mBAAmB,CAAC,SAAiB;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC;IAC7C,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,OAAO,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,QAA0B,EAC1B,OAA8C;IAE9C,IAAI,QAAQ,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,6DAA6D,CAAC,CAAC;IACzG,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,MAAsB,EACtB,OAA8C;IAE9C,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,+CAA+C,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAClD,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,kDAAkD,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/G,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,OAAO,CAAC;AACd,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,EAAc,EACd,MAAsB,EACtB,MAA6B;IAE7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,0BAA0B,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACpE,OAAO;QACL,aAAa,EAAE,GAAG,MAAM,CAAC,SAAS,8BAA8B,kBAAkB,CAAC,KAAK,CAAC,EAAE;QAC3F,SAAS;KACV,CAAC;AACJ,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type PermissionPolicy } from './permissions.js';
|
|
3
|
+
interface PermissionOptions {
|
|
4
|
+
profile?: string;
|
|
5
|
+
allow: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function permissionPolicyFromOptions(opts: PermissionOptions, requireSelection?: boolean): PermissionPolicy;
|
|
2
8
|
export {};
|
|
3
9
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAwCA,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAkC1B,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAMD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,iBAAiB,EAAE,gBAAgB,UAAQ,GAAG,gBAAgB,CAa/G"}
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,8 @@ import { configFilePath, maskStoredConfigValue, readStoredConfig, resolveDataDir
|
|
|
9
9
|
import { createApp } from './http/app.js';
|
|
10
10
|
import { buildMcpServer } from './mcp/buildServer.js';
|
|
11
11
|
import { runLoopbackFlow } from './accounts/googleAuth.js';
|
|
12
|
-
import {
|
|
12
|
+
import { prepareHostedGmailConnection, selectGmailConnectionMode, validateAccountConnectionFlags, } from './accounts/gmailConnection.js';
|
|
13
|
+
import { createApiKey, listApiKeys, revokeApiKey, updateApiKeyPermissions } from './storage/apiKeys.js';
|
|
13
14
|
import { addMember, findMember, listMembers, removeMember } from './storage/members.js';
|
|
14
15
|
import { activateLicense } from './licensing/activation.js';
|
|
15
16
|
import { LICENSE_KEY_PATTERN, releaseLicense } from './licensing/client.js';
|
|
@@ -18,6 +19,27 @@ import { checkLicenseState, clearLease, getEntitlements, readLeaseRow, } from '.
|
|
|
18
19
|
import { loadInstanceId, startLicenseRefresher } from './licensing/refresher.js';
|
|
19
20
|
import { VERSION } from './version.js';
|
|
20
21
|
import { countPending } from './storage/scheduledSends.js';
|
|
22
|
+
import { customPermissionPolicy, FULL_PERMISSION_POLICY, isNamedPermissionProfile, MCP_CAPABILITIES, NAMED_PERMISSION_PROFILES, permissionPolicyForProfile, } from './permissions.js';
|
|
23
|
+
import { getTelemetry, installTelemetryStreamEndHandler, isTelemetryEnabled, setTelemetryEnabled, shutdownTelemetry, } from './telemetry.js';
|
|
24
|
+
function collectOption(value, previous) {
|
|
25
|
+
return [...previous, value];
|
|
26
|
+
}
|
|
27
|
+
export function permissionPolicyFromOptions(opts, requireSelection = false) {
|
|
28
|
+
if (opts.profile && opts.allow.length) {
|
|
29
|
+
throw new Error('--profile cannot be combined with --allow.');
|
|
30
|
+
}
|
|
31
|
+
if (opts.profile) {
|
|
32
|
+
if (!isNamedPermissionProfile(opts.profile)) {
|
|
33
|
+
throw new Error(`Unknown profile "${opts.profile}". Expected one of: ${NAMED_PERMISSION_PROFILES.join(', ')}.`);
|
|
34
|
+
}
|
|
35
|
+
return permissionPolicyForProfile(opts.profile);
|
|
36
|
+
}
|
|
37
|
+
if (opts.allow.length)
|
|
38
|
+
return customPermissionPolicy(opts.allow);
|
|
39
|
+
if (requireSelection)
|
|
40
|
+
throw new Error('Choose --profile or at least one --allow capability.');
|
|
41
|
+
return FULL_PERMISSION_POLICY;
|
|
42
|
+
}
|
|
21
43
|
function connectionPort(value, option) {
|
|
22
44
|
const port = Number(value);
|
|
23
45
|
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
@@ -93,15 +115,72 @@ function warnLicense(db, log = console.error) {
|
|
|
93
115
|
log(`Warning: ${warning}`);
|
|
94
116
|
}
|
|
95
117
|
const program = new Command();
|
|
96
|
-
program
|
|
118
|
+
program
|
|
119
|
+
.name('fluxmail')
|
|
120
|
+
.description('Fluxmail, a self-hosted MCP server for your email')
|
|
121
|
+
.version(VERSION, '-v, --version');
|
|
122
|
+
function commandPath(command) {
|
|
123
|
+
const names = [];
|
|
124
|
+
for (let current = command; current?.parent; current = current.parent)
|
|
125
|
+
names.unshift(current.name());
|
|
126
|
+
return names.join(' ');
|
|
127
|
+
}
|
|
128
|
+
let telemetrySignalHandlersInstalled = false;
|
|
129
|
+
const TELEMETRY_SIGNAL_SHUTDOWN_TIMEOUT_MS = 1_000;
|
|
130
|
+
/** Flush queued telemetry before restoring Node's default signal behavior. */
|
|
131
|
+
function installTelemetrySignalHandlers() {
|
|
132
|
+
if (telemetrySignalHandlersInstalled)
|
|
133
|
+
return;
|
|
134
|
+
telemetrySignalHandlersInstalled = true;
|
|
135
|
+
const shutdownAndResignal = (signal) => {
|
|
136
|
+
process.off('SIGINT', onSigint);
|
|
137
|
+
process.off('SIGTERM', onSigterm);
|
|
138
|
+
process.off('SIGHUP', onSighup);
|
|
139
|
+
let resignaled = false;
|
|
140
|
+
const resignal = () => {
|
|
141
|
+
if (resignaled)
|
|
142
|
+
return;
|
|
143
|
+
resignaled = true;
|
|
144
|
+
process.kill(process.pid, signal);
|
|
145
|
+
};
|
|
146
|
+
const timeout = setTimeout(resignal, TELEMETRY_SIGNAL_SHUTDOWN_TIMEOUT_MS);
|
|
147
|
+
void shutdownTelemetry().finally(() => {
|
|
148
|
+
clearTimeout(timeout);
|
|
149
|
+
resignal();
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const onSigint = () => shutdownAndResignal('SIGINT');
|
|
153
|
+
const onSigterm = () => shutdownAndResignal('SIGTERM');
|
|
154
|
+
const onSighup = () => shutdownAndResignal('SIGHUP');
|
|
155
|
+
process.once('SIGINT', onSigint);
|
|
156
|
+
process.once('SIGTERM', onSigterm);
|
|
157
|
+
process.once('SIGHUP', onSighup);
|
|
158
|
+
}
|
|
159
|
+
program.hook('preAction', (_command, actionCommand) => {
|
|
160
|
+
const command = commandPath(actionCommand);
|
|
161
|
+
// Respect the opt-out before creating a telemetry client or recording this command.
|
|
162
|
+
if (command === 'telemetry disable')
|
|
163
|
+
return;
|
|
164
|
+
const dataDir = resolveDataDir();
|
|
165
|
+
getTelemetry(dataDir).capture('cli command used', {
|
|
166
|
+
product_surface: 'cli',
|
|
167
|
+
command,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
program.hook('postAction', async (_command, actionCommand) => {
|
|
171
|
+
if (actionCommand.name() !== 'serve' && actionCommand.name() !== 'stdio')
|
|
172
|
+
await shutdownTelemetry();
|
|
173
|
+
});
|
|
97
174
|
program
|
|
98
175
|
.command('serve')
|
|
99
176
|
.description('Run the HTTP server (Streamable HTTP MCP at /mcp)')
|
|
100
177
|
.action(() => {
|
|
178
|
+
installTelemetrySignalHandlers();
|
|
101
179
|
const ctx = createContext();
|
|
102
180
|
const app = createApp(ctx);
|
|
103
181
|
ctx.scheduler.start();
|
|
104
182
|
serve({ fetch: app.fetch, port: ctx.config.port }, () => {
|
|
183
|
+
ctx.telemetry.capture('mcp server started', { product_surface: 'mcp', transport: 'http' });
|
|
105
184
|
console.log(`Fluxmail listening on ${ctx.config.publicUrl}`);
|
|
106
185
|
console.log(` MCP endpoint: ${ctx.config.publicUrl}/mcp`);
|
|
107
186
|
console.log(` Auth mode: ${ctx.config.authMode}`);
|
|
@@ -125,11 +204,22 @@ program
|
|
|
125
204
|
program
|
|
126
205
|
.command('stdio')
|
|
127
206
|
.description('Run as a stdio MCP server (for Claude Desktop / Claude Code local config)')
|
|
128
|
-
.
|
|
207
|
+
.option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
|
|
208
|
+
.option('--allow <capability>', 'Allow one MCP capability; repeat as needed', collectOption, [])
|
|
209
|
+
.action(async (opts) => {
|
|
210
|
+
installTelemetrySignalHandlers();
|
|
211
|
+
installTelemetryStreamEndHandler(process.stdin);
|
|
129
212
|
const ctx = createContext();
|
|
130
|
-
const
|
|
213
|
+
const permissions = permissionPolicyFromOptions(opts);
|
|
214
|
+
const server = buildMcpServer(ctx.service, {
|
|
215
|
+
permissions,
|
|
216
|
+
maxAttachmentBytes: ctx.config.maxAttachmentBytes,
|
|
217
|
+
telemetry: ctx.telemetry,
|
|
218
|
+
transport: 'stdio',
|
|
219
|
+
});
|
|
131
220
|
ctx.scheduler.start();
|
|
132
221
|
await server.connect(new StdioServerTransport());
|
|
222
|
+
ctx.telemetry.capture('mcp server started', { product_surface: 'mcp', transport: 'stdio' });
|
|
133
223
|
// stdout belongs to the MCP protocol; log to stderr only.
|
|
134
224
|
startLicenseRefresher({
|
|
135
225
|
db: ctx.db,
|
|
@@ -149,6 +239,8 @@ accounts
|
|
|
149
239
|
.argument('<provider>', 'Email provider: gmail or imap')
|
|
150
240
|
.option('--reauthorize <account-id>', 'Reconnect an existing account')
|
|
151
241
|
.option('--member <member>', 'Member (id or email) who owns the new mailbox; omit for a shared mailbox')
|
|
242
|
+
.option('--local', 'Use the local browser callback for Gmail')
|
|
243
|
+
.option('--hosted', 'Use FLUXMAIL_PUBLIC_URL for the Gmail callback')
|
|
152
244
|
.option('--email <address>', 'Mailbox address (required for IMAP)')
|
|
153
245
|
.option('--display-name <name>', 'Sender name for IMAP messages')
|
|
154
246
|
.option('--imap-host <host>', 'IMAP server hostname')
|
|
@@ -177,6 +269,7 @@ accounts
|
|
|
177
269
|
const ctx = createContext();
|
|
178
270
|
warnLicense(ctx.db);
|
|
179
271
|
try {
|
|
272
|
+
validateAccountConnectionFlags(provider, opts);
|
|
180
273
|
if (opts.reauthorize && opts.member) {
|
|
181
274
|
throw new EmailError('invalid_request', '--member cannot be combined with --reauthorize; use "fluxmail accounts assign" to change ownership.');
|
|
182
275
|
}
|
|
@@ -240,6 +333,17 @@ accounts
|
|
|
240
333
|
console.log(`Warning: ${warning.message}.`);
|
|
241
334
|
return;
|
|
242
335
|
}
|
|
336
|
+
const gmailConnectionMode = selectGmailConnectionMode(ctx.config, opts);
|
|
337
|
+
if (gmailConnectionMode === 'hosted') {
|
|
338
|
+
const { connectionUrl } = prepareHostedGmailConnection(ctx.db, ctx.config, {
|
|
339
|
+
...(member ? { memberId: member.id } : {}),
|
|
340
|
+
...(existing ? { reauthorizeAccountId: existing.id } : {}),
|
|
341
|
+
});
|
|
342
|
+
console.log('\nOpen this URL in your browser to connect Gmail:\n');
|
|
343
|
+
console.log(` ${connectionUrl}\n`);
|
|
344
|
+
console.log('This link expires in 10 minutes and can only be used once.');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
243
347
|
const account = await runLoopbackFlow(ctx.config, (url) => {
|
|
244
348
|
console.log('\nOpen this URL in your browser to authorize Gmail access:\n');
|
|
245
349
|
console.log(` ${url}\n`);
|
|
@@ -425,18 +529,30 @@ membersCmd
|
|
|
425
529
|
}
|
|
426
530
|
});
|
|
427
531
|
const apikey = program.command('apikey').description('Manage API keys for the HTTP MCP endpoint');
|
|
532
|
+
apikey
|
|
533
|
+
.command('capabilities')
|
|
534
|
+
.description('List MCP capabilities for custom permission policies')
|
|
535
|
+
.action(() => {
|
|
536
|
+
for (const capability of MCP_CAPABILITIES)
|
|
537
|
+
console.log(capability);
|
|
538
|
+
});
|
|
428
539
|
apikey
|
|
429
540
|
.command('create')
|
|
430
541
|
.requiredOption('--name <name>', 'Human-readable key name')
|
|
431
542
|
.option('--member <member>', 'Member (id or email) the key is issued to')
|
|
543
|
+
.option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
|
|
544
|
+
.option('--allow <capability>', 'Allow one MCP capability; repeat as needed', collectOption, [])
|
|
432
545
|
.description('Create an API key (shown once)')
|
|
433
546
|
.action((opts) => {
|
|
434
547
|
const ctx = createContext();
|
|
435
548
|
warnLicense(ctx.db);
|
|
436
549
|
try {
|
|
437
550
|
const member = opts.member ? findMember(ctx.db, opts.member) : undefined;
|
|
438
|
-
const
|
|
439
|
-
|
|
551
|
+
const permissions = permissionPolicyFromOptions(opts);
|
|
552
|
+
const { key, info } = createApiKey(ctx.db, opts.name, member?.id, permissions);
|
|
553
|
+
console.log(`Created API key "${info.name}" (id: ${info.id}, profile: ${info.permissionProfile})` +
|
|
554
|
+
(member ? ` for member ${member.name}` : '') +
|
|
555
|
+
'\n');
|
|
440
556
|
console.log(` ${key}\n`);
|
|
441
557
|
console.log('Store it now; it cannot be shown again.');
|
|
442
558
|
}
|
|
@@ -460,7 +576,32 @@ apikey
|
|
|
460
576
|
for (const k of keys) {
|
|
461
577
|
const lastUsed = k.lastUsedAt ? new Date(k.lastUsedAt).toISOString() : 'never';
|
|
462
578
|
const member = k.memberId ? (memberNames.get(k.memberId) ?? k.memberId) : '-';
|
|
463
|
-
console.log(`${k.id} ${k.name} member=${member}
|
|
579
|
+
console.log(`${k.id} ${k.name} member=${member} profile=${k.permissionProfile}` +
|
|
580
|
+
(k.permissionProfile === 'custom' ? ` allows=${k.capabilities.join(',')}` : '') +
|
|
581
|
+
` created=${new Date(k.createdAt).toISOString()} lastUsed=${lastUsed}`);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
apikey
|
|
585
|
+
.command('permissions')
|
|
586
|
+
.argument('<keyId>')
|
|
587
|
+
.option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
|
|
588
|
+
.option('--allow <capability>', 'Allow one MCP capability; repeat as needed', collectOption, [])
|
|
589
|
+
.description('Change the MCP permissions for an API key')
|
|
590
|
+
.action((keyId, opts) => {
|
|
591
|
+
const ctx = createContext();
|
|
592
|
+
warnLicense(ctx.db);
|
|
593
|
+
try {
|
|
594
|
+
const permissions = permissionPolicyFromOptions(opts, true);
|
|
595
|
+
if (!updateApiKeyPermissions(ctx.db, keyId, permissions)) {
|
|
596
|
+
console.error(`No key with id ${keyId}`);
|
|
597
|
+
process.exitCode = 1;
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
console.log(`Updated ${keyId} to profile ${permissions.profile}`);
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
604
|
+
process.exitCode = 1;
|
|
464
605
|
}
|
|
465
606
|
});
|
|
466
607
|
apikey
|
|
@@ -614,6 +755,31 @@ configCmd
|
|
|
614
755
|
console.log(`${key}=${maskStoredConfigValue(key, value)}`);
|
|
615
756
|
}
|
|
616
757
|
});
|
|
758
|
+
const telemetryCmd = program.command('telemetry').description('Manage anonymous usage telemetry');
|
|
759
|
+
telemetryCmd
|
|
760
|
+
.command('disable')
|
|
761
|
+
.description('Stop sending anonymous usage telemetry')
|
|
762
|
+
.action(() => {
|
|
763
|
+
const dataDir = resolveDataDir();
|
|
764
|
+
setTelemetryEnabled(dataDir, false);
|
|
765
|
+
console.log('Telemetry disabled.');
|
|
766
|
+
});
|
|
767
|
+
telemetryCmd
|
|
768
|
+
.command('enable')
|
|
769
|
+
.description('Allow anonymous usage telemetry')
|
|
770
|
+
.action(() => {
|
|
771
|
+
const dataDir = resolveDataDir();
|
|
772
|
+
setTelemetryEnabled(dataDir, true);
|
|
773
|
+
unsetStoredConfig(dataDir, 'FLUXMAIL_TELEMETRY');
|
|
774
|
+
console.log('Telemetry enabled. FLUXMAIL_TELEMETRY=0 or DO_NOT_TRACK=1 can still turn it off.');
|
|
775
|
+
});
|
|
776
|
+
telemetryCmd
|
|
777
|
+
.command('status')
|
|
778
|
+
.description('Show whether anonymous usage telemetry is enabled')
|
|
779
|
+
.action(() => {
|
|
780
|
+
const dataDir = resolveDataDir();
|
|
781
|
+
console.log(`Telemetry is ${isTelemetryEnabled(dataDir) ? 'enabled' : 'disabled'}.`);
|
|
782
|
+
});
|
|
617
783
|
program
|
|
618
784
|
.command('status')
|
|
619
785
|
.description('Show accounts, members, entitlements, and provider availability')
|
|
@@ -622,8 +788,9 @@ program
|
|
|
622
788
|
warnLicense(ctx.db);
|
|
623
789
|
console.log(JSON.stringify(await ctx.service.status(), null, 2));
|
|
624
790
|
});
|
|
625
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
791
|
+
program.parseAsync(process.argv).catch(async (err) => {
|
|
626
792
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
627
793
|
process.exitCode = 1;
|
|
794
|
+
await shutdownTelemetry();
|
|
628
795
|
});
|
|
629
796
|
//# sourceMappingURL=cli.js.map
|