@asiyst/cli 1.0.9 → 1.1.2
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 +19 -4
- package/dist/index.js +1800 -452
- package/dist/index.js.map +1 -1
- package/package.json +18 -2
package/dist/index.js
CHANGED
|
@@ -1,46 +1,486 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
3
|
+
// src/config/ids.ts
|
|
4
|
+
var PROJECT_ID_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]{23}$/;
|
|
5
|
+
var API_KEY_PATTERN = /^[A-Za-z0-9_-]{32}$/;
|
|
6
|
+
var USER_ID_PATTERN = /^[A-Za-z0-9-]{16}$/;
|
|
7
|
+
var AVATAR_ID_PATTERN = /^[A-Za-z0-9]{10}$/;
|
|
8
|
+
function isValidProjectId(value) {
|
|
9
|
+
return typeof value === "string" && PROJECT_ID_PATTERN.test(value.trim());
|
|
10
|
+
}
|
|
11
|
+
function isValidApiKey(value) {
|
|
12
|
+
return typeof value === "string" && API_KEY_PATTERN.test(value.trim());
|
|
13
|
+
}
|
|
14
|
+
function isValidUserId(value) {
|
|
15
|
+
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value);
|
|
16
|
+
}
|
|
17
|
+
function isValidAvatarId(value) {
|
|
18
|
+
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && AVATAR_ID_PATTERN.test(value.trim());
|
|
19
|
+
}
|
|
20
|
+
function parseProjectIdArgument(argv = []) {
|
|
21
|
+
const length = argv.length;
|
|
22
|
+
for (let index = 0; index < length; index += 1) {
|
|
23
|
+
const entry = argv[index];
|
|
24
|
+
if (entry === "--project-id" || entry === "--projectId") {
|
|
25
|
+
const next = argv[index + 1];
|
|
26
|
+
return typeof next === "string" ? next : "";
|
|
27
|
+
}
|
|
28
|
+
if (entry.startsWith("--project-id=") || entry.startsWith("--projectId=")) {
|
|
29
|
+
return entry.slice(entry.indexOf("=") + 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/api/errors.ts
|
|
36
|
+
var ApiError = class extends Error {
|
|
37
|
+
constructor(message, status, code = "UNKNOWN") {
|
|
38
|
+
super(message);
|
|
39
|
+
this.status = status;
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.name = "ApiError";
|
|
42
|
+
}
|
|
43
|
+
status;
|
|
44
|
+
code;
|
|
45
|
+
};
|
|
46
|
+
function errorCodeFromStatus(status, bodyCode) {
|
|
47
|
+
if (bodyCode === "INVALID_API_KEY") return "INVALID_API_KEY";
|
|
48
|
+
if (bodyCode === "API_KEY_REVOKED") return "API_KEY_REVOKED";
|
|
49
|
+
if (bodyCode === "FORBIDDEN") return "FORBIDDEN";
|
|
50
|
+
if (bodyCode === "USER_MISMATCH") return "USER_MISMATCH";
|
|
51
|
+
if (bodyCode === "PROJECT_MISMATCH") return "PROJECT_MISMATCH";
|
|
52
|
+
if (bodyCode === "PROJECT_NOT_FOUND") return "PROJECT_NOT_FOUND";
|
|
53
|
+
if (bodyCode === "AVATAR_NOT_FOUND") return "AVATAR_NOT_FOUND";
|
|
54
|
+
if (bodyCode === "AVATAR_ALREADY_IMPORTED") return "AVATAR_ALREADY_IMPORTED";
|
|
55
|
+
if (bodyCode === "RATE_LIMITED") return "RATE_LIMITED";
|
|
56
|
+
if (bodyCode === "INTERNAL_ERROR") return "INTERNAL_ERROR";
|
|
57
|
+
if (status === 401) return "INVALID_API_KEY";
|
|
58
|
+
if (status === 403) return "FORBIDDEN";
|
|
59
|
+
if (status === 404) return "NOT_FOUND";
|
|
60
|
+
if (status === 409) return "CONFLICT";
|
|
61
|
+
if (status === 400 || status === 422) return "INVALID_REQUEST";
|
|
62
|
+
if (status === 429) return "RATE_LIMITED";
|
|
63
|
+
if (status >= 500) return "INTERNAL_ERROR";
|
|
64
|
+
return "UNKNOWN";
|
|
65
|
+
}
|
|
66
|
+
function friendlyApiMessage(error, endpointUrl) {
|
|
67
|
+
if (error.code === "INVALID_API_KEY") return "\u2717 Invalid API key.";
|
|
68
|
+
if (error.code === "API_KEY_REVOKED") {
|
|
69
|
+
return "\u2717 This API key has been revoked.\nCreate a new key from:\nhttps://asiyst.com";
|
|
70
|
+
}
|
|
71
|
+
if (error.code === "FORBIDDEN") {
|
|
72
|
+
return "\u2717 This API key does not have permission to access this resource.";
|
|
73
|
+
}
|
|
74
|
+
if (error.code === "USER_MISMATCH") return "\u2717 The supplied identifiers do not belong to the verified user.";
|
|
75
|
+
if (error.code === "PROJECT_MISMATCH") return "\u2717 The supplied identifiers do not belong to the verified project.";
|
|
76
|
+
if (error.code === "NOT_FOUND") {
|
|
77
|
+
return endpointUrl ? `\u2717 Asiyst API endpoint was not found.
|
|
78
|
+
Verify that the CLI is using:
|
|
79
|
+
${endpointUrl}` : "\u2717 Asiyst API endpoint was not found.";
|
|
80
|
+
}
|
|
81
|
+
if (error.code === "TIMEOUT") return "Connection to Asiyst timed out.";
|
|
82
|
+
if (error.code === "NETWORK") return "\u2717 Unable to reach Asiyst API.";
|
|
83
|
+
if (error.code === "MALFORMED_RESPONSE") return "Received an unexpected response from Asiyst.";
|
|
84
|
+
if (error.code === "RATE_LIMITED") return "\u2717 Too many requests. Try again shortly.";
|
|
85
|
+
if (error.code === "PROJECT_NOT_FOUND") return "\u2717 Authorized project was not found.";
|
|
86
|
+
if (error.code === "CONFLICT") return "\u2717 The request conflicts with the current project state.";
|
|
87
|
+
if (error.code === "INVALID_REQUEST") return "\u2717 The request was invalid.";
|
|
88
|
+
if (error.code === "AVATAR_NOT_FOUND") return "\u2717 Avatar was not found.";
|
|
89
|
+
if (error.code === "AVATAR_ALREADY_IMPORTED") return "\u2717 This avatar is already imported into the project.";
|
|
90
|
+
if (error.code === "INTERNAL_ERROR") return "\u2717 Asiyst is temporarily unavailable.";
|
|
91
|
+
return "\u2717 Asiyst request failed.";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/api/verification.ts
|
|
95
|
+
function record(value) {
|
|
96
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
97
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
98
|
+
}
|
|
99
|
+
const root = value;
|
|
100
|
+
const data = root.data && typeof root.data === "object" && !Array.isArray(root.data) ? root.data : root;
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
function stringValue(body, ...keys) {
|
|
104
|
+
for (const key of keys) {
|
|
105
|
+
if (typeof body[key] === "string" && body[key].trim()) return body[key].trim();
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
function nestedStringValue(body, containers, ...keys) {
|
|
110
|
+
const direct = stringValue(body, ...keys);
|
|
111
|
+
if (direct) return direct;
|
|
112
|
+
for (const container of containers) {
|
|
113
|
+
const nested = body[container];
|
|
114
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
115
|
+
const value = stringValue(nested, ...keys);
|
|
116
|
+
if (value) return value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
function assertValid(value, name, validator) {
|
|
123
|
+
const trimmed = value.trim();
|
|
124
|
+
if (!validator(trimmed)) {
|
|
125
|
+
const code = name === "User ID" ? "INVALID_USER_ID" : name === "Project ID" ? "INVALID_PROJECT_ID" : name === "Avatar ID" ? "INVALID_AVATAR_ID" : "INVALID_API_KEY";
|
|
126
|
+
throw new ApiError(`Invalid ${name}.`, 400, code);
|
|
127
|
+
}
|
|
128
|
+
return trimmed;
|
|
129
|
+
}
|
|
130
|
+
function assertVerified(body, message) {
|
|
131
|
+
if (body.valid === false || body.verified === false || body.success === false) {
|
|
132
|
+
throw new ApiError(stringValue(body, "message", "error") ?? message, 400, "FORBIDDEN");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function verifyUser(api, userId) {
|
|
136
|
+
const verifiedUserId = assertValid(userId, "User ID", isValidUserId);
|
|
137
|
+
const body = record(await api.request("/verify/user", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
body: JSON.stringify({ userId: verifiedUserId })
|
|
140
|
+
}));
|
|
141
|
+
assertVerified(body, "User ID verification failed.");
|
|
142
|
+
const returned = nestedStringValue(body, ["user"], "userId", "user_id", "id");
|
|
143
|
+
if (returned !== verifiedUserId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
|
|
144
|
+
return { userId: verifiedUserId, userName: stringValue(body, "userName", "name") };
|
|
145
|
+
}
|
|
146
|
+
async function verifyProject(api, userId, projectId) {
|
|
147
|
+
const verifiedUserId = assertValid(userId, "User ID", isValidUserId);
|
|
148
|
+
const verifiedProjectId = assertValid(projectId, "Project ID", isValidProjectId);
|
|
149
|
+
const body = record(await api.request("/verify/project", {
|
|
150
|
+
method: "POST",
|
|
151
|
+
body: JSON.stringify({ userId: verifiedUserId, projectId: verifiedProjectId })
|
|
152
|
+
}));
|
|
153
|
+
assertVerified(body, "Project verification failed.");
|
|
154
|
+
const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
|
|
155
|
+
const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
|
|
156
|
+
if (returnedProjectId && returnedProjectId !== verifiedProjectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
|
|
157
|
+
if (returnedUserId && returnedUserId !== verifiedUserId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
|
|
158
|
+
return {
|
|
159
|
+
userId: verifiedUserId,
|
|
160
|
+
projectId: verifiedProjectId,
|
|
161
|
+
projectName: nestedStringValue(body, ["project"], "projectName", "project_name", "name"),
|
|
162
|
+
website: nestedStringValue(body, ["project"], "website", "websiteUrl", "website_url", "domain"),
|
|
163
|
+
publicKey: nestedStringValue(body, ["project"], "publicKey", "public_key", "publishableKey")
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function verifyApiKeyRelationship(api, input) {
|
|
167
|
+
const userId = assertValid(input.userId, "User ID", isValidUserId);
|
|
168
|
+
const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
|
|
169
|
+
const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
|
|
170
|
+
const body = record(await api.request("/verify/api-key", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
|
|
173
|
+
body: JSON.stringify({ userId, projectId, apiKey })
|
|
174
|
+
}));
|
|
175
|
+
assertVerified(body, "API key verification failed.");
|
|
176
|
+
const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
|
|
177
|
+
const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
|
|
178
|
+
if (returnedUserId && returnedUserId !== userId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
|
|
179
|
+
if (returnedProjectId && returnedProjectId !== projectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
|
|
180
|
+
return {
|
|
181
|
+
apiKey,
|
|
182
|
+
userId,
|
|
183
|
+
projectId,
|
|
184
|
+
projectName: nestedStringValue(body, ["project"], "projectName", "project_name", "name"),
|
|
185
|
+
website: nestedStringValue(body, ["project"], "website", "websiteUrl", "website_url", "domain"),
|
|
186
|
+
publicKey: nestedStringValue(body, ["project"], "publicKey", "public_key", "publishableKey")
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function verifyAvatar(api, input) {
|
|
190
|
+
const userId = assertValid(input.userId, "User ID", isValidUserId);
|
|
191
|
+
const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
|
|
192
|
+
const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
|
|
193
|
+
const avatarId = assertValid(input.avatarId, "Avatar ID", isValidAvatarId);
|
|
194
|
+
const body = record(await api.request("/verify/avatar", {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
|
|
197
|
+
body: JSON.stringify({ userId, projectId, apiKey, avatarId })
|
|
198
|
+
}));
|
|
199
|
+
assertVerified(body, "Avatar verification failed.");
|
|
200
|
+
const returnedAvatarId = nestedStringValue(body, ["avatar"], "avatarId", "avatar_id", "id");
|
|
201
|
+
const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
|
|
202
|
+
const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
|
|
203
|
+
if (returnedAvatarId && returnedAvatarId !== avatarId) throw new ApiError("The API returned a different Avatar ID.", 200, "AVATAR_MISMATCH");
|
|
204
|
+
if (returnedUserId && returnedUserId !== userId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
|
|
205
|
+
if (returnedProjectId && returnedProjectId !== projectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
|
|
206
|
+
return { userId, projectId, avatarId, avatarName: nestedStringValue(body, ["avatar"], "avatarName", "avatar_name", "name") };
|
|
207
|
+
}
|
|
208
|
+
async function createImportSession(api, input) {
|
|
209
|
+
const userId = assertValid(input.userId, "User ID", isValidUserId);
|
|
210
|
+
const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
|
|
211
|
+
const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
|
|
212
|
+
const avatarId = assertValid(input.avatarId, "Avatar ID", isValidAvatarId);
|
|
213
|
+
const body = record(await api.request("/import-session", {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
|
|
216
|
+
body: JSON.stringify({ userId, projectId, apiKey, avatarId })
|
|
217
|
+
}));
|
|
218
|
+
const sessionId = stringValue(body, "sessionId", "session_id", "id");
|
|
219
|
+
if (!sessionId) throw new ApiError("The API did not return an import session.", 200, "MALFORMED_RESPONSE");
|
|
220
|
+
return { sessionId, expiresAt: stringValue(body, "expiresAt", "expires_at") };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/browser/open.ts
|
|
224
|
+
import { execFile } from "child_process";
|
|
225
|
+
import { promisify } from "util";
|
|
226
|
+
var execFileAsync = promisify(execFile);
|
|
227
|
+
async function openBrowser(url) {
|
|
228
|
+
const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
229
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
230
|
+
try {
|
|
231
|
+
await execFileAsync(command, args);
|
|
232
|
+
return true;
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/config/api.ts
|
|
239
|
+
var PRODUCTION_API_ORIGIN = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
|
|
240
|
+
var CLI_API_BASE_URL = PRODUCTION_API_ORIGIN;
|
|
241
|
+
var VERIFY_KEY_PATH = "/auth/api-key/verify";
|
|
242
|
+
var VERIFY_KEY_URL = `${CLI_API_BASE_URL}${VERIFY_KEY_PATH}`;
|
|
243
|
+
var ASIYST_WEB_URL = "https://asiyst.com";
|
|
244
|
+
var ASIIYST_WEB_URL = ASIYST_WEB_URL;
|
|
245
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
246
|
+
function readEnv(env, ...keys) {
|
|
247
|
+
for (const key of keys) {
|
|
248
|
+
const value = env[key]?.trim();
|
|
249
|
+
if (value) return value;
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
function isLocalUrl(value) {
|
|
254
|
+
try {
|
|
255
|
+
const url = new URL(value);
|
|
256
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
257
|
+
} catch {
|
|
258
|
+
return /localhost|127\.0\.0\.1|::1/i.test(value);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function resolveApiBaseUrl(env = process.env) {
|
|
262
|
+
const explicit = readEnv(env, "ASIYST_API_URL", "ASIIYST_API_URL");
|
|
263
|
+
const development = readEnv(env, "ASIYST_API_MODE", "ASIIYST_API_MODE") === "development";
|
|
264
|
+
if (explicit) {
|
|
265
|
+
if (isLocalUrl(explicit)) {
|
|
266
|
+
return CLI_API_BASE_URL;
|
|
267
|
+
}
|
|
268
|
+
return explicit.replace(/\/+$/, "");
|
|
269
|
+
}
|
|
270
|
+
if (development) {
|
|
271
|
+
return CLI_API_BASE_URL;
|
|
272
|
+
}
|
|
273
|
+
return CLI_API_BASE_URL;
|
|
274
|
+
}
|
|
275
|
+
function isDebugEnabled(env = process.env) {
|
|
276
|
+
return readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "1" || readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "true";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/config/credentials.ts
|
|
280
|
+
import { execFile as execFile2 } from "child_process";
|
|
281
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
282
|
+
import { homedir } from "os";
|
|
283
|
+
import { join, resolve } from "path";
|
|
284
|
+
import { promisify as promisify2 } from "util";
|
|
285
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
286
|
+
function configDir() {
|
|
287
|
+
if (process.platform === "win32") return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "asiyst");
|
|
288
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support", "asiyst");
|
|
289
|
+
return join(homedir(), ".config", "asiyst");
|
|
290
|
+
}
|
|
291
|
+
function storePath() {
|
|
292
|
+
return join(configDir(), "credentials.json");
|
|
293
|
+
}
|
|
294
|
+
function accountFor(cwd) {
|
|
295
|
+
return resolve(cwd);
|
|
296
|
+
}
|
|
297
|
+
function readStore() {
|
|
298
|
+
const path = storePath();
|
|
299
|
+
if (!existsSync(path)) return {};
|
|
300
|
+
try {
|
|
301
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
302
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
303
|
+
return parsed;
|
|
304
|
+
} catch {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function writeStore(data) {
|
|
309
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
310
|
+
writeFileSync(storePath(), JSON.stringify(data, null, 2), { encoding: "utf8", mode: 384 });
|
|
311
|
+
}
|
|
312
|
+
async function dpapiProtect(plaintext) {
|
|
313
|
+
if (process.platform !== "win32") return void 0;
|
|
314
|
+
const script = [
|
|
315
|
+
"Add-Type -AssemblyName System.Security",
|
|
316
|
+
"$bytes = [System.Text.Encoding]::UTF8.GetBytes($env:ASIYST_DPAPI_PAYLOAD)",
|
|
317
|
+
"$protected = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
318
|
+
"[Convert]::ToBase64String($protected)"
|
|
319
|
+
].join("; ");
|
|
320
|
+
try {
|
|
321
|
+
const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
322
|
+
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: plaintext },
|
|
323
|
+
windowsHide: true
|
|
324
|
+
});
|
|
325
|
+
const value = stdout5.trim();
|
|
326
|
+
return value || void 0;
|
|
327
|
+
} catch {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function dpapiUnprotect(payload) {
|
|
332
|
+
if (process.platform !== "win32") return void 0;
|
|
333
|
+
const script = [
|
|
334
|
+
"Add-Type -AssemblyName System.Security",
|
|
335
|
+
"$protected = [Convert]::FromBase64String($env:ASIYST_DPAPI_PAYLOAD)",
|
|
336
|
+
"$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($protected, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
337
|
+
"[System.Text.Encoding]::UTF8.GetString($bytes)"
|
|
338
|
+
].join("; ");
|
|
339
|
+
try {
|
|
340
|
+
const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
341
|
+
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: payload },
|
|
342
|
+
windowsHide: true
|
|
343
|
+
});
|
|
344
|
+
const value = stdout5.trim();
|
|
345
|
+
return value || void 0;
|
|
346
|
+
} catch {
|
|
347
|
+
return void 0;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function windowsBlobPath() {
|
|
351
|
+
return join(configDir(), "credentials.dpapi");
|
|
352
|
+
}
|
|
353
|
+
async function saveConnection(cwd, connection) {
|
|
354
|
+
const account = accountFor(cwd);
|
|
355
|
+
const next = { ...readStore(), [account]: connection };
|
|
356
|
+
if (process.platform === "win32") {
|
|
357
|
+
const protectedBlob = await dpapiProtect(JSON.stringify(next));
|
|
358
|
+
if (protectedBlob) {
|
|
359
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
360
|
+
writeFileSync(windowsBlobPath(), protectedBlob, { encoding: "utf8", mode: 384 });
|
|
361
|
+
if (existsSync(storePath())) unlinkSync(storePath());
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
writeStore(next);
|
|
366
|
+
}
|
|
367
|
+
async function loadConnection(cwd) {
|
|
368
|
+
const account = accountFor(cwd);
|
|
369
|
+
if (process.platform === "win32" && existsSync(windowsBlobPath())) {
|
|
370
|
+
const decrypted = await dpapiUnprotect(readFileSync(windowsBlobPath(), "utf8"));
|
|
371
|
+
if (decrypted) {
|
|
372
|
+
try {
|
|
373
|
+
const parsed = JSON.parse(decrypted);
|
|
374
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
375
|
+
const entry2 = parsed[account];
|
|
376
|
+
if (entry2 && typeof entry2.apiKey === "string" && typeof entry2.projectId === "string") return entry2;
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const entry = readStore()[account];
|
|
384
|
+
if (entry && typeof entry.apiKey === "string" && typeof entry.projectId === "string") return entry;
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
387
|
+
async function clearConnection(cwd) {
|
|
388
|
+
const account = accountFor(cwd);
|
|
389
|
+
if (process.platform === "win32" && existsSync(windowsBlobPath())) {
|
|
390
|
+
const decrypted = await dpapiUnprotect(readFileSync(windowsBlobPath(), "utf8"));
|
|
391
|
+
let next2 = {};
|
|
392
|
+
if (decrypted) {
|
|
393
|
+
try {
|
|
394
|
+
const parsed = JSON.parse(decrypted);
|
|
395
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) next2 = { ...parsed };
|
|
396
|
+
} catch {
|
|
397
|
+
next2 = {};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
delete next2[account];
|
|
401
|
+
if (Object.keys(next2).length === 0) {
|
|
402
|
+
unlinkSync(windowsBlobPath());
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const protectedBlob = await dpapiProtect(JSON.stringify(next2));
|
|
406
|
+
if (protectedBlob) {
|
|
407
|
+
writeFileSync(windowsBlobPath(), protectedBlob, { encoding: "utf8", mode: 384 });
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const next = readStore();
|
|
412
|
+
delete next[account];
|
|
413
|
+
if (Object.keys(next).length === 0 && existsSync(storePath())) unlinkSync(storePath());
|
|
414
|
+
else writeStore(next);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/config/project.ts
|
|
418
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
419
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
420
|
+
var PROJECT_CONFIG_DIR = ".asiyst";
|
|
421
|
+
var PROJECT_CONFIG_FILE = "config.json";
|
|
422
|
+
function projectConfigPath(cwd) {
|
|
423
|
+
return join2(resolve2(cwd), PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
424
|
+
}
|
|
425
|
+
function readProjectMetadata(cwd) {
|
|
426
|
+
const path = projectConfigPath(cwd);
|
|
427
|
+
if (!existsSync2(path)) return void 0;
|
|
428
|
+
try {
|
|
429
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
430
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
431
|
+
const body = parsed;
|
|
432
|
+
if (typeof body.apiKey === "string") delete body.apiKey;
|
|
433
|
+
return {
|
|
434
|
+
projectId: typeof body.projectId === "string" ? body.projectId : void 0,
|
|
435
|
+
projectName: typeof body.projectName === "string" ? body.projectName : void 0,
|
|
436
|
+
website: typeof body.website === "string" ? body.website : void 0,
|
|
437
|
+
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0,
|
|
438
|
+
userId: typeof body.userId === "string" ? body.userId : void 0,
|
|
439
|
+
avatarId: typeof body.avatarId === "string" ? body.avatarId : void 0,
|
|
440
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
|
|
441
|
+
connected: body.connected === true
|
|
442
|
+
};
|
|
443
|
+
} catch {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function writeProjectMetadata(cwd, connection) {
|
|
448
|
+
const dir = join2(resolve2(cwd), PROJECT_CONFIG_DIR);
|
|
449
|
+
mkdirSync2(dir, { recursive: true });
|
|
450
|
+
const existing = readProjectMetadata(cwd) ?? {};
|
|
451
|
+
const next = {
|
|
452
|
+
...existing,
|
|
453
|
+
projectId: connection.projectId,
|
|
454
|
+
projectName: connection.projectName,
|
|
455
|
+
website: connection.website,
|
|
456
|
+
publicKey: connection.publicKey,
|
|
457
|
+
userId: connection.userId,
|
|
458
|
+
avatarId: connection.avatarId,
|
|
459
|
+
avatarName: connection.avatarName,
|
|
460
|
+
connected: true
|
|
461
|
+
};
|
|
462
|
+
writeFileSync2(projectConfigPath(cwd), `${JSON.stringify(next, null, 2)}
|
|
463
|
+
`, { encoding: "utf8" });
|
|
464
|
+
}
|
|
465
|
+
function clearProjectMetadata(cwd) {
|
|
466
|
+
const path = projectConfigPath(cwd);
|
|
467
|
+
if (!existsSync2(path)) return;
|
|
468
|
+
const existing = readProjectMetadata(cwd) ?? {};
|
|
469
|
+
const next = {
|
|
470
|
+
...existing,
|
|
471
|
+
connected: false
|
|
472
|
+
};
|
|
473
|
+
writeFileSync2(path, `${JSON.stringify(next, null, 2)}
|
|
474
|
+
`, { encoding: "utf8" });
|
|
35
475
|
}
|
|
36
476
|
|
|
37
477
|
// src/detection/project.ts
|
|
38
|
-
import { existsSync, readFileSync } from "fs";
|
|
39
|
-
import { resolve } from "path";
|
|
478
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
479
|
+
import { resolve as resolve3 } from "path";
|
|
40
480
|
function dependencyVersion(pkg) {
|
|
41
481
|
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
42
|
-
for (const
|
|
43
|
-
const values = pkg[
|
|
482
|
+
for (const section2 of sections) {
|
|
483
|
+
const values = pkg[section2];
|
|
44
484
|
if (values && typeof values === "object" && "@asiyst/sdk" in values) {
|
|
45
485
|
const version = values["@asiyst/sdk"];
|
|
46
486
|
return typeof version === "string" ? version : void 0;
|
|
@@ -48,30 +488,37 @@ function dependencyVersion(pkg) {
|
|
|
48
488
|
}
|
|
49
489
|
return void 0;
|
|
50
490
|
}
|
|
491
|
+
function hasDependency(deps, name) {
|
|
492
|
+
return typeof deps[name] === "string";
|
|
493
|
+
}
|
|
51
494
|
function detectFramework(cwd, pkg) {
|
|
52
495
|
const deps = Object.assign({}, pkg?.dependencies, pkg?.devDependencies);
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
if (
|
|
58
|
-
if (
|
|
59
|
-
|
|
496
|
+
if (hasDependency(deps, "next") || existsSync3(resolve3(cwd, "next.config.js")) || existsSync3(resolve3(cwd, "next.config.mjs")) || existsSync3(resolve3(cwd, "next.config.ts"))) {
|
|
497
|
+
return "Next.js";
|
|
498
|
+
}
|
|
499
|
+
if (hasDependency(deps, "nuxt")) return "Nuxt";
|
|
500
|
+
if (hasDependency(deps, "vue") || existsSync3(resolve3(cwd, "vue.config.js"))) return "Vue";
|
|
501
|
+
if (hasDependency(deps, "react")) return "React";
|
|
502
|
+
if (hasDependency(deps, "vite") || existsSync3(resolve3(cwd, "vite.config.ts")) || existsSync3(resolve3(cwd, "vite.config.js")) || existsSync3(resolve3(cwd, "vite.config.mjs"))) {
|
|
503
|
+
return "Vite";
|
|
504
|
+
}
|
|
505
|
+
if (pkg) return existsSync3(resolve3(cwd, "tsconfig.json")) ? "Vanilla TypeScript" : "Vanilla JavaScript";
|
|
506
|
+
return "Unknown";
|
|
60
507
|
}
|
|
61
508
|
function detectProject(cwd = process.cwd()) {
|
|
62
|
-
const path =
|
|
509
|
+
const path = resolve3(cwd, "package.json");
|
|
63
510
|
let packageJson = null;
|
|
64
|
-
if (
|
|
511
|
+
if (existsSync3(path)) {
|
|
65
512
|
try {
|
|
66
|
-
const value = JSON.parse(
|
|
513
|
+
const value = JSON.parse(readFileSync3(path, "utf8"));
|
|
67
514
|
if (value && typeof value === "object" && !Array.isArray(value)) packageJson = value;
|
|
68
515
|
} catch {
|
|
69
516
|
packageJson = null;
|
|
70
517
|
}
|
|
71
518
|
}
|
|
72
519
|
const env = process.env;
|
|
73
|
-
const packageManager =
|
|
74
|
-
const sourceFiles = ["tsconfig.json", "src", "app"].some((entry) =>
|
|
520
|
+
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";
|
|
521
|
+
const sourceFiles = ["tsconfig.json", "src", "app"].some((entry) => existsSync3(resolve3(cwd, entry)));
|
|
75
522
|
return {
|
|
76
523
|
cwd,
|
|
77
524
|
packageJson,
|
|
@@ -86,370 +533,709 @@ function detectProject(cwd = process.cwd()) {
|
|
|
86
533
|
};
|
|
87
534
|
}
|
|
88
535
|
|
|
89
|
-
// src/
|
|
90
|
-
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
status;
|
|
536
|
+
// src/ui/format.ts
|
|
537
|
+
import { stdout } from "process";
|
|
538
|
+
var supportsColor = Boolean(stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb");
|
|
539
|
+
var codes = {
|
|
540
|
+
reset: "\x1B[0m",
|
|
541
|
+
bold: "\x1B[1m",
|
|
542
|
+
dim: "\x1B[2m",
|
|
543
|
+
cyan: "\x1B[36m",
|
|
544
|
+
green: "\x1B[32m",
|
|
545
|
+
yellow: "\x1B[33m",
|
|
546
|
+
red: "\x1B[31m"
|
|
101
547
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
113
|
-
try {
|
|
114
|
-
response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
115
|
-
...init,
|
|
116
|
-
signal: init?.signal || controller.signal,
|
|
117
|
-
headers: { Accept: "application/json", "Content-Type": "application/json", ...init?.headers }
|
|
118
|
-
});
|
|
119
|
-
} catch (error) {
|
|
120
|
-
if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError") {
|
|
121
|
-
throw new ApiError(`Asiyst API request timed out after 10 seconds: ${this.baseUrl}${path}. Check your connection and try again.`);
|
|
122
|
-
}
|
|
123
|
-
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.`);
|
|
124
|
-
} finally {
|
|
125
|
-
clearTimeout(timeout);
|
|
126
|
-
}
|
|
127
|
-
const body = await response.json().catch(() => void 0);
|
|
128
|
-
if (!response.ok) {
|
|
129
|
-
const detail = response.status === 400 ? "The request data was invalid." : response.status === 401 ? "Authentication is required. Please connect your Asiyst account." : response.status === 403 ? "You are not authorized to perform this operation." : response.status === 404 ? `The API endpoint was not found: ${this.baseUrl}${path}. Please update Asiyst CLI or contact support.` : 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.";
|
|
130
|
-
throw new ApiError(`Asiyst API returned HTTP ${response.status}. ${detail}`, response.status);
|
|
131
|
-
}
|
|
132
|
-
if (body === void 0) {
|
|
133
|
-
throw new ApiError(`Asiyst API returned an invalid JSON response (HTTP ${response.status}).`, response.status);
|
|
134
|
-
}
|
|
135
|
-
return body;
|
|
136
|
-
}
|
|
137
|
-
health() {
|
|
138
|
-
return this.request("/health").then((body) => {
|
|
139
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
140
|
-
throw new ApiError("Asiyst API health endpoint returned an invalid response.");
|
|
141
|
-
}
|
|
142
|
-
return body;
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
createSession() {
|
|
146
|
-
return this.request("/cli/sessions", { method: "POST", body: JSON.stringify({}) }).then((value) => {
|
|
147
|
-
if (!value || typeof value !== "object") throw new ApiError("Asiyst returned an invalid session response");
|
|
148
|
-
const session = value;
|
|
149
|
-
if (typeof session.sessionId !== "string" || typeof session.connectUrl !== "string" || typeof session.expiresAt !== "string") {
|
|
150
|
-
throw new ApiError("Asiyst returned an invalid session response");
|
|
151
|
-
}
|
|
152
|
-
const url = new URL(session.connectUrl);
|
|
153
|
-
if (url.protocol !== "https:") {
|
|
154
|
-
throw new ApiError("Asiyst returned an insecure connection URL");
|
|
155
|
-
}
|
|
156
|
-
return { sessionId: session.sessionId, connectUrl: url.toString(), expiresAt: session.expiresAt };
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
sessionStatus(sessionId) {
|
|
160
|
-
return this.request(`/cli/sessions/${encodeURIComponent(sessionId)}/status`);
|
|
161
|
-
}
|
|
162
|
-
projectInfo(projectId) {
|
|
163
|
-
return this.request(`/cli/projects/${encodeURIComponent(projectId)}`);
|
|
164
|
-
}
|
|
165
|
-
verify(projectId, publicKey, domain) {
|
|
166
|
-
return this.request("/cli/verification", {
|
|
167
|
-
method: "POST",
|
|
168
|
-
body: JSON.stringify({ projectId, publicKey, domain })
|
|
169
|
-
}).then((value) => {
|
|
170
|
-
if (!Array.isArray(value) || !value.every((item) => item && typeof item === "object" && typeof item.name === "string" && typeof item.ok === "boolean")) {
|
|
171
|
-
throw new ApiError("Asiyst returned an invalid verification response");
|
|
172
|
-
}
|
|
173
|
-
return value;
|
|
174
|
-
});
|
|
175
|
-
}
|
|
548
|
+
function color(code, value) {
|
|
549
|
+
return supportsColor ? `${codes[code]}${value}${codes.reset}` : value;
|
|
550
|
+
}
|
|
551
|
+
var symbols = {
|
|
552
|
+
connected: "\u25CF",
|
|
553
|
+
disconnected: "\u25CB",
|
|
554
|
+
warning: "\u26A0",
|
|
555
|
+
error: "\u2715",
|
|
556
|
+
success: "\u2713",
|
|
557
|
+
pointer: "\u203A"
|
|
176
558
|
};
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return
|
|
559
|
+
function title(value) {
|
|
560
|
+
return color("bold", value);
|
|
561
|
+
}
|
|
562
|
+
function section(value) {
|
|
563
|
+
return color("cyan", value);
|
|
564
|
+
}
|
|
565
|
+
function muted(value) {
|
|
566
|
+
return color("dim", value);
|
|
567
|
+
}
|
|
568
|
+
function success(value) {
|
|
569
|
+
return color("green", value);
|
|
570
|
+
}
|
|
571
|
+
function warning(value) {
|
|
572
|
+
return color("yellow", value);
|
|
573
|
+
}
|
|
574
|
+
function maskSecret(value, visible = 0) {
|
|
575
|
+
if (visible <= 0) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
576
|
+
return `${value.slice(0, visible)}\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022`;
|
|
577
|
+
}
|
|
578
|
+
function printHeader(value, subtitle) {
|
|
579
|
+
console.log(`
|
|
580
|
+
${title(value)}`);
|
|
581
|
+
if (subtitle) console.log(muted(subtitle));
|
|
582
|
+
console.log();
|
|
188
583
|
}
|
|
189
584
|
|
|
190
|
-
// src/
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
585
|
+
// src/ui/output.ts
|
|
586
|
+
var ok = (label, detail = "") => console.log(`${success(symbols.success)} ${label}${detail ? ` (${detail})` : ""}`);
|
|
587
|
+
var fail = (label, detail = "") => console.log(`${symbols.error} ${label}${detail ? `: ${detail}` : ""}`);
|
|
588
|
+
function projectChecks(project) {
|
|
589
|
+
project.packageJson ? ok("Project detected", typeof project.packageJson.name === "string" ? project.packageJson.name : project.cwd) : fail("Project not detected", "package.json is missing");
|
|
590
|
+
if (project.framework === "Unknown") fail("Framework detected", "No supported project type was identified.");
|
|
591
|
+
else ok("Framework detected", project.framework);
|
|
592
|
+
ok("Language detected", project.language);
|
|
593
|
+
ok("Node.js detected", process.version);
|
|
594
|
+
ok("Package manager detected", project.packageManager);
|
|
595
|
+
project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : console.log(`${muted(symbols.disconnected)} @asiyst/sdk not installed. Install it with your package manager.`);
|
|
596
|
+
}
|
|
597
|
+
function printProjectSummary(project, connected, projectName, website) {
|
|
598
|
+
console.log(title("Asiyst CLI"));
|
|
599
|
+
console.log();
|
|
600
|
+
console.log(section("Project"));
|
|
601
|
+
console.log(` ${projectName || (typeof project.packageJson?.name === "string" ? project.packageJson.name : project.cwd)}`);
|
|
602
|
+
if (website) console.log(` ${muted(website)}`);
|
|
603
|
+
const marker = connected ? success(symbols.connected) : muted(symbols.disconnected);
|
|
604
|
+
console.log(` ${marker} ${connected ? "Connected" : "Not connected"}`);
|
|
605
|
+
console.log();
|
|
606
|
+
console.log(section("Environment"));
|
|
607
|
+
console.log(` ${project.framework} \xB7 ${project.language} \xB7 ${project.packageManager}`);
|
|
203
608
|
}
|
|
204
609
|
|
|
205
610
|
// src/ui/selector.ts
|
|
206
|
-
import { stdin, stdout } from "process";
|
|
611
|
+
import { stdin, stdout as stdout2 } from "process";
|
|
207
612
|
import { clearLine, cursorTo, emitKeypressEvents, moveCursor } from "readline";
|
|
208
|
-
var
|
|
209
|
-
var
|
|
613
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
614
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
210
615
|
function moveSelection(active, optionCount, direction) {
|
|
211
616
|
if (optionCount === 0) return 0;
|
|
212
617
|
return direction === "up" ? Math.max(0, active - 1) : Math.min(optionCount - 1, active + 1);
|
|
213
618
|
}
|
|
214
619
|
function clearSelectorFrame(output, previousLineCount) {
|
|
215
|
-
if (previousLineCount
|
|
216
|
-
|
|
620
|
+
if (previousLineCount <= 0) return;
|
|
621
|
+
cursorTo(output, 0);
|
|
622
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
623
|
+
moveCursor(output, 0, -1);
|
|
624
|
+
}
|
|
217
625
|
for (let line = 0; line < previousLineCount; line += 1) {
|
|
218
626
|
cursorTo(output, 0);
|
|
219
627
|
clearLine(output, 0);
|
|
220
|
-
if (line < previousLineCount - 1)
|
|
628
|
+
if (line < previousLineCount - 1) {
|
|
629
|
+
moveCursor(output, 0, 1);
|
|
630
|
+
}
|
|
221
631
|
}
|
|
222
632
|
cursorTo(output, 0);
|
|
633
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
634
|
+
moveCursor(output, 0, -1);
|
|
635
|
+
}
|
|
223
636
|
}
|
|
224
637
|
function renderSelectorFrame(output, previousLineCount, lines) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
638
|
+
if (lines.length === 0) return 0;
|
|
639
|
+
if (previousLineCount > 0) {
|
|
640
|
+
cursorTo(output, 0);
|
|
641
|
+
for (let i = 0; i < previousLineCount - 1; i += 1) {
|
|
642
|
+
moveCursor(output, 0, -1);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
646
|
+
cursorTo(output, 0);
|
|
647
|
+
clearLine(output, 0);
|
|
648
|
+
output.write(lines[i]);
|
|
649
|
+
if (i < lines.length - 1) {
|
|
650
|
+
output.write("\n");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
228
653
|
return lines.length;
|
|
229
654
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
655
|
+
function restoreTerminal(wasRaw) {
|
|
656
|
+
stdout2.write(SHOW_CURSOR);
|
|
657
|
+
if (stdin.isTTY) stdin.setRawMode?.(wasRaw);
|
|
658
|
+
}
|
|
659
|
+
function selectOption(title2, options) {
|
|
660
|
+
if (!stdin.isTTY || !stdout2.isTTY) return Promise.resolve({ type: "cancelled" });
|
|
661
|
+
return new Promise((resolve6) => {
|
|
234
662
|
let active = 0;
|
|
235
|
-
let visible = true;
|
|
236
663
|
let renderedLines = 0;
|
|
237
664
|
let settled = false;
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
};
|
|
242
|
-
const clear = () => {
|
|
243
|
-
clearSelectorFrame(stdout, renderedLines);
|
|
244
|
-
renderedLines = 0;
|
|
245
|
-
};
|
|
246
|
-
const render = () => {
|
|
247
|
-
clear();
|
|
248
|
-
const matches = filtered();
|
|
249
|
-
if (active >= matches.length) active = Math.max(0, matches.length - 1);
|
|
250
|
-
const lines = [`${title}`, `${prompt}${fit(input)}`];
|
|
251
|
-
if (visible) {
|
|
252
|
-
if (matches.length === 0) lines.push(" No matching commands.");
|
|
253
|
-
else lines.push(...matches.map((option, index) => `${index === active ? "\u276F" : " "} ${fit(option.label)}`));
|
|
254
|
-
lines.push("\u2191\u2193 Navigate Enter Select Esc Cancel");
|
|
255
|
-
}
|
|
256
|
-
renderedLines = renderSelectorFrame(stdout, renderedLines, lines);
|
|
257
|
-
};
|
|
258
|
-
const finish = (result) => {
|
|
665
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
666
|
+
const enabled = options.filter((option) => !option.disabled);
|
|
667
|
+
const finish = (result, confirmation) => {
|
|
259
668
|
if (settled) return;
|
|
260
669
|
settled = true;
|
|
261
670
|
stdin.off("keypress", onKeypress);
|
|
262
|
-
|
|
263
|
-
|
|
671
|
+
clearSelectorFrame(stdout2, renderedLines);
|
|
672
|
+
restoreTerminal(wasRaw);
|
|
264
673
|
stdin.pause();
|
|
265
|
-
|
|
266
|
-
|
|
674
|
+
if (confirmation) stdout2.write(`${confirmation}
|
|
675
|
+
`);
|
|
676
|
+
resolve6(result);
|
|
677
|
+
};
|
|
678
|
+
const render = () => {
|
|
679
|
+
const lines = [title2, "", ...enabled.map((option, index) => `${index === active ? "\u276F" : " "} ${option.label}`)];
|
|
680
|
+
renderedLines = renderSelectorFrame(stdout2, renderedLines, lines);
|
|
267
681
|
};
|
|
268
|
-
const onKeypress = (
|
|
269
|
-
if (key
|
|
270
|
-
if (key.ctrl && key.name === "
|
|
271
|
-
|
|
272
|
-
|
|
682
|
+
const onKeypress = (_value, key) => {
|
|
683
|
+
if (!key) return;
|
|
684
|
+
if (key.ctrl && key.name === "c") {
|
|
685
|
+
clearSelectorFrame(stdout2, renderedLines);
|
|
686
|
+
restoreTerminal(wasRaw);
|
|
687
|
+
stdin.pause();
|
|
688
|
+
stdout2.write("\n");
|
|
689
|
+
process.exit(130);
|
|
273
690
|
}
|
|
691
|
+
if (key.name === "escape") return finish({ type: "cancelled" });
|
|
274
692
|
if (key.name === "up" || key.name === "down") {
|
|
275
|
-
|
|
276
|
-
if (matches.length > 0) {
|
|
277
|
-
active = moveSelection(active, matches.length, key.name);
|
|
278
|
-
}
|
|
279
|
-
visible = true;
|
|
693
|
+
active = moveSelection(active, enabled.length, key.name);
|
|
280
694
|
return render();
|
|
281
695
|
}
|
|
282
696
|
if (key.name === "return" || key.name === "enter") {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return finish({ type: "cancelled", input });
|
|
287
|
-
}
|
|
288
|
-
if (key.name === "tab") {
|
|
289
|
-
const matches = filtered();
|
|
290
|
-
if (matches.length === 1) input = matches[0].value;
|
|
291
|
-
else if (matches.length > 1) active = (active + 1) % matches.length;
|
|
292
|
-
visible = true;
|
|
293
|
-
return render();
|
|
294
|
-
}
|
|
295
|
-
if (key.name === "backspace") {
|
|
296
|
-
input = input.slice(0, -1);
|
|
297
|
-
visible = true;
|
|
298
|
-
return render();
|
|
299
|
-
}
|
|
300
|
-
const sequence = key.sequence || value;
|
|
301
|
-
if (/^[\x20-\x7e]+$/.test(sequence)) {
|
|
302
|
-
input += sequence;
|
|
303
|
-
active = 0;
|
|
304
|
-
visible = true;
|
|
305
|
-
return render();
|
|
697
|
+
const selected = enabled[active];
|
|
698
|
+
if (!selected) return finish({ type: "cancelled" });
|
|
699
|
+
return finish({ type: "selected", value: selected.value }, `\u2713 ${selected.label} selected.`);
|
|
306
700
|
}
|
|
307
701
|
};
|
|
308
702
|
emitKeypressEvents(stdin);
|
|
309
703
|
stdin.setRawMode?.(true);
|
|
310
704
|
stdin.resume();
|
|
705
|
+
stdout2.write(HIDE_CURSOR);
|
|
311
706
|
stdin.on("keypress", onKeypress);
|
|
312
707
|
render();
|
|
313
708
|
});
|
|
314
709
|
}
|
|
315
710
|
|
|
316
|
-
// src/
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
711
|
+
// src/ui/secret.ts
|
|
712
|
+
import { stdin as stdin2, stdout as stdout3 } from "process";
|
|
713
|
+
import { emitKeypressEvents as emitKeypressEvents2 } from "readline";
|
|
714
|
+
async function readSecret(prompt) {
|
|
715
|
+
if (!stdin2.isTTY) return void 0;
|
|
716
|
+
return new Promise((resolve6) => {
|
|
717
|
+
let value = "";
|
|
718
|
+
let settled = false;
|
|
719
|
+
const wasRaw = Boolean(stdin2.isRaw);
|
|
720
|
+
stdout3.write(prompt);
|
|
721
|
+
const finish = (result) => {
|
|
722
|
+
if (settled) return;
|
|
723
|
+
settled = true;
|
|
724
|
+
stdin2.off("keypress", onKeypress);
|
|
725
|
+
if (stdin2.isTTY) stdin2.setRawMode?.(wasRaw);
|
|
726
|
+
stdin2.pause();
|
|
727
|
+
stdout3.write("\n");
|
|
728
|
+
resolve6(result);
|
|
729
|
+
};
|
|
730
|
+
const onKeypress = (chunk, key) => {
|
|
731
|
+
if (!key) return;
|
|
732
|
+
if (key.ctrl && key.name === "c") {
|
|
733
|
+
finish(void 0);
|
|
734
|
+
process.exit(130);
|
|
735
|
+
}
|
|
736
|
+
if (key.name === "escape") return finish(void 0);
|
|
737
|
+
if (key.name === "return" || key.name === "enter") return finish(value.trim());
|
|
738
|
+
if (key.name === "backspace") {
|
|
739
|
+
value = value.slice(0, -1);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const sequence = key.sequence || chunk;
|
|
743
|
+
if (sequence && /^[\x20-\x7e]+$/.test(sequence)) value += sequence;
|
|
744
|
+
};
|
|
745
|
+
emitKeypressEvents2(stdin2);
|
|
746
|
+
stdin2.setRawMode?.(true);
|
|
747
|
+
stdin2.resume();
|
|
748
|
+
stdin2.on("keypress", onKeypress);
|
|
749
|
+
});
|
|
333
750
|
}
|
|
334
|
-
async function
|
|
335
|
-
if (!
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
751
|
+
async function readInput(prompt) {
|
|
752
|
+
if (!stdin2.isTTY) return void 0;
|
|
753
|
+
return new Promise((resolve6) => {
|
|
754
|
+
let value = "";
|
|
755
|
+
const onData = (chunk) => {
|
|
756
|
+
value += String(chunk);
|
|
757
|
+
const newline = value.search(/[\r\n]/);
|
|
758
|
+
if (newline >= 0) {
|
|
759
|
+
stdin2.off("data", onData);
|
|
760
|
+
resolve6(value.slice(0, newline).trim());
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
stdout3.write(prompt);
|
|
764
|
+
stdin2.resume();
|
|
765
|
+
stdin2.on("data", onData);
|
|
766
|
+
});
|
|
341
767
|
}
|
|
342
768
|
|
|
343
769
|
// src/config/trust.ts
|
|
344
|
-
import { existsSync as
|
|
345
|
-
import { homedir } from "os";
|
|
346
|
-
import { join, resolve as
|
|
347
|
-
var trustFile =
|
|
770
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
771
|
+
import { homedir as homedir2 } from "os";
|
|
772
|
+
import { join as join3, resolve as resolve4 } from "path";
|
|
773
|
+
var trustFile = join3(homedir2(), ".config", "asiyst", "trusted-folders.json");
|
|
348
774
|
function readTrusted() {
|
|
349
|
-
if (!
|
|
775
|
+
if (!existsSync4(trustFile)) return [];
|
|
350
776
|
try {
|
|
351
|
-
const parsed = JSON.parse(
|
|
777
|
+
const parsed = JSON.parse(readFileSync4(trustFile, "utf8"));
|
|
352
778
|
return Array.isArray(parsed) && parsed.every((item) => typeof item === "string") ? parsed : [];
|
|
353
779
|
} catch {
|
|
354
780
|
return [];
|
|
355
781
|
}
|
|
356
782
|
}
|
|
357
783
|
function isTrusted(cwd) {
|
|
358
|
-
return readTrusted().includes(
|
|
784
|
+
return readTrusted().includes(resolve4(cwd));
|
|
359
785
|
}
|
|
360
786
|
function trustFolder(cwd) {
|
|
361
|
-
const folder =
|
|
787
|
+
const folder = resolve4(cwd);
|
|
362
788
|
const trusted = readTrusted();
|
|
363
789
|
if (!trusted.includes(folder)) trusted.push(folder);
|
|
364
|
-
|
|
365
|
-
|
|
790
|
+
mkdirSync3(join3(homedir2(), ".config", "asiyst"), { recursive: true, mode: 448 });
|
|
791
|
+
writeFileSync3(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
|
|
366
792
|
}
|
|
367
793
|
function revokeTrust(cwd) {
|
|
368
|
-
const folder =
|
|
794
|
+
const folder = resolve4(cwd);
|
|
369
795
|
const trusted = readTrusted().filter((item) => item !== folder);
|
|
370
|
-
if (
|
|
796
|
+
if (existsSync4(trustFile)) writeFileSync3(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/api/client.ts
|
|
800
|
+
function asRecord(value) {
|
|
801
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
802
|
+
}
|
|
803
|
+
function bodyErrorCode(body) {
|
|
804
|
+
const record2 = asRecord(body);
|
|
805
|
+
const code = record2?.code ?? record2?.errorCode ?? record2?.error ?? asRecord(record2?.error)?.code;
|
|
806
|
+
return typeof code === "string" ? code : void 0;
|
|
807
|
+
}
|
|
808
|
+
function bodyErrorMessage(body) {
|
|
809
|
+
const record2 = asRecord(body);
|
|
810
|
+
const message = record2?.message ?? asRecord(record2?.error)?.message;
|
|
811
|
+
return typeof message === "string" && message.trim() ? message.trim() : void 0;
|
|
812
|
+
}
|
|
813
|
+
function isAbortError(error) {
|
|
814
|
+
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
815
|
+
}
|
|
816
|
+
var ApiClient = class {
|
|
817
|
+
constructor(baseUrl = resolveApiBaseUrl(), fetcher = fetch) {
|
|
818
|
+
this.fetcher = fetcher;
|
|
819
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
820
|
+
}
|
|
821
|
+
fetcher;
|
|
822
|
+
baseUrl;
|
|
823
|
+
async request(path, init) {
|
|
824
|
+
const url = `${this.baseUrl}${path}`;
|
|
825
|
+
const controller = new AbortController();
|
|
826
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
827
|
+
const headers = new Headers(init?.headers);
|
|
828
|
+
headers.set("Accept", "application/json");
|
|
829
|
+
if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
830
|
+
let response;
|
|
831
|
+
try {
|
|
832
|
+
response = await this.fetcher(url, {
|
|
833
|
+
...init,
|
|
834
|
+
headers,
|
|
835
|
+
signal: init?.signal || controller.signal
|
|
836
|
+
});
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (isAbortError(error)) {
|
|
839
|
+
throw new ApiError("Connection to Asiyst timed out.", void 0, "TIMEOUT");
|
|
840
|
+
}
|
|
841
|
+
throw new ApiError("Unable to reach Asiyst API.", void 0, "NETWORK");
|
|
842
|
+
} finally {
|
|
843
|
+
clearTimeout(timeout);
|
|
844
|
+
}
|
|
845
|
+
const rawText = await response.text();
|
|
846
|
+
let body;
|
|
847
|
+
if (rawText) {
|
|
848
|
+
try {
|
|
849
|
+
body = JSON.parse(rawText);
|
|
850
|
+
} catch {
|
|
851
|
+
body = void 0;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (!response.ok) {
|
|
855
|
+
const code = errorCodeFromStatus(response.status, bodyErrorCode(body));
|
|
856
|
+
if (code === "API_KEY_REVOKED" || bodyErrorCode(body) === "API_KEY_REVOKED") {
|
|
857
|
+
throw new ApiError("This API key has been revoked.", response.status, "API_KEY_REVOKED");
|
|
858
|
+
}
|
|
859
|
+
throw new ApiError(bodyErrorMessage(body) ?? `Asiyst API returned HTTP ${response.status}.`, response.status, code);
|
|
860
|
+
}
|
|
861
|
+
if (rawText && body === void 0) {
|
|
862
|
+
if (isDebugEnabled()) {
|
|
863
|
+
throw new ApiError("Asiyst API returned an invalid JSON response.", response.status, "MALFORMED_RESPONSE");
|
|
864
|
+
}
|
|
865
|
+
throw new ApiError("Received an unexpected response from Asiyst.", response.status, "MALFORMED_RESPONSE");
|
|
866
|
+
}
|
|
867
|
+
return body === void 0 ? {} : body;
|
|
868
|
+
}
|
|
869
|
+
async health() {
|
|
870
|
+
try {
|
|
871
|
+
const body = await this.request("/health");
|
|
872
|
+
const record2 = asRecord(body);
|
|
873
|
+
if (record2) return record2;
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
// src/commands/shared.ts
|
|
881
|
+
async function confirm(question) {
|
|
882
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
883
|
+
const result = await selectOption(question, [
|
|
884
|
+
{ label: "Yes", value: true },
|
|
885
|
+
{ label: "No", value: false }
|
|
886
|
+
]);
|
|
887
|
+
return result.type === "selected" && result.value;
|
|
888
|
+
}
|
|
889
|
+
function createApiClient() {
|
|
890
|
+
return new ApiClient();
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/commands/trust.ts
|
|
894
|
+
async function ensureTrusted(cwd = process.cwd()) {
|
|
895
|
+
if (isTrusted(cwd)) return true;
|
|
896
|
+
if (!await confirm(`Trust this project folder?
|
|
897
|
+
|
|
898
|
+
${cwd}
|
|
899
|
+
|
|
900
|
+
Asiyst may read project files and modify Asiyst configuration during setup.`)) {
|
|
901
|
+
console.log("Folder not trusted.\nExiting...");
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
trustFolder(cwd);
|
|
905
|
+
console.log("\u2713 Folder trusted.");
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
async function trustCommand(cwd = process.cwd()) {
|
|
909
|
+
printHeader("Project Trust", cwd);
|
|
910
|
+
await ensureTrusted(cwd);
|
|
911
|
+
}
|
|
912
|
+
function revokeTrustCommand(cwd = process.cwd()) {
|
|
913
|
+
revokeTrust(cwd);
|
|
914
|
+
console.log("\u2713 Project trust revoked.");
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/commands/connect.ts
|
|
918
|
+
async function retryOrCancel(message) {
|
|
919
|
+
console.log(message);
|
|
920
|
+
const result = await selectOption("Options:", [
|
|
921
|
+
{ label: "Retry", value: true },
|
|
922
|
+
{ label: "Cancel", value: false }
|
|
923
|
+
]);
|
|
924
|
+
return result.type === "selected" && result.value;
|
|
925
|
+
}
|
|
926
|
+
function printApiFailure(error) {
|
|
927
|
+
if (error instanceof ApiError) {
|
|
928
|
+
if (error.status !== void 0 && error.status < 500 && error.code !== "INVALID_API_KEY" && error.code !== "API_KEY_REVOKED") {
|
|
929
|
+
return `\u2717 ${error.message}`;
|
|
930
|
+
}
|
|
931
|
+
return friendlyApiMessage(error, error.code === "NOT_FOUND" ? VERIFY_KEY_URL : void 0);
|
|
932
|
+
}
|
|
933
|
+
if (isDebugEnabled() && error instanceof Error) return `\u2717 ${error.message}`;
|
|
934
|
+
return "\u2717 Unable to reach Asiyst API.";
|
|
935
|
+
}
|
|
936
|
+
async function promptForProjectId() {
|
|
937
|
+
const value = await readInput("Enter your Project ID: ");
|
|
938
|
+
if (value === void 0) {
|
|
939
|
+
console.log("Connection cancelled.");
|
|
940
|
+
return void 0;
|
|
941
|
+
}
|
|
942
|
+
const trimmed = value.trim();
|
|
943
|
+
if (!trimmed) {
|
|
944
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
945
|
+
return void 0;
|
|
946
|
+
}
|
|
947
|
+
return trimmed;
|
|
948
|
+
}
|
|
949
|
+
async function promptForUserId() {
|
|
950
|
+
const value = await readInput("Enter your Asiyst User ID: ");
|
|
951
|
+
if (value === void 0) {
|
|
952
|
+
console.log("Connection cancelled.");
|
|
953
|
+
return void 0;
|
|
954
|
+
}
|
|
955
|
+
if (!isValidUserId(value)) {
|
|
956
|
+
console.log("Invalid Asiyst User ID.");
|
|
957
|
+
console.log("Expected: exactly 16 characters using letters, numbers, and hyphens only.");
|
|
958
|
+
return void 0;
|
|
959
|
+
}
|
|
960
|
+
return value.trim();
|
|
961
|
+
}
|
|
962
|
+
async function connectCommand(cwd = process.cwd(), api = createApiClient(), cliProjectId) {
|
|
963
|
+
printHeader("Connect project", cwd);
|
|
964
|
+
if (!await ensureTrusted(cwd)) return;
|
|
965
|
+
const project = detectProject(cwd);
|
|
966
|
+
projectChecks(project);
|
|
967
|
+
if (project.framework === "Unknown") {
|
|
968
|
+
console.log("No supported framework was detected in this folder.");
|
|
969
|
+
}
|
|
970
|
+
const shouldConnect = await selectOption("Connect this project to Asiyst?", [
|
|
971
|
+
{ label: "Yes", value: true },
|
|
972
|
+
{ label: "No", value: false }
|
|
973
|
+
]);
|
|
974
|
+
if (shouldConnect.type !== "selected" || !shouldConnect.value) {
|
|
975
|
+
console.log("Connection cancelled.");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
console.log("Opening Asiyst...");
|
|
979
|
+
if (!await openBrowser(ASIIYST_WEB_URL)) {
|
|
980
|
+
console.log(`Open this URL manually:
|
|
981
|
+
${ASIIYST_WEB_URL}`);
|
|
982
|
+
}
|
|
983
|
+
console.log("Register or log in, complete onboarding, create or select a project, then create an API key.");
|
|
984
|
+
const userId = await promptForUserId();
|
|
985
|
+
if (!userId) return;
|
|
986
|
+
try {
|
|
987
|
+
await verifyUser(api, userId);
|
|
988
|
+
ok("User verified.");
|
|
989
|
+
} catch (error) {
|
|
990
|
+
console.log(printApiFailure(error));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const projectIdArg = cliProjectId ?? parseProjectIdArgument(process.argv.slice(2));
|
|
994
|
+
if (projectIdArg === "") {
|
|
995
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
let projectId = typeof projectIdArg === "string" ? projectIdArg.trim() : "";
|
|
999
|
+
if (!projectId) projectId = await promptForProjectId() || "";
|
|
1000
|
+
if (!projectId) {
|
|
1001
|
+
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (!isValidProjectId(projectId)) {
|
|
1005
|
+
console.log("Invalid Project ID. It must be exactly 24 characters.");
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
let verifiedProject;
|
|
1009
|
+
try {
|
|
1010
|
+
verifiedProject = await verifyProject(api, userId, projectId);
|
|
1011
|
+
ok("Project verified.");
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
console.log(printApiFailure(error));
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
for (; ; ) {
|
|
1017
|
+
const apiKey = await readSecret("Paste your Asiyst API key: ");
|
|
1018
|
+
if (apiKey === void 0) {
|
|
1019
|
+
console.log("Connection cancelled.");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (!isValidApiKey(apiKey)) {
|
|
1023
|
+
fail("Invalid API key. It must be exactly 32 characters.");
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
const connected = await verifyApiKeyRelationship(api, { userId, projectId, apiKey });
|
|
1028
|
+
const finalConnection = {
|
|
1029
|
+
...connected,
|
|
1030
|
+
projectName: connected.projectName ?? verifiedProject.projectName,
|
|
1031
|
+
website: connected.website ?? verifiedProject.website,
|
|
1032
|
+
publicKey: connected.publicKey ?? verifiedProject.publicKey,
|
|
1033
|
+
projectId,
|
|
1034
|
+
userId
|
|
1035
|
+
};
|
|
1036
|
+
ok("API key verified.");
|
|
1037
|
+
await saveConnection(cwd, finalConnection);
|
|
1038
|
+
writeProjectMetadata(cwd, finalConnection);
|
|
1039
|
+
ok("Project connected successfully.");
|
|
1040
|
+
console.log(`
|
|
1041
|
+
Project ID:
|
|
1042
|
+
${projectId}`);
|
|
1043
|
+
if (connected.projectName) console.log(`
|
|
1044
|
+
Project:
|
|
1045
|
+
${connected.projectName}`);
|
|
1046
|
+
if (connected.website) console.log(`
|
|
1047
|
+
Website:
|
|
1048
|
+
${connected.website}`);
|
|
1049
|
+
return;
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
const message = printApiFailure(error);
|
|
1052
|
+
const code = error instanceof ApiError ? error.code : "NETWORK";
|
|
1053
|
+
if (code === "INVALID_API_KEY") {
|
|
1054
|
+
console.log(message);
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (code === "API_KEY_REVOKED" || code === "FORBIDDEN" || code === "NOT_FOUND" || code === "MALFORMED_RESPONSE") {
|
|
1058
|
+
console.log(message);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (!await retryOrCancel(message)) {
|
|
1062
|
+
console.log("Connection cancelled.");
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
371
1067
|
}
|
|
372
1068
|
|
|
373
|
-
// src/commands/
|
|
374
|
-
async function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
${cwd}
|
|
1069
|
+
// src/commands/login.ts
|
|
1070
|
+
async function loginCommand() {
|
|
1071
|
+
await connectCommand(process.cwd(), createApiClient());
|
|
1072
|
+
}
|
|
379
1073
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
1074
|
+
// src/commands/disconnect.ts
|
|
1075
|
+
async function disconnectCommand(cwd = process.cwd()) {
|
|
1076
|
+
if (!await confirm("Disconnect this project?")) {
|
|
1077
|
+
console.log("Disconnect cancelled.");
|
|
1078
|
+
return;
|
|
383
1079
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
async function trustCommand(cwd = process.cwd()) {
|
|
389
|
-
await ensureTrusted(cwd);
|
|
1080
|
+
await clearConnection(cwd);
|
|
1081
|
+
clearProjectMetadata(cwd);
|
|
1082
|
+
console.log("\u2713 Local connection information removed.");
|
|
1083
|
+
console.log("The cloud project and API key were not changed.");
|
|
390
1084
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
1085
|
+
|
|
1086
|
+
// src/commands/logout.ts
|
|
1087
|
+
async function logoutCommand() {
|
|
1088
|
+
await disconnectCommand();
|
|
394
1089
|
}
|
|
395
1090
|
|
|
396
|
-
// src/commands/
|
|
397
|
-
async function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
1091
|
+
// src/commands/status.ts
|
|
1092
|
+
async function statusCommand(cwd = process.cwd(), api = createApiClient()) {
|
|
1093
|
+
const stored = await loadConnection(cwd);
|
|
1094
|
+
if (!stored?.apiKey) {
|
|
1095
|
+
printHeader("Asiyst Project Status");
|
|
1096
|
+
printProjectSummary(detectProject(cwd), false);
|
|
1097
|
+
console.log(`
|
|
1098
|
+
${warning(symbols.warning)} Not connected to Asiyst.`);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (!stored.userId || !stored.projectId) {
|
|
1102
|
+
console.log(`${symbols.error} Connection is missing a User ID or Project ID. Please run:
|
|
1103
|
+
asiyst connect`);
|
|
404
1104
|
return;
|
|
405
1105
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1106
|
+
try {
|
|
1107
|
+
const connected = await verifyApiKeyRelationship(api, {
|
|
1108
|
+
userId: stored.userId,
|
|
1109
|
+
projectId: stored.projectId,
|
|
1110
|
+
apiKey: stored.apiKey
|
|
1111
|
+
});
|
|
1112
|
+
if (stored.avatarId) {
|
|
1113
|
+
await verifyAvatar(api, {
|
|
1114
|
+
userId: stored.userId,
|
|
1115
|
+
projectId: stored.projectId,
|
|
1116
|
+
apiKey: stored.apiKey,
|
|
1117
|
+
avatarId: stored.avatarId
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
const projectId = stored.projectId || connected.projectId;
|
|
1121
|
+
const userId = stored.userId || connected.userId;
|
|
1122
|
+
printHeader("Asiyst Project Status");
|
|
1123
|
+
printProjectSummary(detectProject(cwd), true, connected.projectName, connected.website);
|
|
1124
|
+
console.log(`
|
|
1125
|
+
${section("Identifiers")}`);
|
|
1126
|
+
if (userId) console.log(` User ID: ${maskSecret(userId)}`);
|
|
1127
|
+
console.log(` Project ID: ${projectId}`);
|
|
1128
|
+
console.log(` API Key: ${maskSecret(stored.apiKey)}`);
|
|
411
1129
|
console.log(`
|
|
412
|
-
|
|
413
|
-
|
|
1130
|
+
${section("Integration")}`);
|
|
1131
|
+
console.log(` User: ${success(symbols.success)} Connected`);
|
|
1132
|
+
console.log(` Project: ${success(symbols.success)} ${connected.projectName ?? projectId}`);
|
|
1133
|
+
if (stored.avatarId) console.log(` Avatar: ${success(symbols.success)} ${stored.avatarName ?? stored.avatarId}`);
|
|
1134
|
+
else console.log(` Avatar: ${symbols.error} Not imported`);
|
|
1135
|
+
const detection = detectProject(cwd);
|
|
1136
|
+
console.log(` SDK: ${detection.sdkVersion ? `${success(symbols.success)} Installed (${detection.sdkVersion})` : `${symbols.error} Not installed`}`);
|
|
1137
|
+
console.log(` Integration: ${detection.sdkVersion && stored.avatarId ? `${success(symbols.success)} Configured` : `${symbols.error} Not configured`}`);
|
|
1138
|
+
console.log(`
|
|
1139
|
+
${success(symbols.success)} Everything looks good.`);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
if (error instanceof ApiError && error.code === "API_KEY_REVOKED") {
|
|
1142
|
+
console.log("\u2717 API key revoked.");
|
|
1143
|
+
console.log("Create a new key from:\nhttps://asiyst.com");
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (error instanceof ApiError && (error.code === "INVALID_API_KEY" || error.code === "FORBIDDEN")) {
|
|
1147
|
+
console.log(`${symbols.error} Connection invalid. Run \`asiyst connect\` to reconnect.`);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
console.log(`${symbols.error} Connection invalid. Run \`asiyst doctor\` for diagnostics.`);
|
|
414
1151
|
}
|
|
415
|
-
const warning = configWarning(cwd);
|
|
416
|
-
if (warning) console.log(`
|
|
417
|
-
Warning: ${warning}`);
|
|
418
|
-
console.log('\nUse the public SDK API:\n\nimport { Asiyst } from "@asiyst/sdk";\n\nawait Asiyst.init({ projectId: "PROJECT_ID", publicKey: "PUBLIC_KEY" });');
|
|
419
|
-
console.log("\nRun `npx asiyst verify` after starting your website.");
|
|
420
1152
|
}
|
|
421
1153
|
|
|
422
|
-
// src/
|
|
423
|
-
async function
|
|
424
|
-
await
|
|
425
|
-
|
|
1154
|
+
// src/api/projects.ts
|
|
1155
|
+
async function verifyInstallation(api, projectId, publicKey, domain) {
|
|
1156
|
+
const value = await api.request("/cli/verification", {
|
|
1157
|
+
method: "POST",
|
|
1158
|
+
body: JSON.stringify({ projectId, publicKey, domain })
|
|
1159
|
+
});
|
|
1160
|
+
if (!Array.isArray(value) || !value.every((item) => item && typeof item === "object" && typeof item.name === "string" && typeof item.ok === "boolean")) {
|
|
1161
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
1162
|
+
}
|
|
1163
|
+
return value;
|
|
426
1164
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
1165
|
+
function requireIdentifier(value, name, valid) {
|
|
1166
|
+
const trimmed = value.trim();
|
|
1167
|
+
if (!valid(trimmed)) throw new ApiError(`Invalid ${name}.`, 400, "INVALID_REQUEST");
|
|
1168
|
+
return trimmed;
|
|
431
1169
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const project = detectProject(cwd);
|
|
436
|
-
console.log(`Project: ${typeof project.packageJson?.name === "string" ? project.packageJson.name : "Unknown"}
|
|
437
|
-
SDK: ${project.sdkVersion || "Not connected"}`);
|
|
438
|
-
if (!project.config.projectId || !project.config.publicKey) {
|
|
439
|
-
console.log("\nProject: Not connected\nSDK: Not connected\nAvatar: Not configured\n\nStatistics:\nConnect your website to show stats.");
|
|
440
|
-
return;
|
|
1170
|
+
function parseAvatarImportResponse(value, projectId, userId, avatarId) {
|
|
1171
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1172
|
+
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
441
1173
|
}
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
1174
|
+
const response = value;
|
|
1175
|
+
const body = response.data && typeof response.data === "object" && !Array.isArray(response.data) ? response.data : response;
|
|
1176
|
+
const imported = body.imported === true || body.success === true || body.status === "imported";
|
|
1177
|
+
const alreadyImported = body.code === "AVATAR_ALREADY_IMPORTED" || body.status === "already_imported";
|
|
1178
|
+
if (alreadyImported) {
|
|
1179
|
+
return {
|
|
1180
|
+
imported: true,
|
|
1181
|
+
alreadyImported: true,
|
|
1182
|
+
avatarId,
|
|
1183
|
+
projectId,
|
|
1184
|
+
userId,
|
|
1185
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
|
|
1186
|
+
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
if (!imported) {
|
|
1190
|
+
const code = typeof body.code === "string" ? body.code : "IMPORT_FAILED";
|
|
1191
|
+
const supportedCode = ["AVATAR_NOT_FOUND", "AVATAR_ALREADY_IMPORTED", "FORBIDDEN", "PROJECT_NOT_FOUND", "CONFLICT"].includes(code) ? code : "IMPORT_FAILED";
|
|
1192
|
+
throw new ApiError(typeof body.message === "string" ? body.message : "Avatar import was not completed.", 200, supportedCode);
|
|
1193
|
+
}
|
|
1194
|
+
const returnedAvatarId = body.avatarId ?? body.avatar_id;
|
|
1195
|
+
if (returnedAvatarId !== void 0 && returnedAvatarId !== avatarId) {
|
|
1196
|
+
throw new ApiError("The API returned a different avatar than requested.", 200, "AVATAR_MISMATCH");
|
|
1197
|
+
}
|
|
1198
|
+
const returnedProjectId = body.projectId ?? body.project_id;
|
|
1199
|
+
if (returnedProjectId !== void 0 && returnedProjectId !== projectId) {
|
|
1200
|
+
throw new ApiError("The API returned a different project than requested.", 200, "PROJECT_MISMATCH");
|
|
1201
|
+
}
|
|
1202
|
+
const returnedUserId = body.userId ?? body.user_id;
|
|
1203
|
+
if (returnedUserId !== void 0 && returnedUserId !== userId) {
|
|
1204
|
+
throw new ApiError("The API returned a different user than requested.", 200, "USER_MISMATCH");
|
|
1205
|
+
}
|
|
1206
|
+
return {
|
|
1207
|
+
imported: true,
|
|
1208
|
+
avatarId,
|
|
1209
|
+
projectId,
|
|
1210
|
+
userId,
|
|
1211
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
|
|
1212
|
+
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
async function importAvatar(api, input) {
|
|
1216
|
+
const userId = requireIdentifier(input.userId, "User ID", isValidUserId);
|
|
1217
|
+
const projectId = requireIdentifier(input.projectId, "Project ID", isValidProjectId);
|
|
1218
|
+
const avatarId = requireIdentifier(input.avatarId, "Avatar ID", isValidAvatarId);
|
|
1219
|
+
const apiKey = input.apiKey.trim();
|
|
1220
|
+
if (!isValidApiKey(apiKey)) throw new ApiError("Invalid API key. It must be exactly 32 characters.", 401, "INVALID_API_KEY");
|
|
1221
|
+
const value = await api.request(`/cli/projects/${encodeURIComponent(projectId)}/avatars/import`, {
|
|
1222
|
+
method: "POST",
|
|
1223
|
+
headers: {
|
|
1224
|
+
Authorization: "Bearer " + apiKey,
|
|
1225
|
+
"X-Asiyst-API-Key": apiKey
|
|
1226
|
+
},
|
|
1227
|
+
body: JSON.stringify({ userId, projectId, avatarId })
|
|
1228
|
+
});
|
|
1229
|
+
return parseAvatarImportResponse(value, projectId, userId, avatarId);
|
|
449
1230
|
}
|
|
450
1231
|
|
|
451
1232
|
// src/commands/verify.ts
|
|
452
|
-
|
|
1233
|
+
function printVerificationSafe(results) {
|
|
1234
|
+
console.log("\nAsiyst Installation Verification\n");
|
|
1235
|
+
for (const result of results) result.ok ? ok(result.name, result.detail) : fail(result.name, result.detail);
|
|
1236
|
+
return results.length > 0 && results.every((result) => result.ok);
|
|
1237
|
+
}
|
|
1238
|
+
async function verifyCommand(cwd = process.cwd()) {
|
|
453
1239
|
const project = detectProject(cwd);
|
|
454
1240
|
if (!project.packageJson) {
|
|
455
1241
|
fail("Project detected", "package.json is missing");
|
|
@@ -457,105 +1243,514 @@ async function verifyCommand(cwd = process.cwd(), api = new ApiClient()) {
|
|
|
457
1243
|
return;
|
|
458
1244
|
}
|
|
459
1245
|
ok("Project detected");
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
ok("SDK installed", project.sdkVersion);
|
|
466
|
-
if (!project.config.projectId || !project.config.publicKey) {
|
|
467
|
-
fail("Configuration found", "set ASIIYST_PROJECT_ID and ASIIYST_PUBLIC_KEY");
|
|
1246
|
+
const stored = await loadConnection(cwd);
|
|
1247
|
+
if (!stored) {
|
|
1248
|
+
fail("Configuration found", "run asiyst connect");
|
|
468
1249
|
process.exitCode = 1;
|
|
469
1250
|
return;
|
|
470
1251
|
}
|
|
471
1252
|
ok("Configuration found");
|
|
472
1253
|
try {
|
|
473
|
-
const results = await
|
|
474
|
-
if (!
|
|
1254
|
+
const results = await verifyInstallation(createApiClient(), stored.projectId, stored.publicKey || "", process.env.ASIIYST_DOMAIN);
|
|
1255
|
+
if (!printVerificationSafe(results)) process.exitCode = 1;
|
|
475
1256
|
} catch (error) {
|
|
476
1257
|
fail("API reachable", error instanceof Error ? error.message : "verification failed");
|
|
477
1258
|
process.exitCode = 1;
|
|
478
1259
|
}
|
|
479
1260
|
}
|
|
480
1261
|
|
|
1262
|
+
// src/config/version.ts
|
|
1263
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1264
|
+
import { fileURLToPath } from "url";
|
|
1265
|
+
function readCurrentVersion() {
|
|
1266
|
+
if ("1.1.2") return "1.1.2";
|
|
1267
|
+
try {
|
|
1268
|
+
const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1269
|
+
if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string") {
|
|
1270
|
+
return packageJson.version;
|
|
1271
|
+
}
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
|
|
1276
|
+
if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string" && packageJson.name === "@asiyst/cli") {
|
|
1277
|
+
return packageJson.version;
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
return "0.0.0";
|
|
1282
|
+
}
|
|
1283
|
+
|
|
481
1284
|
// src/commands/doctor.ts
|
|
482
|
-
async function doctorCommand(cwd = process.cwd(), api =
|
|
483
|
-
|
|
1285
|
+
async function doctorCommand(cwd = process.cwd(), api = createApiClient()) {
|
|
1286
|
+
ok("CLI version", readCurrentVersion());
|
|
1287
|
+
ok("Executable", process.argv[1] || "unknown");
|
|
1288
|
+
ok("API base", api.baseUrl || CLI_API_BASE_URL);
|
|
1289
|
+
ok("Verify endpoint", VERIFY_KEY_URL);
|
|
484
1290
|
ok("System", process.platform);
|
|
485
1291
|
ok("Node.js", process.version);
|
|
486
|
-
|
|
1292
|
+
const project = detectProject(cwd);
|
|
487
1293
|
project.packageJson ? ok("Project", cwd) : fail("Project", "package.json is missing");
|
|
488
1294
|
project.sdkVersion ? ok("SDK", project.sdkVersion) : fail("SDK", "not installed");
|
|
489
|
-
|
|
1295
|
+
const stored = await loadConnection(cwd);
|
|
1296
|
+
stored ? ok("Local credential", "present") : fail("Local credential", "not connected");
|
|
490
1297
|
try {
|
|
491
1298
|
await api.health();
|
|
492
|
-
ok("Asiyst API", "
|
|
1299
|
+
ok("Asiyst API", "health endpoint reachable");
|
|
493
1300
|
} catch (error) {
|
|
494
1301
|
fail("Asiyst API", error instanceof Error ? error.message : "unreachable");
|
|
495
1302
|
}
|
|
496
|
-
if (project.config.projectId && project.config.publicKey) {
|
|
497
|
-
try {
|
|
498
|
-
await api.projectInfo(project.config.projectId);
|
|
499
|
-
ok("Project connection");
|
|
500
|
-
} catch (error) {
|
|
501
|
-
fail("Project connection", error instanceof Error ? error.message : "unavailable");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
1303
|
}
|
|
505
1304
|
|
|
506
1305
|
// src/commands/dashboard.ts
|
|
507
1306
|
async function dashboardCommand() {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const url = parsed.toString();
|
|
512
|
-
if (!await openBrowser(url)) console.log(`Open ${url}`);
|
|
1307
|
+
const url = ASIIYST_WEB_URL;
|
|
1308
|
+
if (!await openBrowser(url)) console.log(`Open this URL manually:
|
|
1309
|
+
${url}`);
|
|
513
1310
|
else console.log(`Opening ${url}`);
|
|
514
1311
|
}
|
|
515
1312
|
|
|
1313
|
+
// src/integration/writer.ts
|
|
1314
|
+
import { execFile as execFile3 } from "child_process";
|
|
1315
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
1316
|
+
import { dirname, join as join4, relative, resolve as resolve5 } from "path";
|
|
1317
|
+
import { promisify as promisify3 } from "util";
|
|
1318
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
1319
|
+
var MARKER_START = "// ASIIYST CLI START";
|
|
1320
|
+
var MARKER_END = "// ASIIYST CLI END";
|
|
1321
|
+
function componentSource(values, extension) {
|
|
1322
|
+
if (extension === "ts" || extension === "js") {
|
|
1323
|
+
return `${MARKER_START}
|
|
1324
|
+
import { Asiyst } from "@asiyst/sdk";
|
|
1325
|
+
|
|
1326
|
+
void Asiyst.init({
|
|
1327
|
+
projectId: ${JSON.stringify(values.projectId)},
|
|
1328
|
+
publicKey: ${JSON.stringify(values.publicKey)},
|
|
1329
|
+
avatarId: ${JSON.stringify(values.avatarId)},
|
|
1330
|
+
position: "bottom-right",
|
|
1331
|
+
});
|
|
1332
|
+
${MARKER_END}
|
|
1333
|
+
`;
|
|
1334
|
+
}
|
|
1335
|
+
const importLine = extension === "tsx" ? 'import * as React from "react";' : 'import React from "react";';
|
|
1336
|
+
return `${MARKER_START}
|
|
1337
|
+
${importLine}
|
|
1338
|
+
import { Asiyst } from "@asiyst/sdk";
|
|
1339
|
+
|
|
1340
|
+
let asiystStarted = false;
|
|
1341
|
+
|
|
1342
|
+
export function AsiystAssistant() {
|
|
1343
|
+
React.useEffect(() => {
|
|
1344
|
+
if (asiystStarted) return;
|
|
1345
|
+
asiystStarted = true;
|
|
1346
|
+
void Asiyst.init({
|
|
1347
|
+
projectId: ${JSON.stringify(values.projectId)},
|
|
1348
|
+
publicKey: ${JSON.stringify(values.publicKey)},
|
|
1349
|
+
avatarId: ${JSON.stringify(values.avatarId)},
|
|
1350
|
+
position: "bottom-right",
|
|
1351
|
+
}).catch((error) => {
|
|
1352
|
+
asiystStarted = false;
|
|
1353
|
+
console.error("Asiyst failed to initialize.", error);
|
|
1354
|
+
});
|
|
1355
|
+
}, []);
|
|
1356
|
+
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
${MARKER_END}
|
|
1360
|
+
`;
|
|
1361
|
+
}
|
|
1362
|
+
function read(path) {
|
|
1363
|
+
return readFileSync6(path, "utf8");
|
|
1364
|
+
}
|
|
1365
|
+
function findFirst(cwd, paths) {
|
|
1366
|
+
return paths.map((path) => resolve5(cwd, path)).find((path) => existsSync5(path));
|
|
1367
|
+
}
|
|
1368
|
+
function isTypeScript(project) {
|
|
1369
|
+
return project.language === "TypeScript" || existsSync5(resolve5(project.cwd, "tsconfig.json"));
|
|
1370
|
+
}
|
|
1371
|
+
function pathsFor(project) {
|
|
1372
|
+
if (project.framework === "Vanilla TypeScript" || project.framework === "Vanilla JavaScript") {
|
|
1373
|
+
const extension2 = project.framework === "Vanilla TypeScript" ? "ts" : "js";
|
|
1374
|
+
return {
|
|
1375
|
+
component: join4("src", `asiyst.${extension2}`),
|
|
1376
|
+
entry: findFirst(project.cwd, [`src/index.${extension2}`, `index.${extension2}`]),
|
|
1377
|
+
extension: extension2
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
const extension = isTypeScript(project) ? "tsx" : "jsx";
|
|
1381
|
+
const component = join4("src", "components", `AsiystAssistant.${extension}`);
|
|
1382
|
+
if (project.framework === "Next.js") {
|
|
1383
|
+
const appEntry = findFirst(project.cwd, ["src/app/layout.tsx", "src/app/layout.jsx", "app/layout.tsx", "app/layout.jsx"]);
|
|
1384
|
+
if (appEntry) return { component, entry: appEntry, extension: appEntry.endsWith(".tsx") ? "tsx" : "jsx" };
|
|
1385
|
+
const pagesEntry = findFirst(project.cwd, ["src/pages/_app.tsx", "src/pages/_app.jsx", "pages/_app.tsx", "pages/_app.jsx"]);
|
|
1386
|
+
if (pagesEntry) return { component, entry: pagesEntry, extension: pagesEntry.endsWith(".tsx") ? "tsx" : "jsx" };
|
|
1387
|
+
return { component, extension };
|
|
1388
|
+
}
|
|
1389
|
+
const entry = findFirst(project.cwd, [
|
|
1390
|
+
"src/main.tsx",
|
|
1391
|
+
"src/main.jsx",
|
|
1392
|
+
"src/main.ts",
|
|
1393
|
+
"src/main.js",
|
|
1394
|
+
"src/index.tsx",
|
|
1395
|
+
"src/index.jsx",
|
|
1396
|
+
"src/index.ts",
|
|
1397
|
+
"src/index.js"
|
|
1398
|
+
]);
|
|
1399
|
+
return { component, entry, extension: entry?.endsWith(".tsx") || entry?.endsWith(".ts") ? "tsx" : "jsx" };
|
|
1400
|
+
}
|
|
1401
|
+
function updateComponent(path, source, values) {
|
|
1402
|
+
if (!existsSync5(path)) {
|
|
1403
|
+
mkdirSync4(dirname(path), { recursive: true });
|
|
1404
|
+
writeFileSync4(path, source, "utf8");
|
|
1405
|
+
return "create";
|
|
1406
|
+
}
|
|
1407
|
+
const current = read(path);
|
|
1408
|
+
const start = current.indexOf(MARKER_START);
|
|
1409
|
+
const end = current.indexOf(MARKER_END);
|
|
1410
|
+
if (start >= 0 && end >= start) {
|
|
1411
|
+
const next2 = current.slice(0, start) + source.trimEnd() + current.slice(end + MARKER_END.length);
|
|
1412
|
+
if (next2 === current) return "unchanged";
|
|
1413
|
+
writeFileSync4(path, next2, "utf8");
|
|
1414
|
+
return "update";
|
|
1415
|
+
}
|
|
1416
|
+
const replacements = {
|
|
1417
|
+
projectId: JSON.stringify(values.projectId),
|
|
1418
|
+
publicKey: JSON.stringify(values.publicKey),
|
|
1419
|
+
avatarId: JSON.stringify(values.avatarId)
|
|
1420
|
+
};
|
|
1421
|
+
let next = source;
|
|
1422
|
+
for (const [name, value] of Object.entries(replacements)) {
|
|
1423
|
+
const pattern = new RegExp(`(${name}\\s*:\\s*)["'\`][^"'\`]+["'\`]`);
|
|
1424
|
+
if (!pattern.test(next)) {
|
|
1425
|
+
throw new Error(`Existing Asiyst component is not safely updateable: ${path}`);
|
|
1426
|
+
}
|
|
1427
|
+
next = next.replace(pattern, `$1${value}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (next === source) return "unchanged";
|
|
1430
|
+
writeFileSync4(path, next, "utf8");
|
|
1431
|
+
return "update";
|
|
1432
|
+
}
|
|
1433
|
+
function updateEntry(path, componentPath) {
|
|
1434
|
+
const current = read(path);
|
|
1435
|
+
const importPath = relative(dirname(path), resolve5(componentPath)).replace(/\\/g, "/").replace(/\.(tsx|jsx)$/, "").replace(/^\.\//, "");
|
|
1436
|
+
const importLine = componentPath.endsWith(".ts") || componentPath.endsWith(".js") ? `import "./${importPath}";` : `import { AsiystAssistant } from "./${importPath}";`;
|
|
1437
|
+
const hasImport = current.includes("AsiystAssistant") && current.includes("@asiyst/sdk");
|
|
1438
|
+
const nextImport = hasImport ? current : `${importLine}
|
|
1439
|
+
${current}`;
|
|
1440
|
+
const componentPattern = /<AsiystAssistant\s*\/>/;
|
|
1441
|
+
let next = componentPattern.test(nextImport) ? nextImport : nextImport;
|
|
1442
|
+
if (!componentPattern.test(nextImport)) {
|
|
1443
|
+
if (/\{children\}/.test(nextImport)) {
|
|
1444
|
+
next = nextImport.replace(/\{children\}/, "<AsiystAssistant />\n {children}");
|
|
1445
|
+
} else if (/<App\s*\/>/.test(nextImport)) {
|
|
1446
|
+
next = nextImport.replace(/<App\s*\/>/, "<><AsiystAssistant /><App /></>");
|
|
1447
|
+
} else if (/<Component\s+\{\.\.\.pageProps\}\s*\/>/.test(nextImport)) {
|
|
1448
|
+
next = nextImport.replace(/<Component\s+\{\.\.\.pageProps\}\s*\/>/, "<><AsiystAssistant /><Component {...pageProps} /></>");
|
|
1449
|
+
} else if (componentPath.endsWith(".ts") || componentPath.endsWith(".js")) {
|
|
1450
|
+
next = nextImport;
|
|
1451
|
+
} else {
|
|
1452
|
+
throw new Error(`Could not find a safe render location in ${path}. Add <AsiystAssistant /> to this entry point.`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (next === current) return "unchanged";
|
|
1456
|
+
writeFileSync4(path, next, "utf8");
|
|
1457
|
+
return "update";
|
|
1458
|
+
}
|
|
1459
|
+
function planIntegration(project, values) {
|
|
1460
|
+
if (!values.projectId || !values.publicKey || !values.avatarId) {
|
|
1461
|
+
throw new Error("Verified projectId, publicKey, and avatarId are required before configuring the SDK.");
|
|
1462
|
+
}
|
|
1463
|
+
const paths = pathsFor(project);
|
|
1464
|
+
const componentPath = resolve5(project.cwd, paths.component);
|
|
1465
|
+
const entryPath = paths.entry;
|
|
1466
|
+
if (!entryPath && !project.framework.startsWith("Vanilla") && project.framework !== "Next.js" && project.framework !== "React" && project.framework !== "Vite") {
|
|
1467
|
+
return {
|
|
1468
|
+
framework: project.framework,
|
|
1469
|
+
sdkInstalled: Boolean(project.sdkVersion),
|
|
1470
|
+
sdkVersion: project.sdkVersion,
|
|
1471
|
+
componentPath,
|
|
1472
|
+
componentAction: existsSync5(componentPath) ? "update" : "create",
|
|
1473
|
+
entryAction: "unsupported",
|
|
1474
|
+
reason: "No supported React entry point was detected. Add AsiystAssistant to the website entry point manually."
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
framework: project.framework,
|
|
1479
|
+
sdkInstalled: Boolean(project.sdkVersion),
|
|
1480
|
+
sdkVersion: project.sdkVersion,
|
|
1481
|
+
componentPath,
|
|
1482
|
+
entryPath,
|
|
1483
|
+
componentAction: existsSync5(componentPath) ? "update" : "create",
|
|
1484
|
+
entryAction: entryPath ? "update" : "unsupported",
|
|
1485
|
+
reason: entryPath ? void 0 : "No supported application entry point was detected."
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
async function installSdk(project) {
|
|
1489
|
+
const commands = {
|
|
1490
|
+
npm: { command: "npm", args: ["install", "@asiyst/sdk"] },
|
|
1491
|
+
pnpm: { command: "pnpm", args: ["add", "@asiyst/sdk"] },
|
|
1492
|
+
yarn: { command: "yarn", args: ["add", "@asiyst/sdk"] },
|
|
1493
|
+
bun: { command: "bun", args: ["add", "@asiyst/sdk"] }
|
|
1494
|
+
};
|
|
1495
|
+
const selected = commands[project.packageManager] ?? commands.npm;
|
|
1496
|
+
await execFileAsync3(selected.command, selected.args, { cwd: project.cwd, windowsHide: true });
|
|
1497
|
+
}
|
|
1498
|
+
function applyIntegration(project, values, plan = planIntegration(project, values)) {
|
|
1499
|
+
if (plan.entryAction === "unsupported" || !plan.entryPath) {
|
|
1500
|
+
throw new Error(plan.reason ?? "The project entry point could not be detected safely.");
|
|
1501
|
+
}
|
|
1502
|
+
const paths = pathsFor(project);
|
|
1503
|
+
const componentAction = updateComponent(plan.componentPath, componentSource(values, paths.extension), values);
|
|
1504
|
+
const entryAction = updateEntry(plan.entryPath, plan.componentPath);
|
|
1505
|
+
return { ...plan, componentAction, entryAction, installed: plan.sdkInstalled };
|
|
1506
|
+
}
|
|
1507
|
+
function printDryRun(plan) {
|
|
1508
|
+
console.log(`Framework: ${plan.framework}`);
|
|
1509
|
+
console.log(`SDK: ${plan.sdkInstalled ? `installed${plan.sdkVersion ? ` (${plan.sdkVersion})` : ""}` : "missing"}`);
|
|
1510
|
+
console.log(`Component: ${plan.componentAction} ${plan.componentPath}`);
|
|
1511
|
+
console.log(`Entry point: ${plan.entryPath ? `${plan.entryAction} ${plan.entryPath}` : "not detected"}`);
|
|
1512
|
+
if (plan.reason) console.log(`Note: ${plan.reason}`);
|
|
1513
|
+
}
|
|
1514
|
+
function readIntegrationValues(plan) {
|
|
1515
|
+
if (!existsSync5(plan.componentPath)) return {};
|
|
1516
|
+
const source = read(plan.componentPath);
|
|
1517
|
+
const value = (name) => {
|
|
1518
|
+
const match = source.match(new RegExp(`${name}\\s*:\\s*["'\`]([^"'\`]+)["'\`]`));
|
|
1519
|
+
return match?.[1];
|
|
1520
|
+
};
|
|
1521
|
+
return {
|
|
1522
|
+
projectId: value("projectId"),
|
|
1523
|
+
publicKey: value("publicKey"),
|
|
1524
|
+
avatarId: value("avatarId")
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
516
1528
|
// src/commands/avatar.ts
|
|
517
|
-
|
|
1529
|
+
function parseAvatarId(argv) {
|
|
1530
|
+
const index = argv.findIndex((value) => value === "--avatar-id");
|
|
1531
|
+
if (index >= 0) return argv[index + 1];
|
|
1532
|
+
const inline = argv.find((value) => value.startsWith("--avatar-id="));
|
|
1533
|
+
return inline?.slice("--avatar-id=".length) || argv.slice(1).find((value) => !value.startsWith("--"));
|
|
1534
|
+
}
|
|
1535
|
+
async function confirm2(message) {
|
|
1536
|
+
const result = await selectOption(message, [
|
|
1537
|
+
{ label: "Continue", value: true },
|
|
1538
|
+
{ label: "Cancel", value: false }
|
|
1539
|
+
]);
|
|
1540
|
+
return result.type === "selected" && result.value;
|
|
1541
|
+
}
|
|
1542
|
+
async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(), suppliedAvatarId) {
|
|
1543
|
+
const stored = await loadConnection(cwd);
|
|
1544
|
+
if (!stored?.apiKey || !stored.userId || !stored.projectId) {
|
|
1545
|
+
console.log("No project is connected. Please run:\nasiyst connect");
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
const avatarId = suppliedAvatarId ?? await readInput("Enter your Avatar ID: ");
|
|
1549
|
+
if (!avatarId || !isValidAvatarId(avatarId)) {
|
|
1550
|
+
console.log("Invalid Avatar ID. It must contain exactly 10 alphanumeric characters.");
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
console.log("Opening Asiyst Avatar Studio...");
|
|
1554
|
+
if (!await openBrowser(ASIIYST_WEB_URL)) {
|
|
1555
|
+
console.log(`Open this URL manually:
|
|
1556
|
+
${ASIIYST_WEB_URL}`);
|
|
1557
|
+
}
|
|
1558
|
+
console.log("Configure and save your avatar in Avatar Studio, then return here.");
|
|
518
1559
|
const project = detectProject(cwd);
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1560
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
1561
|
+
const detectedProjectId = project.config.projectId;
|
|
1562
|
+
if (detectedProjectId && detectedProjectId !== stored.projectId) {
|
|
1563
|
+
console.log(`Existing projectId ${detectedProjectId} differs from verified project ${stored.projectId}.`);
|
|
1564
|
+
if (!await confirm2("Switch this website to the verified project?")) return;
|
|
1565
|
+
}
|
|
1566
|
+
const existingPlan = planIntegration(project, {
|
|
1567
|
+
projectId: stored.projectId,
|
|
1568
|
+
publicKey: stored.publicKey ?? "pending-public-key",
|
|
1569
|
+
avatarId: avatarId.trim()
|
|
1570
|
+
});
|
|
1571
|
+
const existing = readIntegrationValues(existingPlan);
|
|
1572
|
+
if (existing.projectId && existing.projectId !== stored.projectId) {
|
|
1573
|
+
console.log(`\u2717 Existing integration uses project ${existing.projectId}, but the verified project is ${stored.projectId}.`);
|
|
1574
|
+
if (!await confirm2("Replace the existing project configuration?")) return;
|
|
1575
|
+
}
|
|
1576
|
+
if (existing.avatarId && existing.avatarId !== avatarId.trim()) {
|
|
1577
|
+
console.log(`\u2717 Existing integration uses avatar ${existing.avatarId}, but the verified avatar is ${avatarId.trim()}.`);
|
|
1578
|
+
if (!await confirm2("Replace the existing avatar configuration?")) return;
|
|
1579
|
+
}
|
|
1580
|
+
try {
|
|
1581
|
+
if (dryRun) {
|
|
1582
|
+
const plan2 = planIntegration(project, {
|
|
1583
|
+
projectId: stored.projectId,
|
|
1584
|
+
publicKey: stored.publicKey ?? "",
|
|
1585
|
+
avatarId: avatarId.trim()
|
|
1586
|
+
});
|
|
1587
|
+
printDryRun(plan2);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const verifiedConnection = await verifyApiKeyRelationship(api, {
|
|
1591
|
+
userId: stored.userId,
|
|
1592
|
+
projectId: stored.projectId,
|
|
1593
|
+
apiKey: stored.apiKey
|
|
1594
|
+
});
|
|
1595
|
+
await verifyAvatar(api, {
|
|
1596
|
+
userId: stored.userId,
|
|
1597
|
+
projectId: stored.projectId,
|
|
1598
|
+
apiKey: stored.apiKey,
|
|
1599
|
+
avatarId
|
|
1600
|
+
});
|
|
1601
|
+
await createImportSession(api, {
|
|
1602
|
+
userId: stored.userId,
|
|
1603
|
+
projectId: stored.projectId,
|
|
1604
|
+
apiKey: stored.apiKey,
|
|
1605
|
+
avatarId
|
|
1606
|
+
});
|
|
1607
|
+
const shouldImport = await confirm2("Import this avatar into this website?");
|
|
1608
|
+
if (!shouldImport) {
|
|
1609
|
+
console.log("Avatar import cancelled. Your verified connection has been saved.");
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
const result = await importAvatar(api, {
|
|
1613
|
+
userId: stored.userId,
|
|
1614
|
+
projectId: stored.projectId,
|
|
1615
|
+
apiKey: stored.apiKey,
|
|
1616
|
+
avatarId
|
|
1617
|
+
});
|
|
1618
|
+
const publicKey = stored.publicKey ?? verifiedConnection.publicKey ?? result.publicKey;
|
|
1619
|
+
if (!publicKey) {
|
|
1620
|
+
console.log("\u2717 The verified project did not return a public SDK key. No files were changed.");
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
const plan = planIntegration(detectProject(cwd), {
|
|
1624
|
+
projectId: result.projectId,
|
|
1625
|
+
publicKey,
|
|
1626
|
+
avatarId: result.avatarId
|
|
1627
|
+
});
|
|
1628
|
+
if (!plan.sdkInstalled) {
|
|
1629
|
+
console.log(`Installing @asiyst/sdk with ${project.packageManager}...`);
|
|
1630
|
+
await installSdk(project);
|
|
1631
|
+
}
|
|
1632
|
+
const configured = applyIntegration(detectProject(cwd), {
|
|
1633
|
+
projectId: result.projectId,
|
|
1634
|
+
publicKey,
|
|
1635
|
+
avatarId: result.avatarId
|
|
1636
|
+
});
|
|
1637
|
+
const next = { ...stored, avatarId: result.avatarId, avatarName: result.avatarName, publicKey };
|
|
1638
|
+
await saveConnection(cwd, next);
|
|
1639
|
+
writeProjectMetadata(cwd, next);
|
|
1640
|
+
console.log("\u2713 Avatar verified");
|
|
1641
|
+
console.log("\u2713 Project verified");
|
|
1642
|
+
console.log("\u2713 API key verified");
|
|
1643
|
+
console.log("\u2713 SDK installed");
|
|
1644
|
+
console.log(`\u2713 Asiyst integration ${configured.componentAction === "unchanged" ? "already " : ""}configured`);
|
|
1645
|
+
console.log(`\u2713 Avatar ${result.alreadyImported ? "already imported" : "imported"}`);
|
|
1646
|
+
console.log(`
|
|
1647
|
+
Your Asiyst avatar is ready.`);
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
if (error instanceof ApiError) {
|
|
1650
|
+
if (error.code === "FORBIDDEN") console.log("\u2717 You do not have permission to import an avatar into this project.");
|
|
1651
|
+
else if (error.code === "USER_MISMATCH") console.log("\u2717 The avatar is not owned by the verified Asiyst user.");
|
|
1652
|
+
else if (error.code === "PROJECT_MISMATCH") console.log("\u2717 The avatar is not assigned to the verified project.");
|
|
1653
|
+
else if (error.code === "NOT_FOUND") console.log("\u2717 Project or avatar was not found.");
|
|
1654
|
+
else if (error.code === "CONFLICT" || error.code === "AVATAR_ALREADY_IMPORTED") console.log("\u2717 This avatar is already imported into the project.");
|
|
1655
|
+
else console.log(`\u2717 ${error.message}`);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
console.log(`\u2717 ${error instanceof Error ? error.message : "Unable to import the avatar."}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
async function avatarCommand(cwd = process.cwd(), argv = process.argv.slice(2)) {
|
|
1662
|
+
if (argv[0] === "import") {
|
|
1663
|
+
await avatarImportCommand(cwd, createApiClient(), parseAvatarId(argv));
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const stored = await loadConnection(cwd);
|
|
1667
|
+
if (!stored) {
|
|
1668
|
+
console.log("Connect your project to Asiyst to view avatar information.");
|
|
1669
|
+
const url = ASIIYST_WEB_URL;
|
|
522
1670
|
if (await openBrowser(url)) console.log(`Opening ${url}`);
|
|
523
|
-
else console.log(`Open
|
|
1671
|
+
else console.log(`Open this URL manually:
|
|
1672
|
+
${url}`);
|
|
524
1673
|
return;
|
|
525
1674
|
}
|
|
526
|
-
|
|
527
|
-
console.log(`
|
|
528
|
-
|
|
529
|
-
Status ${info.avatarStatus || "Not configured"}
|
|
530
|
-
|
|
531
|
-
Configure your avatar in the Asiyst dashboard.`);
|
|
1675
|
+
console.log(`Avatar configuration is managed at ${ASIIYST_WEB_URL}.`);
|
|
1676
|
+
if (!await openBrowser(ASIIYST_WEB_URL)) console.log(`Open this URL manually:
|
|
1677
|
+
${ASIIYST_WEB_URL}`);
|
|
532
1678
|
}
|
|
533
1679
|
|
|
534
|
-
// src/
|
|
535
|
-
import {
|
|
536
|
-
import {
|
|
1680
|
+
// src/commands/interactive.ts
|
|
1681
|
+
import { stdin as stdin3, stdout as stdout4 } from "process";
|
|
1682
|
+
import { emitKeypressEvents as emitKeypressEvents3 } from "readline";
|
|
537
1683
|
|
|
538
|
-
// src/
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1684
|
+
// src/commands/health.ts
|
|
1685
|
+
async function healthCommand(api = createApiClient(), cwd = process.cwd()) {
|
|
1686
|
+
printHeader("Asiyst Health Check");
|
|
1687
|
+
const project = detectProject(cwd);
|
|
1688
|
+
let healthy = true;
|
|
1689
|
+
ok("Project detected", project.framework);
|
|
1690
|
+
if (project.sdkVersion) ok("SDK installed", project.sdkVersion);
|
|
1691
|
+
else {
|
|
1692
|
+
fail("SDK installed", "Run the avatar import command to install it");
|
|
1693
|
+
healthy = false;
|
|
1694
|
+
}
|
|
1695
|
+
try {
|
|
1696
|
+
await api.health();
|
|
1697
|
+
ok("Asiyst API reachable");
|
|
1698
|
+
} catch {
|
|
1699
|
+
fail("Asiyst API reachable", "Unable to reach Asiyst API");
|
|
1700
|
+
console.log(`
|
|
1701
|
+
Health: ${warning(symbols.warning)} Action required`);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
const stored = await loadConnection(cwd);
|
|
1705
|
+
if (!stored?.apiKey || !stored.userId || !stored.projectId) {
|
|
1706
|
+
console.log(`
|
|
1707
|
+
Health: ${warning(symbols.warning)} Action required`);
|
|
1708
|
+
console.log("Run `asiyst connect` to connect this project.");
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
ok("Authentication valid");
|
|
1712
|
+
try {
|
|
1713
|
+
await verifyApiKeyRelationship(api, {
|
|
1714
|
+
userId: stored.userId,
|
|
1715
|
+
projectId: stored.projectId,
|
|
1716
|
+
apiKey: stored.apiKey
|
|
1717
|
+
});
|
|
1718
|
+
ok("Project authorized");
|
|
1719
|
+
const metadata = readProjectMetadata(cwd);
|
|
1720
|
+
if (metadata?.avatarId) {
|
|
1721
|
+
await verifyAvatar(api, {
|
|
1722
|
+
userId: stored.userId,
|
|
1723
|
+
projectId: stored.projectId,
|
|
1724
|
+
apiKey: stored.apiKey,
|
|
1725
|
+
avatarId: metadata.avatarId
|
|
1726
|
+
});
|
|
1727
|
+
ok("Avatar verified", metadata.avatarName ?? metadata.avatarId);
|
|
1728
|
+
} else {
|
|
1729
|
+
fail("Avatar", "No imported avatar is recorded for this project");
|
|
1730
|
+
healthy = false;
|
|
1731
|
+
}
|
|
1732
|
+
if (metadata?.projectId === stored.projectId && metadata.publicKey) ok("Local configuration", "Project and public key present");
|
|
1733
|
+
else {
|
|
1734
|
+
fail("Local configuration", "Project metadata is incomplete");
|
|
1735
|
+
healthy = false;
|
|
553
1736
|
}
|
|
1737
|
+
if (project.sdkVersion && metadata?.avatarId) ok("Website integration", "SDK and avatar are configured");
|
|
1738
|
+
else {
|
|
1739
|
+
fail("Website integration", "Run `asiyst avatar import`");
|
|
1740
|
+
healthy = false;
|
|
1741
|
+
}
|
|
1742
|
+
console.log(`
|
|
1743
|
+
Health: ${healthy ? `${success(symbols.connected)} Healthy` : `${warning(symbols.warning)} Action required`}`);
|
|
1744
|
+
} catch {
|
|
1745
|
+
fail("Project authorization", "Run `asiyst connect` to reconnect");
|
|
1746
|
+
console.log(`
|
|
1747
|
+
Health: ${warning(symbols.warning)} Action required`);
|
|
554
1748
|
}
|
|
555
|
-
return "0.2.0";
|
|
556
1749
|
}
|
|
557
1750
|
|
|
558
1751
|
// src/update/check.ts
|
|
1752
|
+
import { execFileSync } from "child_process";
|
|
1753
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
559
1754
|
var PACKAGE_NAME = "@asiyst/cli";
|
|
560
1755
|
var REGISTRY_URL = "https://registry.npmjs.org/%40asiyst%2Fcli/latest";
|
|
561
1756
|
var TIMEOUT_MS = 800;
|
|
@@ -627,7 +1822,7 @@ function detectInstallKind(executablePath = process.argv[1] ?? "", env = process
|
|
|
627
1822
|
}
|
|
628
1823
|
function installedVersion(packageRoot) {
|
|
629
1824
|
try {
|
|
630
|
-
const value = JSON.parse(
|
|
1825
|
+
const value = JSON.parse(readFileSync7(`${packageRoot}/package.json`, "utf8"));
|
|
631
1826
|
return value && typeof value === "object" && typeof value.version === "string" ? value.version : void 0;
|
|
632
1827
|
} catch {
|
|
633
1828
|
return void 0;
|
|
@@ -650,18 +1845,18 @@ function installLatest(latestVersion, kind, executablePath = process.argv[1] ??
|
|
|
650
1845
|
|
|
651
1846
|
// src/commands/update.ts
|
|
652
1847
|
async function updateCommand() {
|
|
1848
|
+
printHeader("Asiyst CLI Update");
|
|
653
1849
|
const result = await checkForUpdate();
|
|
654
1850
|
if (!result.latestVersion || compareVersions(result.latestVersion, result.currentVersion) <= 0) {
|
|
655
|
-
console.log(
|
|
1851
|
+
console.log(`${success(symbols.success)} Latest version
|
|
656
1852
|
|
|
657
1853
|
Current version: ${result.currentVersion}`);
|
|
658
1854
|
return;
|
|
659
1855
|
}
|
|
660
|
-
console.log(
|
|
661
|
-
Update available
|
|
1856
|
+
console.log(`${warning(symbols.warning)} Update available
|
|
662
1857
|
|
|
663
1858
|
Current version: ${result.currentVersion}
|
|
664
|
-
|
|
1859
|
+
Latest version: ${result.latestVersion}
|
|
665
1860
|
`);
|
|
666
1861
|
if (!process.stdin.isTTY || !await confirm(`Update Asiyst CLI from ${result.currentVersion} to ${result.latestVersion}?`)) {
|
|
667
1862
|
console.log("Update cancelled.");
|
|
@@ -678,7 +1873,7 @@ New version: ${result.latestVersion}
|
|
|
678
1873
|
if (!installed || compareVersions(installed, result.latestVersion) !== 0) {
|
|
679
1874
|
throw new Error("installed version could not be verified");
|
|
680
1875
|
}
|
|
681
|
-
console.log(
|
|
1876
|
+
console.log(`${success(symbols.success)} Verifying installation
|
|
682
1877
|
|
|
683
1878
|
Asiyst CLI updated from ${result.currentVersion} to ${installed}.
|
|
684
1879
|
Restart Asiyst to use the new version.`);
|
|
@@ -689,101 +1884,253 @@ ${error instanceof Error ? error.message : "Installation could not be verified."
|
|
|
689
1884
|
}
|
|
690
1885
|
|
|
691
1886
|
// src/commands/interactive.ts
|
|
692
|
-
function resolveInput(value) {
|
|
693
|
-
const input = value.trim().toLowerCase();
|
|
694
|
-
if (/^(connect|connect my website|connect this website|connect website|init)/.test(input)) return "connect";
|
|
695
|
-
if (/^(status|check status|check .*installation)/.test(input)) return "status";
|
|
696
|
-
if (/diagnos|doctor/.test(input)) return "diagnostics";
|
|
697
|
-
if (/dashboard/.test(input)) return "dashboard";
|
|
698
|
-
if (/avatar|configure my avatar|create avatar/.test(input)) return "avatar";
|
|
699
|
-
if (/update|upgrade/.test(input)) return "update";
|
|
700
|
-
if (/^(help|\?)$/.test(input)) return "help";
|
|
701
|
-
if (/^(version|--version)$/.test(input)) return "version";
|
|
702
|
-
return input;
|
|
703
|
-
}
|
|
704
1887
|
function interactiveHelp() {
|
|
705
|
-
console.log(
|
|
1888
|
+
console.log(`
|
|
1889
|
+
Asiyst CLI
|
|
1890
|
+
|
|
1891
|
+
Usage: asiyst [command]
|
|
1892
|
+
|
|
1893
|
+
Commands
|
|
1894
|
+
connect Connect this project to Asiyst
|
|
1895
|
+
status Show connection status
|
|
1896
|
+
avatar Manage and import avatars
|
|
1897
|
+
health Check Asiyst API connectivity
|
|
1898
|
+
disconnect Remove local connection information
|
|
1899
|
+
help Show available commands
|
|
1900
|
+
version Show CLI version
|
|
1901
|
+
|
|
1902
|
+
Also available
|
|
1903
|
+
trust Trust this project folder
|
|
1904
|
+
revoke-trust Revoke folder trust
|
|
1905
|
+
doctor Run diagnostics
|
|
1906
|
+
update Check for CLI updates
|
|
1907
|
+
|
|
1908
|
+
Examples
|
|
1909
|
+
asiyst connect
|
|
1910
|
+
asiyst status
|
|
1911
|
+
asiyst health
|
|
1912
|
+
asiyst avatar import
|
|
1913
|
+
asiyst avatar import <avatar-id>
|
|
1914
|
+
asiyst disconnect
|
|
1915
|
+
`);
|
|
706
1916
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1917
|
+
var COMMANDS = [
|
|
1918
|
+
{ input: "connect", label: "Connect this project to Asiyst" },
|
|
1919
|
+
{ input: "status", label: "Show connection status" },
|
|
1920
|
+
{ input: "avatar", label: "Manage avatars" },
|
|
1921
|
+
{ input: "avatar import", label: "Import an avatar" },
|
|
1922
|
+
{ input: "health", label: "Check Asiyst API health" },
|
|
1923
|
+
{ input: "disconnect", label: "Disconnect this project" },
|
|
1924
|
+
{ input: "help", label: "Show available commands" },
|
|
1925
|
+
{ input: "version", label: "Show CLI version" },
|
|
1926
|
+
{ input: "update", label: "Check for CLI updates" },
|
|
1927
|
+
{ input: "exit", label: "Exit the interactive CLI" }
|
|
1928
|
+
];
|
|
1929
|
+
function suggestions(value) {
|
|
1930
|
+
const query = value.trim().toLowerCase();
|
|
1931
|
+
if (!query) return COMMANDS;
|
|
1932
|
+
return COMMANDS.filter((command) => command.input.startsWith(query));
|
|
1933
|
+
}
|
|
1934
|
+
function readCommand() {
|
|
1935
|
+
if (!stdin3.isTTY || !stdout4.isTTY) return Promise.resolve(void 0);
|
|
1936
|
+
return new Promise((resolve6) => {
|
|
1937
|
+
let value = "";
|
|
1938
|
+
let active = 0;
|
|
1939
|
+
let settled = false;
|
|
1940
|
+
let renderedLines = 0;
|
|
1941
|
+
const wasRaw = Boolean(stdin3.isRaw);
|
|
1942
|
+
const clear = () => {
|
|
1943
|
+
if (renderedLines === 0) return;
|
|
1944
|
+
stdout4.write(`\x1B[${renderedLines}A\x1B[0J`);
|
|
1945
|
+
renderedLines = 0;
|
|
1946
|
+
};
|
|
1947
|
+
const render = () => {
|
|
1948
|
+
clear();
|
|
1949
|
+
const matches = suggestions(value);
|
|
1950
|
+
if (active >= matches.length) active = Math.max(0, matches.length - 1);
|
|
1951
|
+
const lines = [
|
|
1952
|
+
`${section("Command")} ${value || muted("type a command")}`,
|
|
1953
|
+
"",
|
|
1954
|
+
...matches.slice(0, 6).map((command, index) => `${index === active ? symbols.pointer : " "} ${command.input.padEnd(18)} ${muted(command.label)}`),
|
|
1955
|
+
"",
|
|
1956
|
+
muted("\u2191\u2193 select Enter run Esc cancel Ctrl+C exit")
|
|
1957
|
+
];
|
|
1958
|
+
stdout4.write(`${lines.join("\n")}
|
|
1959
|
+
`);
|
|
1960
|
+
renderedLines = lines.length;
|
|
1961
|
+
};
|
|
1962
|
+
const finish = (result) => {
|
|
1963
|
+
if (settled) return;
|
|
1964
|
+
settled = true;
|
|
1965
|
+
clear();
|
|
1966
|
+
stdin3.off("keypress", onKeypress);
|
|
1967
|
+
if (stdin3.isTTY) stdin3.setRawMode?.(wasRaw);
|
|
1968
|
+
stdin3.pause();
|
|
1969
|
+
stdout4.write("\n");
|
|
1970
|
+
resolve6(result);
|
|
1971
|
+
};
|
|
1972
|
+
const onKeypress = (chunk, key) => {
|
|
1973
|
+
if (key.ctrl && key.name === "c") {
|
|
1974
|
+
finish(void 0);
|
|
1975
|
+
process.exit(130);
|
|
1976
|
+
}
|
|
1977
|
+
if (key.name === "escape") return finish(void 0);
|
|
1978
|
+
if (key.name === "up") {
|
|
1979
|
+
const matches = suggestions(value);
|
|
1980
|
+
active = Math.max(0, active - 1);
|
|
1981
|
+
if (matches[active]) value = matches[active].input;
|
|
1982
|
+
return render();
|
|
1983
|
+
}
|
|
1984
|
+
if (key.name === "down") {
|
|
1985
|
+
const matches = suggestions(value);
|
|
1986
|
+
active = Math.min(Math.max(0, matches.length - 1), active + 1);
|
|
1987
|
+
if (matches[active]) value = matches[active].input;
|
|
1988
|
+
return render();
|
|
1989
|
+
}
|
|
1990
|
+
if (key.name === "backspace") {
|
|
1991
|
+
value = value.slice(0, -1);
|
|
1992
|
+
active = 0;
|
|
1993
|
+
return render();
|
|
1994
|
+
}
|
|
1995
|
+
if (key.name === "return" || key.name === "enter") {
|
|
1996
|
+
const matches = suggestions(value);
|
|
1997
|
+
const selected = value.trim() || matches[active]?.input;
|
|
1998
|
+
return finish(selected);
|
|
1999
|
+
}
|
|
2000
|
+
if (key.name === "tab") {
|
|
2001
|
+
const matches = suggestions(value);
|
|
2002
|
+
if (matches[active]) value = matches[active].input;
|
|
2003
|
+
return render();
|
|
2004
|
+
}
|
|
2005
|
+
const sequence = key.sequence || chunk;
|
|
2006
|
+
if (sequence && /^[\x20-\x7e]+$/.test(sequence)) {
|
|
2007
|
+
value += sequence;
|
|
2008
|
+
active = 0;
|
|
2009
|
+
return render();
|
|
2010
|
+
}
|
|
2011
|
+
if (key.name === "left" || key.name === "right") return;
|
|
2012
|
+
};
|
|
2013
|
+
emitKeypressEvents3(stdin3);
|
|
2014
|
+
stdin3.setRawMode?.(true);
|
|
2015
|
+
stdin3.resume();
|
|
2016
|
+
stdin3.on("keypress", onKeypress);
|
|
2017
|
+
render();
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
async function runInteractiveCommand(command) {
|
|
2021
|
+
const parts = command.trim().split(/\s+/).filter(Boolean);
|
|
2022
|
+
const root = parts[0];
|
|
2023
|
+
if (root === "exit" || root === "quit") return false;
|
|
2024
|
+
if (root === "connect") await connectCommand();
|
|
2025
|
+
else if (root === "status") await statusCommand();
|
|
2026
|
+
else if (root === "avatar") await avatarCommand(process.cwd(), parts.slice(1));
|
|
2027
|
+
else if (root === "health") await healthCommand();
|
|
2028
|
+
else if (root === "disconnect") await disconnectCommand();
|
|
2029
|
+
else if (root === "help") interactiveHelp();
|
|
2030
|
+
else if (root === "version") console.log(readCurrentVersion());
|
|
2031
|
+
else if (root === "update") await updateCommand();
|
|
2032
|
+
else {
|
|
2033
|
+
console.log(`
|
|
2034
|
+
${symbols.error} Unknown command: ${command}`);
|
|
2035
|
+
console.log(`Try ${muted("help")} to see available commands.`);
|
|
2036
|
+
}
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
function renderLogo() {
|
|
2040
|
+
const lines = [
|
|
2041
|
+
" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
2042
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
|
|
2043
|
+
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
2044
|
+
"\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
2045
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
2046
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
|
|
719
2047
|
];
|
|
2048
|
+
console.log(title(lines.join("\n")));
|
|
2049
|
+
console.log();
|
|
2050
|
+
}
|
|
2051
|
+
function renderWelcome(project, connected) {
|
|
2052
|
+
console.log(muted("Developer Assistant CLI"));
|
|
2053
|
+
console.log();
|
|
2054
|
+
console.log("Welcome to Asiyst CLI.");
|
|
2055
|
+
console.log("Connect your project, manage your avatar, and verify your website integration.");
|
|
2056
|
+
console.log();
|
|
2057
|
+
console.log(section("Project"));
|
|
2058
|
+
console.log(` ${project && project.packageJson?.name ? String(project.packageJson.name) : process.cwd().split(/[\\/]/).pop() || "current-project"}`);
|
|
2059
|
+
console.log(` ${connected ? `${symbols.connected} Connected` : `${symbols.disconnected} Not connected`}`);
|
|
2060
|
+
console.log();
|
|
2061
|
+
console.log(section("Environment"));
|
|
2062
|
+
if (project) {
|
|
2063
|
+
console.log(` ${project.framework} \xB7 ${project.language} \xB7 ${project.packageManager}`);
|
|
2064
|
+
console.log(` SDK: ${project.sdkVersion ? project.sdkVersion : "not installed"}`);
|
|
2065
|
+
} else {
|
|
2066
|
+
console.log(" Unable to detect project metadata automatically.");
|
|
2067
|
+
}
|
|
2068
|
+
console.log();
|
|
2069
|
+
console.log(muted("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2070
|
+
console.log();
|
|
2071
|
+
}
|
|
2072
|
+
async function interactiveHome() {
|
|
2073
|
+
const project = detectProject();
|
|
2074
|
+
const initialConnection = await loadConnection(process.cwd());
|
|
2075
|
+
console.clear();
|
|
2076
|
+
renderLogo();
|
|
2077
|
+
renderWelcome(project, Boolean(initialConnection));
|
|
720
2078
|
for (; ; ) {
|
|
721
|
-
const
|
|
722
|
-
if (
|
|
723
|
-
if (result.type !== "selected") continue;
|
|
724
|
-
const command = resolveInput(result.input || String(result.value));
|
|
725
|
-
if (command === "exit" || command === "quit") {
|
|
726
|
-
const confirmed = await selectOption("Exit Asiyst?", [
|
|
727
|
-
{ label: "Yes", value: true },
|
|
728
|
-
{ label: "No", value: false }
|
|
729
|
-
]);
|
|
730
|
-
if (confirmed.type === "selected") {
|
|
731
|
-
if (confirmed.value) break;
|
|
732
|
-
}
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
if (command === "connect") await initCommand();
|
|
736
|
-
else if (command === "status") await statusCommand();
|
|
737
|
-
else if (command === "diagnostics") await doctorCommand();
|
|
738
|
-
else if (command === "dashboard") await dashboardCommand();
|
|
739
|
-
else if (command === "avatar") await avatarCommand();
|
|
740
|
-
else if (command === "update") await updateCommand();
|
|
741
|
-
else if (command === "logout") logoutCommand();
|
|
742
|
-
else if (command === "trust") await trustCommand();
|
|
743
|
-
else if (command === "revoke-trust") revokeTrustCommand();
|
|
744
|
-
else if (command === "help") interactiveHelp();
|
|
745
|
-
else if (command === "version") console.log(readCurrentVersion());
|
|
746
|
-
else console.log(`I don't recognize that command: ${result.input}
|
|
747
|
-
Run 'asiyst help' for available commands.`);
|
|
2079
|
+
const command = await readCommand();
|
|
2080
|
+
if (!command || !await runInteractiveCommand(command)) break;
|
|
748
2081
|
}
|
|
749
2082
|
}
|
|
750
2083
|
|
|
751
2084
|
// src/index.ts
|
|
752
2085
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2086
|
+
import { realpathSync } from "fs";
|
|
753
2087
|
var CLI_VERSION = readCurrentVersion();
|
|
754
2088
|
function help() {
|
|
755
2089
|
interactiveHelp();
|
|
756
2090
|
}
|
|
757
2091
|
async function main(argv = process.argv.slice(2)) {
|
|
758
2092
|
const command = argv[0] || "";
|
|
2093
|
+
const projectId = parseProjectIdArgument(argv);
|
|
759
2094
|
if (command !== "update" && !argv.includes("--version") && !argv.includes("-v") && !argv.includes("--help") && !argv.includes("-h")) {
|
|
760
2095
|
await notifyIfUpdateAvailable();
|
|
761
2096
|
}
|
|
762
2097
|
if (command === "--help" || command === "-h") return help();
|
|
763
2098
|
if (command === "--version" || command === "-v") return console.log(CLI_VERSION);
|
|
764
|
-
if (command === "init"
|
|
2099
|
+
if (command === "init") return connectCommand(process.cwd(), void 0, projectId);
|
|
2100
|
+
if (command === "connect") return connectCommand(process.cwd(), void 0, projectId);
|
|
765
2101
|
if (command === "login") return loginCommand();
|
|
766
2102
|
if (command === "logout") return logoutCommand();
|
|
2103
|
+
if (command === "disconnect") return disconnectCommand();
|
|
767
2104
|
if (command === "status") return statusCommand();
|
|
768
2105
|
if (command === "verify") return verifyCommand();
|
|
2106
|
+
if (command === "health") return healthCommand();
|
|
769
2107
|
if (command === "doctor") return doctorCommand();
|
|
770
2108
|
if (command === "diagnostics") return doctorCommand();
|
|
771
2109
|
if (command === "dashboard") return dashboardCommand();
|
|
772
|
-
if (command === "avatar") return avatarCommand();
|
|
2110
|
+
if (command === "avatar") return avatarCommand(process.cwd(), argv.slice(1));
|
|
773
2111
|
if (command === "trust") return trustCommand();
|
|
774
2112
|
if (command === "revoke-trust") return revokeTrustCommand();
|
|
775
2113
|
if (command === "update") return updateCommand();
|
|
776
2114
|
if (command === "help") return help();
|
|
777
|
-
if (command === "version") return console.log(CLI_VERSION
|
|
2115
|
+
if (command === "version") return console.log(`Asiyst CLI v${CLI_VERSION}
|
|
2116
|
+
\u2713 Latest version`);
|
|
778
2117
|
if (!process.stdin.isTTY) {
|
|
779
|
-
|
|
780
|
-
projectChecks(project);
|
|
781
|
-
console.log("\nRun `npx @asiyst/cli connect` to connect this project, or `npx @asiyst/cli --help` for commands.");
|
|
2118
|
+
help();
|
|
782
2119
|
return;
|
|
783
2120
|
}
|
|
784
2121
|
return interactiveHome();
|
|
785
2122
|
}
|
|
786
|
-
|
|
2123
|
+
function isDirectExecution() {
|
|
2124
|
+
if (!process.argv[1]) return false;
|
|
2125
|
+
try {
|
|
2126
|
+
const entryPath = realpathSync(fileURLToPath2(import.meta.url));
|
|
2127
|
+
const execPath = realpathSync(process.argv[1]);
|
|
2128
|
+
return entryPath.toLowerCase() === execPath.toLowerCase();
|
|
2129
|
+
} catch {
|
|
2130
|
+
return true;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
if (isDirectExecution()) {
|
|
787
2134
|
main().catch((error) => {
|
|
788
2135
|
console.error(`
|
|
789
2136
|
Error: ${error instanceof Error ? error.message : "command failed"}`);
|
|
@@ -792,6 +2139,7 @@ Error: ${error instanceof Error ? error.message : "command failed"}`);
|
|
|
792
2139
|
}
|
|
793
2140
|
export {
|
|
794
2141
|
CLI_VERSION,
|
|
2142
|
+
connectCommand as initCommand,
|
|
795
2143
|
main
|
|
796
2144
|
};
|
|
797
2145
|
//# sourceMappingURL=index.js.map
|