@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.
Files changed (56) hide show
  1. package/README.md +77 -8
  2. package/dist/agent-auth.d.ts +37 -0
  3. package/dist/agent-auth.d.ts.map +1 -0
  4. package/dist/agent-auth.js +348 -0
  5. package/dist/base-command.d.ts +11 -4
  6. package/dist/base-command.d.ts.map +1 -1
  7. package/dist/base-command.js +55 -4
  8. package/dist/commands/agent/invite-owner.d.ts +12 -0
  9. package/dist/commands/agent/invite-owner.d.ts.map +1 -0
  10. package/dist/commands/agent/invite-owner.js +36 -0
  11. package/dist/commands/agent/register.d.ts +16 -0
  12. package/dist/commands/agent/register.d.ts.map +1 -0
  13. package/dist/commands/agent/register.js +42 -0
  14. package/dist/commands/auth/login.d.ts +14 -0
  15. package/dist/commands/auth/login.d.ts.map +1 -0
  16. package/dist/commands/auth/login.js +36 -0
  17. package/dist/commands/auth/logout.d.ts +9 -0
  18. package/dist/commands/auth/logout.d.ts.map +1 -0
  19. package/dist/commands/auth/logout.js +22 -0
  20. package/dist/commands/mailbox/get-connection.d.ts +26 -0
  21. package/dist/commands/mailbox/get-connection.d.ts.map +1 -0
  22. package/dist/commands/mailbox/get-connection.js +7 -0
  23. package/dist/commands/mailbox/stream-events.d.ts +6 -6
  24. package/dist/commands/management/get-connection.d.ts +26 -0
  25. package/dist/commands/management/get-connection.d.ts.map +1 -0
  26. package/dist/commands/management/get-connection.js +7 -0
  27. package/dist/commands/profiles/list.d.ts.map +1 -1
  28. package/dist/commands/profiles/list.js +28 -8
  29. package/dist/commands/profiles/set.d.ts.map +1 -1
  30. package/dist/commands/profiles/set.js +15 -11
  31. package/dist/commands/profiles/show.d.ts.map +1 -1
  32. package/dist/commands/profiles/show.js +26 -12
  33. package/dist/commands/sending/get-connection.d.ts +26 -0
  34. package/dist/commands/sending/get-connection.d.ts.map +1 -0
  35. package/dist/commands/sending/get-connection.js +7 -0
  36. package/dist/generated/operations.d.ts +69 -6
  37. package/dist/generated/operations.d.ts.map +1 -1
  38. package/dist/generated/operations.js +76 -7
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/oauth-http.d.ts +18 -0
  42. package/dist/oauth-http.d.ts.map +1 -0
  43. package/dist/oauth-http.js +113 -0
  44. package/dist/oauth-login.d.ts +13 -0
  45. package/dist/oauth-login.d.ts.map +1 -0
  46. package/dist/oauth-login.js +236 -0
  47. package/dist/oauth-profile.d.ts +6 -0
  48. package/dist/oauth-profile.d.ts.map +1 -0
  49. package/dist/oauth-profile.js +123 -0
  50. package/dist/operation-runner.d.ts.map +1 -1
  51. package/dist/operation-runner.js +1 -1
  52. package/dist/profiles.d.ts +60 -2
  53. package/dist/profiles.d.ts.map +1 -1
  54. package/dist/profiles.js +150 -4
  55. package/oclif.manifest.json +2061 -1377
  56. package/package.json +5 -2
