@sendmux/cli 1.4.1 → 1.6.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/README.md +77 -8
- package/dist/agent-auth.d.ts +37 -0
- package/dist/agent-auth.d.ts.map +1 -0
- package/dist/agent-auth.js +348 -0
- package/dist/base-command.d.ts +11 -4
- package/dist/base-command.d.ts.map +1 -1
- package/dist/base-command.js +55 -4
- package/dist/commands/agent/invite-owner.d.ts +12 -0
- package/dist/commands/agent/invite-owner.d.ts.map +1 -0
- package/dist/commands/agent/invite-owner.js +36 -0
- package/dist/commands/agent/register.d.ts +16 -0
- package/dist/commands/agent/register.d.ts.map +1 -0
- package/dist/commands/agent/register.js +42 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +36 -0
- package/dist/commands/auth/logout.d.ts +9 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +22 -0
- package/dist/commands/mailbox/get-connection.d.ts +26 -0
- package/dist/commands/mailbox/get-connection.d.ts.map +1 -0
- package/dist/commands/mailbox/get-connection.js +7 -0
- package/dist/commands/mailbox/stream-events.d.ts +6 -6
- package/dist/commands/management/get-connection.d.ts +26 -0
- package/dist/commands/management/get-connection.d.ts.map +1 -0
- package/dist/commands/management/get-connection.js +7 -0
- package/dist/commands/profiles/list.d.ts.map +1 -1
- package/dist/commands/profiles/list.js +28 -8
- package/dist/commands/profiles/set.d.ts.map +1 -1
- package/dist/commands/profiles/set.js +15 -11
- package/dist/commands/profiles/show.d.ts.map +1 -1
- package/dist/commands/profiles/show.js +26 -12
- package/dist/commands/sending/get-connection.d.ts +26 -0
- package/dist/commands/sending/get-connection.d.ts.map +1 -0
- package/dist/commands/sending/get-connection.js +7 -0
- package/dist/generated/operations.d.ts +69 -6
- package/dist/generated/operations.d.ts.map +1 -1
- package/dist/generated/operations.js +76 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/oauth-http.d.ts +18 -0
- package/dist/oauth-http.d.ts.map +1 -0
- package/dist/oauth-http.js +113 -0
- package/dist/oauth-login.d.ts +13 -0
- package/dist/oauth-login.d.ts.map +1 -0
- package/dist/oauth-login.js +236 -0
- package/dist/oauth-profile.d.ts +6 -0
- package/dist/oauth-profile.d.ts.map +1 -0
- package/dist/oauth-profile.js +123 -0
- package/dist/operation-runner.d.ts.map +1 -1
- package/dist/operation-runner.js +1 -1
- package/dist/profiles.d.ts +60 -2
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +150 -4
- package/oclif.manifest.json +2061 -1377
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -17,10 +17,11 @@ Agent-drivable command line interface for Sendmux.
|
|
|
17
17
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
|
20
|
+
- No existing Sendmux account or API key is required to register an agent inbox.
|
|
20
21
|
- npm global installs, `npx`, or a downloaded release tarball.
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
22
|
+
- Management commands require an OAuth profile with the requested permissions or a root `smx_root_*` key.
|
|
23
|
+
- Sending commands require an OAuth profile with `email.send`, a send-capable `smx_mbx_*` key, or an owner-approved agent profile.
|
|
24
|
+
- Mailbox commands require an OAuth profile with the requested mailbox permissions, a mailbox-scoped `smx_mbx_*` key, or a registered agent profile.
|
|
24
25
|
|
|
25
26
|
## Installation
|
|
26
27
|
|
|
@@ -30,9 +31,66 @@ npm install -g @sendmux/cli
|
|
|
30
31
|
|
|
31
32
|
The package exposes the `sendmux` binary.
|
|
32
33
|
|
|
34
|
+
## Sign in with OAuth
|
|
35
|
+
|
|
36
|
+
Sign in through your browser and request only the permissions your workflow needs:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
sendmux auth:login work --scope mailbox.read
|
|
40
|
+
sendmux mailbox:get-connection --profile work --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Repeat `--scope` for additional permissions, such as `email.send` or `domain.read`. The consent screen lets you choose the team and mailboxes. A login creates a new profile; it cannot overwrite an existing profile. Use `--no-browser` to print the authorization URL without opening it automatically; open that URL on the same computer so the loopback callback reaches the CLI.
|
|
44
|
+
|
|
45
|
+
The CLI uses an authorization code with S256 PKCE and validates the callback state and issuer. Access and refresh tokens are saved in the protected local configuration file; command results and profile listings do not reveal them. API commands refresh tokens when needed. Concurrent commands share one refresh operation. If a refresh response is lost, the CLI requires a new login instead of replaying the old refresh token.
|
|
46
|
+
|
|
47
|
+
Revoke the connection and remove its profile:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
sendmux auth:logout work
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If revocation fails, the profile stays available for another logout attempt. Revocation affects the connection associated with that profile.
|
|
54
|
+
|
|
55
|
+
For a headless workflow that already has an access token, supply it through `SENDMUX_ACCESS_TOKEN`. Do not also set `SENDMUX_API_KEY` or pass `--api-key`. The CLI does not refresh an environment-supplied token; its issuer or your credential provider must renew it.
|
|
56
|
+
|
|
33
57
|
## Usage
|
|
34
58
|
|
|
35
|
-
|
|
59
|
+
Register an agent inbox and save its durable read credential in a local profile:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
sendmux agent:register my-agent \
|
|
63
|
+
--mailbox-local-part my-agent \
|
|
64
|
+
--client-name "My agent" \
|
|
65
|
+
--default \
|
|
66
|
+
--json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The registration result never prints the credential. The profile can read and receive mail without an expiry date, unless the registration is fully revoked. Inbox readiness may take a moment; the command waits for provisioning for up to 10 minutes and can be rerun safely with the same profile.
|
|
70
|
+
|
|
71
|
+
Read the inbox from any later process:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
sendmux mailbox:messages:list --profile my-agent --query limit=25 --json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To enable sending, invite the inbox owner either during registration with `--owner-email` or afterward:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
sendmux agent:invite-owner owner@example.com --profile my-agent --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The owner must accept the invitation and approve sending. After approval, Sending API commands automatically exchange the durable read credential for a one-hour `email.send` token and reuse it until it approaches expiry:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
sendmux sending:send \
|
|
87
|
+
--profile my-agent \
|
|
88
|
+
--idempotency-key "$IDEMPOTENCY_KEY" \
|
|
89
|
+
--body '{"from":{"email":"sender@example.com"},"to":{"email":"recipient@example.com"},"subject":"Hello","text_body":"Hello"}' \
|
|
90
|
+
--json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Existing Sendmux users can continue creating API-key profiles:
|
|
36
94
|
|
|
37
95
|
```sh
|
|
38
96
|
sendmux profiles:set default --api-key smx_root_... --default
|
|
@@ -50,6 +108,16 @@ sendmux sending:send --profile sending --body '{"from":{"email":"sender@example.
|
|
|
50
108
|
|
|
51
109
|
Commands reject mismatched key types before making a network request.
|
|
52
110
|
|
|
111
|
+
Check the selected profile's connection without sending an email:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
sendmux management:get-connection --profile default --json
|
|
115
|
+
sendmux mailbox:get-connection --profile mailbox --json
|
|
116
|
+
sendmux sending:get-connection --profile sending --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each command returns the team, credential, connection label, permissions, and authorised mailboxes. Mailbox connection checks do not need a mailbox selector.
|
|
120
|
+
|
|
53
121
|
## Attachments And Events
|
|
54
122
|
|
|
55
123
|
Send a mailbox message with a local file in one command:
|
|
@@ -98,11 +166,12 @@ sendmux mailbox:stream-events \
|
|
|
98
166
|
|
|
99
167
|
## Commands
|
|
100
168
|
|
|
101
|
-
The CLI includes `
|
|
169
|
+
The CLI includes `104` generated API operation commands:
|
|
102
170
|
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
171
|
+
- `42` Mailbox commands, including `mailbox:get-connection`, `mailbox:messages:list`, `mailbox:send-message`, and `mailbox:list-granted-mailboxes`.
|
|
172
|
+
- `54` Management commands, including `management:get-connection`, `management:domains:list`, `management:create-mailbox`, and `management:create-webhook`.
|
|
173
|
+
- `8` Sending commands, including `sending:get-connection`, `sending:get-open-api-spec`, `sending:send`, `sending:send:batch`, and attachment upload commands.
|
|
174
|
+
- Agent onboarding commands: `agent:register` and `agent:invite-owner`.
|
|
106
175
|
- Profile commands: `profiles:list`, `profiles:set`, and `profiles:show`.
|
|
107
176
|
|
|
108
177
|
Use command-level help for required path, query, header, and body fields.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ActiveAgentCliProfile, type CliConfig } from "./profiles.js";
|
|
2
|
+
interface RegisterAgentInput {
|
|
3
|
+
appOrigin?: string;
|
|
4
|
+
clientName?: string;
|
|
5
|
+
configDir: string;
|
|
6
|
+
makeDefault: boolean;
|
|
7
|
+
mailboxLocalPart?: string;
|
|
8
|
+
ownerEmail?: string;
|
|
9
|
+
profileName: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SafeAgentRegistrationResult {
|
|
12
|
+
default: boolean;
|
|
13
|
+
mailbox_email: string;
|
|
14
|
+
name: string;
|
|
15
|
+
owner_invite_status?: "pending";
|
|
16
|
+
registration_id: string;
|
|
17
|
+
status: "active";
|
|
18
|
+
}
|
|
19
|
+
export declare function registerAgent(input: RegisterAgentInput): Promise<SafeAgentRegistrationResult>;
|
|
20
|
+
export declare function inviteAgentOwner({ configDir, email, profileName, }: {
|
|
21
|
+
configDir: string;
|
|
22
|
+
email: string;
|
|
23
|
+
profileName: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
email: string;
|
|
26
|
+
status: "pending";
|
|
27
|
+
}>;
|
|
28
|
+
export declare function resolveAgentSendingToken({ config, configDir, profile, profileName, }: {
|
|
29
|
+
config: CliConfig;
|
|
30
|
+
configDir: string;
|
|
31
|
+
profile: ActiveAgentCliProfile;
|
|
32
|
+
profileName: string;
|
|
33
|
+
}): Promise<string>;
|
|
34
|
+
export declare function normaliseAppOrigin(value: string): string;
|
|
35
|
+
export declare function waitForMailbox(profile: ActiveAgentCliProfile, timeoutMs?: number): Promise<void>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=agent-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-auth.d.ts","sourceRoot":"","sources":["../src/agent-auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,qBAAqB,EAE1B,KAAK,SAAS,EAGf,MAAM,eAAe,CAAC;AAUvB,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAyGnG;AAED,wBAAsB,gBAAgB,CAAC,EACrC,SAAS,EACT,KAAK,EACL,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAiChD;AAED,wBAAsB,wBAAwB,CAAC,EAC7C,MAAM,EACN,SAAS,EACT,OAAO,EACP,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ClB;AA6DD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYxD;AAaD,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,EAC9B,SAAS,SAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { clearAgentRegistrationIntent, isActiveAgentProfile, readCliConfig, reserveAgentRegistrationIntent, updateCliConfig, } from "./profiles.js";
|
|
4
|
+
const DEFAULT_APP_ORIGIN = "https://app.sendmux.ai";
|
|
5
|
+
const DEFAULT_SENDING_API_BASE_URL = "https://smtp.sendmux.ai/api/v1";
|
|
6
|
+
const SENDING_API_RESOURCE = "https://smtp.sendmux.ai/api/v1";
|
|
7
|
+
const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
8
|
+
const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
9
|
+
const READINESS_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
10
|
+
const SENDING_TOKEN_SKEW_MS = 60 * 1_000;
|
|
11
|
+
export async function registerAgent(input) {
|
|
12
|
+
const initialConfig = await readCliConfig(input.configDir);
|
|
13
|
+
const existing = initialConfig.profiles[input.profileName];
|
|
14
|
+
let activeProfile;
|
|
15
|
+
if (existing && isActiveAgentProfile(existing)) {
|
|
16
|
+
activeProfile = await updateCliConfig(input.configDir, (config) => {
|
|
17
|
+
const current = config.profiles[input.profileName];
|
|
18
|
+
if (!current || !isActiveAgentProfile(current)) {
|
|
19
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" is not active.`);
|
|
20
|
+
}
|
|
21
|
+
assertMatchingRegistration(current, input);
|
|
22
|
+
if (input.makeDefault || !config.defaultProfile)
|
|
23
|
+
config.defaultProfile = input.profileName;
|
|
24
|
+
return current;
|
|
25
|
+
});
|
|
26
|
+
await clearAgentRegistrationIntent(input.configDir, input.profileName);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const candidate = registrationIntent({ existing, input });
|
|
30
|
+
const registering = await reserveAgentRegistrationIntent(input.configDir, input.profileName, candidate);
|
|
31
|
+
assertMatchingRegistration(registering, input);
|
|
32
|
+
const persisted = await updateCliConfig(input.configDir, (config) => {
|
|
33
|
+
const current = config.profiles[input.profileName];
|
|
34
|
+
if (current && isActiveAgentProfile(current)) {
|
|
35
|
+
assertMatchingRegistration(current, input);
|
|
36
|
+
if (input.makeDefault || !config.defaultProfile)
|
|
37
|
+
config.defaultProfile = input.profileName;
|
|
38
|
+
return { active: current };
|
|
39
|
+
}
|
|
40
|
+
if (current) {
|
|
41
|
+
if (current.type !== "agent" || current.state !== "registering") {
|
|
42
|
+
throw new Error(`Sendmux profile "${input.profileName}" already exists and is not an agent profile.`);
|
|
43
|
+
}
|
|
44
|
+
if (current.idempotencyKey !== registering.idempotencyKey) {
|
|
45
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" belongs to a different registration request.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
config.profiles[input.profileName] = registering;
|
|
49
|
+
if (input.makeDefault || !config.defaultProfile)
|
|
50
|
+
config.defaultProfile = input.profileName;
|
|
51
|
+
return { active: null };
|
|
52
|
+
});
|
|
53
|
+
if (persisted.active) {
|
|
54
|
+
activeProfile = persisted.active;
|
|
55
|
+
await clearAgentRegistrationIntent(input.configDir, input.profileName);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const registration = await postJson(`${registering.authBaseUrl}/agent/identity`, {
|
|
59
|
+
headers: { "Idempotency-Key": registering.idempotencyKey },
|
|
60
|
+
body: {
|
|
61
|
+
type: "anonymous",
|
|
62
|
+
...(registering.mailboxLocalPart ? { mailbox_local_part: registering.mailboxLocalPart } : {}),
|
|
63
|
+
...(registering.clientName ? { client_name: registering.clientName } : {}),
|
|
64
|
+
idempotency_key: registering.idempotencyKey,
|
|
65
|
+
},
|
|
66
|
+
expectedStatuses: [200, 201],
|
|
67
|
+
});
|
|
68
|
+
assertRegistrationResponse(registration);
|
|
69
|
+
activeProfile = await updateCliConfig(input.configDir, (config) => {
|
|
70
|
+
const current = config.profiles[input.profileName];
|
|
71
|
+
if (current && isActiveAgentProfile(current)) {
|
|
72
|
+
if (current.idempotencyKey !== registering.idempotencyKey) {
|
|
73
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" belongs to a different registration request.`);
|
|
74
|
+
}
|
|
75
|
+
return current;
|
|
76
|
+
}
|
|
77
|
+
if (!current ||
|
|
78
|
+
current.type !== "agent" ||
|
|
79
|
+
current.state !== "registering" ||
|
|
80
|
+
current.idempotencyKey !== registering.idempotencyKey) {
|
|
81
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" belongs to a different registration request.`);
|
|
82
|
+
}
|
|
83
|
+
const activated = {
|
|
84
|
+
...current,
|
|
85
|
+
accessToken: registration.access_token,
|
|
86
|
+
mailboxEmail: registration.mailbox.email,
|
|
87
|
+
registrationId: registration.registration_id,
|
|
88
|
+
state: "active",
|
|
89
|
+
};
|
|
90
|
+
config.profiles[input.profileName] = activated;
|
|
91
|
+
return activated;
|
|
92
|
+
});
|
|
93
|
+
await clearAgentRegistrationIntent(input.configDir, input.profileName);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const reloaded = await activeAgentProfile(input.configDir, input.profileName);
|
|
97
|
+
await waitForMailbox(reloaded);
|
|
98
|
+
let ownerInviteStatus;
|
|
99
|
+
if (input.ownerEmail) {
|
|
100
|
+
await inviteAgentOwner({ configDir: input.configDir, email: input.ownerEmail, profileName: input.profileName });
|
|
101
|
+
ownerInviteStatus = "pending";
|
|
102
|
+
}
|
|
103
|
+
const finalConfig = await readCliConfig(input.configDir);
|
|
104
|
+
return {
|
|
105
|
+
default: finalConfig.defaultProfile === input.profileName,
|
|
106
|
+
mailbox_email: activeProfile.mailboxEmail,
|
|
107
|
+
name: input.profileName,
|
|
108
|
+
...(ownerInviteStatus ? { owner_invite_status: ownerInviteStatus } : {}),
|
|
109
|
+
registration_id: activeProfile.registrationId,
|
|
110
|
+
status: "active",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export async function inviteAgentOwner({ configDir, email, profileName, }) {
|
|
114
|
+
const invitation = await updateCliConfig(configDir, (config) => {
|
|
115
|
+
const profile = activeAgentProfileFromConfig(config, profileName);
|
|
116
|
+
const existingInvite = profile.ownerInvite;
|
|
117
|
+
const idempotencyKey = existingInvite?.email.toLowerCase() === email.toLowerCase() ? existingInvite.idempotencyKey : randomUUID();
|
|
118
|
+
const updatedProfile = {
|
|
119
|
+
...profile,
|
|
120
|
+
ownerInvite: { email, idempotencyKey, status: "dispatching" },
|
|
121
|
+
};
|
|
122
|
+
config.profiles[profileName] = updatedProfile;
|
|
123
|
+
return { idempotencyKey, profile: updatedProfile };
|
|
124
|
+
});
|
|
125
|
+
await postJson(`${invitation.profile.authBaseUrl}/agent/identity/invite`, {
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${invitation.profile.accessToken}`,
|
|
128
|
+
"Idempotency-Key": invitation.idempotencyKey,
|
|
129
|
+
},
|
|
130
|
+
body: { email, idempotency_key: invitation.idempotencyKey, requested_role: "owner" },
|
|
131
|
+
expectedStatuses: [200, 202],
|
|
132
|
+
});
|
|
133
|
+
await updateCliConfig(configDir, (config) => {
|
|
134
|
+
const current = activeAgentProfileFromConfig(config, profileName);
|
|
135
|
+
if (current.ownerInvite?.idempotencyKey === invitation.idempotencyKey) {
|
|
136
|
+
config.profiles[profileName] = {
|
|
137
|
+
...current,
|
|
138
|
+
ownerInvite: { email, idempotencyKey: invitation.idempotencyKey, status: "pending" },
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return { email, status: "pending" };
|
|
143
|
+
}
|
|
144
|
+
export async function resolveAgentSendingToken({ config, configDir, profile, profileName, }) {
|
|
145
|
+
if (profile.sendingToken && Date.parse(profile.sendingToken.expiresAt) > Date.now() + SENDING_TOKEN_SKEW_MS) {
|
|
146
|
+
return profile.sendingToken.accessToken;
|
|
147
|
+
}
|
|
148
|
+
const response = await fetch(`${profile.authBaseUrl}/oauth2/token`, {
|
|
149
|
+
body: new URLSearchParams({
|
|
150
|
+
grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
|
|
151
|
+
resource: SENDING_API_RESOURCE,
|
|
152
|
+
scope: "email.send",
|
|
153
|
+
subject_token: profile.accessToken,
|
|
154
|
+
subject_token_type: ACCESS_TOKEN_TYPE,
|
|
155
|
+
}),
|
|
156
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
157
|
+
method: "POST",
|
|
158
|
+
});
|
|
159
|
+
const body = await responseJson(response);
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(agentAuthFailureMessage(response.status, body));
|
|
162
|
+
}
|
|
163
|
+
const accessToken = requiredString(body, "access_token", "token exchange");
|
|
164
|
+
const expiresIn = body.expires_in;
|
|
165
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
166
|
+
throw new Error("Sendmux token exchange returned an invalid expiry.");
|
|
167
|
+
}
|
|
168
|
+
const updatedProfile = await updateCliConfig(configDir, (latestConfig) => {
|
|
169
|
+
const latestProfile = activeAgentProfileFromConfig(latestConfig, profileName);
|
|
170
|
+
if (latestProfile.sendingToken && Date.parse(latestProfile.sendingToken.expiresAt) > Date.now() + SENDING_TOKEN_SKEW_MS) {
|
|
171
|
+
return latestProfile;
|
|
172
|
+
}
|
|
173
|
+
const updated = {
|
|
174
|
+
...latestProfile,
|
|
175
|
+
sendingToken: {
|
|
176
|
+
accessToken,
|
|
177
|
+
expiresAt: new Date(Date.now() + expiresIn * 1_000).toISOString(),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
latestConfig.profiles[profileName] = updated;
|
|
181
|
+
return updated;
|
|
182
|
+
});
|
|
183
|
+
config.profiles[profileName] = updatedProfile;
|
|
184
|
+
return updatedProfile.sendingToken.accessToken;
|
|
185
|
+
}
|
|
186
|
+
function registrationIntent({ existing, input, }) {
|
|
187
|
+
const urls = agentUrls(input.appOrigin);
|
|
188
|
+
if (existing?.type === "agent") {
|
|
189
|
+
if (existing.state !== "registering") {
|
|
190
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" is already active.`);
|
|
191
|
+
}
|
|
192
|
+
assertMatchingRegistration(existing, input);
|
|
193
|
+
return existing;
|
|
194
|
+
}
|
|
195
|
+
if (existing) {
|
|
196
|
+
throw new Error(`Sendmux profile "${input.profileName}" already exists and is not an agent profile.`);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
...urls,
|
|
200
|
+
...(input.clientName ? { clientName: input.clientName } : {}),
|
|
201
|
+
idempotencyKey: randomUUID(),
|
|
202
|
+
...(input.mailboxLocalPart ? { mailboxLocalPart: input.mailboxLocalPart } : {}),
|
|
203
|
+
state: "registering",
|
|
204
|
+
type: "agent",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function assertMatchingRegistration(profile, input) {
|
|
208
|
+
if ((input.appOrigin !== undefined && !registrationUrlsMatch(profile, agentUrls(input.appOrigin))) ||
|
|
209
|
+
(input.clientName !== undefined && profile.clientName !== input.clientName) ||
|
|
210
|
+
(input.mailboxLocalPart !== undefined && profile.mailboxLocalPart !== input.mailboxLocalPart)) {
|
|
211
|
+
throw new Error(`Sendmux agent profile "${input.profileName}" belongs to a different registration request.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function registrationUrlsMatch(profile, urls) {
|
|
215
|
+
return (profile.appApiBaseUrl === urls.appApiBaseUrl &&
|
|
216
|
+
profile.authBaseUrl === urls.authBaseUrl &&
|
|
217
|
+
profile.sendingApiBaseUrl === urls.sendingApiBaseUrl);
|
|
218
|
+
}
|
|
219
|
+
function agentUrls(appOriginInput) {
|
|
220
|
+
const customOrigin = appOriginInput ? normaliseAppOrigin(appOriginInput) : null;
|
|
221
|
+
const appOrigin = customOrigin ?? DEFAULT_APP_ORIGIN;
|
|
222
|
+
return {
|
|
223
|
+
appApiBaseUrl: `${appOrigin}/api/v1`,
|
|
224
|
+
authBaseUrl: `${appOrigin}/agent-auth`,
|
|
225
|
+
sendingApiBaseUrl: customOrigin ? `${customOrigin}/api/v1` : DEFAULT_SENDING_API_BASE_URL,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export function normaliseAppOrigin(value) {
|
|
229
|
+
const url = new URL(value);
|
|
230
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
231
|
+
throw new Error("--base-url must be an HTTP(S) origin without credentials, query, or fragment.");
|
|
232
|
+
}
|
|
233
|
+
if (url.pathname !== "/") {
|
|
234
|
+
throw new Error("--base-url must be an origin without a path.");
|
|
235
|
+
}
|
|
236
|
+
if (url.protocol === "http:" && !isLoopbackHttpOrigin(value)) {
|
|
237
|
+
throw new Error("--base-url must use HTTPS unless it is a canonical loopback origin.");
|
|
238
|
+
}
|
|
239
|
+
return url.origin;
|
|
240
|
+
}
|
|
241
|
+
function isLoopbackHttpOrigin(value) {
|
|
242
|
+
const authority = value.match(/^http:\/\/([^/?#]+)/i)?.[1];
|
|
243
|
+
if (!authority)
|
|
244
|
+
return false;
|
|
245
|
+
const rawHostname = authority.startsWith("[")
|
|
246
|
+
? authority.slice(1, authority.indexOf("]"))
|
|
247
|
+
: authority.split(":", 1)[0];
|
|
248
|
+
if (!rawHostname)
|
|
249
|
+
return false;
|
|
250
|
+
if (rawHostname.toLowerCase() === "localhost" || rawHostname === "::1")
|
|
251
|
+
return true;
|
|
252
|
+
return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(rawHostname) && isIP(rawHostname) === 4 && rawHostname.split(".")[0] === "127";
|
|
253
|
+
}
|
|
254
|
+
export async function waitForMailbox(profile, timeoutMs = READINESS_TIMEOUT_MS) {
|
|
255
|
+
const deadline = Date.now() + timeoutMs;
|
|
256
|
+
while (true) {
|
|
257
|
+
const remainingBeforeFetch = deadline - Date.now();
|
|
258
|
+
if (remainingBeforeFetch <= 0)
|
|
259
|
+
throw mailboxReadinessTimeoutError();
|
|
260
|
+
let response;
|
|
261
|
+
try {
|
|
262
|
+
response = await fetch(`${profile.appApiBaseUrl}/mailbox/me`, {
|
|
263
|
+
headers: { Authorization: `Bearer ${profile.accessToken}` },
|
|
264
|
+
signal: AbortSignal.timeout(remainingBeforeFetch),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (isAbortError(error) || Date.now() >= deadline)
|
|
269
|
+
throw mailboxReadinessTimeoutError();
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
if (response.ok)
|
|
273
|
+
return;
|
|
274
|
+
const body = await responseJson(response);
|
|
275
|
+
const provisioningUnavailable = response.status === 503 && (body.error === "service_unavailable" || body.error === "temporarily_unavailable");
|
|
276
|
+
if (!provisioningUnavailable) {
|
|
277
|
+
throw new Error(agentAuthFailureMessage(response.status, body));
|
|
278
|
+
}
|
|
279
|
+
const remainingBeforeRetry = deadline - Date.now();
|
|
280
|
+
if (remainingBeforeRetry <= 0)
|
|
281
|
+
throw mailboxReadinessTimeoutError();
|
|
282
|
+
const retryDelayMs = Math.min(Math.max(retryAfterSeconds(response, body) * 1_000, 1_000), remainingBeforeRetry);
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function isAbortError(error) {
|
|
287
|
+
return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
288
|
+
}
|
|
289
|
+
function mailboxReadinessTimeoutError() {
|
|
290
|
+
return new Error("Agent mailbox provisioning did not finish within the allowed time.");
|
|
291
|
+
}
|
|
292
|
+
async function activeAgentProfile(configDir, profileName) {
|
|
293
|
+
return activeAgentProfileFromConfig(await readCliConfig(configDir), profileName);
|
|
294
|
+
}
|
|
295
|
+
function activeAgentProfileFromConfig(config, profileName) {
|
|
296
|
+
const profile = config.profiles[profileName];
|
|
297
|
+
if (!profile || !isActiveAgentProfile(profile)) {
|
|
298
|
+
throw new Error(`Sendmux agent profile "${profileName}" is not active.`);
|
|
299
|
+
}
|
|
300
|
+
return profile;
|
|
301
|
+
}
|
|
302
|
+
async function postJson(url, options) {
|
|
303
|
+
const response = await fetch(url, {
|
|
304
|
+
body: JSON.stringify(options.body),
|
|
305
|
+
headers: { "Content-Type": "application/json", ...options.headers },
|
|
306
|
+
method: "POST",
|
|
307
|
+
});
|
|
308
|
+
const body = await responseJson(response);
|
|
309
|
+
if (!options.expectedStatuses.includes(response.status)) {
|
|
310
|
+
throw new Error(agentAuthFailureMessage(response.status, body));
|
|
311
|
+
}
|
|
312
|
+
return body;
|
|
313
|
+
}
|
|
314
|
+
async function responseJson(response) {
|
|
315
|
+
const body = await response.json().catch(() => null);
|
|
316
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
317
|
+
throw new Error(`Sendmux agent authentication returned HTTP ${response.status} without a JSON object.`);
|
|
318
|
+
}
|
|
319
|
+
return body;
|
|
320
|
+
}
|
|
321
|
+
function retryAfterSeconds(response, body) {
|
|
322
|
+
const value = Number(response.headers.get("Retry-After") ?? body.retry_after ?? 10);
|
|
323
|
+
return Number.isFinite(value) && value > 0 ? value : 1;
|
|
324
|
+
}
|
|
325
|
+
function agentAuthFailureMessage(status, body) {
|
|
326
|
+
if (status === 503 && body.error === "authorization_pending") {
|
|
327
|
+
return "Agent sending is awaiting owner acceptance or approval.";
|
|
328
|
+
}
|
|
329
|
+
if (status === 503 && (body.error === "service_unavailable" || body.error === "temporarily_unavailable")) {
|
|
330
|
+
return "Agent mailbox provisioning did not finish within the allowed time.";
|
|
331
|
+
}
|
|
332
|
+
const description = typeof body.error_description === "string" ? body.error_description : null;
|
|
333
|
+
return description ?? `Sendmux agent authentication failed with HTTP ${status}.`;
|
|
334
|
+
}
|
|
335
|
+
function assertRegistrationResponse(value) {
|
|
336
|
+
requiredString(value, "access_token", "agent registration");
|
|
337
|
+
requiredString(value, "registration_id", "agent registration");
|
|
338
|
+
if (!value.mailbox || typeof value.mailbox !== "object" || typeof value.mailbox.email !== "string") {
|
|
339
|
+
throw new Error("Sendmux agent registration did not return a mailbox email.");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function requiredString(record, field, source) {
|
|
343
|
+
const value = record[field];
|
|
344
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
345
|
+
throw new Error(`Sendmux ${source} did not return ${field}.`);
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
package/dist/base-command.d.ts
CHANGED
|
@@ -5,12 +5,18 @@ export interface AuthFlags {
|
|
|
5
5
|
"base-url"?: string;
|
|
6
6
|
profile?: string;
|
|
7
7
|
}
|
|
8
|
-
export
|
|
9
|
-
apiKey: string;
|
|
10
|
-
apiKeyKind: ApiKeyKind;
|
|
8
|
+
export type ResolvedAuth = {
|
|
11
9
|
baseUrl?: string;
|
|
12
10
|
source: string;
|
|
13
|
-
}
|
|
11
|
+
} & ({
|
|
12
|
+
apiKey: string;
|
|
13
|
+
apiKeyKind: ApiKeyKind;
|
|
14
|
+
accessToken?: never;
|
|
15
|
+
} | {
|
|
16
|
+
accessToken: string | (() => Promise<string>);
|
|
17
|
+
apiKey?: never;
|
|
18
|
+
apiKeyKind: "oauth";
|
|
19
|
+
});
|
|
14
20
|
export declare const authFlags: {
|
|
15
21
|
"api-key": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
22
|
"base-url": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -20,6 +26,7 @@ export declare abstract class SendmuxCommand extends Command {
|
|
|
20
26
|
static enableJsonFlag: boolean;
|
|
21
27
|
resolveAuth(flags: AuthFlags, expectedKind: RequiredApiKeyKind): Promise<ResolvedAuth>;
|
|
22
28
|
renderResult(value: unknown): unknown;
|
|
29
|
+
protected toErrorJson(error: unknown): unknown;
|
|
23
30
|
renderTextResult(value: string): unknown;
|
|
24
31
|
renderBinaryResult(value: ArrayBuffer | ArrayBufferView | string): unknown;
|
|
25
32
|
private assertKeyKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-command.d.ts","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAE7C,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"base-command.d.ts","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAE7C,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AAUvB,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,CACA;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/D;IAAE,WAAW,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CACzF,CAAC;AAEF,eAAO,MAAM,SAAS;;;;CAcrB,CAAC;AAEF,8BAAsB,cAAe,SAAQ,OAAO;IAClD,MAAM,CAAC,cAAc,UAAQ;IAEvB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAoG5F,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IASrC,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAM9C,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxC,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,MAAM,GAAG,OAAO;IAa1E,OAAO,CAAC,aAAa;CA4BtB"}
|
package/dist/base-command.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
2
|
import { apiKeyKindLabel, inferApiKeyKind, isApiKeyCompatibleWithKind, } from "./key-kind.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveAgentSendingToken } from "./agent-auth.js";
|
|
4
|
+
import { resolveOAuthToken } from "./oauth-profile.js";
|
|
5
|
+
import { isActiveAgentProfile, isAgentProfile, isOAuthProfile, readCliConfig, } from "./profiles.js";
|
|
4
6
|
export const authFlags = {
|
|
5
7
|
"api-key": Flags.string({
|
|
6
8
|
description: "Sendmux API key. Defaults to SENDMUX_API_KEY.",
|
|
@@ -19,8 +21,22 @@ export const authFlags = {
|
|
|
19
21
|
export class SendmuxCommand extends Command {
|
|
20
22
|
static enableJsonFlag = true;
|
|
21
23
|
async resolveAuth(flags, expectedKind) {
|
|
22
|
-
const envApiKey = process.env.SENDMUX_API_KEY;
|
|
23
|
-
const
|
|
24
|
+
const envApiKey = process.env.SENDMUX_API_KEY || undefined;
|
|
25
|
+
const accessToken = process.env.SENDMUX_ACCESS_TOKEN || undefined;
|
|
26
|
+
const envBaseUrl = process.env.SENDMUX_BASE_URL || undefined;
|
|
27
|
+
const envProfile = process.env.SENDMUX_PROFILE || undefined;
|
|
28
|
+
if (accessToken && (flags["api-key"] || envApiKey)) {
|
|
29
|
+
this.error("Provide exactly one of an API key or SENDMUX_ACCESS_TOKEN.", { exit: 2 });
|
|
30
|
+
}
|
|
31
|
+
if (accessToken) {
|
|
32
|
+
const baseUrl = flags["base-url"] ?? envBaseUrl;
|
|
33
|
+
return {
|
|
34
|
+
accessToken,
|
|
35
|
+
apiKeyKind: "oauth",
|
|
36
|
+
source: "SENDMUX_ACCESS_TOKEN",
|
|
37
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
24
40
|
if (flags["api-key"] || envApiKey) {
|
|
25
41
|
const apiKey = flags["api-key"] ?? envApiKey;
|
|
26
42
|
if (!apiKey) {
|
|
@@ -38,7 +54,7 @@ export class SendmuxCommand extends Command {
|
|
|
38
54
|
return this.assertKeyKind(input);
|
|
39
55
|
}
|
|
40
56
|
const config = await readCliConfig(this.config.configDir);
|
|
41
|
-
const profileName = flags.profile ??
|
|
57
|
+
const profileName = flags.profile ?? envProfile ?? config.defaultProfile;
|
|
42
58
|
if (!profileName) {
|
|
43
59
|
this.error("No Sendmux profile configured. Run `sendmux profiles:set <name> --api-key <key> --default` or pass --api-key.", {
|
|
44
60
|
exit: 2,
|
|
@@ -50,6 +66,36 @@ export class SendmuxCommand extends Command {
|
|
|
50
66
|
exit: 2,
|
|
51
67
|
});
|
|
52
68
|
}
|
|
69
|
+
if (isOAuthProfile(profile)) {
|
|
70
|
+
const baseUrl = flags["base-url"] ?? envBaseUrl;
|
|
71
|
+
return { accessToken: () => resolveOAuthToken(this.config.configDir, profileName), apiKeyKind: "oauth", source: `OAuth profile "${profileName}"`, ...(baseUrl ? { baseUrl } : {}) };
|
|
72
|
+
}
|
|
73
|
+
if (isAgentProfile(profile)) {
|
|
74
|
+
if (!isActiveAgentProfile(profile)) {
|
|
75
|
+
this.error(`Sendmux agent profile "${profileName}" has not finished registration. Re-run \`sendmux agent:register ${profileName}\`.`, {
|
|
76
|
+
exit: 2,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (expectedKind === "root") {
|
|
80
|
+
this.error(`Command requires a root API key, but profile "${profileName}" is an agent profile.`, { exit: 2 });
|
|
81
|
+
}
|
|
82
|
+
const apiKey = expectedKind === "sending"
|
|
83
|
+
? await resolveAgentSendingToken({
|
|
84
|
+
config,
|
|
85
|
+
configDir: this.config.configDir,
|
|
86
|
+
profile,
|
|
87
|
+
profileName,
|
|
88
|
+
})
|
|
89
|
+
: profile.accessToken;
|
|
90
|
+
const profileBaseUrl = expectedKind === "sending" ? profile.sendingApiBaseUrl : profile.appApiBaseUrl;
|
|
91
|
+
const baseUrl = flags["base-url"] ?? envBaseUrl ?? profileBaseUrl;
|
|
92
|
+
return this.assertKeyKind({
|
|
93
|
+
apiKey,
|
|
94
|
+
baseUrl,
|
|
95
|
+
expectedKind,
|
|
96
|
+
source: `agent profile "${profileName}"`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
53
99
|
const baseUrl = flags["base-url"] ?? envBaseUrl ?? profile.baseUrl;
|
|
54
100
|
const input = {
|
|
55
101
|
apiKey: profile.apiKey,
|
|
@@ -68,6 +114,11 @@ export class SendmuxCommand extends Command {
|
|
|
68
114
|
this.log(JSON.stringify(value, null, 2));
|
|
69
115
|
return value;
|
|
70
116
|
}
|
|
117
|
+
toErrorJson(error) {
|
|
118
|
+
return {
|
|
119
|
+
error: error instanceof Error ? { ...error, message: error.message, name: error.name } : error,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
71
122
|
renderTextResult(value) {
|
|
72
123
|
if (this.jsonEnabled()) {
|
|
73
124
|
return { text: value };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SendmuxCommand } from "../../base-command.js";
|
|
2
|
+
export default class AgentInviteOwner extends SendmuxCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
email: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static flags: {
|
|
8
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=invite-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invite-owner.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/invite-owner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,cAAc;IAC1D,MAAM,CAAC,IAAI;;MAKT;IACF,MAAM,CAAC,WAAW,SAA4D;IAC9E,MAAM,CAAC,KAAK;;MAMV;IAEI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAkB9B"}
|