@asiyst/cli 1.0.8 → 1.1.1
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 +17 -4
- package/dist/index.js +1083 -413
- package/dist/index.js.map +1 -1
- package/package.json +18 -2
package/dist/index.js
CHANGED
|
@@ -1,42 +1,414 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
3
|
+
// src/config/api.ts
|
|
4
|
+
var PRODUCTION_API_ORIGIN = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
|
|
5
|
+
var CLI_API_BASE_URL = PRODUCTION_API_ORIGIN;
|
|
6
|
+
var VERIFY_KEY_PATH = "/auth/api-key/verify";
|
|
7
|
+
var VERIFY_KEY_URL = `${CLI_API_BASE_URL}${VERIFY_KEY_PATH}`;
|
|
8
|
+
var ASIYST_WEB_URL = "https://asiyst.com";
|
|
9
|
+
var ASIIYST_WEB_URL = ASIYST_WEB_URL;
|
|
10
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
11
|
+
function readEnv(env, ...keys) {
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
const value = env[key]?.trim();
|
|
14
|
+
if (value) return value;
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
function isLocalUrl(value) {
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(value);
|
|
21
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
22
|
+
} catch {
|
|
23
|
+
return /localhost|127\.0\.0\.1|::1/i.test(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function resolveApiBaseUrl(env = process.env) {
|
|
27
|
+
const explicit = readEnv(env, "ASIYST_API_URL", "ASIIYST_API_URL");
|
|
28
|
+
const development = readEnv(env, "ASIYST_API_MODE", "ASIIYST_API_MODE") === "development";
|
|
29
|
+
if (explicit) {
|
|
30
|
+
if (isLocalUrl(explicit)) {
|
|
31
|
+
return CLI_API_BASE_URL;
|
|
32
|
+
}
|
|
33
|
+
return explicit.replace(/\/+$/, "");
|
|
34
|
+
}
|
|
35
|
+
if (development) {
|
|
36
|
+
return CLI_API_BASE_URL;
|
|
37
|
+
}
|
|
38
|
+
return CLI_API_BASE_URL;
|
|
39
|
+
}
|
|
40
|
+
function isDebugEnabled(env = process.env) {
|
|
41
|
+
return readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "1" || readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "true";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/config/ids.ts
|
|
45
|
+
var PUBLIC_IDENTIFIER_PATTERN = /^(?=.{1,128}$)[A-Za-z0-9_][A-Za-z0-9_-]*$/;
|
|
46
|
+
var USER_ID_PATTERN = /^[A-Za-z0-9_]{24}$/;
|
|
47
|
+
var AVATAR_ID_PATTERN = /^[A-Za-z0-9]{10}$/;
|
|
48
|
+
function isValidPublicIdentifier(value) {
|
|
49
|
+
if (typeof value !== "string") return false;
|
|
50
|
+
const trimmed = value.trim();
|
|
51
|
+
if (!trimmed || trimmed.length > 128) return false;
|
|
52
|
+
if (trimmed === "undefined" || trimmed === "null") return false;
|
|
53
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return PUBLIC_IDENTIFIER_PATTERN.test(trimmed);
|
|
57
|
+
}
|
|
58
|
+
function isValidUserId(value) {
|
|
59
|
+
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value.trim());
|
|
60
|
+
}
|
|
61
|
+
function isValidAvatarId(value) {
|
|
62
|
+
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && AVATAR_ID_PATTERN.test(value.trim());
|
|
63
|
+
}
|
|
64
|
+
function parseProjectIdArgument(argv = []) {
|
|
65
|
+
const length = argv.length;
|
|
66
|
+
for (let index = 0; index < length; index += 1) {
|
|
67
|
+
const entry = argv[index];
|
|
68
|
+
if (entry === "--project-id" || entry === "--projectId") {
|
|
69
|
+
const next = argv[index + 1];
|
|
70
|
+
return typeof next === "string" ? next : "";
|
|
71
|
+
}
|
|
72
|
+
if (entry.startsWith("--project-id=") || entry.startsWith("--projectId=")) {
|
|
73
|
+
return entry.slice(entry.indexOf("=") + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/api/errors.ts
|
|
80
|
+
var ApiError = class extends Error {
|
|
81
|
+
constructor(message, status, code = "UNKNOWN") {
|
|
82
|
+
super(message);
|
|
83
|
+
this.status = status;
|
|
84
|
+
this.code = code;
|
|
85
|
+
this.name = "ApiError";
|
|
86
|
+
}
|
|
87
|
+
status;
|
|
88
|
+
code;
|
|
89
|
+
};
|
|
90
|
+
function errorCodeFromStatus(status, bodyCode) {
|
|
91
|
+
if (bodyCode === "INVALID_API_KEY") return "INVALID_API_KEY";
|
|
92
|
+
if (bodyCode === "API_KEY_REVOKED") return "API_KEY_REVOKED";
|
|
93
|
+
if (bodyCode === "FORBIDDEN") return "FORBIDDEN";
|
|
94
|
+
if (bodyCode === "PROJECT_NOT_FOUND") return "PROJECT_NOT_FOUND";
|
|
95
|
+
if (bodyCode === "AVATAR_NOT_FOUND") return "AVATAR_NOT_FOUND";
|
|
96
|
+
if (bodyCode === "AVATAR_ALREADY_IMPORTED") return "AVATAR_ALREADY_IMPORTED";
|
|
97
|
+
if (bodyCode === "RATE_LIMITED") return "RATE_LIMITED";
|
|
98
|
+
if (bodyCode === "INTERNAL_ERROR") return "INTERNAL_ERROR";
|
|
99
|
+
if (status === 401) return "INVALID_API_KEY";
|
|
100
|
+
if (status === 403) return "FORBIDDEN";
|
|
101
|
+
if (status === 404) return "NOT_FOUND";
|
|
102
|
+
if (status === 409) return "CONFLICT";
|
|
103
|
+
if (status === 400 || status === 422) return "INVALID_REQUEST";
|
|
104
|
+
if (status === 429) return "RATE_LIMITED";
|
|
105
|
+
if (status >= 500) return "INTERNAL_ERROR";
|
|
106
|
+
return "UNKNOWN";
|
|
107
|
+
}
|
|
108
|
+
function friendlyApiMessage(error, endpointUrl) {
|
|
109
|
+
if (error.code === "INVALID_API_KEY") return "\u2717 Invalid API key.";
|
|
110
|
+
if (error.code === "API_KEY_REVOKED") {
|
|
111
|
+
return "\u2717 This API key has been revoked.\nCreate a new key from:\nhttps://asiyst.com";
|
|
112
|
+
}
|
|
113
|
+
if (error.code === "FORBIDDEN") {
|
|
114
|
+
return "\u2717 This API key does not have permission to access this resource.";
|
|
115
|
+
}
|
|
116
|
+
if (error.code === "NOT_FOUND") {
|
|
117
|
+
return endpointUrl ? `\u2717 Asiyst API endpoint was not found.
|
|
118
|
+
Verify that the CLI is using:
|
|
119
|
+
${endpointUrl}` : "\u2717 Asiyst API endpoint was not found.";
|
|
120
|
+
}
|
|
121
|
+
if (error.code === "TIMEOUT") return "Connection to Asiyst timed out.";
|
|
122
|
+
if (error.code === "NETWORK") return "\u2717 Unable to reach Asiyst API.";
|
|
123
|
+
if (error.code === "MALFORMED_RESPONSE") return "Received an unexpected response from Asiyst.";
|
|
124
|
+
if (error.code === "RATE_LIMITED") return "\u2717 Too many requests. Try again shortly.";
|
|
125
|
+
if (error.code === "PROJECT_NOT_FOUND") return "\u2717 Authorized project was not found.";
|
|
126
|
+
if (error.code === "CONFLICT") return "\u2717 The request conflicts with the current project state.";
|
|
127
|
+
if (error.code === "INVALID_REQUEST") return "\u2717 The request was invalid.";
|
|
128
|
+
if (error.code === "AVATAR_NOT_FOUND") return "\u2717 Avatar was not found.";
|
|
129
|
+
if (error.code === "AVATAR_ALREADY_IMPORTED") return "\u2717 This avatar is already imported into the project.";
|
|
130
|
+
if (error.code === "INTERNAL_ERROR") return "\u2717 Asiyst is temporarily unavailable.";
|
|
131
|
+
return "\u2717 Asiyst request failed.";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/api/auth.ts
|
|
135
|
+
function asRecord(value) {
|
|
136
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
137
|
+
}
|
|
138
|
+
function bearerToken(apiKey) {
|
|
139
|
+
const trimmed = apiKey.trim();
|
|
140
|
+
return trimmed.toLowerCase().startsWith("bearer ") ? trimmed.slice(7).trim() : trimmed;
|
|
141
|
+
}
|
|
142
|
+
function projectFromBody(body) {
|
|
143
|
+
const nested = asRecord(body.project) ?? asRecord(body.data);
|
|
144
|
+
const nestedProject = nested ? asRecord(nested.project) ?? nested : void 0;
|
|
145
|
+
return nestedProject ?? body;
|
|
146
|
+
}
|
|
147
|
+
function parseVerifyKeyResponse(value, apiKey) {
|
|
148
|
+
const body = asRecord(value);
|
|
149
|
+
if (!body) throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
150
|
+
if (body.valid === false) {
|
|
151
|
+
const code = typeof body.code === "string" ? body.code : void 0;
|
|
152
|
+
if (code === "API_KEY_REVOKED" || body.revoked === true) {
|
|
153
|
+
throw new ApiError("This API key has been revoked.", 401, "API_KEY_REVOKED");
|
|
154
|
+
}
|
|
155
|
+
throw new ApiError("The Asiyst API key was rejected.", 401, "INVALID_API_KEY");
|
|
156
|
+
}
|
|
157
|
+
if (body.valid !== void 0 && body.valid !== true) {
|
|
158
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
159
|
+
}
|
|
160
|
+
const project = projectFromBody(body);
|
|
161
|
+
if (!project) throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
162
|
+
const projectIdCandidate = project.projectId ?? project.project_id ?? project.projectID ?? project.id;
|
|
163
|
+
const projectId = typeof projectIdCandidate === "string" && isValidPublicIdentifier(projectIdCandidate) ? projectIdCandidate.trim() : void 0;
|
|
164
|
+
if (!projectId) {
|
|
165
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
166
|
+
}
|
|
167
|
+
const website = project.website ?? project.websiteUrl ?? project.url ?? project.domain ?? project.website_url;
|
|
168
|
+
const projectName = project.projectName ?? project.name ?? project.title;
|
|
169
|
+
const publicKey = project.publicKey ?? project.public_key ?? project.publishableKey;
|
|
170
|
+
const userId = project.userId ?? project.user_id ?? project.asiystUserId ?? project.asiyst_user_id ?? body.userId ?? body.user_id ?? body.asiystUserId ?? body.asiyst_user_id;
|
|
171
|
+
return {
|
|
172
|
+
projectId,
|
|
173
|
+
projectName: typeof projectName === "string" ? projectName : void 0,
|
|
174
|
+
website: typeof website === "string" ? website : void 0,
|
|
175
|
+
publicKey: typeof publicKey === "string" ? publicKey : void 0,
|
|
176
|
+
apiKey,
|
|
177
|
+
userId: typeof userId === "string" && isValidPublicIdentifier(userId) ? userId : void 0
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function verifyApiKey(api, apiKey, expected) {
|
|
181
|
+
const token = bearerToken(apiKey);
|
|
182
|
+
if (!token) throw new ApiError("Invalid API key.", 401, "INVALID_API_KEY");
|
|
183
|
+
const value = await api.request(VERIFY_KEY_PATH, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
Authorization: "Bearer " + token,
|
|
187
|
+
"X-Asiyst-API-Key": token
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
apiKey: token,
|
|
191
|
+
...expected?.userId ? { userId: expected.userId } : {},
|
|
192
|
+
...expected?.projectId ? { projectId: expected.projectId } : {}
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
return parseVerifyKeyResponse(value, token);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/browser/open.ts
|
|
199
|
+
import { execFile } from "child_process";
|
|
200
|
+
import { promisify } from "util";
|
|
201
|
+
var execFileAsync = promisify(execFile);
|
|
202
|
+
async function openBrowser(url) {
|
|
203
|
+
const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
204
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
205
|
+
try {
|
|
206
|
+
await execFileAsync(command, args);
|
|
207
|
+
return true;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/config/credentials.ts
|
|
214
|
+
import { execFile as execFile2 } from "child_process";
|
|
215
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
216
|
+
import { homedir } from "os";
|
|
217
|
+
import { join, resolve } from "path";
|
|
218
|
+
import { promisify as promisify2 } from "util";
|
|
219
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
220
|
+
function configDir() {
|
|
221
|
+
if (process.platform === "win32") return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "asiyst");
|
|
222
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support", "asiyst");
|
|
223
|
+
return join(homedir(), ".config", "asiyst");
|
|
224
|
+
}
|
|
225
|
+
function storePath() {
|
|
226
|
+
return join(configDir(), "credentials.json");
|
|
227
|
+
}
|
|
228
|
+
function accountFor(cwd) {
|
|
229
|
+
return resolve(cwd);
|
|
230
|
+
}
|
|
231
|
+
function readStore() {
|
|
232
|
+
const path = storePath();
|
|
233
|
+
if (!existsSync(path)) return {};
|
|
234
|
+
try {
|
|
235
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
236
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
237
|
+
return parsed;
|
|
238
|
+
} catch {
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function writeStore(data) {
|
|
243
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
244
|
+
writeFileSync(storePath(), JSON.stringify(data, null, 2), { encoding: "utf8", mode: 384 });
|
|
245
|
+
}
|
|
246
|
+
async function dpapiProtect(plaintext) {
|
|
247
|
+
if (process.platform !== "win32") return void 0;
|
|
248
|
+
const script = [
|
|
249
|
+
"Add-Type -AssemblyName System.Security",
|
|
250
|
+
"$bytes = [System.Text.Encoding]::UTF8.GetBytes($env:ASIYST_DPAPI_PAYLOAD)",
|
|
251
|
+
"$protected = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
252
|
+
"[Convert]::ToBase64String($protected)"
|
|
253
|
+
].join("; ");
|
|
254
|
+
try {
|
|
255
|
+
const { stdout: stdout3 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
256
|
+
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: plaintext },
|
|
257
|
+
windowsHide: true
|
|
258
|
+
});
|
|
259
|
+
const value = stdout3.trim();
|
|
260
|
+
return value || void 0;
|
|
261
|
+
} catch {
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function dpapiUnprotect(payload) {
|
|
266
|
+
if (process.platform !== "win32") return void 0;
|
|
267
|
+
const script = [
|
|
268
|
+
"Add-Type -AssemblyName System.Security",
|
|
269
|
+
"$protected = [Convert]::FromBase64String($env:ASIYST_DPAPI_PAYLOAD)",
|
|
270
|
+
"$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($protected, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
271
|
+
"[System.Text.Encoding]::UTF8.GetString($bytes)"
|
|
272
|
+
].join("; ");
|
|
273
|
+
try {
|
|
274
|
+
const { stdout: stdout3 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
275
|
+
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: payload },
|
|
276
|
+
windowsHide: true
|
|
277
|
+
});
|
|
278
|
+
const value = stdout3.trim();
|
|
279
|
+
return value || void 0;
|
|
280
|
+
} catch {
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function windowsBlobPath() {
|
|
285
|
+
return join(configDir(), "credentials.dpapi");
|
|
286
|
+
}
|
|
287
|
+
async function saveConnection(cwd, connection) {
|
|
288
|
+
const account = accountFor(cwd);
|
|
289
|
+
const next = { ...readStore(), [account]: connection };
|
|
290
|
+
if (process.platform === "win32") {
|
|
291
|
+
const protectedBlob = await dpapiProtect(JSON.stringify(next));
|
|
292
|
+
if (protectedBlob) {
|
|
293
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
294
|
+
writeFileSync(windowsBlobPath(), protectedBlob, { encoding: "utf8", mode: 384 });
|
|
295
|
+
if (existsSync(storePath())) unlinkSync(storePath());
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
writeStore(next);
|
|
300
|
+
}
|
|
301
|
+
async function loadConnection(cwd) {
|
|
302
|
+
const account = accountFor(cwd);
|
|
303
|
+
if (process.platform === "win32" && existsSync(windowsBlobPath())) {
|
|
304
|
+
const decrypted = await dpapiUnprotect(readFileSync(windowsBlobPath(), "utf8"));
|
|
305
|
+
if (decrypted) {
|
|
306
|
+
try {
|
|
307
|
+
const parsed = JSON.parse(decrypted);
|
|
308
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
309
|
+
const entry2 = parsed[account];
|
|
310
|
+
if (entry2 && typeof entry2.apiKey === "string" && typeof entry2.projectId === "string") return entry2;
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const entry = readStore()[account];
|
|
318
|
+
if (entry && typeof entry.apiKey === "string" && typeof entry.projectId === "string") return entry;
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
async function clearConnection(cwd) {
|
|
322
|
+
const account = accountFor(cwd);
|
|
323
|
+
if (process.platform === "win32" && existsSync(windowsBlobPath())) {
|
|
324
|
+
const decrypted = await dpapiUnprotect(readFileSync(windowsBlobPath(), "utf8"));
|
|
325
|
+
let next2 = {};
|
|
326
|
+
if (decrypted) {
|
|
327
|
+
try {
|
|
328
|
+
const parsed = JSON.parse(decrypted);
|
|
329
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) next2 = { ...parsed };
|
|
330
|
+
} catch {
|
|
331
|
+
next2 = {};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
delete next2[account];
|
|
335
|
+
if (Object.keys(next2).length === 0) {
|
|
336
|
+
unlinkSync(windowsBlobPath());
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const protectedBlob = await dpapiProtect(JSON.stringify(next2));
|
|
340
|
+
if (protectedBlob) {
|
|
341
|
+
writeFileSync(windowsBlobPath(), protectedBlob, { encoding: "utf8", mode: 384 });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const next = readStore();
|
|
346
|
+
delete next[account];
|
|
347
|
+
if (Object.keys(next).length === 0 && existsSync(storePath())) unlinkSync(storePath());
|
|
348
|
+
else writeStore(next);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/config/project.ts
|
|
352
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
353
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
354
|
+
var PROJECT_CONFIG_DIR = ".asiyst";
|
|
355
|
+
var PROJECT_CONFIG_FILE = "config.json";
|
|
356
|
+
function projectConfigPath(cwd) {
|
|
357
|
+
return join2(resolve2(cwd), PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
358
|
+
}
|
|
359
|
+
function readProjectMetadata(cwd) {
|
|
360
|
+
const path = projectConfigPath(cwd);
|
|
361
|
+
if (!existsSync2(path)) return void 0;
|
|
362
|
+
try {
|
|
363
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
364
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
365
|
+
const body = parsed;
|
|
366
|
+
if (typeof body.apiKey === "string") delete body.apiKey;
|
|
367
|
+
return {
|
|
368
|
+
projectId: typeof body.projectId === "string" ? body.projectId : void 0,
|
|
369
|
+
projectName: typeof body.projectName === "string" ? body.projectName : void 0,
|
|
370
|
+
website: typeof body.website === "string" ? body.website : void 0,
|
|
371
|
+
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0,
|
|
372
|
+
userId: typeof body.userId === "string" ? body.userId : void 0,
|
|
373
|
+
avatarId: typeof body.avatarId === "string" ? body.avatarId : void 0,
|
|
374
|
+
connected: body.connected === true
|
|
375
|
+
};
|
|
376
|
+
} catch {
|
|
377
|
+
return void 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function writeProjectMetadata(cwd, connection) {
|
|
381
|
+
const dir = join2(resolve2(cwd), PROJECT_CONFIG_DIR);
|
|
382
|
+
mkdirSync2(dir, { recursive: true });
|
|
383
|
+
const existing = readProjectMetadata(cwd) ?? {};
|
|
384
|
+
const next = {
|
|
385
|
+
...existing,
|
|
386
|
+
projectId: connection.projectId,
|
|
387
|
+
projectName: connection.projectName,
|
|
388
|
+
website: connection.website,
|
|
389
|
+
publicKey: connection.publicKey,
|
|
390
|
+
userId: connection.userId,
|
|
391
|
+
avatarId: connection.avatarId,
|
|
392
|
+
connected: true
|
|
393
|
+
};
|
|
394
|
+
writeFileSync2(projectConfigPath(cwd), `${JSON.stringify(next, null, 2)}
|
|
395
|
+
`, { encoding: "utf8" });
|
|
396
|
+
}
|
|
397
|
+
function clearProjectMetadata(cwd) {
|
|
398
|
+
const path = projectConfigPath(cwd);
|
|
399
|
+
if (!existsSync2(path)) return;
|
|
400
|
+
const existing = readProjectMetadata(cwd) ?? {};
|
|
401
|
+
const next = {
|
|
402
|
+
...existing,
|
|
403
|
+
connected: false
|
|
404
|
+
};
|
|
405
|
+
writeFileSync2(path, `${JSON.stringify(next, null, 2)}
|
|
406
|
+
`, { encoding: "utf8" });
|
|
35
407
|
}
|
|
36
408
|
|
|
37
409
|
// src/detection/project.ts
|
|
38
|
-
import { existsSync, readFileSync } from "fs";
|
|
39
|
-
import { resolve } from "path";
|
|
410
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
411
|
+
import { resolve as resolve3 } from "path";
|
|
40
412
|
function dependencyVersion(pkg) {
|
|
41
413
|
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
42
414
|
for (const section of sections) {
|
|
@@ -48,30 +420,37 @@ function dependencyVersion(pkg) {
|
|
|
48
420
|
}
|
|
49
421
|
return void 0;
|
|
50
422
|
}
|
|
423
|
+
function hasDependency(deps, name) {
|
|
424
|
+
return typeof deps[name] === "string";
|
|
425
|
+
}
|
|
51
426
|
function detectFramework(cwd, pkg) {
|
|
52
427
|
const deps = Object.assign({}, pkg?.dependencies, pkg?.devDependencies);
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
if (
|
|
58
|
-
if (
|
|
59
|
-
|
|
428
|
+
if (hasDependency(deps, "next") || existsSync3(resolve3(cwd, "next.config.js")) || existsSync3(resolve3(cwd, "next.config.mjs")) || existsSync3(resolve3(cwd, "next.config.ts"))) {
|
|
429
|
+
return "Next.js";
|
|
430
|
+
}
|
|
431
|
+
if (hasDependency(deps, "nuxt")) return "Nuxt";
|
|
432
|
+
if (hasDependency(deps, "vue") || existsSync3(resolve3(cwd, "vue.config.js"))) return "Vue";
|
|
433
|
+
if (hasDependency(deps, "react")) return "React";
|
|
434
|
+
if (hasDependency(deps, "vite") || existsSync3(resolve3(cwd, "vite.config.ts")) || existsSync3(resolve3(cwd, "vite.config.js")) || existsSync3(resolve3(cwd, "vite.config.mjs"))) {
|
|
435
|
+
return "Vite";
|
|
436
|
+
}
|
|
437
|
+
if (pkg) return existsSync3(resolve3(cwd, "tsconfig.json")) ? "Vanilla TypeScript" : "Vanilla JavaScript";
|
|
438
|
+
return "Unknown";
|
|
60
439
|
}
|
|
61
440
|
function detectProject(cwd = process.cwd()) {
|
|
62
|
-
const path =
|
|
441
|
+
const path = resolve3(cwd, "package.json");
|
|
63
442
|
let packageJson = null;
|
|
64
|
-
if (
|
|
443
|
+
if (existsSync3(path)) {
|
|
65
444
|
try {
|
|
66
|
-
const value = JSON.parse(
|
|
445
|
+
const value = JSON.parse(readFileSync3(path, "utf8"));
|
|
67
446
|
if (value && typeof value === "object" && !Array.isArray(value)) packageJson = value;
|
|
68
447
|
} catch {
|
|
69
448
|
packageJson = null;
|
|
70
449
|
}
|
|
71
450
|
}
|
|
72
451
|
const env = process.env;
|
|
73
|
-
const packageManager =
|
|
74
|
-
const sourceFiles = ["tsconfig.json", "src", "app"].some((entry) =>
|
|
452
|
+
const packageManager = existsSync3(resolve3(cwd, "pnpm-lock.yaml")) ? "pnpm" : existsSync3(resolve3(cwd, "yarn.lock")) ? "yarn" : existsSync3(resolve3(cwd, "bun.lockb")) || existsSync3(resolve3(cwd, "bun.lock")) ? "bun" : "npm";
|
|
453
|
+
const sourceFiles = ["tsconfig.json", "src", "app"].some((entry) => existsSync3(resolve3(cwd, entry)));
|
|
75
454
|
return {
|
|
76
455
|
cwd,
|
|
77
456
|
packageJson,
|
|
@@ -86,263 +465,298 @@ function detectProject(cwd = process.cwd()) {
|
|
|
86
465
|
};
|
|
87
466
|
}
|
|
88
467
|
|
|
89
|
-
// src/
|
|
90
|
-
var
|
|
91
|
-
var
|
|
468
|
+
// src/ui/output.ts
|
|
469
|
+
var ok = (label, detail = "") => console.log(`\u2713 ${label}${detail ? ` (${detail})` : ""}`);
|
|
470
|
+
var fail = (label, detail = "") => console.log(`\u2717 ${label}${detail ? `: ${detail}` : ""}`);
|
|
471
|
+
function projectChecks(project) {
|
|
472
|
+
project.packageJson ? ok("Project detected", typeof project.packageJson.name === "string" ? project.packageJson.name : project.cwd) : fail("Project not detected", "package.json is missing");
|
|
473
|
+
if (project.framework === "Unknown") fail("Framework detected", "No supported project type was identified.");
|
|
474
|
+
else ok("Framework detected", project.framework);
|
|
475
|
+
ok("Language detected", project.language);
|
|
476
|
+
ok("Node.js detected", process.version);
|
|
477
|
+
ok("Package manager detected", project.packageManager);
|
|
478
|
+
project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : console.log("@asiyst/sdk is not installed. You can connect the project now and install the SDK later.");
|
|
479
|
+
}
|
|
480
|
+
function homeStatus() {
|
|
481
|
+
console.log("\nConnect your project to Asiyst to view stats.\n");
|
|
482
|
+
}
|
|
92
483
|
|
|
93
|
-
// src/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
484
|
+
// src/ui/selector.ts
|
|
485
|
+
import { stdin, stdout } from "process";
|
|
486
|
+
import { clearLine, cursorTo, emitKeypressEvents, moveCursor } from "readline";
|
|
487
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
488
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
489
|
+
function moveSelection(active, optionCount, direction) {
|
|
490
|
+
if (optionCount === 0) return 0;
|
|
491
|
+
return direction === "up" ? Math.max(0, active - 1) : Math.min(optionCount - 1, active + 1);
|
|
492
|
+
}
|
|
493
|
+
function clearSelectorFrame(output, previousLineCount) {
|
|
494
|
+
if (previousLineCount <= 0) return;
|
|
495
|
+
cursorTo(output, 0);
|
|
496
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
497
|
+
moveCursor(output, 0, -1);
|
|
106
498
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
113
|
-
...init,
|
|
114
|
-
headers: { Accept: "application/json", "Content-Type": "application/json", ...init?.headers }
|
|
115
|
-
});
|
|
116
|
-
} catch (error) {
|
|
117
|
-
throw new ApiError(`Unable to reach Asiyst API at ${this.baseUrl}${path}. Check your internet connection, DNS, TLS/HTTPS, or whether the service is unavailable.`);
|
|
118
|
-
}
|
|
119
|
-
const body = await response.json().catch(() => void 0);
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
const detail = response.status === 401 ? "Authentication is required." : response.status === 403 ? "The request is not authorized." : response.status === 404 ? "The requested resource was not found." : response.status === 409 ? "The request conflicts with the current project state." : response.status === 422 ? "The request data was invalid." : response.status === 429 ? "Too many requests; try again shortly." : response.status >= 500 ? "Asiyst is temporarily unavailable." : "The request was rejected.";
|
|
122
|
-
throw new ApiError(`Asiyst API returned HTTP ${response.status}. ${detail}`, response.status);
|
|
123
|
-
}
|
|
124
|
-
if (body === void 0) {
|
|
125
|
-
throw new ApiError(`Asiyst API returned an invalid JSON response (HTTP ${response.status}).`, response.status);
|
|
499
|
+
for (let line = 0; line < previousLineCount; line += 1) {
|
|
500
|
+
cursorTo(output, 0);
|
|
501
|
+
clearLine(output, 0);
|
|
502
|
+
if (line < previousLineCount - 1) {
|
|
503
|
+
moveCursor(output, 0, 1);
|
|
126
504
|
}
|
|
127
|
-
return body;
|
|
128
505
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
throw new ApiError("Asiyst API health endpoint returned an invalid response.");
|
|
133
|
-
}
|
|
134
|
-
return body;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
createSession() {
|
|
138
|
-
return this.request("/cli/sessions", { method: "POST", body: JSON.stringify({}) }).then((value) => {
|
|
139
|
-
if (!value || typeof value !== "object") throw new ApiError("Asiyst returned an invalid session response");
|
|
140
|
-
const session = value;
|
|
141
|
-
if (typeof session.sessionId !== "string" || typeof session.connectUrl !== "string" || typeof session.expiresAt !== "string") {
|
|
142
|
-
throw new ApiError("Asiyst returned an invalid session response");
|
|
143
|
-
}
|
|
144
|
-
const url = new URL(session.connectUrl);
|
|
145
|
-
if (url.protocol !== "https:") {
|
|
146
|
-
throw new ApiError("Asiyst returned an insecure connection URL");
|
|
147
|
-
}
|
|
148
|
-
return { sessionId: session.sessionId, connectUrl: url.toString(), expiresAt: session.expiresAt };
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
sessionStatus(sessionId) {
|
|
152
|
-
return this.request(`/cli/sessions/${encodeURIComponent(sessionId)}/status`);
|
|
506
|
+
cursorTo(output, 0);
|
|
507
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
508
|
+
moveCursor(output, 0, -1);
|
|
153
509
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
if (!Array.isArray(value) || !value.every((item) => item && typeof item === "object" && typeof item.name === "string" && typeof item.ok === "boolean")) {
|
|
163
|
-
throw new ApiError("Asiyst returned an invalid verification response");
|
|
164
|
-
}
|
|
165
|
-
return value;
|
|
166
|
-
});
|
|
510
|
+
}
|
|
511
|
+
function renderSelectorFrame(output, previousLineCount, lines) {
|
|
512
|
+
if (lines.length === 0) return 0;
|
|
513
|
+
if (previousLineCount > 0) {
|
|
514
|
+
cursorTo(output, 0);
|
|
515
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
516
|
+
moveCursor(output, 0, -1);
|
|
517
|
+
}
|
|
167
518
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (existsSync2(envPath)) {
|
|
176
|
-
const ignored = existsSync2(resolve2(cwd, ".gitignore")) && readFileSync2(resolve2(cwd, ".gitignore"), "utf8").split(/\r?\n/).some((line) => line.trim() === ".env" || line.trim() === ".env.*");
|
|
177
|
-
if (!ignored) return "Your .env file is not clearly ignored by .gitignore; do not commit public or private configuration accidentally.";
|
|
519
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
520
|
+
cursorTo(output, 0);
|
|
521
|
+
clearLine(output, 0);
|
|
522
|
+
output.write(lines[i]);
|
|
523
|
+
if (i < lines.length - 1) {
|
|
524
|
+
output.write("\n");
|
|
525
|
+
}
|
|
178
526
|
}
|
|
179
|
-
return
|
|
527
|
+
return lines.length;
|
|
180
528
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
import { promisify } from "util";
|
|
185
|
-
var execFileAsync = promisify(execFile);
|
|
186
|
-
async function openBrowser(url) {
|
|
187
|
-
const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
188
|
-
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
189
|
-
try {
|
|
190
|
-
await execFileAsync(command, args);
|
|
191
|
-
return true;
|
|
192
|
-
} catch {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
529
|
+
function restoreTerminal(wasRaw) {
|
|
530
|
+
stdout.write(SHOW_CURSOR);
|
|
531
|
+
if (stdin.isTTY) stdin.setRawMode?.(wasRaw);
|
|
195
532
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
import { emitKeypressEvents } from "readline";
|
|
200
|
-
var escape = "\x1B[";
|
|
201
|
-
var width = () => Math.max(40, (stdout.columns || 80) - 2);
|
|
202
|
-
var fit = (value) => value.length > width() ? `${value.slice(0, width() - 1)}\u2026` : value;
|
|
203
|
-
function selectOption(title, options, prompt = "> ") {
|
|
204
|
-
if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve({ type: "cancelled", input: "" });
|
|
205
|
-
return new Promise((resolve4) => {
|
|
206
|
-
let input = "";
|
|
533
|
+
function selectOption(title, options) {
|
|
534
|
+
if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve({ type: "cancelled" });
|
|
535
|
+
return new Promise((resolve5) => {
|
|
207
536
|
let active = 0;
|
|
208
|
-
let visible = true;
|
|
209
537
|
let renderedLines = 0;
|
|
210
538
|
let settled = false;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
215
|
-
const clear = () => {
|
|
216
|
-
if (renderedLines === 0) return;
|
|
217
|
-
stdout.write(`${escape}${renderedLines}A`);
|
|
218
|
-
for (let line = 0; line < renderedLines; line += 1) stdout.write(`${escape}2K${line < renderedLines - 1 ? `${escape}1B` : ""}`);
|
|
219
|
-
stdout.write(`${escape}${renderedLines}A`);
|
|
220
|
-
};
|
|
221
|
-
const render = () => {
|
|
222
|
-
clear();
|
|
223
|
-
const matches = filtered();
|
|
224
|
-
if (active >= matches.length) active = Math.max(0, matches.length - 1);
|
|
225
|
-
const lines = [`${title}`, `${prompt}${fit(input)}`];
|
|
226
|
-
if (visible) {
|
|
227
|
-
if (matches.length === 0) lines.push(" No matching commands.");
|
|
228
|
-
else lines.push(...matches.map((option, index) => `${index === active ? "\u276F" : " "} ${fit(option.label)}`));
|
|
229
|
-
lines.push("\u2191\u2193 Navigate Enter Select Esc Cancel");
|
|
230
|
-
}
|
|
231
|
-
stdout.write(`${lines.join("\n")}
|
|
232
|
-
`);
|
|
233
|
-
renderedLines = lines.length;
|
|
234
|
-
};
|
|
235
|
-
const finish = (result) => {
|
|
539
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
540
|
+
const enabled = options.filter((option) => !option.disabled);
|
|
541
|
+
const finish = (result, confirmation) => {
|
|
236
542
|
if (settled) return;
|
|
237
543
|
settled = true;
|
|
238
544
|
stdin.off("keypress", onKeypress);
|
|
239
|
-
|
|
240
|
-
|
|
545
|
+
clearSelectorFrame(stdout, renderedLines);
|
|
546
|
+
restoreTerminal(wasRaw);
|
|
241
547
|
stdin.pause();
|
|
242
|
-
stdout.write(
|
|
243
|
-
|
|
548
|
+
if (confirmation) stdout.write(`${confirmation}
|
|
549
|
+
`);
|
|
550
|
+
resolve5(result);
|
|
551
|
+
};
|
|
552
|
+
const render = () => {
|
|
553
|
+
const lines = [title, "", ...enabled.map((option, index) => `${index === active ? "\u276F" : " "} ${option.label}`)];
|
|
554
|
+
renderedLines = renderSelectorFrame(stdout, renderedLines, lines);
|
|
244
555
|
};
|
|
245
|
-
const onKeypress = (
|
|
246
|
-
if (key
|
|
247
|
-
if (key.ctrl && key.name === "
|
|
248
|
-
|
|
249
|
-
|
|
556
|
+
const onKeypress = (_value, key) => {
|
|
557
|
+
if (!key) return;
|
|
558
|
+
if (key.ctrl && key.name === "c") {
|
|
559
|
+
clearSelectorFrame(stdout, renderedLines);
|
|
560
|
+
restoreTerminal(wasRaw);
|
|
561
|
+
stdin.pause();
|
|
562
|
+
stdout.write("\n");
|
|
563
|
+
process.exit(130);
|
|
250
564
|
}
|
|
565
|
+
if (key.name === "escape") return finish({ type: "cancelled" });
|
|
251
566
|
if (key.name === "up" || key.name === "down") {
|
|
252
|
-
|
|
253
|
-
if (matches.length > 0) active = key.name === "up" ? (active + matches.length - 1) % matches.length : (active + 1) % matches.length;
|
|
254
|
-
visible = true;
|
|
567
|
+
active = moveSelection(active, enabled.length, key.name);
|
|
255
568
|
return render();
|
|
256
569
|
}
|
|
257
570
|
if (key.name === "return" || key.name === "enter") {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return finish({ type: "cancelled", input });
|
|
262
|
-
}
|
|
263
|
-
if (key.name === "tab") {
|
|
264
|
-
const matches = filtered();
|
|
265
|
-
if (matches.length === 1) input = matches[0].value;
|
|
266
|
-
else if (matches.length > 1) active = (active + 1) % matches.length;
|
|
267
|
-
visible = true;
|
|
268
|
-
return render();
|
|
269
|
-
}
|
|
270
|
-
if (key.name === "backspace") {
|
|
271
|
-
input = input.slice(0, -1);
|
|
272
|
-
visible = true;
|
|
273
|
-
return render();
|
|
274
|
-
}
|
|
275
|
-
const sequence = key.sequence || value;
|
|
276
|
-
if (/^[\x20-\x7e]+$/.test(sequence)) {
|
|
277
|
-
input += sequence;
|
|
278
|
-
active = 0;
|
|
279
|
-
visible = true;
|
|
280
|
-
return render();
|
|
571
|
+
const selected = enabled[active];
|
|
572
|
+
if (!selected) return finish({ type: "cancelled" });
|
|
573
|
+
return finish({ type: "selected", value: selected.value }, `\u2713 ${selected.label} selected.`);
|
|
281
574
|
}
|
|
282
575
|
};
|
|
283
576
|
emitKeypressEvents(stdin);
|
|
284
577
|
stdin.setRawMode?.(true);
|
|
285
578
|
stdin.resume();
|
|
579
|
+
stdout.write(HIDE_CURSOR);
|
|
286
580
|
stdin.on("keypress", onKeypress);
|
|
287
581
|
render();
|
|
288
582
|
});
|
|
289
583
|
}
|
|
290
584
|
|
|
291
|
-
// src/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
585
|
+
// src/ui/secret.ts
|
|
586
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
587
|
+
import { emitKeypressEvents as emitKeypressEvents2 } from "readline";
|
|
588
|
+
async function readSecret(prompt) {
|
|
589
|
+
if (!stdin2.isTTY) return void 0;
|
|
590
|
+
return new Promise((resolve5) => {
|
|
591
|
+
let value = "";
|
|
592
|
+
let settled = false;
|
|
593
|
+
const wasRaw = Boolean(stdin2.isRaw);
|
|
594
|
+
stdout2.write(prompt);
|
|
595
|
+
const finish = (result) => {
|
|
596
|
+
if (settled) return;
|
|
597
|
+
settled = true;
|
|
598
|
+
stdin2.off("keypress", onKeypress);
|
|
599
|
+
if (stdin2.isTTY) stdin2.setRawMode?.(wasRaw);
|
|
600
|
+
stdin2.pause();
|
|
601
|
+
stdout2.write("\n");
|
|
602
|
+
resolve5(result);
|
|
603
|
+
};
|
|
604
|
+
const onKeypress = (chunk, key) => {
|
|
605
|
+
if (!key) return;
|
|
606
|
+
if (key.ctrl && key.name === "c") {
|
|
607
|
+
finish(void 0);
|
|
608
|
+
process.exit(130);
|
|
609
|
+
}
|
|
610
|
+
if (key.name === "escape") return finish(void 0);
|
|
611
|
+
if (key.name === "return" || key.name === "enter") return finish(value.trim());
|
|
612
|
+
if (key.name === "backspace") {
|
|
613
|
+
value = value.slice(0, -1);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const sequence = key.sequence || chunk;
|
|
617
|
+
if (sequence && /^[\x20-\x7e]+$/.test(sequence)) value += sequence;
|
|
618
|
+
};
|
|
619
|
+
emitKeypressEvents2(stdin2);
|
|
620
|
+
stdin2.setRawMode?.(true);
|
|
621
|
+
stdin2.resume();
|
|
622
|
+
stdin2.on("keypress", onKeypress);
|
|
623
|
+
});
|
|
308
624
|
}
|
|
309
|
-
async function
|
|
310
|
-
if (!
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
625
|
+
async function readInput(prompt) {
|
|
626
|
+
if (!stdin2.isTTY) return void 0;
|
|
627
|
+
return new Promise((resolve5) => {
|
|
628
|
+
let value = "";
|
|
629
|
+
const onData = (chunk) => {
|
|
630
|
+
value += String(chunk);
|
|
631
|
+
const newline = value.search(/[\r\n]/);
|
|
632
|
+
if (newline >= 0) {
|
|
633
|
+
stdin2.off("data", onData);
|
|
634
|
+
resolve5(value.slice(0, newline).trim());
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
stdout2.write(prompt);
|
|
638
|
+
stdin2.resume();
|
|
639
|
+
stdin2.on("data", onData);
|
|
640
|
+
});
|
|
316
641
|
}
|
|
317
642
|
|
|
318
643
|
// src/config/trust.ts
|
|
319
|
-
import { existsSync as
|
|
320
|
-
import { homedir } from "os";
|
|
321
|
-
import { join, resolve as
|
|
322
|
-
var trustFile =
|
|
644
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
645
|
+
import { homedir as homedir2 } from "os";
|
|
646
|
+
import { join as join3, resolve as resolve4 } from "path";
|
|
647
|
+
var trustFile = join3(homedir2(), ".config", "asiyst", "trusted-folders.json");
|
|
323
648
|
function readTrusted() {
|
|
324
|
-
if (!
|
|
649
|
+
if (!existsSync4(trustFile)) return [];
|
|
325
650
|
try {
|
|
326
|
-
const parsed = JSON.parse(
|
|
651
|
+
const parsed = JSON.parse(readFileSync4(trustFile, "utf8"));
|
|
327
652
|
return Array.isArray(parsed) && parsed.every((item) => typeof item === "string") ? parsed : [];
|
|
328
653
|
} catch {
|
|
329
654
|
return [];
|
|
330
655
|
}
|
|
331
656
|
}
|
|
332
657
|
function isTrusted(cwd) {
|
|
333
|
-
return readTrusted().includes(
|
|
658
|
+
return readTrusted().includes(resolve4(cwd));
|
|
334
659
|
}
|
|
335
660
|
function trustFolder(cwd) {
|
|
336
|
-
const folder =
|
|
661
|
+
const folder = resolve4(cwd);
|
|
337
662
|
const trusted = readTrusted();
|
|
338
663
|
if (!trusted.includes(folder)) trusted.push(folder);
|
|
339
|
-
|
|
340
|
-
|
|
664
|
+
mkdirSync3(join3(homedir2(), ".config", "asiyst"), { recursive: true, mode: 448 });
|
|
665
|
+
writeFileSync3(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
|
|
341
666
|
}
|
|
342
667
|
function revokeTrust(cwd) {
|
|
343
|
-
const folder =
|
|
668
|
+
const folder = resolve4(cwd);
|
|
344
669
|
const trusted = readTrusted().filter((item) => item !== folder);
|
|
345
|
-
if (
|
|
670
|
+
if (existsSync4(trustFile)) writeFileSync3(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/api/client.ts
|
|
674
|
+
function asRecord2(value) {
|
|
675
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
676
|
+
}
|
|
677
|
+
function bodyErrorCode(body) {
|
|
678
|
+
const record = asRecord2(body);
|
|
679
|
+
const code = record?.code ?? record?.errorCode ?? asRecord2(record?.error)?.code;
|
|
680
|
+
return typeof code === "string" ? code : void 0;
|
|
681
|
+
}
|
|
682
|
+
function isAbortError(error) {
|
|
683
|
+
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
684
|
+
}
|
|
685
|
+
var ApiClient = class {
|
|
686
|
+
constructor(baseUrl = resolveApiBaseUrl(), fetcher = fetch) {
|
|
687
|
+
this.fetcher = fetcher;
|
|
688
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
689
|
+
}
|
|
690
|
+
fetcher;
|
|
691
|
+
baseUrl;
|
|
692
|
+
async request(path, init) {
|
|
693
|
+
const url = `${this.baseUrl}${path}`;
|
|
694
|
+
const controller = new AbortController();
|
|
695
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
696
|
+
const headers = new Headers(init?.headers);
|
|
697
|
+
headers.set("Accept", "application/json");
|
|
698
|
+
if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
699
|
+
let response;
|
|
700
|
+
try {
|
|
701
|
+
response = await this.fetcher(url, {
|
|
702
|
+
...init,
|
|
703
|
+
headers,
|
|
704
|
+
signal: init?.signal || controller.signal
|
|
705
|
+
});
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (isAbortError(error)) {
|
|
708
|
+
throw new ApiError("Connection to Asiyst timed out.", void 0, "TIMEOUT");
|
|
709
|
+
}
|
|
710
|
+
throw new ApiError("Unable to reach Asiyst API.", void 0, "NETWORK");
|
|
711
|
+
} finally {
|
|
712
|
+
clearTimeout(timeout);
|
|
713
|
+
}
|
|
714
|
+
const rawText = await response.text();
|
|
715
|
+
let body;
|
|
716
|
+
if (rawText) {
|
|
717
|
+
try {
|
|
718
|
+
body = JSON.parse(rawText);
|
|
719
|
+
} catch {
|
|
720
|
+
body = void 0;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (!response.ok) {
|
|
724
|
+
const code = errorCodeFromStatus(response.status, bodyErrorCode(body));
|
|
725
|
+
if (code === "API_KEY_REVOKED" || bodyErrorCode(body) === "API_KEY_REVOKED") {
|
|
726
|
+
throw new ApiError("This API key has been revoked.", response.status, "API_KEY_REVOKED");
|
|
727
|
+
}
|
|
728
|
+
throw new ApiError(`Asiyst API returned HTTP ${response.status}.`, response.status, code);
|
|
729
|
+
}
|
|
730
|
+
if (rawText && body === void 0) {
|
|
731
|
+
if (isDebugEnabled()) {
|
|
732
|
+
throw new ApiError("Asiyst API returned an invalid JSON response.", response.status, "MALFORMED_RESPONSE");
|
|
733
|
+
}
|
|
734
|
+
throw new ApiError("Received an unexpected response from Asiyst.", response.status, "MALFORMED_RESPONSE");
|
|
735
|
+
}
|
|
736
|
+
return body === void 0 ? {} : body;
|
|
737
|
+
}
|
|
738
|
+
async health() {
|
|
739
|
+
try {
|
|
740
|
+
const body = await this.request("/health");
|
|
741
|
+
const record = asRecord2(body);
|
|
742
|
+
if (record) return record;
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// src/commands/shared.ts
|
|
750
|
+
async function confirm(question) {
|
|
751
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
752
|
+
const result = await selectOption(question, [
|
|
753
|
+
{ label: "Yes", value: true },
|
|
754
|
+
{ label: "No", value: false }
|
|
755
|
+
]);
|
|
756
|
+
return result.type === "selected" && result.value;
|
|
757
|
+
}
|
|
758
|
+
function createApiClient() {
|
|
759
|
+
return new ApiClient();
|
|
346
760
|
}
|
|
347
761
|
|
|
348
762
|
// src/commands/trust.ts
|
|
@@ -368,63 +782,280 @@ function revokeTrustCommand(cwd = process.cwd()) {
|
|
|
368
782
|
console.log("\u2713 Folder trust revoked.");
|
|
369
783
|
}
|
|
370
784
|
|
|
371
|
-
// src/commands/
|
|
372
|
-
async function
|
|
785
|
+
// src/commands/connect.ts
|
|
786
|
+
async function retryOrCancel(message) {
|
|
787
|
+
console.log(message);
|
|
788
|
+
const result = await selectOption("Options:", [
|
|
789
|
+
{ label: "Retry", value: true },
|
|
790
|
+
{ label: "Cancel", value: false }
|
|
791
|
+
]);
|
|
792
|
+
return result.type === "selected" && result.value;
|
|
793
|
+
}
|
|
794
|
+
function printApiFailure(error) {
|
|
795
|
+
if (error instanceof ApiError) {
|
|
796
|
+
return friendlyApiMessage(error, error.code === "NOT_FOUND" ? VERIFY_KEY_URL : void 0);
|
|
797
|
+
}
|
|
798
|
+
if (isDebugEnabled() && error instanceof Error) return `\u2717 ${error.message}`;
|
|
799
|
+
return "\u2717 Unable to reach Asiyst API.";
|
|
800
|
+
}
|
|
801
|
+
async function promptForProjectId() {
|
|
802
|
+
const value = await readInput("Enter your Project ID: ");
|
|
803
|
+
if (value === void 0) {
|
|
804
|
+
console.log("Connection cancelled.");
|
|
805
|
+
return void 0;
|
|
806
|
+
}
|
|
807
|
+
const trimmed = value.trim();
|
|
808
|
+
if (!trimmed) {
|
|
809
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
810
|
+
return void 0;
|
|
811
|
+
}
|
|
812
|
+
return trimmed;
|
|
813
|
+
}
|
|
814
|
+
async function promptForUserId() {
|
|
815
|
+
const value = await readInput("Enter your Asiyst User ID: ");
|
|
816
|
+
if (value === void 0) {
|
|
817
|
+
console.log("Connection cancelled.");
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
if (!isValidUserId(value)) {
|
|
821
|
+
console.log("Invalid User ID. It must be a 24-character public Asiyst account ID.");
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
return value.trim();
|
|
825
|
+
}
|
|
826
|
+
async function connectCommand(cwd = process.cwd(), api = createApiClient(), cliProjectId) {
|
|
827
|
+
console.log(`Project folder:
|
|
828
|
+
${cwd}`);
|
|
373
829
|
if (!await ensureTrusted(cwd)) return;
|
|
374
830
|
const project = detectProject(cwd);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
831
|
+
projectChecks(project);
|
|
832
|
+
if (project.framework === "Unknown") {
|
|
833
|
+
console.log("No supported framework was detected in this folder.");
|
|
834
|
+
}
|
|
835
|
+
const shouldConnect = await selectOption("Connect this project to Asiyst?", [
|
|
836
|
+
{ label: "Yes", value: true },
|
|
837
|
+
{ label: "No", value: false }
|
|
838
|
+
]);
|
|
839
|
+
if (shouldConnect.type !== "selected" || !shouldConnect.value) {
|
|
840
|
+
console.log("Connection cancelled.");
|
|
379
841
|
return;
|
|
380
842
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
843
|
+
const projectIdArg = cliProjectId ?? parseProjectIdArgument(process.argv.slice(2));
|
|
844
|
+
if (projectIdArg === "") {
|
|
845
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
let projectId = typeof projectIdArg === "string" ? projectIdArg.trim() : "";
|
|
849
|
+
if (!projectId) {
|
|
850
|
+
projectId = await promptForProjectId() || "";
|
|
851
|
+
}
|
|
852
|
+
if (!projectId) {
|
|
853
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (!isValidPublicIdentifier(projectId)) {
|
|
857
|
+
console.log("Invalid Project ID. Use a public project ID such as K8mP2xQ7_vL4N9cR5T1zB6Y3");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const userId = await promptForUserId();
|
|
861
|
+
if (!userId) return;
|
|
862
|
+
console.log("Opening Asiyst...");
|
|
863
|
+
if (!await openBrowser(ASIIYST_WEB_URL)) {
|
|
864
|
+
console.log(`Open this URL manually:
|
|
865
|
+
${ASIIYST_WEB_URL}`);
|
|
866
|
+
}
|
|
867
|
+
console.log("Register or log in, complete onboarding, create or select a project, then create an API key.");
|
|
868
|
+
for (; ; ) {
|
|
869
|
+
const apiKey = await readSecret("Paste your Asiyst API key: ");
|
|
870
|
+
if (apiKey === void 0) {
|
|
871
|
+
console.log("Connection cancelled.");
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (!apiKey) {
|
|
875
|
+
fail("Invalid API key.");
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
const connected = await verifyApiKey(api, apiKey, { userId, projectId });
|
|
880
|
+
if (connected.projectId && connected.projectId !== projectId) {
|
|
881
|
+
console.log("The provided Project ID does not match the authenticated project.");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const finalConnection = { ...connected, projectId, userId };
|
|
885
|
+
ok("API key verified.");
|
|
886
|
+
ok("Project ID verified.");
|
|
887
|
+
await saveConnection(cwd, finalConnection);
|
|
888
|
+
writeProjectMetadata(cwd, finalConnection);
|
|
889
|
+
ok("Project connected successfully.");
|
|
890
|
+
console.log(`
|
|
891
|
+
Project ID:
|
|
892
|
+
${projectId}`);
|
|
893
|
+
if (connected.projectName) console.log(`
|
|
894
|
+
Project:
|
|
895
|
+
${connected.projectName}`);
|
|
896
|
+
if (connected.website) console.log(`
|
|
897
|
+
Website:
|
|
898
|
+
${connected.website}`);
|
|
899
|
+
return;
|
|
900
|
+
} catch (error) {
|
|
901
|
+
const message = printApiFailure(error);
|
|
902
|
+
const code = error instanceof ApiError ? error.code : "NETWORK";
|
|
903
|
+
if (code === "INVALID_API_KEY") {
|
|
904
|
+
console.log(message);
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (code === "API_KEY_REVOKED" || code === "FORBIDDEN" || code === "NOT_FOUND" || code === "MALFORMED_RESPONSE") {
|
|
908
|
+
console.log(message);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (!await retryOrCancel(message)) {
|
|
912
|
+
console.log("Connection cancelled.");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
389
916
|
}
|
|
390
|
-
const warning = configWarning(cwd);
|
|
391
|
-
if (warning) console.log(`
|
|
392
|
-
Warning: ${warning}`);
|
|
393
|
-
console.log('\nUse the public SDK API:\n\nimport { Asiyst } from "@asiyst/sdk";\n\nawait Asiyst.init({ projectId: "PROJECT_ID", publicKey: "PUBLIC_KEY" });');
|
|
394
|
-
console.log("\nRun `npx asiyst verify` after starting your website.");
|
|
395
917
|
}
|
|
396
918
|
|
|
397
919
|
// src/commands/login.ts
|
|
398
|
-
async function loginCommand(
|
|
399
|
-
await
|
|
400
|
-
|
|
920
|
+
async function loginCommand() {
|
|
921
|
+
await connectCommand(process.cwd(), createApiClient());
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/commands/disconnect.ts
|
|
925
|
+
async function disconnectCommand(cwd = process.cwd()) {
|
|
926
|
+
if (!await confirm("Disconnect this project?")) {
|
|
927
|
+
console.log("Disconnect cancelled.");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
await clearConnection(cwd);
|
|
931
|
+
clearProjectMetadata(cwd);
|
|
932
|
+
console.log("\u2713 Local connection information removed.");
|
|
933
|
+
console.log("The cloud project and API key were not changed.");
|
|
401
934
|
}
|
|
402
935
|
|
|
403
936
|
// src/commands/logout.ts
|
|
404
|
-
function logoutCommand() {
|
|
405
|
-
|
|
937
|
+
async function logoutCommand() {
|
|
938
|
+
await disconnectCommand();
|
|
406
939
|
}
|
|
407
940
|
|
|
408
941
|
// src/commands/status.ts
|
|
409
|
-
async function statusCommand(cwd = process.cwd(), api =
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
942
|
+
async function statusCommand(cwd = process.cwd(), api = createApiClient()) {
|
|
943
|
+
const stored = await loadConnection(cwd);
|
|
944
|
+
if (!stored?.apiKey) {
|
|
945
|
+
const metadata = readProjectMetadata(cwd);
|
|
946
|
+
if (metadata?.connected && metadata.projectId) {
|
|
947
|
+
console.log("\u2713 Connected");
|
|
948
|
+
console.log(`Project:
|
|
949
|
+
${metadata.projectId}`);
|
|
950
|
+
if (metadata.userId) console.log(`User ID:
|
|
951
|
+
${metadata.userId}`);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
console.log("Connect your website to show stats.");
|
|
415
955
|
return;
|
|
416
956
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
957
|
+
if (!stored.userId || !stored.projectId) {
|
|
958
|
+
console.log("\u2717 Connection is missing a User ID or Project ID. Please run:\nasiyst connect");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
try {
|
|
962
|
+
const connected = await verifyApiKey(api, stored.apiKey, {
|
|
963
|
+
userId: stored.userId,
|
|
964
|
+
projectId: stored.projectId
|
|
965
|
+
});
|
|
966
|
+
const projectId = stored.projectId || connected.projectId;
|
|
967
|
+
const userId = stored.userId || connected.userId;
|
|
968
|
+
console.log("\u2713 Connected");
|
|
969
|
+
if (userId) console.log(`
|
|
970
|
+
User ID:
|
|
971
|
+
${userId}`);
|
|
972
|
+
console.log(`
|
|
973
|
+
Project ID:
|
|
974
|
+
${projectId || connected.projectName || "Unknown"}`);
|
|
975
|
+
console.log("\nAPI Key:\n\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
|
|
976
|
+
if (stored.avatarId) console.log(`
|
|
977
|
+
Avatar:
|
|
978
|
+
${stored.avatarId}`);
|
|
979
|
+
if (connected.website) console.log(`
|
|
980
|
+
Website:
|
|
981
|
+
${connected.website}`);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
if (error instanceof ApiError && error.code === "API_KEY_REVOKED") {
|
|
984
|
+
console.log("\u2717 API key revoked.");
|
|
985
|
+
console.log("Create a new key from:\nhttps://asiyst.com");
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (error instanceof ApiError && (error.code === "INVALID_API_KEY" || error.code === "FORBIDDEN")) {
|
|
989
|
+
console.log("\u2717 Connection invalid.");
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
console.log("\u2717 Connection invalid.");
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/api/projects.ts
|
|
997
|
+
async function verifyInstallation(api, projectId, publicKey, domain) {
|
|
998
|
+
const value = await api.request("/cli/verification", {
|
|
999
|
+
method: "POST",
|
|
1000
|
+
body: JSON.stringify({ projectId, publicKey, domain })
|
|
1001
|
+
});
|
|
1002
|
+
if (!Array.isArray(value) || !value.every((item) => item && typeof item === "object" && typeof item.name === "string" && typeof item.ok === "boolean")) {
|
|
1003
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
1004
|
+
}
|
|
1005
|
+
return value;
|
|
1006
|
+
}
|
|
1007
|
+
function requireIdentifier(value, name, valid) {
|
|
1008
|
+
const trimmed = value.trim();
|
|
1009
|
+
if (!valid(trimmed)) throw new ApiError(`Invalid ${name}.`, 400, "INVALID_REQUEST");
|
|
1010
|
+
return trimmed;
|
|
1011
|
+
}
|
|
1012
|
+
function parseAvatarImportResponse(value, projectId, userId, avatarId) {
|
|
1013
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1014
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
1015
|
+
}
|
|
1016
|
+
const body = value;
|
|
1017
|
+
const imported = body.imported === true || body.success === true || body.status === "imported";
|
|
1018
|
+
if (!imported) {
|
|
1019
|
+
const code = typeof body.code === "string" ? body.code : "IMPORT_FAILED";
|
|
1020
|
+
const supportedCode = ["AVATAR_NOT_FOUND", "AVATAR_ALREADY_IMPORTED", "FORBIDDEN", "PROJECT_NOT_FOUND", "CONFLICT"].includes(code) ? code : "IMPORT_FAILED";
|
|
1021
|
+
throw new ApiError(typeof body.message === "string" ? body.message : "Avatar import was not completed.", 200, supportedCode);
|
|
1022
|
+
}
|
|
1023
|
+
const returnedAvatarId = body.avatarId ?? body.avatar_id;
|
|
1024
|
+
if (returnedAvatarId !== void 0 && returnedAvatarId !== avatarId) {
|
|
1025
|
+
throw new ApiError("The API returned a different avatar than requested.", 200, "AVATAR_MISMATCH");
|
|
1026
|
+
}
|
|
1027
|
+
return {
|
|
1028
|
+
imported: true,
|
|
1029
|
+
avatarId,
|
|
1030
|
+
projectId,
|
|
1031
|
+
userId,
|
|
1032
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
async function importAvatar(api, input) {
|
|
1036
|
+
const userId = requireIdentifier(input.userId, "User ID", isValidUserId);
|
|
1037
|
+
const projectId = requireIdentifier(input.projectId, "Project ID", isValidPublicIdentifier);
|
|
1038
|
+
const avatarId = requireIdentifier(input.avatarId, "Avatar ID", isValidAvatarId);
|
|
1039
|
+
const apiKey = input.apiKey.trim();
|
|
1040
|
+
if (!apiKey) throw new ApiError("An API key is required.", 401, "INVALID_API_KEY");
|
|
1041
|
+
const value = await api.request(`/cli/projects/${encodeURIComponent(projectId)}/avatars/import`, {
|
|
1042
|
+
method: "POST",
|
|
1043
|
+
headers: {
|
|
1044
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1045
|
+
"X-Asiyst-API-Key": apiKey
|
|
1046
|
+
},
|
|
1047
|
+
body: JSON.stringify({ userId, projectId, avatarId })
|
|
1048
|
+
});
|
|
1049
|
+
return parseAvatarImportResponse(value, projectId, userId, avatarId);
|
|
424
1050
|
}
|
|
425
1051
|
|
|
426
1052
|
// src/commands/verify.ts
|
|
427
|
-
|
|
1053
|
+
function printVerificationSafe(results) {
|
|
1054
|
+
console.log("\nAsiyst Installation Verification\n");
|
|
1055
|
+
for (const result of results) result.ok ? ok(result.name, result.detail) : fail(result.name, result.detail);
|
|
1056
|
+
return results.length > 0 && results.every((result) => result.ok);
|
|
1057
|
+
}
|
|
1058
|
+
async function verifyCommand(cwd = process.cwd()) {
|
|
428
1059
|
const project = detectProject(cwd);
|
|
429
1060
|
if (!project.packageJson) {
|
|
430
1061
|
fail("Project detected", "package.json is missing");
|
|
@@ -432,105 +1063,180 @@ async function verifyCommand(cwd = process.cwd(), api = new ApiClient()) {
|
|
|
432
1063
|
return;
|
|
433
1064
|
}
|
|
434
1065
|
ok("Project detected");
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
ok("SDK installed", project.sdkVersion);
|
|
441
|
-
if (!project.config.projectId || !project.config.publicKey) {
|
|
442
|
-
fail("Configuration found", "set ASIIYST_PROJECT_ID and ASIIYST_PUBLIC_KEY");
|
|
1066
|
+
const stored = await loadConnection(cwd);
|
|
1067
|
+
if (!stored) {
|
|
1068
|
+
fail("Configuration found", "run asiyst connect");
|
|
443
1069
|
process.exitCode = 1;
|
|
444
1070
|
return;
|
|
445
1071
|
}
|
|
446
1072
|
ok("Configuration found");
|
|
447
1073
|
try {
|
|
448
|
-
const results = await
|
|
449
|
-
if (!
|
|
1074
|
+
const results = await verifyInstallation(createApiClient(), stored.projectId, stored.publicKey || "", process.env.ASIIYST_DOMAIN);
|
|
1075
|
+
if (!printVerificationSafe(results)) process.exitCode = 1;
|
|
450
1076
|
} catch (error) {
|
|
451
1077
|
fail("API reachable", error instanceof Error ? error.message : "verification failed");
|
|
452
1078
|
process.exitCode = 1;
|
|
453
1079
|
}
|
|
454
1080
|
}
|
|
455
1081
|
|
|
1082
|
+
// src/config/version.ts
|
|
1083
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1084
|
+
import { fileURLToPath } from "url";
|
|
1085
|
+
function readCurrentVersion() {
|
|
1086
|
+
if ("1.1.1") return "1.1.1";
|
|
1087
|
+
try {
|
|
1088
|
+
const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1089
|
+
if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string") {
|
|
1090
|
+
return packageJson.version;
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
|
|
1096
|
+
if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string" && packageJson.name === "@asiyst/cli") {
|
|
1097
|
+
return packageJson.version;
|
|
1098
|
+
}
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
return "0.0.0";
|
|
1102
|
+
}
|
|
1103
|
+
|
|
456
1104
|
// src/commands/doctor.ts
|
|
457
|
-
async function doctorCommand(cwd = process.cwd(), api =
|
|
458
|
-
|
|
1105
|
+
async function doctorCommand(cwd = process.cwd(), api = createApiClient()) {
|
|
1106
|
+
ok("CLI version", readCurrentVersion());
|
|
1107
|
+
ok("Executable", process.argv[1] || "unknown");
|
|
1108
|
+
ok("API base", api.baseUrl || CLI_API_BASE_URL);
|
|
1109
|
+
ok("Verify endpoint", VERIFY_KEY_URL);
|
|
459
1110
|
ok("System", process.platform);
|
|
460
1111
|
ok("Node.js", process.version);
|
|
461
|
-
|
|
1112
|
+
const project = detectProject(cwd);
|
|
462
1113
|
project.packageJson ? ok("Project", cwd) : fail("Project", "package.json is missing");
|
|
463
1114
|
project.sdkVersion ? ok("SDK", project.sdkVersion) : fail("SDK", "not installed");
|
|
464
|
-
|
|
1115
|
+
const stored = await loadConnection(cwd);
|
|
1116
|
+
stored ? ok("Local credential", "present") : fail("Local credential", "not connected");
|
|
465
1117
|
try {
|
|
466
1118
|
await api.health();
|
|
467
|
-
ok("Asiyst API", "
|
|
1119
|
+
ok("Asiyst API", "health endpoint reachable");
|
|
468
1120
|
} catch (error) {
|
|
469
1121
|
fail("Asiyst API", error instanceof Error ? error.message : "unreachable");
|
|
470
1122
|
}
|
|
471
|
-
if (project.config.projectId && project.config.publicKey) {
|
|
472
|
-
try {
|
|
473
|
-
await api.projectInfo(project.config.projectId);
|
|
474
|
-
ok("Project connection");
|
|
475
|
-
} catch (error) {
|
|
476
|
-
fail("Project connection", error instanceof Error ? error.message : "unavailable");
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
1123
|
}
|
|
480
1124
|
|
|
481
1125
|
// src/commands/dashboard.ts
|
|
482
1126
|
async function dashboardCommand() {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const url = parsed.toString();
|
|
487
|
-
if (!await openBrowser(url)) console.log(`Open ${url}`);
|
|
1127
|
+
const url = ASIIYST_WEB_URL;
|
|
1128
|
+
if (!await openBrowser(url)) console.log(`Open this URL manually:
|
|
1129
|
+
${url}`);
|
|
488
1130
|
else console.log(`Opening ${url}`);
|
|
489
1131
|
}
|
|
490
1132
|
|
|
491
1133
|
// src/commands/avatar.ts
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
1134
|
+
function parseAvatarId(argv) {
|
|
1135
|
+
const index = argv.findIndex((value) => value === "--avatar-id");
|
|
1136
|
+
if (index >= 0) return argv[index + 1];
|
|
1137
|
+
const inline = argv.find((value) => value.startsWith("--avatar-id="));
|
|
1138
|
+
return inline?.slice("--avatar-id=".length);
|
|
1139
|
+
}
|
|
1140
|
+
async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(), suppliedAvatarId) {
|
|
1141
|
+
const stored = await loadConnection(cwd);
|
|
1142
|
+
if (!stored?.apiKey || !stored.userId || !stored.projectId) {
|
|
1143
|
+
console.log("No project is connected. Please run:\nasiyst connect");
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const avatarId = suppliedAvatarId ?? await readInput("Enter your Avatar ID: ");
|
|
1147
|
+
if (!avatarId || !isValidAvatarId(avatarId)) {
|
|
1148
|
+
console.log("Invalid Avatar ID. It must contain exactly 10 alphanumeric characters.");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
const result = await importAvatar(api, {
|
|
1153
|
+
userId: stored.userId,
|
|
1154
|
+
projectId: stored.projectId,
|
|
1155
|
+
apiKey: stored.apiKey,
|
|
1156
|
+
avatarId
|
|
1157
|
+
});
|
|
1158
|
+
await saveConnection(cwd, { ...stored, avatarId: result.avatarId });
|
|
1159
|
+
console.log(`\u2713 Avatar ${result.avatarId} imported into project ${result.projectId}.`);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
if (error instanceof ApiError) {
|
|
1162
|
+
if (error.code === "FORBIDDEN") console.log("\u2717 You do not have permission to import an avatar into this project.");
|
|
1163
|
+
else if (error.code === "NOT_FOUND") console.log("\u2717 Project or avatar was not found.");
|
|
1164
|
+
else if (error.code === "CONFLICT" || error.code === "AVATAR_ALREADY_IMPORTED") console.log("\u2717 This avatar is already imported into the project.");
|
|
1165
|
+
else console.log(`\u2717 ${error.message}`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
console.log("\u2717 Unable to import the avatar.");
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
async function avatarCommand(cwd = process.cwd(), argv = process.argv.slice(2)) {
|
|
1172
|
+
if (argv[0] === "import") {
|
|
1173
|
+
await avatarImportCommand(cwd, createApiClient(), parseAvatarId(argv));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const stored = await loadConnection(cwd);
|
|
1177
|
+
if (!stored) {
|
|
1178
|
+
console.log("Connect your project to Asiyst to view avatar information.");
|
|
1179
|
+
const url = ASIIYST_WEB_URL;
|
|
497
1180
|
if (await openBrowser(url)) console.log(`Opening ${url}`);
|
|
498
|
-
else console.log(`Open
|
|
1181
|
+
else console.log(`Open this URL manually:
|
|
1182
|
+
${url}`);
|
|
499
1183
|
return;
|
|
500
1184
|
}
|
|
501
|
-
|
|
502
|
-
console.log(`
|
|
503
|
-
|
|
504
|
-
Status ${info.avatarStatus || "Not configured"}
|
|
505
|
-
|
|
506
|
-
Configure your avatar in the Asiyst dashboard.`);
|
|
1185
|
+
console.log(`Avatar configuration is managed at ${ASIIYST_WEB_URL}.`);
|
|
1186
|
+
if (!await openBrowser(ASIIYST_WEB_URL)) console.log(`Open this URL manually:
|
|
1187
|
+
${ASIIYST_WEB_URL}`);
|
|
507
1188
|
}
|
|
508
1189
|
|
|
509
|
-
// src/
|
|
510
|
-
|
|
511
|
-
|
|
1190
|
+
// src/commands/interactive.ts
|
|
1191
|
+
function interactiveHelp() {
|
|
1192
|
+
console.log(`
|
|
1193
|
+
Asiyst CLI
|
|
512
1194
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
1195
|
+
Usage: asiyst [command]
|
|
1196
|
+
|
|
1197
|
+
Commands
|
|
1198
|
+
connect Connect this project to Asiyst
|
|
1199
|
+
avatar import Import an avatar into the connected project
|
|
1200
|
+
status Show connection status
|
|
1201
|
+
health Check Asiyst API connectivity
|
|
1202
|
+
disconnect Remove local connection information
|
|
1203
|
+
help Show available commands
|
|
1204
|
+
version Show CLI version
|
|
1205
|
+
|
|
1206
|
+
Also available
|
|
1207
|
+
trust Trust this project folder
|
|
1208
|
+
revoke-trust Revoke folder trust
|
|
1209
|
+
doctor Run diagnostics
|
|
1210
|
+
update Check for CLI updates
|
|
1211
|
+
|
|
1212
|
+
Examples
|
|
1213
|
+
asiyst connect
|
|
1214
|
+
asiyst status
|
|
1215
|
+
asiyst disconnect
|
|
1216
|
+
`);
|
|
1217
|
+
}
|
|
1218
|
+
async function interactiveHome() {
|
|
1219
|
+
homeStatus();
|
|
1220
|
+
for (; ; ) {
|
|
1221
|
+
const result = await selectOption("What would you like to do?", [
|
|
1222
|
+
{ label: "Connect", value: "connect" },
|
|
1223
|
+
{ label: "Status", value: "status" },
|
|
1224
|
+
{ label: "Help", value: "help" },
|
|
1225
|
+
{ label: "Exit", value: "exit" }
|
|
1226
|
+
]);
|
|
1227
|
+
if (result.type !== "selected" || result.value === "exit") {
|
|
1228
|
+
break;
|
|
528
1229
|
}
|
|
1230
|
+
if (result.value === "connect") await connectCommand();
|
|
1231
|
+
else if (result.value === "status") await statusCommand();
|
|
1232
|
+
else if (result.value === "help") interactiveHelp();
|
|
1233
|
+
else if (result.value === "version") console.log(readCurrentVersion());
|
|
529
1234
|
}
|
|
530
|
-
return "0.2.0";
|
|
531
1235
|
}
|
|
532
1236
|
|
|
533
1237
|
// src/update/check.ts
|
|
1238
|
+
import { execFileSync } from "child_process";
|
|
1239
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
534
1240
|
var PACKAGE_NAME = "@asiyst/cli";
|
|
535
1241
|
var REGISTRY_URL = "https://registry.npmjs.org/%40asiyst%2Fcli/latest";
|
|
536
1242
|
var TIMEOUT_MS = 800;
|
|
@@ -602,7 +1308,7 @@ function detectInstallKind(executablePath = process.argv[1] ?? "", env = process
|
|
|
602
1308
|
}
|
|
603
1309
|
function installedVersion(packageRoot) {
|
|
604
1310
|
try {
|
|
605
|
-
const value = JSON.parse(
|
|
1311
|
+
const value = JSON.parse(readFileSync6(`${packageRoot}/package.json`, "utf8"));
|
|
606
1312
|
return value && typeof value === "object" && typeof value.version === "string" ? value.version : void 0;
|
|
607
1313
|
} catch {
|
|
608
1314
|
return void 0;
|
|
@@ -663,102 +1369,65 @@ ${error instanceof Error ? error.message : "Installation could not be verified."
|
|
|
663
1369
|
}
|
|
664
1370
|
}
|
|
665
1371
|
|
|
666
|
-
// src/commands/
|
|
667
|
-
function
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (/avatar|configure my avatar|create avatar/.test(input)) return "avatar";
|
|
674
|
-
if (/update|upgrade/.test(input)) return "update";
|
|
675
|
-
if (/^(help|\?)$/.test(input)) return "help";
|
|
676
|
-
if (/^(version|--version)$/.test(input)) return "version";
|
|
677
|
-
return input;
|
|
678
|
-
}
|
|
679
|
-
function interactiveHelp() {
|
|
680
|
-
console.log("\nASIYST CLI\n\nUsage: asiyst [command]\n\nCommands\n connect Connect this project to Asiyst\n status Show project and Asiyst status\n diagnostics Run diagnostics\n dashboard Open Asiyst dashboard\n avatar Configure avatar\n update Check for and install CLI updates\n help Show available commands\n version Show CLI version\n trust Trust this project folder\n revoke-trust Revoke folder trust\n logout Remove local CLI session information\n\nExamples\n asiyst connect\n asiyst status\n asiyst update");
|
|
681
|
-
}
|
|
682
|
-
async function interactiveHome() {
|
|
683
|
-
homeStatus(detectProject());
|
|
684
|
-
console.log("\nWelcome to Asiyst CLI.\nUse UP/DOWN to select a command, or type a command.\n");
|
|
685
|
-
const suggestions = [
|
|
686
|
-
{ label: "Connect this project", value: "connect" },
|
|
687
|
-
{ label: "Check project status", value: "status" },
|
|
688
|
-
{ label: "Run diagnostics", value: "diagnostics" },
|
|
689
|
-
{ label: "Open dashboard", value: "dashboard" },
|
|
690
|
-
{ label: "Configure avatar", value: "avatar" },
|
|
691
|
-
{ label: "Update CLI", value: "update" },
|
|
692
|
-
{ label: "Help", value: "help" },
|
|
693
|
-
{ label: "Exit", value: "exit" }
|
|
694
|
-
];
|
|
695
|
-
for (; ; ) {
|
|
696
|
-
const result = await selectOption("What would you like to do?", suggestions, "\u203A ");
|
|
697
|
-
if (result.type === "exit") break;
|
|
698
|
-
if (result.type !== "selected") continue;
|
|
699
|
-
const command = resolveInput(result.input || String(result.value));
|
|
700
|
-
if (command === "exit" || command === "quit") {
|
|
701
|
-
const confirmed = await selectOption("Exit Asiyst?", [
|
|
702
|
-
{ label: "Yes", value: true },
|
|
703
|
-
{ label: "No", value: false }
|
|
704
|
-
]);
|
|
705
|
-
if (confirmed.type === "selected") {
|
|
706
|
-
if (confirmed.value) break;
|
|
707
|
-
}
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
if (command === "connect") await initCommand();
|
|
711
|
-
else if (command === "status") await statusCommand();
|
|
712
|
-
else if (command === "diagnostics") await doctorCommand();
|
|
713
|
-
else if (command === "dashboard") await dashboardCommand();
|
|
714
|
-
else if (command === "avatar") await avatarCommand();
|
|
715
|
-
else if (command === "update") await updateCommand();
|
|
716
|
-
else if (command === "logout") logoutCommand();
|
|
717
|
-
else if (command === "trust") await trustCommand();
|
|
718
|
-
else if (command === "revoke-trust") revokeTrustCommand();
|
|
719
|
-
else if (command === "help") interactiveHelp();
|
|
720
|
-
else if (command === "version") console.log(readCurrentVersion());
|
|
721
|
-
else console.log(`I don't recognize that command: ${result.input}
|
|
722
|
-
Run 'asiyst help' for available commands.`);
|
|
1372
|
+
// src/commands/health.ts
|
|
1373
|
+
async function healthCommand(api = createApiClient()) {
|
|
1374
|
+
try {
|
|
1375
|
+
await api.health();
|
|
1376
|
+
console.log("\u2713 Asiyst API is reachable.");
|
|
1377
|
+
} catch {
|
|
1378
|
+
console.log("\u2717 Unable to reach Asiyst API.");
|
|
723
1379
|
}
|
|
724
1380
|
}
|
|
725
1381
|
|
|
726
1382
|
// src/index.ts
|
|
727
1383
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1384
|
+
import { realpathSync } from "fs";
|
|
728
1385
|
var CLI_VERSION = readCurrentVersion();
|
|
729
1386
|
function help() {
|
|
730
1387
|
interactiveHelp();
|
|
731
1388
|
}
|
|
732
1389
|
async function main(argv = process.argv.slice(2)) {
|
|
733
1390
|
const command = argv[0] || "";
|
|
1391
|
+
const projectId = parseProjectIdArgument(argv);
|
|
734
1392
|
if (command !== "update" && !argv.includes("--version") && !argv.includes("-v") && !argv.includes("--help") && !argv.includes("-h")) {
|
|
735
1393
|
await notifyIfUpdateAvailable();
|
|
736
1394
|
}
|
|
737
1395
|
if (command === "--help" || command === "-h") return help();
|
|
738
1396
|
if (command === "--version" || command === "-v") return console.log(CLI_VERSION);
|
|
739
|
-
if (command === "init"
|
|
1397
|
+
if (command === "init") return connectCommand(process.cwd(), void 0, projectId);
|
|
1398
|
+
if (command === "connect") return connectCommand(process.cwd(), void 0, projectId);
|
|
740
1399
|
if (command === "login") return loginCommand();
|
|
741
1400
|
if (command === "logout") return logoutCommand();
|
|
1401
|
+
if (command === "disconnect") return disconnectCommand();
|
|
742
1402
|
if (command === "status") return statusCommand();
|
|
743
1403
|
if (command === "verify") return verifyCommand();
|
|
1404
|
+
if (command === "health") return healthCommand();
|
|
744
1405
|
if (command === "doctor") return doctorCommand();
|
|
745
1406
|
if (command === "diagnostics") return doctorCommand();
|
|
746
1407
|
if (command === "dashboard") return dashboardCommand();
|
|
747
|
-
if (command === "avatar") return avatarCommand();
|
|
1408
|
+
if (command === "avatar") return avatarCommand(process.cwd(), argv.slice(1));
|
|
748
1409
|
if (command === "trust") return trustCommand();
|
|
749
1410
|
if (command === "revoke-trust") return revokeTrustCommand();
|
|
750
1411
|
if (command === "update") return updateCommand();
|
|
751
1412
|
if (command === "help") return help();
|
|
752
1413
|
if (command === "version") return console.log(CLI_VERSION);
|
|
753
1414
|
if (!process.stdin.isTTY) {
|
|
754
|
-
|
|
755
|
-
projectChecks(project);
|
|
756
|
-
console.log("\nRun `npx @asiyst/cli connect` to connect this project, or `npx @asiyst/cli --help` for commands.");
|
|
1415
|
+
help();
|
|
757
1416
|
return;
|
|
758
1417
|
}
|
|
759
1418
|
return interactiveHome();
|
|
760
1419
|
}
|
|
761
|
-
|
|
1420
|
+
function isDirectExecution() {
|
|
1421
|
+
if (!process.argv[1]) return false;
|
|
1422
|
+
try {
|
|
1423
|
+
const entryPath = realpathSync(fileURLToPath2(import.meta.url));
|
|
1424
|
+
const execPath = realpathSync(process.argv[1]);
|
|
1425
|
+
return entryPath.toLowerCase() === execPath.toLowerCase();
|
|
1426
|
+
} catch {
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (isDirectExecution()) {
|
|
762
1431
|
main().catch((error) => {
|
|
763
1432
|
console.error(`
|
|
764
1433
|
Error: ${error instanceof Error ? error.message : "command failed"}`);
|
|
@@ -767,6 +1436,7 @@ Error: ${error instanceof Error ? error.message : "command failed"}`);
|
|
|
767
1436
|
}
|
|
768
1437
|
export {
|
|
769
1438
|
CLI_VERSION,
|
|
1439
|
+
connectCommand as initCommand,
|
|
770
1440
|
main
|
|
771
1441
|
};
|
|
772
1442
|
//# sourceMappingURL=index.js.map
|