@asiyst/cli 1.1.1 → 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 +4 -2
- package/dist/index.js +966 -263
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,62 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/config/api.ts
|
|
4
|
-
var PRODUCTION_API_ORIGIN = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
|
|
5
|
-
var CLI_API_BASE_URL = PRODUCTION_API_ORIGIN;
|
|
6
|
-
var VERIFY_KEY_PATH = "/auth/api-key/verify";
|
|
7
|
-
var VERIFY_KEY_URL = `${CLI_API_BASE_URL}${VERIFY_KEY_PATH}`;
|
|
8
|
-
var ASIYST_WEB_URL = "https://asiyst.com";
|
|
9
|
-
var ASIIYST_WEB_URL = ASIYST_WEB_URL;
|
|
10
|
-
var REQUEST_TIMEOUT_MS = 15e3;
|
|
11
|
-
function readEnv(env, ...keys) {
|
|
12
|
-
for (const key of keys) {
|
|
13
|
-
const value = env[key]?.trim();
|
|
14
|
-
if (value) return value;
|
|
15
|
-
}
|
|
16
|
-
return void 0;
|
|
17
|
-
}
|
|
18
|
-
function isLocalUrl(value) {
|
|
19
|
-
try {
|
|
20
|
-
const url = new URL(value);
|
|
21
|
-
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
22
|
-
} catch {
|
|
23
|
-
return /localhost|127\.0\.0\.1|::1/i.test(value);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function resolveApiBaseUrl(env = process.env) {
|
|
27
|
-
const explicit = readEnv(env, "ASIYST_API_URL", "ASIIYST_API_URL");
|
|
28
|
-
const development = readEnv(env, "ASIYST_API_MODE", "ASIIYST_API_MODE") === "development";
|
|
29
|
-
if (explicit) {
|
|
30
|
-
if (isLocalUrl(explicit)) {
|
|
31
|
-
return CLI_API_BASE_URL;
|
|
32
|
-
}
|
|
33
|
-
return explicit.replace(/\/+$/, "");
|
|
34
|
-
}
|
|
35
|
-
if (development) {
|
|
36
|
-
return CLI_API_BASE_URL;
|
|
37
|
-
}
|
|
38
|
-
return CLI_API_BASE_URL;
|
|
39
|
-
}
|
|
40
|
-
function isDebugEnabled(env = process.env) {
|
|
41
|
-
return readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "1" || readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "true";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
3
|
// src/config/ids.ts
|
|
45
|
-
var
|
|
46
|
-
var
|
|
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}$/;
|
|
47
7
|
var AVATAR_ID_PATTERN = /^[A-Za-z0-9]{10}$/;
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
return PUBLIC_IDENTIFIER_PATTERN.test(trimmed);
|
|
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());
|
|
57
13
|
}
|
|
58
14
|
function isValidUserId(value) {
|
|
59
|
-
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value
|
|
15
|
+
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value);
|
|
60
16
|
}
|
|
61
17
|
function isValidAvatarId(value) {
|
|
62
18
|
return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && AVATAR_ID_PATTERN.test(value.trim());
|
|
@@ -91,6 +47,8 @@ function errorCodeFromStatus(status, bodyCode) {
|
|
|
91
47
|
if (bodyCode === "INVALID_API_KEY") return "INVALID_API_KEY";
|
|
92
48
|
if (bodyCode === "API_KEY_REVOKED") return "API_KEY_REVOKED";
|
|
93
49
|
if (bodyCode === "FORBIDDEN") return "FORBIDDEN";
|
|
50
|
+
if (bodyCode === "USER_MISMATCH") return "USER_MISMATCH";
|
|
51
|
+
if (bodyCode === "PROJECT_MISMATCH") return "PROJECT_MISMATCH";
|
|
94
52
|
if (bodyCode === "PROJECT_NOT_FOUND") return "PROJECT_NOT_FOUND";
|
|
95
53
|
if (bodyCode === "AVATAR_NOT_FOUND") return "AVATAR_NOT_FOUND";
|
|
96
54
|
if (bodyCode === "AVATAR_ALREADY_IMPORTED") return "AVATAR_ALREADY_IMPORTED";
|
|
@@ -113,6 +71,8 @@ function friendlyApiMessage(error, endpointUrl) {
|
|
|
113
71
|
if (error.code === "FORBIDDEN") {
|
|
114
72
|
return "\u2717 This API key does not have permission to access this resource.";
|
|
115
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.";
|
|
116
76
|
if (error.code === "NOT_FOUND") {
|
|
117
77
|
return endpointUrl ? `\u2717 Asiyst API endpoint was not found.
|
|
118
78
|
Verify that the CLI is using:
|
|
@@ -131,68 +91,133 @@ ${endpointUrl}` : "\u2717 Asiyst API endpoint was not found.";
|
|
|
131
91
|
return "\u2717 Asiyst request failed.";
|
|
132
92
|
}
|
|
133
93
|
|
|
134
|
-
// src/api/
|
|
135
|
-
function
|
|
136
|
-
|
|
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;
|
|
137
108
|
}
|
|
138
|
-
function
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
function parseVerifyKeyResponse(value, apiKey) {
|
|
148
|
-
const body = asRecord(value);
|
|
149
|
-
if (!body) throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
150
|
-
if (body.valid === false) {
|
|
151
|
-
const code = typeof body.code === "string" ? body.code : void 0;
|
|
152
|
-
if (code === "API_KEY_REVOKED" || body.revoked === true) {
|
|
153
|
-
throw new ApiError("This API key has been revoked.", 401, "API_KEY_REVOKED");
|
|
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;
|
|
154
117
|
}
|
|
155
|
-
throw new ApiError("The Asiyst API key was rejected.", 401, "INVALID_API_KEY");
|
|
156
118
|
}
|
|
157
|
-
|
|
158
|
-
|
|
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);
|
|
159
127
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
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");
|
|
166
133
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
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");
|
|
171
180
|
return {
|
|
172
|
-
projectId,
|
|
173
|
-
projectName: typeof projectName === "string" ? projectName : void 0,
|
|
174
|
-
website: typeof website === "string" ? website : void 0,
|
|
175
|
-
publicKey: typeof publicKey === "string" ? publicKey : void 0,
|
|
176
181
|
apiKey,
|
|
177
|
-
userId
|
|
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")
|
|
178
187
|
};
|
|
179
188
|
}
|
|
180
|
-
async function
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const
|
|
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", {
|
|
184
195
|
method: "POST",
|
|
185
|
-
headers: {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return
|
|
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") };
|
|
196
221
|
}
|
|
197
222
|
|
|
198
223
|
// src/browser/open.ts
|
|
@@ -210,6 +235,47 @@ async function openBrowser(url) {
|
|
|
210
235
|
}
|
|
211
236
|
}
|
|
212
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
|
+
|
|
213
279
|
// src/config/credentials.ts
|
|
214
280
|
import { execFile as execFile2 } from "child_process";
|
|
215
281
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -252,11 +318,11 @@ async function dpapiProtect(plaintext) {
|
|
|
252
318
|
"[Convert]::ToBase64String($protected)"
|
|
253
319
|
].join("; ");
|
|
254
320
|
try {
|
|
255
|
-
const { stdout:
|
|
321
|
+
const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
256
322
|
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: plaintext },
|
|
257
323
|
windowsHide: true
|
|
258
324
|
});
|
|
259
|
-
const value =
|
|
325
|
+
const value = stdout5.trim();
|
|
260
326
|
return value || void 0;
|
|
261
327
|
} catch {
|
|
262
328
|
return void 0;
|
|
@@ -271,11 +337,11 @@ async function dpapiUnprotect(payload) {
|
|
|
271
337
|
"[System.Text.Encoding]::UTF8.GetString($bytes)"
|
|
272
338
|
].join("; ");
|
|
273
339
|
try {
|
|
274
|
-
const { stdout:
|
|
340
|
+
const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
275
341
|
env: { ...process.env, ASIYST_DPAPI_PAYLOAD: payload },
|
|
276
342
|
windowsHide: true
|
|
277
343
|
});
|
|
278
|
-
const value =
|
|
344
|
+
const value = stdout5.trim();
|
|
279
345
|
return value || void 0;
|
|
280
346
|
} catch {
|
|
281
347
|
return void 0;
|
|
@@ -371,6 +437,7 @@ function readProjectMetadata(cwd) {
|
|
|
371
437
|
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0,
|
|
372
438
|
userId: typeof body.userId === "string" ? body.userId : void 0,
|
|
373
439
|
avatarId: typeof body.avatarId === "string" ? body.avatarId : void 0,
|
|
440
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
|
|
374
441
|
connected: body.connected === true
|
|
375
442
|
};
|
|
376
443
|
} catch {
|
|
@@ -389,6 +456,7 @@ function writeProjectMetadata(cwd, connection) {
|
|
|
389
456
|
publicKey: connection.publicKey,
|
|
390
457
|
userId: connection.userId,
|
|
391
458
|
avatarId: connection.avatarId,
|
|
459
|
+
avatarName: connection.avatarName,
|
|
392
460
|
connected: true
|
|
393
461
|
};
|
|
394
462
|
writeFileSync2(projectConfigPath(cwd), `${JSON.stringify(next, null, 2)}
|
|
@@ -411,8 +479,8 @@ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
|
411
479
|
import { resolve as resolve3 } from "path";
|
|
412
480
|
function dependencyVersion(pkg) {
|
|
413
481
|
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
414
|
-
for (const
|
|
415
|
-
const values = pkg[
|
|
482
|
+
for (const section2 of sections) {
|
|
483
|
+
const values = pkg[section2];
|
|
416
484
|
if (values && typeof values === "object" && "@asiyst/sdk" in values) {
|
|
417
485
|
const version = values["@asiyst/sdk"];
|
|
418
486
|
return typeof version === "string" ? version : void 0;
|
|
@@ -465,9 +533,58 @@ function detectProject(cwd = process.cwd()) {
|
|
|
465
533
|
};
|
|
466
534
|
}
|
|
467
535
|
|
|
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"
|
|
547
|
+
};
|
|
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"
|
|
558
|
+
};
|
|
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();
|
|
583
|
+
}
|
|
584
|
+
|
|
468
585
|
// src/ui/output.ts
|
|
469
|
-
var ok = (label, detail = "") => console.log(
|
|
470
|
-
var fail = (label, detail = "") => console.log(
|
|
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}` : ""}`);
|
|
471
588
|
function projectChecks(project) {
|
|
472
589
|
project.packageJson ? ok("Project detected", typeof project.packageJson.name === "string" ? project.packageJson.name : project.cwd) : fail("Project not detected", "package.json is missing");
|
|
473
590
|
if (project.framework === "Unknown") fail("Framework detected", "No supported project type was identified.");
|
|
@@ -475,14 +592,23 @@ function projectChecks(project) {
|
|
|
475
592
|
ok("Language detected", project.language);
|
|
476
593
|
ok("Node.js detected", process.version);
|
|
477
594
|
ok("Package manager detected", project.packageManager);
|
|
478
|
-
project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : console.log(
|
|
479
|
-
}
|
|
480
|
-
function
|
|
481
|
-
console.log("
|
|
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}`);
|
|
482
608
|
}
|
|
483
609
|
|
|
484
610
|
// src/ui/selector.ts
|
|
485
|
-
import { stdin, stdout } from "process";
|
|
611
|
+
import { stdin, stdout as stdout2 } from "process";
|
|
486
612
|
import { clearLine, cursorTo, emitKeypressEvents, moveCursor } from "readline";
|
|
487
613
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
488
614
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
@@ -527,12 +653,12 @@ function renderSelectorFrame(output, previousLineCount, lines) {
|
|
|
527
653
|
return lines.length;
|
|
528
654
|
}
|
|
529
655
|
function restoreTerminal(wasRaw) {
|
|
530
|
-
|
|
656
|
+
stdout2.write(SHOW_CURSOR);
|
|
531
657
|
if (stdin.isTTY) stdin.setRawMode?.(wasRaw);
|
|
532
658
|
}
|
|
533
|
-
function selectOption(
|
|
534
|
-
if (!stdin.isTTY || !
|
|
535
|
-
return new Promise((
|
|
659
|
+
function selectOption(title2, options) {
|
|
660
|
+
if (!stdin.isTTY || !stdout2.isTTY) return Promise.resolve({ type: "cancelled" });
|
|
661
|
+
return new Promise((resolve6) => {
|
|
536
662
|
let active = 0;
|
|
537
663
|
let renderedLines = 0;
|
|
538
664
|
let settled = false;
|
|
@@ -542,24 +668,24 @@ function selectOption(title, options) {
|
|
|
542
668
|
if (settled) return;
|
|
543
669
|
settled = true;
|
|
544
670
|
stdin.off("keypress", onKeypress);
|
|
545
|
-
clearSelectorFrame(
|
|
671
|
+
clearSelectorFrame(stdout2, renderedLines);
|
|
546
672
|
restoreTerminal(wasRaw);
|
|
547
673
|
stdin.pause();
|
|
548
|
-
if (confirmation)
|
|
674
|
+
if (confirmation) stdout2.write(`${confirmation}
|
|
549
675
|
`);
|
|
550
|
-
|
|
676
|
+
resolve6(result);
|
|
551
677
|
};
|
|
552
678
|
const render = () => {
|
|
553
|
-
const lines = [
|
|
554
|
-
renderedLines = renderSelectorFrame(
|
|
679
|
+
const lines = [title2, "", ...enabled.map((option, index) => `${index === active ? "\u276F" : " "} ${option.label}`)];
|
|
680
|
+
renderedLines = renderSelectorFrame(stdout2, renderedLines, lines);
|
|
555
681
|
};
|
|
556
682
|
const onKeypress = (_value, key) => {
|
|
557
683
|
if (!key) return;
|
|
558
684
|
if (key.ctrl && key.name === "c") {
|
|
559
|
-
clearSelectorFrame(
|
|
685
|
+
clearSelectorFrame(stdout2, renderedLines);
|
|
560
686
|
restoreTerminal(wasRaw);
|
|
561
687
|
stdin.pause();
|
|
562
|
-
|
|
688
|
+
stdout2.write("\n");
|
|
563
689
|
process.exit(130);
|
|
564
690
|
}
|
|
565
691
|
if (key.name === "escape") return finish({ type: "cancelled" });
|
|
@@ -576,30 +702,30 @@ function selectOption(title, options) {
|
|
|
576
702
|
emitKeypressEvents(stdin);
|
|
577
703
|
stdin.setRawMode?.(true);
|
|
578
704
|
stdin.resume();
|
|
579
|
-
|
|
705
|
+
stdout2.write(HIDE_CURSOR);
|
|
580
706
|
stdin.on("keypress", onKeypress);
|
|
581
707
|
render();
|
|
582
708
|
});
|
|
583
709
|
}
|
|
584
710
|
|
|
585
711
|
// src/ui/secret.ts
|
|
586
|
-
import { stdin as stdin2, stdout as
|
|
712
|
+
import { stdin as stdin2, stdout as stdout3 } from "process";
|
|
587
713
|
import { emitKeypressEvents as emitKeypressEvents2 } from "readline";
|
|
588
714
|
async function readSecret(prompt) {
|
|
589
715
|
if (!stdin2.isTTY) return void 0;
|
|
590
|
-
return new Promise((
|
|
716
|
+
return new Promise((resolve6) => {
|
|
591
717
|
let value = "";
|
|
592
718
|
let settled = false;
|
|
593
719
|
const wasRaw = Boolean(stdin2.isRaw);
|
|
594
|
-
|
|
720
|
+
stdout3.write(prompt);
|
|
595
721
|
const finish = (result) => {
|
|
596
722
|
if (settled) return;
|
|
597
723
|
settled = true;
|
|
598
724
|
stdin2.off("keypress", onKeypress);
|
|
599
725
|
if (stdin2.isTTY) stdin2.setRawMode?.(wasRaw);
|
|
600
726
|
stdin2.pause();
|
|
601
|
-
|
|
602
|
-
|
|
727
|
+
stdout3.write("\n");
|
|
728
|
+
resolve6(result);
|
|
603
729
|
};
|
|
604
730
|
const onKeypress = (chunk, key) => {
|
|
605
731
|
if (!key) return;
|
|
@@ -624,17 +750,17 @@ async function readSecret(prompt) {
|
|
|
624
750
|
}
|
|
625
751
|
async function readInput(prompt) {
|
|
626
752
|
if (!stdin2.isTTY) return void 0;
|
|
627
|
-
return new Promise((
|
|
753
|
+
return new Promise((resolve6) => {
|
|
628
754
|
let value = "";
|
|
629
755
|
const onData = (chunk) => {
|
|
630
756
|
value += String(chunk);
|
|
631
757
|
const newline = value.search(/[\r\n]/);
|
|
632
758
|
if (newline >= 0) {
|
|
633
759
|
stdin2.off("data", onData);
|
|
634
|
-
|
|
760
|
+
resolve6(value.slice(0, newline).trim());
|
|
635
761
|
}
|
|
636
762
|
};
|
|
637
|
-
|
|
763
|
+
stdout3.write(prompt);
|
|
638
764
|
stdin2.resume();
|
|
639
765
|
stdin2.on("data", onData);
|
|
640
766
|
});
|
|
@@ -671,14 +797,19 @@ function revokeTrust(cwd) {
|
|
|
671
797
|
}
|
|
672
798
|
|
|
673
799
|
// src/api/client.ts
|
|
674
|
-
function
|
|
800
|
+
function asRecord(value) {
|
|
675
801
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
676
802
|
}
|
|
677
803
|
function bodyErrorCode(body) {
|
|
678
|
-
const
|
|
679
|
-
const code =
|
|
804
|
+
const record2 = asRecord(body);
|
|
805
|
+
const code = record2?.code ?? record2?.errorCode ?? record2?.error ?? asRecord(record2?.error)?.code;
|
|
680
806
|
return typeof code === "string" ? code : void 0;
|
|
681
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
|
+
}
|
|
682
813
|
function isAbortError(error) {
|
|
683
814
|
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
684
815
|
}
|
|
@@ -725,7 +856,7 @@ var ApiClient = class {
|
|
|
725
856
|
if (code === "API_KEY_REVOKED" || bodyErrorCode(body) === "API_KEY_REVOKED") {
|
|
726
857
|
throw new ApiError("This API key has been revoked.", response.status, "API_KEY_REVOKED");
|
|
727
858
|
}
|
|
728
|
-
throw new ApiError(`Asiyst API returned HTTP ${response.status}.`, response.status, code);
|
|
859
|
+
throw new ApiError(bodyErrorMessage(body) ?? `Asiyst API returned HTTP ${response.status}.`, response.status, code);
|
|
729
860
|
}
|
|
730
861
|
if (rawText && body === void 0) {
|
|
731
862
|
if (isDebugEnabled()) {
|
|
@@ -738,8 +869,8 @@ var ApiClient = class {
|
|
|
738
869
|
async health() {
|
|
739
870
|
try {
|
|
740
871
|
const body = await this.request("/health");
|
|
741
|
-
const
|
|
742
|
-
if (
|
|
872
|
+
const record2 = asRecord(body);
|
|
873
|
+
if (record2) return record2;
|
|
743
874
|
} catch {
|
|
744
875
|
}
|
|
745
876
|
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
@@ -775,11 +906,12 @@ Asiyst may read project files and modify Asiyst configuration during setup.`)) {
|
|
|
775
906
|
return true;
|
|
776
907
|
}
|
|
777
908
|
async function trustCommand(cwd = process.cwd()) {
|
|
909
|
+
printHeader("Project Trust", cwd);
|
|
778
910
|
await ensureTrusted(cwd);
|
|
779
911
|
}
|
|
780
912
|
function revokeTrustCommand(cwd = process.cwd()) {
|
|
781
913
|
revokeTrust(cwd);
|
|
782
|
-
console.log("\u2713
|
|
914
|
+
console.log("\u2713 Project trust revoked.");
|
|
783
915
|
}
|
|
784
916
|
|
|
785
917
|
// src/commands/connect.ts
|
|
@@ -793,6 +925,9 @@ async function retryOrCancel(message) {
|
|
|
793
925
|
}
|
|
794
926
|
function printApiFailure(error) {
|
|
795
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
|
+
}
|
|
796
931
|
return friendlyApiMessage(error, error.code === "NOT_FOUND" ? VERIFY_KEY_URL : void 0);
|
|
797
932
|
}
|
|
798
933
|
if (isDebugEnabled() && error instanceof Error) return `\u2717 ${error.message}`;
|
|
@@ -818,14 +953,14 @@ async function promptForUserId() {
|
|
|
818
953
|
return void 0;
|
|
819
954
|
}
|
|
820
955
|
if (!isValidUserId(value)) {
|
|
821
|
-
console.log("Invalid
|
|
956
|
+
console.log("Invalid Asiyst User ID.");
|
|
957
|
+
console.log("Expected: exactly 16 characters using letters, numbers, and hyphens only.");
|
|
822
958
|
return void 0;
|
|
823
959
|
}
|
|
824
960
|
return value.trim();
|
|
825
961
|
}
|
|
826
962
|
async function connectCommand(cwd = process.cwd(), api = createApiClient(), cliProjectId) {
|
|
827
|
-
|
|
828
|
-
${cwd}`);
|
|
963
|
+
printHeader("Connect project", cwd);
|
|
829
964
|
if (!await ensureTrusted(cwd)) return;
|
|
830
965
|
const project = detectProject(cwd);
|
|
831
966
|
projectChecks(project);
|
|
@@ -840,50 +975,65 @@ ${cwd}`);
|
|
|
840
975
|
console.log("Connection cancelled.");
|
|
841
976
|
return;
|
|
842
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
|
+
}
|
|
843
993
|
const projectIdArg = cliProjectId ?? parseProjectIdArgument(process.argv.slice(2));
|
|
844
994
|
if (projectIdArg === "") {
|
|
845
995
|
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
846
996
|
return;
|
|
847
997
|
}
|
|
848
998
|
let projectId = typeof projectIdArg === "string" ? projectIdArg.trim() : "";
|
|
849
|
-
if (!projectId)
|
|
850
|
-
projectId = await promptForProjectId() || "";
|
|
851
|
-
}
|
|
999
|
+
if (!projectId) projectId = await promptForProjectId() || "";
|
|
852
1000
|
if (!projectId) {
|
|
853
1001
|
console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
|
|
854
1002
|
return;
|
|
855
1003
|
}
|
|
856
|
-
if (!
|
|
857
|
-
console.log("Invalid Project ID.
|
|
1004
|
+
if (!isValidProjectId(projectId)) {
|
|
1005
|
+
console.log("Invalid Project ID. It must be exactly 24 characters.");
|
|
858
1006
|
return;
|
|
859
1007
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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;
|
|
866
1015
|
}
|
|
867
|
-
console.log("Register or log in, complete onboarding, create or select a project, then create an API key.");
|
|
868
1016
|
for (; ; ) {
|
|
869
1017
|
const apiKey = await readSecret("Paste your Asiyst API key: ");
|
|
870
1018
|
if (apiKey === void 0) {
|
|
871
1019
|
console.log("Connection cancelled.");
|
|
872
1020
|
return;
|
|
873
1021
|
}
|
|
874
|
-
if (!apiKey) {
|
|
875
|
-
fail("Invalid API key.");
|
|
1022
|
+
if (!isValidApiKey(apiKey)) {
|
|
1023
|
+
fail("Invalid API key. It must be exactly 32 characters.");
|
|
876
1024
|
continue;
|
|
877
1025
|
}
|
|
878
1026
|
try {
|
|
879
|
-
const connected = await
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
+
};
|
|
885
1036
|
ok("API key verified.");
|
|
886
|
-
ok("Project ID verified.");
|
|
887
1037
|
await saveConnection(cwd, finalConnection);
|
|
888
1038
|
writeProjectMetadata(cwd, finalConnection);
|
|
889
1039
|
ok("Project connected successfully.");
|
|
@@ -942,43 +1092,51 @@ async function logoutCommand() {
|
|
|
942
1092
|
async function statusCommand(cwd = process.cwd(), api = createApiClient()) {
|
|
943
1093
|
const stored = await loadConnection(cwd);
|
|
944
1094
|
if (!stored?.apiKey) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
${metadata.projectId}`);
|
|
950
|
-
if (metadata.userId) console.log(`User ID:
|
|
951
|
-
${metadata.userId}`);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
console.log("Connect your website to show stats.");
|
|
1095
|
+
printHeader("Asiyst Project Status");
|
|
1096
|
+
printProjectSummary(detectProject(cwd), false);
|
|
1097
|
+
console.log(`
|
|
1098
|
+
${warning(symbols.warning)} Not connected to Asiyst.`);
|
|
955
1099
|
return;
|
|
956
1100
|
}
|
|
957
1101
|
if (!stored.userId || !stored.projectId) {
|
|
958
|
-
console.log(
|
|
1102
|
+
console.log(`${symbols.error} Connection is missing a User ID or Project ID. Please run:
|
|
1103
|
+
asiyst connect`);
|
|
959
1104
|
return;
|
|
960
1105
|
}
|
|
961
1106
|
try {
|
|
962
|
-
const connected = await
|
|
1107
|
+
const connected = await verifyApiKeyRelationship(api, {
|
|
963
1108
|
userId: stored.userId,
|
|
964
|
-
projectId: stored.projectId
|
|
1109
|
+
projectId: stored.projectId,
|
|
1110
|
+
apiKey: stored.apiKey
|
|
965
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
|
+
}
|
|
966
1120
|
const projectId = stored.projectId || connected.projectId;
|
|
967
1121
|
const userId = stored.userId || connected.userId;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
User ID:
|
|
971
|
-
${userId}`);
|
|
1122
|
+
printHeader("Asiyst Project Status");
|
|
1123
|
+
printProjectSummary(detectProject(cwd), true, connected.projectName, connected.website);
|
|
972
1124
|
console.log(`
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
console.log(
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
${
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
${
|
|
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)}`);
|
|
1129
|
+
console.log(`
|
|
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.`);
|
|
982
1140
|
} catch (error) {
|
|
983
1141
|
if (error instanceof ApiError && error.code === "API_KEY_REVOKED") {
|
|
984
1142
|
console.log("\u2717 API key revoked.");
|
|
@@ -986,10 +1144,10 @@ ${connected.website}`);
|
|
|
986
1144
|
return;
|
|
987
1145
|
}
|
|
988
1146
|
if (error instanceof ApiError && (error.code === "INVALID_API_KEY" || error.code === "FORBIDDEN")) {
|
|
989
|
-
console.log(
|
|
1147
|
+
console.log(`${symbols.error} Connection invalid. Run \`asiyst connect\` to reconnect.`);
|
|
990
1148
|
return;
|
|
991
1149
|
}
|
|
992
|
-
console.log(
|
|
1150
|
+
console.log(`${symbols.error} Connection invalid. Run \`asiyst doctor\` for diagnostics.`);
|
|
993
1151
|
}
|
|
994
1152
|
}
|
|
995
1153
|
|
|
@@ -1013,8 +1171,21 @@ function parseAvatarImportResponse(value, projectId, userId, avatarId) {
|
|
|
1013
1171
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1014
1172
|
throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
|
|
1015
1173
|
}
|
|
1016
|
-
const
|
|
1174
|
+
const response = value;
|
|
1175
|
+
const body = response.data && typeof response.data === "object" && !Array.isArray(response.data) ? response.data : response;
|
|
1017
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
|
+
}
|
|
1018
1189
|
if (!imported) {
|
|
1019
1190
|
const code = typeof body.code === "string" ? body.code : "IMPORT_FAILED";
|
|
1020
1191
|
const supportedCode = ["AVATAR_NOT_FOUND", "AVATAR_ALREADY_IMPORTED", "FORBIDDEN", "PROJECT_NOT_FOUND", "CONFLICT"].includes(code) ? code : "IMPORT_FAILED";
|
|
@@ -1024,24 +1195,33 @@ function parseAvatarImportResponse(value, projectId, userId, avatarId) {
|
|
|
1024
1195
|
if (returnedAvatarId !== void 0 && returnedAvatarId !== avatarId) {
|
|
1025
1196
|
throw new ApiError("The API returned a different avatar than requested.", 200, "AVATAR_MISMATCH");
|
|
1026
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
|
+
}
|
|
1027
1206
|
return {
|
|
1028
1207
|
imported: true,
|
|
1029
1208
|
avatarId,
|
|
1030
1209
|
projectId,
|
|
1031
1210
|
userId,
|
|
1032
|
-
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0
|
|
1211
|
+
avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
|
|
1212
|
+
publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0
|
|
1033
1213
|
};
|
|
1034
1214
|
}
|
|
1035
1215
|
async function importAvatar(api, input) {
|
|
1036
1216
|
const userId = requireIdentifier(input.userId, "User ID", isValidUserId);
|
|
1037
|
-
const projectId = requireIdentifier(input.projectId, "Project ID",
|
|
1217
|
+
const projectId = requireIdentifier(input.projectId, "Project ID", isValidProjectId);
|
|
1038
1218
|
const avatarId = requireIdentifier(input.avatarId, "Avatar ID", isValidAvatarId);
|
|
1039
1219
|
const apiKey = input.apiKey.trim();
|
|
1040
|
-
if (!apiKey) throw new ApiError("
|
|
1220
|
+
if (!isValidApiKey(apiKey)) throw new ApiError("Invalid API key. It must be exactly 32 characters.", 401, "INVALID_API_KEY");
|
|
1041
1221
|
const value = await api.request(`/cli/projects/${encodeURIComponent(projectId)}/avatars/import`, {
|
|
1042
1222
|
method: "POST",
|
|
1043
1223
|
headers: {
|
|
1044
|
-
Authorization:
|
|
1224
|
+
Authorization: "Bearer " + apiKey,
|
|
1045
1225
|
"X-Asiyst-API-Key": apiKey
|
|
1046
1226
|
},
|
|
1047
1227
|
body: JSON.stringify({ userId, projectId, avatarId })
|
|
@@ -1083,7 +1263,7 @@ async function verifyCommand(cwd = process.cwd()) {
|
|
|
1083
1263
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1084
1264
|
import { fileURLToPath } from "url";
|
|
1085
1265
|
function readCurrentVersion() {
|
|
1086
|
-
if ("1.1.
|
|
1266
|
+
if ("1.1.2") return "1.1.2";
|
|
1087
1267
|
try {
|
|
1088
1268
|
const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1089
1269
|
if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string") {
|
|
@@ -1130,12 +1310,234 @@ ${url}`);
|
|
|
1130
1310
|
else console.log(`Opening ${url}`);
|
|
1131
1311
|
}
|
|
1132
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
|
+
|
|
1133
1528
|
// src/commands/avatar.ts
|
|
1134
1529
|
function parseAvatarId(argv) {
|
|
1135
1530
|
const index = argv.findIndex((value) => value === "--avatar-id");
|
|
1136
1531
|
if (index >= 0) return argv[index + 1];
|
|
1137
1532
|
const inline = argv.find((value) => value.startsWith("--avatar-id="));
|
|
1138
|
-
return inline?.slice("--avatar-id=".length);
|
|
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;
|
|
1139
1541
|
}
|
|
1140
1542
|
async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(), suppliedAvatarId) {
|
|
1141
1543
|
const stored = await loadConnection(cwd);
|
|
@@ -1148,24 +1550,112 @@ async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(),
|
|
|
1148
1550
|
console.log("Invalid Avatar ID. It must contain exactly 10 alphanumeric characters.");
|
|
1149
1551
|
return;
|
|
1150
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.");
|
|
1559
|
+
const project = detectProject(cwd);
|
|
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
|
+
}
|
|
1151
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
|
+
}
|
|
1152
1612
|
const result = await importAvatar(api, {
|
|
1153
1613
|
userId: stored.userId,
|
|
1154
1614
|
projectId: stored.projectId,
|
|
1155
1615
|
apiKey: stored.apiKey,
|
|
1156
1616
|
avatarId
|
|
1157
1617
|
});
|
|
1158
|
-
|
|
1159
|
-
|
|
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.`);
|
|
1160
1648
|
} catch (error) {
|
|
1161
1649
|
if (error instanceof ApiError) {
|
|
1162
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.");
|
|
1163
1653
|
else if (error.code === "NOT_FOUND") console.log("\u2717 Project or avatar was not found.");
|
|
1164
1654
|
else if (error.code === "CONFLICT" || error.code === "AVATAR_ALREADY_IMPORTED") console.log("\u2717 This avatar is already imported into the project.");
|
|
1165
1655
|
else console.log(`\u2717 ${error.message}`);
|
|
1166
1656
|
return;
|
|
1167
1657
|
}
|
|
1168
|
-
console.log(
|
|
1658
|
+
console.log(`\u2717 ${error instanceof Error ? error.message : "Unable to import the avatar."}`);
|
|
1169
1659
|
}
|
|
1170
1660
|
}
|
|
1171
1661
|
async function avatarCommand(cwd = process.cwd(), argv = process.argv.slice(2)) {
|
|
@@ -1188,55 +1678,79 @@ ${ASIIYST_WEB_URL}`);
|
|
|
1188
1678
|
}
|
|
1189
1679
|
|
|
1190
1680
|
// src/commands/interactive.ts
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
Asiyst CLI
|
|
1194
|
-
|
|
1195
|
-
Usage: asiyst [command]
|
|
1196
|
-
|
|
1197
|
-
Commands
|
|
1198
|
-
connect Connect this project to Asiyst
|
|
1199
|
-
avatar import Import an avatar into the connected project
|
|
1200
|
-
status Show connection status
|
|
1201
|
-
health Check Asiyst API connectivity
|
|
1202
|
-
disconnect Remove local connection information
|
|
1203
|
-
help Show available commands
|
|
1204
|
-
version Show CLI version
|
|
1205
|
-
|
|
1206
|
-
Also available
|
|
1207
|
-
trust Trust this project folder
|
|
1208
|
-
revoke-trust Revoke folder trust
|
|
1209
|
-
doctor Run diagnostics
|
|
1210
|
-
update Check for CLI updates
|
|
1681
|
+
import { stdin as stdin3, stdout as stdout4 } from "process";
|
|
1682
|
+
import { emitKeypressEvents as emitKeypressEvents3 } from "readline";
|
|
1211
1683
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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;
|
|
1229
1731
|
}
|
|
1230
|
-
if (
|
|
1231
|
-
else
|
|
1232
|
-
|
|
1233
|
-
|
|
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;
|
|
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`);
|
|
1234
1748
|
}
|
|
1235
1749
|
}
|
|
1236
1750
|
|
|
1237
1751
|
// src/update/check.ts
|
|
1238
1752
|
import { execFileSync } from "child_process";
|
|
1239
|
-
import { readFileSync as
|
|
1753
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1240
1754
|
var PACKAGE_NAME = "@asiyst/cli";
|
|
1241
1755
|
var REGISTRY_URL = "https://registry.npmjs.org/%40asiyst%2Fcli/latest";
|
|
1242
1756
|
var TIMEOUT_MS = 800;
|
|
@@ -1308,7 +1822,7 @@ function detectInstallKind(executablePath = process.argv[1] ?? "", env = process
|
|
|
1308
1822
|
}
|
|
1309
1823
|
function installedVersion(packageRoot) {
|
|
1310
1824
|
try {
|
|
1311
|
-
const value = JSON.parse(
|
|
1825
|
+
const value = JSON.parse(readFileSync7(`${packageRoot}/package.json`, "utf8"));
|
|
1312
1826
|
return value && typeof value === "object" && typeof value.version === "string" ? value.version : void 0;
|
|
1313
1827
|
} catch {
|
|
1314
1828
|
return void 0;
|
|
@@ -1331,18 +1845,18 @@ function installLatest(latestVersion, kind, executablePath = process.argv[1] ??
|
|
|
1331
1845
|
|
|
1332
1846
|
// src/commands/update.ts
|
|
1333
1847
|
async function updateCommand() {
|
|
1848
|
+
printHeader("Asiyst CLI Update");
|
|
1334
1849
|
const result = await checkForUpdate();
|
|
1335
1850
|
if (!result.latestVersion || compareVersions(result.latestVersion, result.currentVersion) <= 0) {
|
|
1336
|
-
console.log(
|
|
1851
|
+
console.log(`${success(symbols.success)} Latest version
|
|
1337
1852
|
|
|
1338
1853
|
Current version: ${result.currentVersion}`);
|
|
1339
1854
|
return;
|
|
1340
1855
|
}
|
|
1341
|
-
console.log(
|
|
1342
|
-
Update available
|
|
1856
|
+
console.log(`${warning(symbols.warning)} Update available
|
|
1343
1857
|
|
|
1344
1858
|
Current version: ${result.currentVersion}
|
|
1345
|
-
|
|
1859
|
+
Latest version: ${result.latestVersion}
|
|
1346
1860
|
`);
|
|
1347
1861
|
if (!process.stdin.isTTY || !await confirm(`Update Asiyst CLI from ${result.currentVersion} to ${result.latestVersion}?`)) {
|
|
1348
1862
|
console.log("Update cancelled.");
|
|
@@ -1359,7 +1873,7 @@ New version: ${result.latestVersion}
|
|
|
1359
1873
|
if (!installed || compareVersions(installed, result.latestVersion) !== 0) {
|
|
1360
1874
|
throw new Error("installed version could not be verified");
|
|
1361
1875
|
}
|
|
1362
|
-
console.log(
|
|
1876
|
+
console.log(`${success(symbols.success)} Verifying installation
|
|
1363
1877
|
|
|
1364
1878
|
Asiyst CLI updated from ${result.currentVersion} to ${installed}.
|
|
1365
1879
|
Restart Asiyst to use the new version.`);
|
|
@@ -1369,13 +1883,201 @@ ${error instanceof Error ? error.message : "Installation could not be verified."
|
|
|
1369
1883
|
}
|
|
1370
1884
|
}
|
|
1371
1885
|
|
|
1372
|
-
// src/commands/
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1886
|
+
// src/commands/interactive.ts
|
|
1887
|
+
function interactiveHelp() {
|
|
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
|
+
`);
|
|
1916
|
+
}
|
|
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 "
|
|
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));
|
|
2078
|
+
for (; ; ) {
|
|
2079
|
+
const command = await readCommand();
|
|
2080
|
+
if (!command || !await runInteractiveCommand(command)) break;
|
|
1379
2081
|
}
|
|
1380
2082
|
}
|
|
1381
2083
|
|
|
@@ -1410,7 +2112,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1410
2112
|
if (command === "revoke-trust") return revokeTrustCommand();
|
|
1411
2113
|
if (command === "update") return updateCommand();
|
|
1412
2114
|
if (command === "help") return help();
|
|
1413
|
-
if (command === "version") return console.log(CLI_VERSION
|
|
2115
|
+
if (command === "version") return console.log(`Asiyst CLI v${CLI_VERSION}
|
|
2116
|
+
\u2713 Latest version`);
|
|
1414
2117
|
if (!process.stdin.isTTY) {
|
|
1415
2118
|
help();
|
|
1416
2119
|
return;
|