@sendmux/cli 1.5.0 → 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 +26 -3
- package/dist/base-command.d.ts +11 -4
- package/dist/base-command.d.ts.map +1 -1
- package/dist/base-command.js +24 -1
- 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/stream-events.d.ts +6 -6
- package/dist/commands/profiles/list.d.ts.map +1 -1
- package/dist/commands/profiles/list.js +3 -1
- package/dist/commands/profiles/set.d.ts.map +1 -1
- package/dist/commands/profiles/set.js +4 -1
- package/dist/commands/profiles/show.d.ts.map +1 -1
- package/dist/commands/profiles/show.js +25 -23
- package/dist/generated/operations.d.ts +6 -6
- package/dist/generated/operations.js +7 -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 +23 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +4 -1
- package/oclif.manifest.json +1459 -1368
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,9 +19,9 @@ Agent-drivable command line interface for Sendmux.
|
|
|
19
19
|
|
|
20
20
|
- No existing Sendmux account or API key is required to register an agent inbox.
|
|
21
21
|
- npm global installs, `npx`, or a downloaded release tarball.
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
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.
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
@@ -31,6 +31,29 @@ npm install -g @sendmux/cli
|
|
|
31
31
|
|
|
32
32
|
The package exposes the `sendmux` binary.
|
|
33
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
|
+
|
|
34
57
|
## Usage
|
|
35
58
|
|
|
36
59
|
Register an agent inbox and save its durable read credential in a local profile:
|
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,7 +1,8 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
2
|
import { apiKeyKindLabel, inferApiKeyKind, isApiKeyCompatibleWithKind, } from "./key-kind.js";
|
|
3
3
|
import { resolveAgentSendingToken } from "./agent-auth.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveOAuthToken } from "./oauth-profile.js";
|
|
5
|
+
import { isActiveAgentProfile, isAgentProfile, isOAuthProfile, readCliConfig, } from "./profiles.js";
|
|
5
6
|
export const authFlags = {
|
|
6
7
|
"api-key": Flags.string({
|
|
7
8
|
description: "Sendmux API key. Defaults to SENDMUX_API_KEY.",
|
|
@@ -21,8 +22,21 @@ export class SendmuxCommand extends Command {
|
|
|
21
22
|
static enableJsonFlag = true;
|
|
22
23
|
async resolveAuth(flags, expectedKind) {
|
|
23
24
|
const envApiKey = process.env.SENDMUX_API_KEY || undefined;
|
|
25
|
+
const accessToken = process.env.SENDMUX_ACCESS_TOKEN || undefined;
|
|
24
26
|
const envBaseUrl = process.env.SENDMUX_BASE_URL || undefined;
|
|
25
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
|
+
}
|
|
26
40
|
if (flags["api-key"] || envApiKey) {
|
|
27
41
|
const apiKey = flags["api-key"] ?? envApiKey;
|
|
28
42
|
if (!apiKey) {
|
|
@@ -52,6 +66,10 @@ export class SendmuxCommand extends Command {
|
|
|
52
66
|
exit: 2,
|
|
53
67
|
});
|
|
54
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
|
+
}
|
|
55
73
|
if (isAgentProfile(profile)) {
|
|
56
74
|
if (!isActiveAgentProfile(profile)) {
|
|
57
75
|
this.error(`Sendmux agent profile "${profileName}" has not finished registration. Re-run \`sendmux agent:register ${profileName}\`.`, {
|
|
@@ -96,6 +114,11 @@ export class SendmuxCommand extends Command {
|
|
|
96
114
|
this.log(JSON.stringify(value, null, 2));
|
|
97
115
|
return value;
|
|
98
116
|
}
|
|
117
|
+
toErrorJson(error) {
|
|
118
|
+
return {
|
|
119
|
+
error: error instanceof Error ? { ...error, message: error.message, name: error.name } : error,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
99
122
|
renderTextResult(value) {
|
|
100
123
|
if (this.jsonEnabled()) {
|
|
101
124
|
return { text: value };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SendmuxCommand } from "../../base-command.js";
|
|
2
|
+
export default class AuthLogin extends SendmuxCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
issuer: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
scope: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
"no-browser": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/login.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,cAAc;IACnD,MAAM,CAAC,WAAW,SAA4D;IAC9E,MAAM,CAAC,IAAI;;MAET;IACF,MAAM,CAAC,KAAK;;;;MAaV;IAEI,GAAG;CAYV"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { SendmuxCommand } from "../../base-command.js";
|
|
3
|
+
import { DEFAULT_OAUTH_ISSUER } from "../../oauth-http.js";
|
|
4
|
+
import { loginOAuth } from "../../oauth-login.js";
|
|
5
|
+
export default class AuthLogin extends SendmuxCommand {
|
|
6
|
+
static description = "Sign in through the browser and save an OAuth profile.";
|
|
7
|
+
static args = {
|
|
8
|
+
name: Args.string({ required: true, description: "New profile name." }),
|
|
9
|
+
};
|
|
10
|
+
static flags = {
|
|
11
|
+
issuer: Flags.string({
|
|
12
|
+
default: DEFAULT_OAUTH_ISSUER,
|
|
13
|
+
description: "Sendmux authorization-server issuer.",
|
|
14
|
+
}),
|
|
15
|
+
scope: Flags.string({
|
|
16
|
+
multiple: true,
|
|
17
|
+
required: true,
|
|
18
|
+
description: "Permission to request. Repeat for multiple scopes.",
|
|
19
|
+
}),
|
|
20
|
+
"no-browser": Flags.boolean({
|
|
21
|
+
description: "Print the authorization URL without opening a browser.",
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args, flags } = await this.parse(AuthLogin);
|
|
26
|
+
const data = await loginOAuth({
|
|
27
|
+
configDir: this.config.configDir,
|
|
28
|
+
name: args.name,
|
|
29
|
+
issuer: flags.issuer,
|
|
30
|
+
scopes: flags.scope,
|
|
31
|
+
noBrowser: flags["no-browser"] ?? false,
|
|
32
|
+
report: (message) => process.stderr.write(`${message}\n`),
|
|
33
|
+
});
|
|
34
|
+
return this.renderResult({ ok: true, data, meta: {} });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SendmuxCommand } from "../../base-command.js";
|
|
2
|
+
export default class AuthLogout extends SendmuxCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
run(): Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=logout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/logout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,cAAc;IACpD,MAAM,CAAC,WAAW,SAC2C;IAC7D,MAAM,CAAC,IAAI;;MAIT;IAEI,GAAG;CAUV"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Args } from "@oclif/core";
|
|
2
|
+
import { SendmuxCommand } from "../../base-command.js";
|
|
3
|
+
import { logoutOAuth } from "../../oauth-profile.js";
|
|
4
|
+
import { readCliConfig } from "../../profiles.js";
|
|
5
|
+
export default class AuthLogout extends SendmuxCommand {
|
|
6
|
+
static description = "Revoke an OAuth connection and remove its local profile.";
|
|
7
|
+
static args = {
|
|
8
|
+
name: Args.string({
|
|
9
|
+
description: "OAuth profile name. Defaults to the configured profile.",
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(AuthLogout);
|
|
14
|
+
const name = args.name ??
|
|
15
|
+
process.env.SENDMUX_PROFILE ??
|
|
16
|
+
(await readCliConfig(this.config.configDir)).defaultProfile;
|
|
17
|
+
if (!name)
|
|
18
|
+
this.error("Choose an OAuth profile to log out.", { exit: 2 });
|
|
19
|
+
const data = await logoutOAuth(this.config.configDir, name);
|
|
20
|
+
return this.renderResult({ ok: true, data, meta: {} });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -35,6 +35,12 @@ export default class MailboxStreamEventsCommand extends OperationCommand {
|
|
|
35
35
|
readonly path: "/mailbox/events";
|
|
36
36
|
readonly pathParams: readonly [];
|
|
37
37
|
readonly queryParams: readonly [{
|
|
38
|
+
readonly name: "mailbox_id";
|
|
39
|
+
readonly required: false;
|
|
40
|
+
readonly schema: {
|
|
41
|
+
readonly type: "string";
|
|
42
|
+
};
|
|
43
|
+
}, {
|
|
38
44
|
readonly name: "event_types";
|
|
39
45
|
readonly required: false;
|
|
40
46
|
readonly schema: {
|
|
@@ -62,12 +68,6 @@ export default class MailboxStreamEventsCommand extends OperationCommand {
|
|
|
62
68
|
readonly minimum: 30;
|
|
63
69
|
readonly maximum: 3600;
|
|
64
70
|
};
|
|
65
|
-
}, {
|
|
66
|
-
readonly name: "mailbox_id";
|
|
67
|
-
readonly required: false;
|
|
68
|
-
readonly schema: {
|
|
69
|
-
readonly type: "string";
|
|
70
|
-
};
|
|
71
71
|
}];
|
|
72
72
|
readonly responseKind: "json";
|
|
73
73
|
readonly requestBodyRequired: false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAYvD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,cAAc;IACtD,MAAM,CAAC,WAAW,SAAsC;IACxD,MAAM,CAAC,KAAK;;MAIV;IAEI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAqC9B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import { SendmuxCommand } from "../../base-command.js";
|
|
3
3
|
import { inferApiKeyKind, maskApiKey, } from "../../key-kind.js";
|
|
4
|
-
import { isActiveAgentProfile, isApiKeyProfile, readCliConfig, } from "../../profiles.js";
|
|
4
|
+
import { isActiveAgentProfile, isApiKeyProfile, isOAuthProfile, readCliConfig, } from "../../profiles.js";
|
|
5
5
|
export default class ProfilesList extends SendmuxCommand {
|
|
6
6
|
static description = "List local Sendmux CLI profiles.";
|
|
7
7
|
static flags = {
|
|
@@ -13,6 +13,8 @@ export default class ProfilesList extends SendmuxCommand {
|
|
|
13
13
|
const { flags } = await this.parse(ProfilesList);
|
|
14
14
|
const config = await readCliConfig(this.config.configDir);
|
|
15
15
|
const profiles = Object.entries(config.profiles).map(([name, profile]) => {
|
|
16
|
+
if (isOAuthProfile(profile))
|
|
17
|
+
return { name, type: "oauth", status: profile.state, default: name === config.defaultProfile, ...(profile.state === "authorizing" ? {} : { scopes: profile.scopes }) };
|
|
16
18
|
if (isApiKeyProfile(profile)) {
|
|
17
19
|
return {
|
|
18
20
|
default: name === config.defaultProfile,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/set.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,cAAc;IACrD,MAAM,CAAC,IAAI;;MAKT;IACF,MAAM,CAAC,WAAW,SAAmD;IACrE,MAAM,CAAC,KAAK;;;;MAWV;IAEI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/set.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,cAAc;IACrD,MAAM,CAAC,IAAI;;MAKT;IACF,MAAM,CAAC,WAAW,SAAmD;IACrE,MAAM,CAAC,KAAK;;;;MAWV;IAEI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAsC9B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from "@oclif/core";
|
|
2
2
|
import { SendmuxCommand } from "../../base-command.js";
|
|
3
3
|
import { inferApiKeyKind } from "../../key-kind.js";
|
|
4
|
-
import { updateCliConfig } from "../../profiles.js";
|
|
4
|
+
import { isOAuthProfile, updateCliConfig } from "../../profiles.js";
|
|
5
5
|
export default class ProfilesSet extends SendmuxCommand {
|
|
6
6
|
static args = {
|
|
7
7
|
name: Args.string({
|
|
@@ -30,6 +30,9 @@ export default class ProfilesSet extends SendmuxCommand {
|
|
|
30
30
|
}
|
|
31
31
|
const keyKind = inferApiKeyKind(apiKey);
|
|
32
32
|
const isDefault = await updateCliConfig(this.config.configDir, (config) => {
|
|
33
|
+
const existing = config.profiles[args.name];
|
|
34
|
+
if (existing && isOAuthProfile(existing))
|
|
35
|
+
throw new Error("Log out of this OAuth profile before replacing it.");
|
|
33
36
|
config.profiles[args.name] = {
|
|
34
37
|
apiKey,
|
|
35
38
|
...(flags["base-url"] ? { baseUrl: flags["base-url"] } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/show.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../../src/commands/profiles/show.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAYvD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,cAAc;IACtD,MAAM,CAAC,IAAI;;MAKT;IACF,MAAM,CAAC,WAAW,SAAiE;IAE7E,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAwC9B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args } from "@oclif/core";
|
|
2
2
|
import { SendmuxCommand } from "../../base-command.js";
|
|
3
3
|
import { inferApiKeyKind, maskApiKey, } from "../../key-kind.js";
|
|
4
|
-
import { isActiveAgentProfile, isApiKeyProfile, readCliConfig, } from "../../profiles.js";
|
|
4
|
+
import { isActiveAgentProfile, isApiKeyProfile, isOAuthProfile, readCliConfig, } from "../../profiles.js";
|
|
5
5
|
export default class ProfilesShow extends SendmuxCommand {
|
|
6
6
|
static args = {
|
|
7
7
|
name: Args.string({
|
|
@@ -21,28 +21,30 @@ export default class ProfilesShow extends SendmuxCommand {
|
|
|
21
21
|
if (!profile) {
|
|
22
22
|
this.error(`Sendmux profile "${profileName}" was not found.`, { exit: 2 });
|
|
23
23
|
}
|
|
24
|
-
const data =
|
|
25
|
-
? {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
24
|
+
const data = isOAuthProfile(profile)
|
|
25
|
+
? { name: profileName, type: "oauth", status: profile.state, default: profileName === config.defaultProfile, ...(profile.state === "authorizing" ? {} : { scopes: profile.scopes }) }
|
|
26
|
+
: isApiKeyProfile(profile)
|
|
27
|
+
? {
|
|
28
|
+
api_key: maskApiKey(profile.apiKey),
|
|
29
|
+
default: profileName === config.defaultProfile,
|
|
30
|
+
key_kind: inferApiKeyKind(profile.apiKey),
|
|
31
|
+
name: profileName,
|
|
32
|
+
...(profile.baseUrl ? { base_url: profile.baseUrl } : {}),
|
|
33
|
+
type: "api_key",
|
|
34
|
+
}
|
|
35
|
+
: {
|
|
36
|
+
default: profileName === config.defaultProfile,
|
|
37
|
+
...(isActiveAgentProfile(profile)
|
|
38
|
+
? {
|
|
39
|
+
mailbox_email: profile.mailboxEmail,
|
|
40
|
+
owner_invite_status: profile.ownerInvite?.status,
|
|
41
|
+
registration_id: profile.registrationId,
|
|
42
|
+
}
|
|
43
|
+
: {}),
|
|
44
|
+
name: profileName,
|
|
45
|
+
status: profile.state,
|
|
46
|
+
type: "agent",
|
|
47
|
+
};
|
|
46
48
|
return this.renderResult({ ok: true, data, meta: {} });
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -1994,6 +1994,12 @@ export declare const operations: {
|
|
|
1994
1994
|
readonly path: "/mailbox/events";
|
|
1995
1995
|
readonly pathParams: readonly [];
|
|
1996
1996
|
readonly queryParams: readonly [{
|
|
1997
|
+
readonly name: "mailbox_id";
|
|
1998
|
+
readonly required: false;
|
|
1999
|
+
readonly schema: {
|
|
2000
|
+
readonly type: "string";
|
|
2001
|
+
};
|
|
2002
|
+
}, {
|
|
1997
2003
|
readonly name: "event_types";
|
|
1998
2004
|
readonly required: false;
|
|
1999
2005
|
readonly schema: {
|
|
@@ -2021,12 +2027,6 @@ export declare const operations: {
|
|
|
2021
2027
|
readonly minimum: 30;
|
|
2022
2028
|
readonly maximum: 3600;
|
|
2023
2029
|
};
|
|
2024
|
-
}, {
|
|
2025
|
-
readonly name: "mailbox_id";
|
|
2026
|
-
readonly required: false;
|
|
2027
|
-
readonly schema: {
|
|
2028
|
-
readonly type: "string";
|
|
2029
|
-
};
|
|
2030
2030
|
}];
|
|
2031
2031
|
readonly responseKind: "json";
|
|
2032
2032
|
readonly requestBodyRequired: false;
|
|
@@ -2364,6 +2364,13 @@ export const operations = {
|
|
|
2364
2364
|
"path": "/mailbox/events",
|
|
2365
2365
|
"pathParams": [],
|
|
2366
2366
|
"queryParams": [
|
|
2367
|
+
{
|
|
2368
|
+
"name": "mailbox_id",
|
|
2369
|
+
"required": false,
|
|
2370
|
+
"schema": {
|
|
2371
|
+
"type": "string"
|
|
2372
|
+
}
|
|
2373
|
+
},
|
|
2367
2374
|
{
|
|
2368
2375
|
"name": "event_types",
|
|
2369
2376
|
"required": false,
|
|
@@ -2395,13 +2402,6 @@ export const operations = {
|
|
|
2395
2402
|
"minimum": 30,
|
|
2396
2403
|
"maximum": 3600
|
|
2397
2404
|
}
|
|
2398
|
-
},
|
|
2399
|
-
{
|
|
2400
|
-
"name": "mailbox_id",
|
|
2401
|
-
"required": false,
|
|
2402
|
-
"schema": {
|
|
2403
|
-
"type": "string"
|
|
2404
|
-
}
|
|
2405
2405
|
}
|
|
2406
2406
|
],
|
|
2407
2407
|
"responseKind": "json",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ActiveAgentCliProfile, AgentCliProfile, ApiKeyCliProfile, CliConfig, CliProfile, RegisteringAgentCliProfile, } from "./profiles.js";
|
|
1
|
+
export type { ActiveAgentCliProfile, AgentCliProfile, ApiKeyCliProfile, ActiveOAuthCliProfile, AuthorizingOAuthCliProfile, OAuthCliProfile, CliConfig, CliProfile, RegisteringAgentCliProfile, } from "./profiles.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,0BAA0B,GAC3B,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,0BAA0B,EAC1B,eAAe,EACf,SAAS,EACT,UAAU,EACV,0BAA0B,GAC3B,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const OAUTH_RESOURCE = "https://sendmux.ai/api";
|
|
2
|
+
export declare const DEFAULT_OAUTH_ISSUER = "https://app.sendmux.ai";
|
|
3
|
+
export declare function oauthUrl(value: string, issuer?: string): URL;
|
|
4
|
+
export declare function oauthRequest(endpoint: string, init?: RequestInit): Promise<Record<string, unknown>>;
|
|
5
|
+
export declare function discoverOAuth(issuer: string, scopes: string[]): Promise<{
|
|
6
|
+
authorization: string;
|
|
7
|
+
registration: string;
|
|
8
|
+
token: string;
|
|
9
|
+
revocation: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function oauthTokenFields(value: Record<string, unknown>, allowedScopes: string[]): {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
scopes: string[];
|
|
16
|
+
};
|
|
17
|
+
export declare function oauthForm(fields: Record<string, string>): RequestInit;
|
|
18
|
+
//# sourceMappingURL=oauth-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-http.d.ts","sourceRoot":"","sources":["../src/oauth-http.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,GAAG,CAe5D;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA2ClC;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;;;;;GA6BnE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,aAAa,EAAE,MAAM,EAAE;;;;;EA2BxB;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAMrE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const OAUTH_RESOURCE = "https://sendmux.ai/api";
|
|
2
|
+
export const DEFAULT_OAUTH_ISSUER = "https://app.sendmux.ai";
|
|
3
|
+
export function oauthUrl(value, issuer) {
|
|
4
|
+
const url = new URL(value);
|
|
5
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
6
|
+
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
|
|
7
|
+
url.username ||
|
|
8
|
+
url.password ||
|
|
9
|
+
url.hash ||
|
|
10
|
+
url.search ||
|
|
11
|
+
(issuer && url.origin !== new URL(issuer).origin))
|
|
12
|
+
throw new Error("OAuth endpoints must use HTTPS and the configured issuer origin.");
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
export async function oauthRequest(endpoint, init = {}) {
|
|
16
|
+
oauthUrl(endpoint);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(endpoint, {
|
|
19
|
+
...init,
|
|
20
|
+
redirect: "error",
|
|
21
|
+
signal: init.signal ?? AbortSignal.timeout(15_000),
|
|
22
|
+
headers: { Accept: "application/json", ...init.headers },
|
|
23
|
+
});
|
|
24
|
+
const reader = response.body?.getReader();
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let bytes = 0;
|
|
27
|
+
if (reader) {
|
|
28
|
+
try {
|
|
29
|
+
while (true) {
|
|
30
|
+
const chunk = await reader.read();
|
|
31
|
+
if (chunk.done)
|
|
32
|
+
break;
|
|
33
|
+
bytes += chunk.value.byteLength;
|
|
34
|
+
if (bytes > 256 * 1024)
|
|
35
|
+
throw new Error("Response exceeds the OAuth size limit.");
|
|
36
|
+
chunks.push(chunk.value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await reader.cancel();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
throw new Error("OAuth request was rejected.");
|
|
45
|
+
if (bytes === 0)
|
|
46
|
+
return {};
|
|
47
|
+
if (!/^application\/json(?:;|$)/i.test(response.headers.get("content-type") ?? ""))
|
|
48
|
+
throw new Error("Invalid OAuth content type.");
|
|
49
|
+
const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
50
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51
|
+
throw new Error("Invalid OAuth response.");
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error("OAuth request failed. Check the connection and try signing in again.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function discoverOAuth(issuer, scopes) {
|
|
59
|
+
const url = oauthUrl(issuer);
|
|
60
|
+
const metadataUrl = `${url.origin}/.well-known/oauth-authorization-server${url.pathname === "/" ? "" : url.pathname}`;
|
|
61
|
+
const metadata = await oauthRequest(metadataUrl);
|
|
62
|
+
const supports = (field, value) => Array.isArray(metadata[field]) && metadata[field].includes(value);
|
|
63
|
+
if (metadata.issuer !== issuer ||
|
|
64
|
+
!supports("code_challenge_methods_supported", "S256") ||
|
|
65
|
+
!supports("token_endpoint_auth_methods_supported", "none") ||
|
|
66
|
+
metadata.authorization_response_iss_parameter_supported !== true ||
|
|
67
|
+
!supports("protected_resources", OAUTH_RESOURCE) ||
|
|
68
|
+
!scopes.every((scope) => supports("scopes_supported", scope)))
|
|
69
|
+
throw new Error("The issuer does not support the requested Sendmux OAuth connection.");
|
|
70
|
+
const endpoint = (name) => {
|
|
71
|
+
const value = metadata[name];
|
|
72
|
+
if (typeof value !== "string")
|
|
73
|
+
throw new Error("OAuth discovery is missing an endpoint.");
|
|
74
|
+
return oauthUrl(value, issuer).href;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
authorization: endpoint("authorization_endpoint"),
|
|
78
|
+
registration: endpoint("registration_endpoint"),
|
|
79
|
+
token: endpoint("token_endpoint"),
|
|
80
|
+
revocation: endpoint("revocation_endpoint"),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function oauthTokenFields(value, allowedScopes) {
|
|
84
|
+
const validToken = (token) => typeof token === "string" && /^[A-Za-z0-9\-._~+/]+=*$/.test(token);
|
|
85
|
+
if (!validToken(value.access_token) ||
|
|
86
|
+
!validToken(value.refresh_token) ||
|
|
87
|
+
typeof value.token_type !== "string" ||
|
|
88
|
+
value.token_type.toLowerCase() !== "bearer" ||
|
|
89
|
+
typeof value.expires_in !== "number" ||
|
|
90
|
+
!Number.isSafeInteger(value.expires_in) ||
|
|
91
|
+
value.expires_in <= 0 ||
|
|
92
|
+
typeof value.scope !== "string")
|
|
93
|
+
throw new Error("The issuer returned invalid OAuth token metadata.");
|
|
94
|
+
const scopes = value.scope.split(" ").filter(Boolean);
|
|
95
|
+
if (!scopes.length || !scopes.every((scope) => allowedScopes.includes(scope)))
|
|
96
|
+
throw new Error("The issuer returned unexpected OAuth scopes.");
|
|
97
|
+
const expiresAt = Date.now() + value.expires_in * 1000;
|
|
98
|
+
if (!Number.isSafeInteger(expiresAt))
|
|
99
|
+
throw new Error("The issuer returned an invalid token lifetime.");
|
|
100
|
+
return {
|
|
101
|
+
accessToken: value.access_token,
|
|
102
|
+
refreshToken: value.refresh_token,
|
|
103
|
+
expiresAt,
|
|
104
|
+
scopes,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function oauthForm(fields) {
|
|
108
|
+
return {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
111
|
+
body: new URLSearchParams(fields),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function loginOAuth(input: {
|
|
2
|
+
configDir: string;
|
|
3
|
+
name: string;
|
|
4
|
+
issuer: string;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
noBrowser: boolean;
|
|
7
|
+
report: (message: string) => void;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
profile: string;
|
|
10
|
+
type: string;
|
|
11
|
+
scopes: string[];
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=oauth-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-login.d.ts","sourceRoot":"","sources":["../src/oauth-login.ts"],"names":[],"mappings":"AAmBA,wBAAsB,UAAU,CAAC,KAAK,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;;;;GAsJA"}
|