@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/dist/index.js CHANGED
@@ -1,46 +1,486 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/ui/output.ts
4
- var ok = (label, detail = "") => console.log(` \u2713 ${label}${detail ? ` (${detail})` : ""}`);
5
- var fail = (label, detail = "") => console.log(` \u2717 ${label}${detail ? `: ${detail}` : ""}`);
6
- function projectChecks(project) {
7
- project.packageJson ? ok("Project detected", typeof project.packageJson.name === "string" ? project.packageJson.name : project.cwd) : fail("Project not detected", "package.json is missing");
8
- ok("Framework detected", project.framework);
9
- ok("Language detected", project.language);
10
- ok("Node.js detected", process.version);
11
- ok("Package manager detected", project.packageManager);
12
- project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : fail("@asiyst/sdk is not installed", "npm install @asiyst/sdk");
13
- }
14
- function homeStatus(project, connection = "Not connected") {
15
- console.log("\n PROJECT STATUS");
16
- console.log(` Project ${typeof project.packageJson?.name === "string" ? project.packageJson.name : "Not detected"}`);
17
- console.log(` Framework ${project.framework}`);
18
- console.log(` Language ${project.language}`);
19
- console.log(` Node.js ${process.version}`);
20
- console.log(` Package Manager ${project.packageManager}`);
21
- console.log(` @asiyst/sdk ${project.sdkVersion || "Not installed"}`);
22
- console.log("\n ASIYST STATUS");
23
- console.log(` Connection ${connection}`);
24
- console.log(` Avatar Not configured`);
25
- console.log(` AI Provider Not configured`);
26
- console.log(` Project ID ${project.config.projectId || "\u2014"}`);
27
- if (connection === "Not connected") console.log("\n Run 'connect' to link this project.");
28
- }
29
- function printVerification(results) {
30
- console.log("\n Asiyst Installation Verification\n");
31
- for (const result of results) result.ok ? ok(result.name, result.detail) : fail(result.name, result.detail);
32
- const success = results.length > 0 && results.every((result) => result.ok);
33
- if (success) console.log("\n \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\n\n ASIYST IS READY \u2713");
34
- return success;
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 section of sections) {
43
- const values = pkg[section];
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 (typeof deps.next === "string") return "Next.js";
54
- if (typeof deps.react === "string") return "React";
55
- if (typeof deps.vite === "string") return "Vite";
56
- if (typeof deps.vue === "string") return "Vue";
57
- if (existsSync(resolve(cwd, "vite.config.ts")) || existsSync(resolve(cwd, "vite.config.js"))) return "Vite";
58
- if (existsSync(resolve(cwd, "next.config.js")) || existsSync(resolve(cwd, "next.config.mjs"))) return "Next.js";
59
- return "Vanilla JavaScript/TypeScript";
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 = resolve(cwd, "package.json");
509
+ const path = resolve3(cwd, "package.json");
63
510
  let packageJson = null;
64
- if (existsSync(path)) {
511
+ if (existsSync3(path)) {
65
512
  try {
66
- const value = JSON.parse(readFileSync(path, "utf8"));
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 = existsSync(resolve(cwd, "pnpm-lock.yaml")) ? "pnpm" : existsSync(resolve(cwd, "yarn.lock")) ? "yarn" : existsSync(resolve(cwd, "bun.lockb")) || existsSync(resolve(cwd, "bun.lock")) ? "bun" : "npm";
74
- const sourceFiles = ["tsconfig.json", "src", "app"].some((entry) => existsSync(resolve(cwd, 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/config/api.ts
90
- var CLI_API_BASE_URL = "https://asiyst.com/api/v1";
91
- var ASIIYST_WEB_URL = "https://asiyst.com";
92
-
93
- // src/api/client.ts
94
- var ApiError = class extends Error {
95
- constructor(message, status) {
96
- super(message);
97
- this.status = status;
98
- this.name = "ApiError";
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
- var ApiClient = class {
103
- constructor(baseUrl = process.env.ASIIYST_API_URL || CLI_API_BASE_URL, fetcher = fetch) {
104
- this.fetcher = fetcher;
105
- this.baseUrl = baseUrl.replace(/\/+$/, "");
106
- }
107
- fetcher;
108
- baseUrl;
109
- async request(path, init) {
110
- let response;
111
- const controller = new AbortController();
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
- // src/config/local.ts
179
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
180
- import { resolve as resolve2 } from "path";
181
- function configWarning(cwd) {
182
- const envPath = resolve2(cwd, ".env");
183
- if (existsSync2(envPath)) {
184
- const ignored = existsSync2(resolve2(cwd, ".gitignore")) && readFileSync2(resolve2(cwd, ".gitignore"), "utf8").split(/\r?\n/).some((line) => line.trim() === ".env" || line.trim() === ".env.*");
185
- if (!ignored) return "Your .env file is not clearly ignored by .gitignore; do not commit public or private configuration accidentally.";
186
- }
187
- return void 0;
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/browser/open.ts
191
- import { execFile } from "child_process";
192
- import { promisify } from "util";
193
- var execFileAsync = promisify(execFile);
194
- async function openBrowser(url) {
195
- const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
196
- const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
197
- try {
198
- await execFileAsync(command, args);
199
- return true;
200
- } catch {
201
- return false;
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 width = () => Math.max(40, (stdout.columns || 80) - 2);
209
- var fit = (value) => value.length > width() ? `${value.slice(0, width() - 1)}\u2026` : value;
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 === 0) return;
216
- moveCursor(output, 0, -previousLineCount);
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) moveCursor(output, 0, 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
- clearSelectorFrame(output, previousLineCount);
226
- output.write(lines.join("\n"));
227
- output.write("\n");
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 selectOption(title, options, prompt = "> ") {
231
- if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve({ type: "cancelled", input: "" });
232
- return new Promise((resolve4) => {
233
- let input = "";
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 filtered = () => {
239
- const query = input.trim().toLowerCase();
240
- return query ? options.filter((option) => option.label.toLowerCase().includes(query) || String(option.value).toLowerCase().includes(query)) : options;
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
- clear();
263
- stdin.setRawMode?.(false);
671
+ clearSelectorFrame(stdout2, renderedLines);
672
+ restoreTerminal(wasRaw);
264
673
  stdin.pause();
265
- stdout.write("\n");
266
- resolve4(result);
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 = (value, key) => {
269
- if (key.ctrl && key.name === "c") return finish({ type: "cancelled", input });
270
- if (key.ctrl && key.name === "d") return finish({ type: "exit", input });
271
- if (key.name === "escape") {
272
- return finish({ type: "cancelled", input });
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
- const matches = filtered();
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 matches = filtered();
284
- const selected = matches[active];
285
- if (selected) return finish({ type: "selected", value: selected.value, input });
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/commands/shared.ts
317
- async function connect(api = new ApiClient()) {
318
- await api.health();
319
- const session = await api.createSession();
320
- console.log(`Opening Asiyst...
321
- ${session.connectUrl}`);
322
- if (!await openBrowser(session.connectUrl)) console.log("Automatic browser opening failed; open the URL above.");
323
- console.log("\u25CF Waiting for authentication\n\u25CF Waiting for project selection\n\u25CF Waiting for connection");
324
- const expires = Date.parse(session.expiresAt);
325
- for (; ; ) {
326
- const result = await api.sessionStatus(session.sessionId);
327
- if (result.state === "completed") return { session, project: result.project };
328
- if (result.state === "expired") throw new Error("Connection session expired.");
329
- if (result.state === "cancelled") throw new Error("Connection session was cancelled.");
330
- if (Number.isFinite(expires) && Date.now() >= expires) throw new Error("Connection session expired.");
331
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
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 confirm(question) {
335
- if (!process.stdin.isTTY || !process.stdout.isTTY) return true;
336
- const result = await selectOption(question, [
337
- { label: "Yes", value: true },
338
- { label: "No", value: false }
339
- ]);
340
- return result.type === "selected" && result.value;
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 existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
345
- import { homedir } from "os";
346
- import { join, resolve as resolve3 } from "path";
347
- var trustFile = join(homedir(), ".config", "asiyst", "trusted-folders.json");
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 (!existsSync3(trustFile)) return [];
775
+ if (!existsSync4(trustFile)) return [];
350
776
  try {
351
- const parsed = JSON.parse(readFileSync3(trustFile, "utf8"));
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(resolve3(cwd));
784
+ return readTrusted().includes(resolve4(cwd));
359
785
  }
360
786
  function trustFolder(cwd) {
361
- const folder = resolve3(cwd);
787
+ const folder = resolve4(cwd);
362
788
  const trusted = readTrusted();
363
789
  if (!trusted.includes(folder)) trusted.push(folder);
364
- mkdirSync(join(homedir(), ".config", "asiyst"), { recursive: true, mode: 448 });
365
- writeFileSync(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
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 = resolve3(cwd);
794
+ const folder = resolve4(cwd);
369
795
  const trusted = readTrusted().filter((item) => item !== folder);
370
- if (existsSync3(trustFile)) writeFileSync(trustFile, JSON.stringify(trusted, null, 2), { encoding: "utf8", mode: 384 });
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/trust.ts
374
- async function ensureTrusted(cwd = process.cwd()) {
375
- if (isTrusted(cwd)) return true;
376
- if (!await confirm(`Trust this project folder?
377
-
378
- ${cwd}
1069
+ // src/commands/login.ts
1070
+ async function loginCommand() {
1071
+ await connectCommand(process.cwd(), createApiClient());
1072
+ }
379
1073
 
380
- Asiyst may read project files and modify Asiyst configuration during setup.`)) {
381
- console.log("Folder not trusted.\nExiting...");
382
- return false;
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
- trustFolder(cwd);
385
- console.log("\u2713 Folder trusted.");
386
- return true;
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
- function revokeTrustCommand(cwd = process.cwd()) {
392
- revokeTrust(cwd);
393
- console.log("\u2713 Folder trust revoked.");
1085
+
1086
+ // src/commands/logout.ts
1087
+ async function logoutCommand() {
1088
+ await disconnectCommand();
394
1089
  }
395
1090
 
396
- // src/commands/init.ts
397
- async function initCommand(cwd = process.cwd(), api = new ApiClient()) {
398
- if (!await ensureTrusted(cwd)) return;
399
- const project = detectProject(cwd);
400
- if (!project.packageJson) throw new Error("No package.json found. Run this command from your website project.");
401
- console.log(`Detected ${project.framework} project.`);
402
- if (!project.sdkVersion) {
403
- console.log("\n@asiyst/sdk is not installed.\nRun: npm install @asiyst/sdk");
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
- if (!await confirm("Connect this project to Asiyst?")) return;
407
- const result = await connect(api);
408
- ok("Asiyst account connected");
409
- ok("Project connected", result.project?.projectName);
410
- if (result.project?.projectId) {
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
- Project ID: ${result.project.projectId}
413
- Public Key: ${result.project.publicKey || project.config.publicKey || "Use the public key from the Asiyst project settings."}`);
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/commands/login.ts
423
- async function loginCommand(api = new ApiClient()) {
424
- await connect(api);
425
- console.log("\u2713 Already connected to Asiyst.");
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
- // src/commands/logout.ts
429
- function logoutCommand() {
430
- console.log("\u2713 Local CLI session information removed (the CLI does not store passwords or private credentials).");
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
- // src/commands/status.ts
434
- async function statusCommand(cwd = process.cwd(), api = new ApiClient()) {
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 info = await api.projectInfo(project.config.projectId);
443
- console.log(`
444
- Project: ${info.connectionStatus || "Connected"}
445
- SDK: ${project.sdkVersion}
446
- Avatar: ${info.avatarStatus || "Not configured"}`);
447
- console.log("\nStatistics:");
448
- console.log(info.lastSdkConnection ? `Last SDK heartbeat: ${info.lastSdkConnection}` : "No SDK heartbeat recorded.");
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
- async function verifyCommand(cwd = process.cwd(), api = new ApiClient()) {
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
- if (!project.sdkVersion) {
461
- fail("SDK installed", "npm install @asiyst/sdk");
462
- process.exitCode = 1;
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 api.verify(project.config.projectId, project.config.publicKey, process.env.ASIIYST_DOMAIN);
474
- if (!printVerification(results)) process.exitCode = 1;
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 = new ApiClient()) {
483
- const project = detectProject(cwd);
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
- ok("npm", "available through npm");
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
- project.config.projectId && project.config.publicKey ? ok("Configuration") : fail("Configuration", "public project values are missing");
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", "HTTPS health endpoint reachable");
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 configured = process.env.ASIIYST_DASHBOARD_URL || `${ASIIYST_WEB_URL}/dashboard`;
509
- const parsed = new URL(configured);
510
- if (parsed.protocol !== "https:") throw new Error("Configured dashboard URL must use HTTPS.");
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
- async function avatarCommand(cwd = process.cwd(), api = new ApiClient()) {
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
- if (!project.config.projectId) {
520
- console.log("Avatar\n\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\nNo avatar configured.\n\nCreate your avatar in the Asiyst dashboard.");
521
- const url = `${ASIIYST_WEB_URL}/dashboard`;
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 ${url}`);
1671
+ else console.log(`Open this URL manually:
1672
+ ${url}`);
524
1673
  return;
525
1674
  }
526
- const info = await api.projectInfo(project.config.projectId);
527
- console.log(`Avatar
528
- \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
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/update/check.ts
535
- import { execFileSync } from "child_process";
536
- import { readFileSync as readFileSync5 } from "fs";
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/config/version.ts
539
- import { readFileSync as readFileSync4 } from "fs";
540
- import { fileURLToPath } from "url";
541
- function readCurrentVersion() {
542
- for (const packageUrl of [
543
- new URL("../package.json", import.meta.url),
544
- new URL("../../package.json", import.meta.url),
545
- new URL("../../../package.json", import.meta.url)
546
- ]) {
547
- try {
548
- const packageJson = JSON.parse(readFileSync4(fileURLToPath(packageUrl), "utf8"));
549
- if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string") {
550
- return packageJson.version;
551
- }
552
- } catch {
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(readFileSync5(`${packageRoot}/package.json`, "utf8"));
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(`\u2713 Asiyst CLI is already up to date.
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
- New version: ${result.latestVersion}
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(`\u2713 Verifying installation
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("\nASIYST CLI\n\nUsage: asiyst [command]\n\nCommands\n connect Connect this project to Asiyst\n status Show project and Asiyst status\n diagnostics Run diagnostics\n dashboard Open Asiyst dashboard\n avatar Configure avatar\n update Check for and install CLI updates\n help Show available commands\n version Show CLI version\n trust Trust this project folder\n revoke-trust Revoke folder trust\n logout Remove local CLI session information\n\nExamples\n asiyst connect\n asiyst status\n asiyst update");
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
- async function interactiveHome() {
708
- homeStatus(detectProject());
709
- console.log("\nWelcome to Asiyst CLI.\nUse UP/DOWN to select a command, or type a command.\n");
710
- const suggestions = [
711
- { label: "Connect this project", value: "connect" },
712
- { label: "Check project status", value: "status" },
713
- { label: "Run diagnostics", value: "diagnostics" },
714
- { label: "Open dashboard", value: "dashboard" },
715
- { label: "Configure avatar", value: "avatar" },
716
- { label: "Update CLI", value: "update" },
717
- { label: "Help", value: "help" },
718
- { label: "Exit", value: "exit" }
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 result = await selectOption("What would you like to do?", suggestions, "\u203A ");
722
- if (result.type === "exit") break;
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" || command === "connect") return initCommand();
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
- const project = detectProject();
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
- if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
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