@sendmux/cli 1.4.0 → 1.5.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 +53 -7
- 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.map +1 -1
- package/dist/base-command.js +32 -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/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/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 +26 -8
- package/dist/commands/profiles/set.d.ts.map +1 -1
- package/dist/commands/profiles/set.js +12 -11
- package/dist/commands/profiles/show.d.ts.map +1 -1
- package/dist/commands/profiles/show.js +19 -7
- 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 +63 -0
- package/dist/generated/operations.d.ts.map +1 -1
- package/dist/generated/operations.js +69 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/profiles.d.ts +38 -2
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +147 -4
- package/oclif.manifest.json +743 -150
- package/package.json +5 -2
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
|
|
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,84 @@ export async function writeCliConfig(configDir, config) {
|
|
|
27
94
|
await rename(tempPath, path);
|
|
28
95
|
await chmod(path, 0o600);
|
|
29
96
|
}
|
|
30
|
-
|
|
31
|
-
return
|
|
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 !isAgentProfile(profile);
|
|
172
|
+
}
|
|
173
|
+
export function isActiveAgentProfile(profile) {
|
|
174
|
+
return isAgentProfile(profile) && profile.state === "active";
|
|
32
175
|
}
|
|
33
176
|
function isNodeError(error) {
|
|
34
177
|
return Boolean(error && typeof error === "object" && "code" in error);
|