@@ -1,12 +1,70 @@
1
- export interface CliProfile {
1
+ export interface ApiKeyCliProfile {
2
2
  apiKey: string;
3
3
  baseUrl?: string;
4
+ type?: "api_key";
4
5
  }
6
+ interface AgentProfileBase {
7
+ appApiBaseUrl: string;
8
+ authBaseUrl: string;
9
+ clientName?: string;
10
+ idempotencyKey: string;
11
+ mailboxLocalPart?: string;
12
+ sendingApiBaseUrl: string;
13
+ type: "agent";
14
+ }
15
+ export interface RegisteringAgentCliProfile extends AgentProfileBase {
16
+ state: "registering";
17
+ }
18
+ export interface ActiveAgentCliProfile extends AgentProfileBase {
19
+ accessToken: string;
20
+ mailboxEmail: string;
21
+ ownerInvite?: {
22
+ email: string;
23
+ idempotencyKey: string;
24
+ status: "dispatching" | "pending";
25
+ };
26
+ registrationId: string;
27
+ sendingToken?: {
28
+ accessToken: string;
29
+ expiresAt: string;
30
+ };
31
+ state: "active";
32
+ }
33
+ export type AgentCliProfile = ActiveAgentCliProfile | RegisteringAgentCliProfile;
34
+ export interface AuthorizingOAuthCliProfile {
35
+ type: "oauth";
36
+ state: "authorizing";
37
+ sessionId: string;
38
+ issuer: string;
39
+ }
40
+ export interface ActiveOAuthCliProfile {
41
+ type: "oauth";
42
+ state: "active" | "refreshing" | "revoking" | "reauthorize";
43
+ sessionId: string;
44
+ issuer: string;
45
+ clientId: string;
46
+ tokenEndpoint: string;
47
+ revocationEndpoint: string;
48
+ accessToken: string;
49
+ refreshToken: string;
50
+ expiresAt: number;
51
+ scopes: string[];
52
+ refreshStartedAt?: number;
53
+ }
54
+ export type OAuthCliProfile = AuthorizingOAuthCliProfile | ActiveOAuthCliProfile;
55
+ export type CliProfile = AgentCliProfile | ApiKeyCliProfile | OAuthCliProfile;
5
56
  export interface CliConfig {
6
57
  defaultProfile?: string;
7
58
  profiles: Record<string, CliProfile>;
8
59
  }
9
60
  export declare function readCliConfig(configDir: string): Promise<CliConfig>;
10
- export declare function writeCliConfig(configDir: string, config: CliConfig): Promise<void>;
61
+ export declare function updateCliConfig<T>(configDir: string, update: (config: CliConfig) => T): Promise<T>;
62
+ export declare function reserveAgentRegistrationIntent(configDir: string, profileName: string, candidate: RegisteringAgentCliProfile): Promise<RegisteringAgentCliProfile>;
63
+ export declare function clearAgentRegistrationIntent(configDir: string, profileName: string): Promise<void>;
11
64
  export declare function configPath(configDir: string): string;
65
+ export declare function isAgentProfile(profile: CliProfile): profile is AgentCliProfile;
66
+ export declare function isApiKeyProfile(profile: CliProfile): profile is ApiKeyCliProfile;
67
+ export declare function isOAuthProfile(profile: CliProfile): profile is OAuthCliProfile;
68
+ export declare function isActiveAgentProfile(profile: CliProfile): profile is ActiveAgentCliProfile;
69
+ export {};
12
70
  //# sourceMappingURL=profiles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC;AAID,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAiBzE;AAED,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"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"}
package/dist/profiles.js CHANGED
@@ -1,6 +1,10 @@
1
- import { chmod, mkdir, readFile, rename, writeFile, } from "node:fs/promises";
1
+ import { chmod, mkdir, open, readFile, rename, stat, unlink, writeFile, } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
2
3
  import { join } from "node:path";
3
4
  const CONFIG_FILE = "config.json";
5
+ const CONFIG_LOCK_RETRY_MS = 25;
6
+ const CONFIG_LOCK_STALE_MS = 5_000;
7
+ const CONFIG_LOCK_TIMEOUT_MS = 10_000;
4
8
  export async function readCliConfig(configDir) {
5
9
  const path = configPath(configDir);
6
10
  try {
@@ -18,8 +22,71 @@ export async function readCliConfig(configDir) {
18
22
  throw error;
19
23
  }
20
24
  }
21
- export async function writeCliConfig(configDir, config) {
25
+ export async function updateCliConfig(configDir, update) {
22
26
  await mkdir(configDir, { mode: 0o700, recursive: true });
27
+ const releaseLock = await acquireConfigWriteLock(configDir);
28
+ try {
29
+ const config = await readCliConfig(configDir);
30
+ const result = update(config);
31
+ await writeCliConfigFile(configDir, config);
32
+ return result;
33
+ }
34
+ finally {
35
+ await releaseLock();
36
+ }
37
+ }
38
+ export async function reserveAgentRegistrationIntent(configDir, profileName, candidate) {
39
+ await mkdir(configDir, { mode: 0o700, recursive: true });
40
+ const path = registrationIntentPath(configDir, profileName);
41
+ const releaseLock = await acquireFileLock(`${path}.lock`, "Timed out waiting for another Sendmux process to reserve the agent registration.");
42
+ try {
43
+ while (true) {
44
+ try {
45
+ const existing = JSON.parse(await readFile(path, "utf8"));
46
+ if (isRegisteringAgentProfile(existing)) {
47
+ return existing;
48
+ }
49
+ await unlink(path);
50
+ }
51
+ catch (error) {
52
+ if (error instanceof SyntaxError) {
53
+ await unlink(path).catch((unlinkError) => {
54
+ if (!isNodeError(unlinkError) || unlinkError.code !== "ENOENT")
55
+ throw unlinkError;
56
+ });
57
+ }
58
+ else if (!isNodeError(error) || error.code !== "ENOENT") {
59
+ throw error;
60
+ }
61
+ }
62
+ try {
63
+ await writeFile(path, `${JSON.stringify(candidate, null, 2)}\n`, { flag: "wx", mode: 0o600 });
64
+ await chmod(path, 0o600);
65
+ return candidate;
66
+ }
67
+ catch (error) {
68
+ if (!isNodeError(error) || error.code !== "EEXIST")
69
+ throw error;
70
+ }
71
+ }
72
+ }
73
+ finally {
74
+ await releaseLock();
75
+ }
76
+ }
77
+ export async function clearAgentRegistrationIntent(configDir, profileName) {
78
+ try {
79
+ await unlink(registrationIntentPath(configDir, profileName));
80
+ }
81
+ catch (error) {
82
+ if (!isNodeError(error) || error.code !== "ENOENT")
83
+ throw error;
84
+ }
85
+ }
86
+ export function configPath(configDir) {
87
+ return join(configDir, CONFIG_FILE);
88
+ }
89
+ async function writeCliConfigFile(configDir, config) {
23
90
  const path = configPath(configDir);
24
91
  const tempPath = `${path}.${process.pid}.tmp`;
25
92
  await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
@@ -27,8 +94,87 @@ export async function writeCliConfig(configDir, config) {
27
94
  await rename(tempPath, path);
28
95
  await chmod(path, 0o600);
29
96
  }
30
- export function configPath(configDir) {
31
- return join(configDir, CONFIG_FILE);
97
+ async function acquireConfigWriteLock(configDir) {
98
+ return acquireFileLock(`${configPath(configDir)}.lock`, "Timed out waiting for another Sendmux process to finish updating the profile config.");
99
+ }
100
+ async function acquireFileLock(lockPath, timeoutMessage) {
101
+ const deadline = Date.now() + CONFIG_LOCK_TIMEOUT_MS;
102
+ while (true) {
103
+ try {
104
+ const handle = await open(lockPath, "wx", 0o600);
105
+ return async () => {
106
+ try {
107
+ await handle.close();
108
+ }
109
+ finally {
110
+ try {
111
+ await unlink(lockPath);
112
+ }
113
+ catch (error) {
114
+ if (!isNodeError(error) || error.code !== "ENOENT")
115
+ throw error;
116
+ }
117
+ }
118
+ };
119
+ }
120
+ catch (error) {
121
+ if (!isNodeError(error) || error.code !== "EEXIST")
122
+ throw error;
123
+ }
124
+ try {
125
+ const lock = await stat(lockPath);
126
+ if (Date.now() - lock.mtimeMs >= CONFIG_LOCK_STALE_MS) {
127
+ try {
128
+ await unlink(lockPath);
129
+ continue;
130
+ }
131
+ catch (error) {
132
+ if (isNodeError(error) && error.code === "ENOENT")
133
+ continue;
134
+ if (!isNodeError(error) || (error.code !== "EACCES" && error.code !== "EPERM"))
135
+ throw error;
136
+ }
137
+ }
138
+ }
139
+ catch (error) {
140
+ if (isNodeError(error) && error.code === "ENOENT")
141
+ continue;
142
+ throw error;
143
+ }
144
+ if (Date.now() >= deadline) {
145
+ throw new Error(timeoutMessage);
146
+ }
147
+ await new Promise((resolve) => setTimeout(resolve, CONFIG_LOCK_RETRY_MS));
148
+ }
149
+ }
150
+ function registrationIntentPath(configDir, profileName) {
151
+ const profileHash = createHash("sha256").update(profileName, "utf8").digest("hex");
152
+ return join(configDir, `agent-registration-${profileHash}.json`);
153
+ }
154
+ function isRegisteringAgentProfile(value) {
155
+ if (!value || typeof value !== "object")
156
+ return false;
157
+ const profile = value;
158
+ return (profile.type === "agent" &&
159
+ profile.state === "registering" &&
160
+ typeof profile.appApiBaseUrl === "string" &&
161
+ typeof profile.authBaseUrl === "string" &&
162
+ typeof profile.idempotencyKey === "string" &&
163
+ typeof profile.sendingApiBaseUrl === "string" &&
164
+ (profile.clientName === undefined || typeof profile.clientName === "string") &&
165
+ (profile.mailboxLocalPart === undefined || typeof profile.mailboxLocalPart === "string"));
166
+ }
167
+ export function isAgentProfile(profile) {
168
+ return profile.type === "agent";
169
+ }
170
+ export function isApiKeyProfile(profile) {
171
+ return profile.type === undefined || profile.type === "api_key";
172
+ }
173
+ export function isOAuthProfile(profile) {
174
+ return profile.type === "oauth";
175
+ }
176
+ export function isActiveAgentProfile(profile) {
177
+ return isAgentProfile(profile) && profile.state === "active";
32
178
  }
33
179
  function isNodeError(error) {
34
180
  return Boolean(error && typeof error === "object" && "code" in error);