@asiyst/cli 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,62 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/config/api.ts
4
- var PRODUCTION_API_ORIGIN = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
5
- var CLI_API_BASE_URL = PRODUCTION_API_ORIGIN;
6
- var VERIFY_KEY_PATH = "/auth/api-key/verify";
7
- var VERIFY_KEY_URL = `${CLI_API_BASE_URL}${VERIFY_KEY_PATH}`;
8
- var ASIYST_WEB_URL = "https://asiyst.com";
9
- var ASIIYST_WEB_URL = ASIYST_WEB_URL;
10
- var REQUEST_TIMEOUT_MS = 15e3;
11
- function readEnv(env, ...keys) {
12
- for (const key of keys) {
13
- const value = env[key]?.trim();
14
- if (value) return value;
15
- }
16
- return void 0;
17
- }
18
- function isLocalUrl(value) {
19
- try {
20
- const url = new URL(value);
21
- return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
22
- } catch {
23
- return /localhost|127\.0\.0\.1|::1/i.test(value);
24
- }
25
- }
26
- function resolveApiBaseUrl(env = process.env) {
27
- const explicit = readEnv(env, "ASIYST_API_URL", "ASIIYST_API_URL");
28
- const development = readEnv(env, "ASIYST_API_MODE", "ASIIYST_API_MODE") === "development";
29
- if (explicit) {
30
- if (isLocalUrl(explicit)) {
31
- return CLI_API_BASE_URL;
32
- }
33
- return explicit.replace(/\/+$/, "");
34
- }
35
- if (development) {
36
- return CLI_API_BASE_URL;
37
- }
38
- return CLI_API_BASE_URL;
39
- }
40
- function isDebugEnabled(env = process.env) {
41
- return readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "1" || readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "true";
42
- }
43
-
44
3
  // src/config/ids.ts
45
- var PUBLIC_IDENTIFIER_PATTERN = /^(?=.{1,128}$)[A-Za-z0-9_][A-Za-z0-9_-]*$/;
46
- var USER_ID_PATTERN = /^[A-Za-z0-9_]{24}$/;
4
+ var PROJECT_ID_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]{23}$/;
5
+ var API_KEY_PATTERN = /^[A-Za-z0-9_-]{32}$/;
6
+ var USER_ID_PATTERN = /^[A-Za-z0-9-]{16}$/;
47
7
  var AVATAR_ID_PATTERN = /^[A-Za-z0-9]{10}$/;
48
- function isValidPublicIdentifier(value) {
49
- if (typeof value !== "string") return false;
50
- const trimmed = value.trim();
51
- if (!trimmed || trimmed.length > 128) return false;
52
- if (trimmed === "undefined" || trimmed === "null") return false;
53
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
54
- return false;
55
- }
56
- return PUBLIC_IDENTIFIER_PATTERN.test(trimmed);
8
+ function isValidProjectId(value) {
9
+ return typeof value === "string" && PROJECT_ID_PATTERN.test(value.trim());
10
+ }
11
+ function isValidApiKey(value) {
12
+ return typeof value === "string" && API_KEY_PATTERN.test(value.trim());
57
13
  }
58
14
  function isValidUserId(value) {
59
- return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value.trim());
15
+ return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && USER_ID_PATTERN.test(value);
60
16
  }
61
17
  function isValidAvatarId(value) {
62
18
  return typeof value === "string" && value.trim() !== "undefined" && value.trim() !== "null" && AVATAR_ID_PATTERN.test(value.trim());
@@ -91,6 +47,8 @@ function errorCodeFromStatus(status, bodyCode) {
91
47
  if (bodyCode === "INVALID_API_KEY") return "INVALID_API_KEY";
92
48
  if (bodyCode === "API_KEY_REVOKED") return "API_KEY_REVOKED";
93
49
  if (bodyCode === "FORBIDDEN") return "FORBIDDEN";
50
+ if (bodyCode === "USER_MISMATCH") return "USER_MISMATCH";
51
+ if (bodyCode === "PROJECT_MISMATCH") return "PROJECT_MISMATCH";
94
52
  if (bodyCode === "PROJECT_NOT_FOUND") return "PROJECT_NOT_FOUND";
95
53
  if (bodyCode === "AVATAR_NOT_FOUND") return "AVATAR_NOT_FOUND";
96
54
  if (bodyCode === "AVATAR_ALREADY_IMPORTED") return "AVATAR_ALREADY_IMPORTED";
@@ -113,6 +71,8 @@ function friendlyApiMessage(error, endpointUrl) {
113
71
  if (error.code === "FORBIDDEN") {
114
72
  return "\u2717 This API key does not have permission to access this resource.";
115
73
  }
74
+ if (error.code === "USER_MISMATCH") return "\u2717 The supplied identifiers do not belong to the verified user.";
75
+ if (error.code === "PROJECT_MISMATCH") return "\u2717 The supplied identifiers do not belong to the verified project.";
116
76
  if (error.code === "NOT_FOUND") {
117
77
  return endpointUrl ? `\u2717 Asiyst API endpoint was not found.
118
78
  Verify that the CLI is using:
@@ -131,68 +91,133 @@ ${endpointUrl}` : "\u2717 Asiyst API endpoint was not found.";
131
91
  return "\u2717 Asiyst request failed.";
132
92
  }
133
93
 
134
- // src/api/auth.ts
135
- function asRecord(value) {
136
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
94
+ // src/api/verification.ts
95
+ function record(value) {
96
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
97
+ throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
98
+ }
99
+ const root = value;
100
+ const data = root.data && typeof root.data === "object" && !Array.isArray(root.data) ? root.data : root;
101
+ return data;
102
+ }
103
+ function stringValue(body, ...keys) {
104
+ for (const key of keys) {
105
+ if (typeof body[key] === "string" && body[key].trim()) return body[key].trim();
106
+ }
107
+ return void 0;
137
108
  }
138
- function bearerToken(apiKey) {
139
- const trimmed = apiKey.trim();
140
- return trimmed.toLowerCase().startsWith("bearer ") ? trimmed.slice(7).trim() : trimmed;
141
- }
142
- function projectFromBody(body) {
143
- const nested = asRecord(body.project) ?? asRecord(body.data);
144
- const nestedProject = nested ? asRecord(nested.project) ?? nested : void 0;
145
- return nestedProject ?? body;
146
- }
147
- function parseVerifyKeyResponse(value, apiKey) {
148
- const body = asRecord(value);
149
- if (!body) throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
150
- if (body.valid === false) {
151
- const code = typeof body.code === "string" ? body.code : void 0;
152
- if (code === "API_KEY_REVOKED" || body.revoked === true) {
153
- throw new ApiError("This API key has been revoked.", 401, "API_KEY_REVOKED");
109
+ function nestedStringValue(body, containers, ...keys) {
110
+ const direct = stringValue(body, ...keys);
111
+ if (direct) return direct;
112
+ for (const container of containers) {
113
+ const nested = body[container];
114
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
115
+ const value = stringValue(nested, ...keys);
116
+ if (value) return value;
154
117
  }
155
- throw new ApiError("The Asiyst API key was rejected.", 401, "INVALID_API_KEY");
156
118
  }
157
- if (body.valid !== void 0 && body.valid !== true) {
158
- throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
119
+ return void 0;
120
+ return void 0;
121
+ }
122
+ function assertValid(value, name, validator) {
123
+ const trimmed = value.trim();
124
+ if (!validator(trimmed)) {
125
+ const code = name === "User ID" ? "INVALID_USER_ID" : name === "Project ID" ? "INVALID_PROJECT_ID" : name === "Avatar ID" ? "INVALID_AVATAR_ID" : "INVALID_API_KEY";
126
+ throw new ApiError(`Invalid ${name}.`, 400, code);
159
127
  }
160
- const project = projectFromBody(body);
161
- if (!project) throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
162
- const projectIdCandidate = project.projectId ?? project.project_id ?? project.projectID ?? project.id;
163
- const projectId = typeof projectIdCandidate === "string" && isValidPublicIdentifier(projectIdCandidate) ? projectIdCandidate.trim() : void 0;
164
- if (!projectId) {
165
- throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
128
+ return trimmed;
129
+ }
130
+ function assertVerified(body, message) {
131
+ if (body.valid === false || body.verified === false || body.success === false) {
132
+ throw new ApiError(stringValue(body, "message", "error") ?? message, 400, "FORBIDDEN");
166
133
  }
167
- const website = project.website ?? project.websiteUrl ?? project.url ?? project.domain ?? project.website_url;
168
- const projectName = project.projectName ?? project.name ?? project.title;
169
- const publicKey = project.publicKey ?? project.public_key ?? project.publishableKey;
170
- const userId = project.userId ?? project.user_id ?? project.asiystUserId ?? project.asiyst_user_id ?? body.userId ?? body.user_id ?? body.asiystUserId ?? body.asiyst_user_id;
134
+ }
135
+ async function verifyUser(api, userId) {
136
+ const verifiedUserId = assertValid(userId, "User ID", isValidUserId);
137
+ const body = record(await api.request("/verify/user", {
138
+ method: "POST",
139
+ body: JSON.stringify({ userId: verifiedUserId })
140
+ }));
141
+ assertVerified(body, "User ID verification failed.");
142
+ const returned = nestedStringValue(body, ["user"], "userId", "user_id", "id");
143
+ if (returned !== verifiedUserId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
144
+ return { userId: verifiedUserId, userName: stringValue(body, "userName", "name") };
145
+ }
146
+ async function verifyProject(api, userId, projectId) {
147
+ const verifiedUserId = assertValid(userId, "User ID", isValidUserId);
148
+ const verifiedProjectId = assertValid(projectId, "Project ID", isValidProjectId);
149
+ const body = record(await api.request("/verify/project", {
150
+ method: "POST",
151
+ body: JSON.stringify({ userId: verifiedUserId, projectId: verifiedProjectId })
152
+ }));
153
+ assertVerified(body, "Project verification failed.");
154
+ const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
155
+ const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
156
+ if (returnedProjectId && returnedProjectId !== verifiedProjectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
157
+ if (returnedUserId && returnedUserId !== verifiedUserId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
158
+ return {
159
+ userId: verifiedUserId,
160
+ projectId: verifiedProjectId,
161
+ projectName: nestedStringValue(body, ["project"], "projectName", "project_name", "name"),
162
+ website: nestedStringValue(body, ["project"], "website", "websiteUrl", "website_url", "domain"),
163
+ publicKey: nestedStringValue(body, ["project"], "publicKey", "public_key", "publishableKey")
164
+ };
165
+ }
166
+ async function verifyApiKeyRelationship(api, input) {
167
+ const userId = assertValid(input.userId, "User ID", isValidUserId);
168
+ const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
169
+ const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
170
+ const body = record(await api.request("/verify/api-key", {
171
+ method: "POST",
172
+ headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
173
+ body: JSON.stringify({ userId, projectId, apiKey })
174
+ }));
175
+ assertVerified(body, "API key verification failed.");
176
+ const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
177
+ const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
178
+ if (returnedUserId && returnedUserId !== userId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
179
+ if (returnedProjectId && returnedProjectId !== projectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
171
180
  return {
172
- projectId,
173
- projectName: typeof projectName === "string" ? projectName : void 0,
174
- website: typeof website === "string" ? website : void 0,
175
- publicKey: typeof publicKey === "string" ? publicKey : void 0,
176
181
  apiKey,
177
- userId: typeof userId === "string" && isValidPublicIdentifier(userId) ? userId : void 0
182
+ userId,
183
+ projectId,
184
+ projectName: nestedStringValue(body, ["project"], "projectName", "project_name", "name"),
185
+ website: nestedStringValue(body, ["project"], "website", "websiteUrl", "website_url", "domain"),
186
+ publicKey: nestedStringValue(body, ["project"], "publicKey", "public_key", "publishableKey")
178
187
  };
179
188
  }
180
- async function verifyApiKey(api, apiKey, expected) {
181
- const token = bearerToken(apiKey);
182
- if (!token) throw new ApiError("Invalid API key.", 401, "INVALID_API_KEY");
183
- const value = await api.request(VERIFY_KEY_PATH, {
189
+ async function verifyAvatar(api, input) {
190
+ const userId = assertValid(input.userId, "User ID", isValidUserId);
191
+ const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
192
+ const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
193
+ const avatarId = assertValid(input.avatarId, "Avatar ID", isValidAvatarId);
194
+ const body = record(await api.request("/verify/avatar", {
184
195
  method: "POST",
185
- headers: {
186
- Authorization: "Bearer " + token,
187
- "X-Asiyst-API-Key": token
188
- },
189
- body: JSON.stringify({
190
- apiKey: token,
191
- ...expected?.userId ? { userId: expected.userId } : {},
192
- ...expected?.projectId ? { projectId: expected.projectId } : {}
193
- })
194
- });
195
- return parseVerifyKeyResponse(value, token);
196
+ headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
197
+ body: JSON.stringify({ userId, projectId, apiKey, avatarId })
198
+ }));
199
+ assertVerified(body, "Avatar verification failed.");
200
+ const returnedAvatarId = nestedStringValue(body, ["avatar"], "avatarId", "avatar_id", "id");
201
+ const returnedUserId = nestedStringValue(body, ["user"], "userId", "user_id", "id");
202
+ const returnedProjectId = nestedStringValue(body, ["project"], "projectId", "project_id", "id");
203
+ if (returnedAvatarId && returnedAvatarId !== avatarId) throw new ApiError("The API returned a different Avatar ID.", 200, "AVATAR_MISMATCH");
204
+ if (returnedUserId && returnedUserId !== userId) throw new ApiError("The API returned a different User ID.", 200, "USER_MISMATCH");
205
+ if (returnedProjectId && returnedProjectId !== projectId) throw new ApiError("The API returned a different Project ID.", 200, "PROJECT_MISMATCH");
206
+ return { userId, projectId, avatarId, avatarName: nestedStringValue(body, ["avatar"], "avatarName", "avatar_name", "name") };
207
+ }
208
+ async function createImportSession(api, input) {
209
+ const userId = assertValid(input.userId, "User ID", isValidUserId);
210
+ const projectId = assertValid(input.projectId, "Project ID", isValidProjectId);
211
+ const apiKey = assertValid(input.apiKey, "API key", isValidApiKey);
212
+ const avatarId = assertValid(input.avatarId, "Avatar ID", isValidAvatarId);
213
+ const body = record(await api.request("/import-session", {
214
+ method: "POST",
215
+ headers: { Authorization: `Bearer ${apiKey}`, "X-Asiyst-API-Key": apiKey },
216
+ body: JSON.stringify({ userId, projectId, apiKey, avatarId })
217
+ }));
218
+ const sessionId = stringValue(body, "sessionId", "session_id", "id");
219
+ if (!sessionId) throw new ApiError("The API did not return an import session.", 200, "MALFORMED_RESPONSE");
220
+ return { sessionId, expiresAt: stringValue(body, "expiresAt", "expires_at") };
196
221
  }
197
222
 
198
223
  // src/browser/open.ts
@@ -210,6 +235,47 @@ async function openBrowser(url) {
210
235
  }
211
236
  }
212
237
 
238
+ // src/config/api.ts
239
+ var PRODUCTION_API_ORIGIN = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
240
+ var CLI_API_BASE_URL = PRODUCTION_API_ORIGIN;
241
+ var VERIFY_KEY_PATH = "/auth/api-key/verify";
242
+ var VERIFY_KEY_URL = `${CLI_API_BASE_URL}${VERIFY_KEY_PATH}`;
243
+ var ASIYST_WEB_URL = "https://asiyst.com";
244
+ var ASIIYST_WEB_URL = ASIYST_WEB_URL;
245
+ var REQUEST_TIMEOUT_MS = 15e3;
246
+ function readEnv(env, ...keys) {
247
+ for (const key of keys) {
248
+ const value = env[key]?.trim();
249
+ if (value) return value;
250
+ }
251
+ return void 0;
252
+ }
253
+ function isLocalUrl(value) {
254
+ try {
255
+ const url = new URL(value);
256
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
257
+ } catch {
258
+ return /localhost|127\.0\.0\.1|::1/i.test(value);
259
+ }
260
+ }
261
+ function resolveApiBaseUrl(env = process.env) {
262
+ const explicit = readEnv(env, "ASIYST_API_URL", "ASIIYST_API_URL");
263
+ const development = readEnv(env, "ASIYST_API_MODE", "ASIIYST_API_MODE") === "development";
264
+ if (explicit) {
265
+ if (isLocalUrl(explicit)) {
266
+ return CLI_API_BASE_URL;
267
+ }
268
+ return explicit.replace(/\/+$/, "");
269
+ }
270
+ if (development) {
271
+ return CLI_API_BASE_URL;
272
+ }
273
+ return CLI_API_BASE_URL;
274
+ }
275
+ function isDebugEnabled(env = process.env) {
276
+ return readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "1" || readEnv(env, "ASIYST_DEBUG", "ASIIYST_DEBUG") === "true";
277
+ }
278
+
213
279
  // src/config/credentials.ts
214
280
  import { execFile as execFile2 } from "child_process";
215
281
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
@@ -252,11 +318,11 @@ async function dpapiProtect(plaintext) {
252
318
  "[Convert]::ToBase64String($protected)"
253
319
  ].join("; ");
254
320
  try {
255
- const { stdout: stdout3 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
321
+ const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
256
322
  env: { ...process.env, ASIYST_DPAPI_PAYLOAD: plaintext },
257
323
  windowsHide: true
258
324
  });
259
- const value = stdout3.trim();
325
+ const value = stdout5.trim();
260
326
  return value || void 0;
261
327
  } catch {
262
328
  return void 0;
@@ -271,11 +337,11 @@ async function dpapiUnprotect(payload) {
271
337
  "[System.Text.Encoding]::UTF8.GetString($bytes)"
272
338
  ].join("; ");
273
339
  try {
274
- const { stdout: stdout3 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
340
+ const { stdout: stdout5 } = await execFileAsync2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
275
341
  env: { ...process.env, ASIYST_DPAPI_PAYLOAD: payload },
276
342
  windowsHide: true
277
343
  });
278
- const value = stdout3.trim();
344
+ const value = stdout5.trim();
279
345
  return value || void 0;
280
346
  } catch {
281
347
  return void 0;
@@ -371,6 +437,7 @@ function readProjectMetadata(cwd) {
371
437
  publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0,
372
438
  userId: typeof body.userId === "string" ? body.userId : void 0,
373
439
  avatarId: typeof body.avatarId === "string" ? body.avatarId : void 0,
440
+ avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
374
441
  connected: body.connected === true
375
442
  };
376
443
  } catch {
@@ -389,6 +456,7 @@ function writeProjectMetadata(cwd, connection) {
389
456
  publicKey: connection.publicKey,
390
457
  userId: connection.userId,
391
458
  avatarId: connection.avatarId,
459
+ avatarName: connection.avatarName,
392
460
  connected: true
393
461
  };
394
462
  writeFileSync2(projectConfigPath(cwd), `${JSON.stringify(next, null, 2)}
@@ -411,8 +479,8 @@ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
411
479
  import { resolve as resolve3 } from "path";
412
480
  function dependencyVersion(pkg) {
413
481
  const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
414
- for (const section of sections) {
415
- const values = pkg[section];
482
+ for (const section2 of sections) {
483
+ const values = pkg[section2];
416
484
  if (values && typeof values === "object" && "@asiyst/sdk" in values) {
417
485
  const version = values["@asiyst/sdk"];
418
486
  return typeof version === "string" ? version : void 0;
@@ -465,9 +533,58 @@ function detectProject(cwd = process.cwd()) {
465
533
  };
466
534
  }
467
535
 
536
+ // src/ui/format.ts
537
+ import { stdout } from "process";
538
+ var supportsColor = Boolean(stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb");
539
+ var codes = {
540
+ reset: "\x1B[0m",
541
+ bold: "\x1B[1m",
542
+ dim: "\x1B[2m",
543
+ cyan: "\x1B[36m",
544
+ green: "\x1B[32m",
545
+ yellow: "\x1B[33m",
546
+ red: "\x1B[31m"
547
+ };
548
+ function color(code, value) {
549
+ return supportsColor ? `${codes[code]}${value}${codes.reset}` : value;
550
+ }
551
+ var symbols = {
552
+ connected: "\u25CF",
553
+ disconnected: "\u25CB",
554
+ warning: "\u26A0",
555
+ error: "\u2715",
556
+ success: "\u2713",
557
+ pointer: "\u203A"
558
+ };
559
+ function title(value) {
560
+ return color("bold", value);
561
+ }
562
+ function section(value) {
563
+ return color("cyan", value);
564
+ }
565
+ function muted(value) {
566
+ return color("dim", value);
567
+ }
568
+ function success(value) {
569
+ return color("green", value);
570
+ }
571
+ function warning(value) {
572
+ return color("yellow", value);
573
+ }
574
+ function maskSecret(value, visible = 0) {
575
+ if (visible <= 0) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
576
+ return `${value.slice(0, visible)}\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022`;
577
+ }
578
+ function printHeader(value, subtitle) {
579
+ console.log(`
580
+ ${title(value)}`);
581
+ if (subtitle) console.log(muted(subtitle));
582
+ console.log();
583
+ }
584
+
468
585
  // src/ui/output.ts
469
- var ok = (label, detail = "") => console.log(`\u2713 ${label}${detail ? ` (${detail})` : ""}`);
470
- var fail = (label, detail = "") => console.log(`\u2717 ${label}${detail ? `: ${detail}` : ""}`);
586
+ var ok = (label, detail = "") => console.log(`${success(symbols.success)} ${label}${detail ? ` (${detail})` : ""}`);
587
+ var fail = (label, detail = "") => console.log(`${symbols.error} ${label}${detail ? `: ${detail}` : ""}`);
471
588
  function projectChecks(project) {
472
589
  project.packageJson ? ok("Project detected", typeof project.packageJson.name === "string" ? project.packageJson.name : project.cwd) : fail("Project not detected", "package.json is missing");
473
590
  if (project.framework === "Unknown") fail("Framework detected", "No supported project type was identified.");
@@ -475,14 +592,23 @@ function projectChecks(project) {
475
592
  ok("Language detected", project.language);
476
593
  ok("Node.js detected", process.version);
477
594
  ok("Package manager detected", project.packageManager);
478
- project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : console.log("@asiyst/sdk is not installed. You can connect the project now and install the SDK later.");
479
- }
480
- function homeStatus() {
481
- console.log("\nConnect your project to Asiyst to view stats.\n");
595
+ project.sdkVersion ? ok("@asiyst/sdk detected", project.sdkVersion) : console.log(`${muted(symbols.disconnected)} @asiyst/sdk not installed. Install it with your package manager.`);
596
+ }
597
+ function printProjectSummary(project, connected, projectName, website) {
598
+ console.log(title("Asiyst CLI"));
599
+ console.log();
600
+ console.log(section("Project"));
601
+ console.log(` ${projectName || (typeof project.packageJson?.name === "string" ? project.packageJson.name : project.cwd)}`);
602
+ if (website) console.log(` ${muted(website)}`);
603
+ const marker = connected ? success(symbols.connected) : muted(symbols.disconnected);
604
+ console.log(` ${marker} ${connected ? "Connected" : "Not connected"}`);
605
+ console.log();
606
+ console.log(section("Environment"));
607
+ console.log(` ${project.framework} \xB7 ${project.language} \xB7 ${project.packageManager}`);
482
608
  }
483
609
 
484
610
  // src/ui/selector.ts
485
- import { stdin, stdout } from "process";
611
+ import { stdin, stdout as stdout2 } from "process";
486
612
  import { clearLine, cursorTo, emitKeypressEvents, moveCursor } from "readline";
487
613
  var HIDE_CURSOR = "\x1B[?25l";
488
614
  var SHOW_CURSOR = "\x1B[?25h";
@@ -527,12 +653,12 @@ function renderSelectorFrame(output, previousLineCount, lines) {
527
653
  return lines.length;
528
654
  }
529
655
  function restoreTerminal(wasRaw) {
530
- stdout.write(SHOW_CURSOR);
656
+ stdout2.write(SHOW_CURSOR);
531
657
  if (stdin.isTTY) stdin.setRawMode?.(wasRaw);
532
658
  }
533
- function selectOption(title, options) {
534
- if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve({ type: "cancelled" });
535
- return new Promise((resolve5) => {
659
+ function selectOption(title2, options) {
660
+ if (!stdin.isTTY || !stdout2.isTTY) return Promise.resolve({ type: "cancelled" });
661
+ return new Promise((resolve6) => {
536
662
  let active = 0;
537
663
  let renderedLines = 0;
538
664
  let settled = false;
@@ -542,24 +668,24 @@ function selectOption(title, options) {
542
668
  if (settled) return;
543
669
  settled = true;
544
670
  stdin.off("keypress", onKeypress);
545
- clearSelectorFrame(stdout, renderedLines);
671
+ clearSelectorFrame(stdout2, renderedLines);
546
672
  restoreTerminal(wasRaw);
547
673
  stdin.pause();
548
- if (confirmation) stdout.write(`${confirmation}
674
+ if (confirmation) stdout2.write(`${confirmation}
549
675
  `);
550
- resolve5(result);
676
+ resolve6(result);
551
677
  };
552
678
  const render = () => {
553
- const lines = [title, "", ...enabled.map((option, index) => `${index === active ? "\u276F" : " "} ${option.label}`)];
554
- renderedLines = renderSelectorFrame(stdout, renderedLines, lines);
679
+ const lines = [title2, "", ...enabled.map((option, index) => `${index === active ? "\u276F" : " "} ${option.label}`)];
680
+ renderedLines = renderSelectorFrame(stdout2, renderedLines, lines);
555
681
  };
556
682
  const onKeypress = (_value, key) => {
557
683
  if (!key) return;
558
684
  if (key.ctrl && key.name === "c") {
559
- clearSelectorFrame(stdout, renderedLines);
685
+ clearSelectorFrame(stdout2, renderedLines);
560
686
  restoreTerminal(wasRaw);
561
687
  stdin.pause();
562
- stdout.write("\n");
688
+ stdout2.write("\n");
563
689
  process.exit(130);
564
690
  }
565
691
  if (key.name === "escape") return finish({ type: "cancelled" });
@@ -576,30 +702,30 @@ function selectOption(title, options) {
576
702
  emitKeypressEvents(stdin);
577
703
  stdin.setRawMode?.(true);
578
704
  stdin.resume();
579
- stdout.write(HIDE_CURSOR);
705
+ stdout2.write(HIDE_CURSOR);
580
706
  stdin.on("keypress", onKeypress);
581
707
  render();
582
708
  });
583
709
  }
584
710
 
585
711
  // src/ui/secret.ts
586
- import { stdin as stdin2, stdout as stdout2 } from "process";
712
+ import { stdin as stdin2, stdout as stdout3 } from "process";
587
713
  import { emitKeypressEvents as emitKeypressEvents2 } from "readline";
588
714
  async function readSecret(prompt) {
589
715
  if (!stdin2.isTTY) return void 0;
590
- return new Promise((resolve5) => {
716
+ return new Promise((resolve6) => {
591
717
  let value = "";
592
718
  let settled = false;
593
719
  const wasRaw = Boolean(stdin2.isRaw);
594
- stdout2.write(prompt);
720
+ stdout3.write(prompt);
595
721
  const finish = (result) => {
596
722
  if (settled) return;
597
723
  settled = true;
598
724
  stdin2.off("keypress", onKeypress);
599
725
  if (stdin2.isTTY) stdin2.setRawMode?.(wasRaw);
600
726
  stdin2.pause();
601
- stdout2.write("\n");
602
- resolve5(result);
727
+ stdout3.write("\n");
728
+ resolve6(result);
603
729
  };
604
730
  const onKeypress = (chunk, key) => {
605
731
  if (!key) return;
@@ -624,17 +750,17 @@ async function readSecret(prompt) {
624
750
  }
625
751
  async function readInput(prompt) {
626
752
  if (!stdin2.isTTY) return void 0;
627
- return new Promise((resolve5) => {
753
+ return new Promise((resolve6) => {
628
754
  let value = "";
629
755
  const onData = (chunk) => {
630
756
  value += String(chunk);
631
757
  const newline = value.search(/[\r\n]/);
632
758
  if (newline >= 0) {
633
759
  stdin2.off("data", onData);
634
- resolve5(value.slice(0, newline).trim());
760
+ resolve6(value.slice(0, newline).trim());
635
761
  }
636
762
  };
637
- stdout2.write(prompt);
763
+ stdout3.write(prompt);
638
764
  stdin2.resume();
639
765
  stdin2.on("data", onData);
640
766
  });
@@ -671,14 +797,19 @@ function revokeTrust(cwd) {
671
797
  }
672
798
 
673
799
  // src/api/client.ts
674
- function asRecord2(value) {
800
+ function asRecord(value) {
675
801
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
676
802
  }
677
803
  function bodyErrorCode(body) {
678
- const record = asRecord2(body);
679
- const code = record?.code ?? record?.errorCode ?? asRecord2(record?.error)?.code;
804
+ const record2 = asRecord(body);
805
+ const code = record2?.code ?? record2?.errorCode ?? record2?.error ?? asRecord(record2?.error)?.code;
680
806
  return typeof code === "string" ? code : void 0;
681
807
  }
808
+ function bodyErrorMessage(body) {
809
+ const record2 = asRecord(body);
810
+ const message = record2?.message ?? asRecord(record2?.error)?.message;
811
+ return typeof message === "string" && message.trim() ? message.trim() : void 0;
812
+ }
682
813
  function isAbortError(error) {
683
814
  return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
684
815
  }
@@ -725,7 +856,7 @@ var ApiClient = class {
725
856
  if (code === "API_KEY_REVOKED" || bodyErrorCode(body) === "API_KEY_REVOKED") {
726
857
  throw new ApiError("This API key has been revoked.", response.status, "API_KEY_REVOKED");
727
858
  }
728
- throw new ApiError(`Asiyst API returned HTTP ${response.status}.`, response.status, code);
859
+ throw new ApiError(bodyErrorMessage(body) ?? `Asiyst API returned HTTP ${response.status}.`, response.status, code);
729
860
  }
730
861
  if (rawText && body === void 0) {
731
862
  if (isDebugEnabled()) {
@@ -738,8 +869,8 @@ var ApiClient = class {
738
869
  async health() {
739
870
  try {
740
871
  const body = await this.request("/health");
741
- const record = asRecord2(body);
742
- if (record) return record;
872
+ const record2 = asRecord(body);
873
+ if (record2) return record2;
743
874
  } catch {
744
875
  }
745
876
  throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
@@ -775,11 +906,12 @@ Asiyst may read project files and modify Asiyst configuration during setup.`)) {
775
906
  return true;
776
907
  }
777
908
  async function trustCommand(cwd = process.cwd()) {
909
+ printHeader("Project Trust", cwd);
778
910
  await ensureTrusted(cwd);
779
911
  }
780
912
  function revokeTrustCommand(cwd = process.cwd()) {
781
913
  revokeTrust(cwd);
782
- console.log("\u2713 Folder trust revoked.");
914
+ console.log("\u2713 Project trust revoked.");
783
915
  }
784
916
 
785
917
  // src/commands/connect.ts
@@ -793,6 +925,9 @@ async function retryOrCancel(message) {
793
925
  }
794
926
  function printApiFailure(error) {
795
927
  if (error instanceof ApiError) {
928
+ if (error.status !== void 0 && error.status < 500 && error.code !== "INVALID_API_KEY" && error.code !== "API_KEY_REVOKED") {
929
+ return `\u2717 ${error.message}`;
930
+ }
796
931
  return friendlyApiMessage(error, error.code === "NOT_FOUND" ? VERIFY_KEY_URL : void 0);
797
932
  }
798
933
  if (isDebugEnabled() && error instanceof Error) return `\u2717 ${error.message}`;
@@ -818,14 +953,14 @@ async function promptForUserId() {
818
953
  return void 0;
819
954
  }
820
955
  if (!isValidUserId(value)) {
821
- console.log("Invalid User ID. It must be a 24-character public Asiyst account ID.");
956
+ console.log("Invalid Asiyst User ID.");
957
+ console.log("Expected: exactly 16 characters using letters, numbers, and hyphens only.");
822
958
  return void 0;
823
959
  }
824
960
  return value.trim();
825
961
  }
826
962
  async function connectCommand(cwd = process.cwd(), api = createApiClient(), cliProjectId) {
827
- console.log(`Project folder:
828
- ${cwd}`);
963
+ printHeader("Connect project", cwd);
829
964
  if (!await ensureTrusted(cwd)) return;
830
965
  const project = detectProject(cwd);
831
966
  projectChecks(project);
@@ -840,50 +975,65 @@ ${cwd}`);
840
975
  console.log("Connection cancelled.");
841
976
  return;
842
977
  }
978
+ console.log("Opening Asiyst...");
979
+ if (!await openBrowser(ASIIYST_WEB_URL)) {
980
+ console.log(`Open this URL manually:
981
+ ${ASIIYST_WEB_URL}`);
982
+ }
983
+ console.log("Register or log in, complete onboarding, create or select a project, then create an API key.");
984
+ const userId = await promptForUserId();
985
+ if (!userId) return;
986
+ try {
987
+ await verifyUser(api, userId);
988
+ ok("User verified.");
989
+ } catch (error) {
990
+ console.log(printApiFailure(error));
991
+ return;
992
+ }
843
993
  const projectIdArg = cliProjectId ?? parseProjectIdArgument(process.argv.slice(2));
844
994
  if (projectIdArg === "") {
845
995
  console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
846
996
  return;
847
997
  }
848
998
  let projectId = typeof projectIdArg === "string" ? projectIdArg.trim() : "";
849
- if (!projectId) {
850
- projectId = await promptForProjectId() || "";
851
- }
999
+ if (!projectId) projectId = await promptForProjectId() || "";
852
1000
  if (!projectId) {
853
1001
  console.log("Project ID is required. Use: asiyst connect --project-id <PROJECT_ID>");
854
1002
  return;
855
1003
  }
856
- if (!isValidPublicIdentifier(projectId)) {
857
- console.log("Invalid Project ID. Use a public project ID such as K8mP2xQ7_vL4N9cR5T1zB6Y3");
1004
+ if (!isValidProjectId(projectId)) {
1005
+ console.log("Invalid Project ID. It must be exactly 24 characters.");
858
1006
  return;
859
1007
  }
860
- const userId = await promptForUserId();
861
- if (!userId) return;
862
- console.log("Opening Asiyst...");
863
- if (!await openBrowser(ASIIYST_WEB_URL)) {
864
- console.log(`Open this URL manually:
865
- ${ASIIYST_WEB_URL}`);
1008
+ let verifiedProject;
1009
+ try {
1010
+ verifiedProject = await verifyProject(api, userId, projectId);
1011
+ ok("Project verified.");
1012
+ } catch (error) {
1013
+ console.log(printApiFailure(error));
1014
+ return;
866
1015
  }
867
- console.log("Register or log in, complete onboarding, create or select a project, then create an API key.");
868
1016
  for (; ; ) {
869
1017
  const apiKey = await readSecret("Paste your Asiyst API key: ");
870
1018
  if (apiKey === void 0) {
871
1019
  console.log("Connection cancelled.");
872
1020
  return;
873
1021
  }
874
- if (!apiKey) {
875
- fail("Invalid API key.");
1022
+ if (!isValidApiKey(apiKey)) {
1023
+ fail("Invalid API key. It must be exactly 32 characters.");
876
1024
  continue;
877
1025
  }
878
1026
  try {
879
- const connected = await verifyApiKey(api, apiKey, { userId, projectId });
880
- if (connected.projectId && connected.projectId !== projectId) {
881
- console.log("The provided Project ID does not match the authenticated project.");
882
- return;
883
- }
884
- const finalConnection = { ...connected, projectId, userId };
1027
+ const connected = await verifyApiKeyRelationship(api, { userId, projectId, apiKey });
1028
+ const finalConnection = {
1029
+ ...connected,
1030
+ projectName: connected.projectName ?? verifiedProject.projectName,
1031
+ website: connected.website ?? verifiedProject.website,
1032
+ publicKey: connected.publicKey ?? verifiedProject.publicKey,
1033
+ projectId,
1034
+ userId
1035
+ };
885
1036
  ok("API key verified.");
886
- ok("Project ID verified.");
887
1037
  await saveConnection(cwd, finalConnection);
888
1038
  writeProjectMetadata(cwd, finalConnection);
889
1039
  ok("Project connected successfully.");
@@ -942,43 +1092,51 @@ async function logoutCommand() {
942
1092
  async function statusCommand(cwd = process.cwd(), api = createApiClient()) {
943
1093
  const stored = await loadConnection(cwd);
944
1094
  if (!stored?.apiKey) {
945
- const metadata = readProjectMetadata(cwd);
946
- if (metadata?.connected && metadata.projectId) {
947
- console.log("\u2713 Connected");
948
- console.log(`Project:
949
- ${metadata.projectId}`);
950
- if (metadata.userId) console.log(`User ID:
951
- ${metadata.userId}`);
952
- return;
953
- }
954
- console.log("Connect your website to show stats.");
1095
+ printHeader("Asiyst Project Status");
1096
+ printProjectSummary(detectProject(cwd), false);
1097
+ console.log(`
1098
+ ${warning(symbols.warning)} Not connected to Asiyst.`);
955
1099
  return;
956
1100
  }
957
1101
  if (!stored.userId || !stored.projectId) {
958
- console.log("\u2717 Connection is missing a User ID or Project ID. Please run:\nasiyst connect");
1102
+ console.log(`${symbols.error} Connection is missing a User ID or Project ID. Please run:
1103
+ asiyst connect`);
959
1104
  return;
960
1105
  }
961
1106
  try {
962
- const connected = await verifyApiKey(api, stored.apiKey, {
1107
+ const connected = await verifyApiKeyRelationship(api, {
963
1108
  userId: stored.userId,
964
- projectId: stored.projectId
1109
+ projectId: stored.projectId,
1110
+ apiKey: stored.apiKey
965
1111
  });
1112
+ if (stored.avatarId) {
1113
+ await verifyAvatar(api, {
1114
+ userId: stored.userId,
1115
+ projectId: stored.projectId,
1116
+ apiKey: stored.apiKey,
1117
+ avatarId: stored.avatarId
1118
+ });
1119
+ }
966
1120
  const projectId = stored.projectId || connected.projectId;
967
1121
  const userId = stored.userId || connected.userId;
968
- console.log("\u2713 Connected");
969
- if (userId) console.log(`
970
- User ID:
971
- ${userId}`);
1122
+ printHeader("Asiyst Project Status");
1123
+ printProjectSummary(detectProject(cwd), true, connected.projectName, connected.website);
972
1124
  console.log(`
973
- Project ID:
974
- ${projectId || connected.projectName || "Unknown"}`);
975
- console.log("\nAPI Key:\n\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
976
- if (stored.avatarId) console.log(`
977
- Avatar:
978
- ${stored.avatarId}`);
979
- if (connected.website) console.log(`
980
- Website:
981
- ${connected.website}`);
1125
+ ${section("Identifiers")}`);
1126
+ if (userId) console.log(` User ID: ${maskSecret(userId)}`);
1127
+ console.log(` Project ID: ${projectId}`);
1128
+ console.log(` API Key: ${maskSecret(stored.apiKey)}`);
1129
+ console.log(`
1130
+ ${section("Integration")}`);
1131
+ console.log(` User: ${success(symbols.success)} Connected`);
1132
+ console.log(` Project: ${success(symbols.success)} ${connected.projectName ?? projectId}`);
1133
+ if (stored.avatarId) console.log(` Avatar: ${success(symbols.success)} ${stored.avatarName ?? stored.avatarId}`);
1134
+ else console.log(` Avatar: ${symbols.error} Not imported`);
1135
+ const detection = detectProject(cwd);
1136
+ console.log(` SDK: ${detection.sdkVersion ? `${success(symbols.success)} Installed (${detection.sdkVersion})` : `${symbols.error} Not installed`}`);
1137
+ console.log(` Integration: ${detection.sdkVersion && stored.avatarId ? `${success(symbols.success)} Configured` : `${symbols.error} Not configured`}`);
1138
+ console.log(`
1139
+ ${success(symbols.success)} Everything looks good.`);
982
1140
  } catch (error) {
983
1141
  if (error instanceof ApiError && error.code === "API_KEY_REVOKED") {
984
1142
  console.log("\u2717 API key revoked.");
@@ -986,10 +1144,10 @@ ${connected.website}`);
986
1144
  return;
987
1145
  }
988
1146
  if (error instanceof ApiError && (error.code === "INVALID_API_KEY" || error.code === "FORBIDDEN")) {
989
- console.log("\u2717 Connection invalid.");
1147
+ console.log(`${symbols.error} Connection invalid. Run \`asiyst connect\` to reconnect.`);
990
1148
  return;
991
1149
  }
992
- console.log("\u2717 Connection invalid.");
1150
+ console.log(`${symbols.error} Connection invalid. Run \`asiyst doctor\` for diagnostics.`);
993
1151
  }
994
1152
  }
995
1153
 
@@ -1013,8 +1171,21 @@ function parseAvatarImportResponse(value, projectId, userId, avatarId) {
1013
1171
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1014
1172
  throw new ApiError("Received an unexpected response from Asiyst.", 200, "MALFORMED_RESPONSE");
1015
1173
  }
1016
- const body = value;
1174
+ const response = value;
1175
+ const body = response.data && typeof response.data === "object" && !Array.isArray(response.data) ? response.data : response;
1017
1176
  const imported = body.imported === true || body.success === true || body.status === "imported";
1177
+ const alreadyImported = body.code === "AVATAR_ALREADY_IMPORTED" || body.status === "already_imported";
1178
+ if (alreadyImported) {
1179
+ return {
1180
+ imported: true,
1181
+ alreadyImported: true,
1182
+ avatarId,
1183
+ projectId,
1184
+ userId,
1185
+ avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
1186
+ publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0
1187
+ };
1188
+ }
1018
1189
  if (!imported) {
1019
1190
  const code = typeof body.code === "string" ? body.code : "IMPORT_FAILED";
1020
1191
  const supportedCode = ["AVATAR_NOT_FOUND", "AVATAR_ALREADY_IMPORTED", "FORBIDDEN", "PROJECT_NOT_FOUND", "CONFLICT"].includes(code) ? code : "IMPORT_FAILED";
@@ -1024,24 +1195,33 @@ function parseAvatarImportResponse(value, projectId, userId, avatarId) {
1024
1195
  if (returnedAvatarId !== void 0 && returnedAvatarId !== avatarId) {
1025
1196
  throw new ApiError("The API returned a different avatar than requested.", 200, "AVATAR_MISMATCH");
1026
1197
  }
1198
+ const returnedProjectId = body.projectId ?? body.project_id;
1199
+ if (returnedProjectId !== void 0 && returnedProjectId !== projectId) {
1200
+ throw new ApiError("The API returned a different project than requested.", 200, "PROJECT_MISMATCH");
1201
+ }
1202
+ const returnedUserId = body.userId ?? body.user_id;
1203
+ if (returnedUserId !== void 0 && returnedUserId !== userId) {
1204
+ throw new ApiError("The API returned a different user than requested.", 200, "USER_MISMATCH");
1205
+ }
1027
1206
  return {
1028
1207
  imported: true,
1029
1208
  avatarId,
1030
1209
  projectId,
1031
1210
  userId,
1032
- avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0
1211
+ avatarName: typeof body.avatarName === "string" ? body.avatarName : void 0,
1212
+ publicKey: typeof body.publicKey === "string" ? body.publicKey : void 0
1033
1213
  };
1034
1214
  }
1035
1215
  async function importAvatar(api, input) {
1036
1216
  const userId = requireIdentifier(input.userId, "User ID", isValidUserId);
1037
- const projectId = requireIdentifier(input.projectId, "Project ID", isValidPublicIdentifier);
1217
+ const projectId = requireIdentifier(input.projectId, "Project ID", isValidProjectId);
1038
1218
  const avatarId = requireIdentifier(input.avatarId, "Avatar ID", isValidAvatarId);
1039
1219
  const apiKey = input.apiKey.trim();
1040
- if (!apiKey) throw new ApiError("An API key is required.", 401, "INVALID_API_KEY");
1220
+ if (!isValidApiKey(apiKey)) throw new ApiError("Invalid API key. It must be exactly 32 characters.", 401, "INVALID_API_KEY");
1041
1221
  const value = await api.request(`/cli/projects/${encodeURIComponent(projectId)}/avatars/import`, {
1042
1222
  method: "POST",
1043
1223
  headers: {
1044
- Authorization: `Bearer ${apiKey}`,
1224
+ Authorization: "Bearer " + apiKey,
1045
1225
  "X-Asiyst-API-Key": apiKey
1046
1226
  },
1047
1227
  body: JSON.stringify({ userId, projectId, avatarId })
@@ -1083,7 +1263,7 @@ async function verifyCommand(cwd = process.cwd()) {
1083
1263
  import { readFileSync as readFileSync5 } from "fs";
1084
1264
  import { fileURLToPath } from "url";
1085
1265
  function readCurrentVersion() {
1086
- if ("1.1.1") return "1.1.1";
1266
+ if ("1.1.2") return "1.1.2";
1087
1267
  try {
1088
1268
  const packageJson = JSON.parse(readFileSync5(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
1089
1269
  if (packageJson && typeof packageJson === "object" && typeof packageJson.version === "string") {
@@ -1130,12 +1310,234 @@ ${url}`);
1130
1310
  else console.log(`Opening ${url}`);
1131
1311
  }
1132
1312
 
1313
+ // src/integration/writer.ts
1314
+ import { execFile as execFile3 } from "child_process";
1315
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1316
+ import { dirname, join as join4, relative, resolve as resolve5 } from "path";
1317
+ import { promisify as promisify3 } from "util";
1318
+ var execFileAsync3 = promisify3(execFile3);
1319
+ var MARKER_START = "// ASIIYST CLI START";
1320
+ var MARKER_END = "// ASIIYST CLI END";
1321
+ function componentSource(values, extension) {
1322
+ if (extension === "ts" || extension === "js") {
1323
+ return `${MARKER_START}
1324
+ import { Asiyst } from "@asiyst/sdk";
1325
+
1326
+ void Asiyst.init({
1327
+ projectId: ${JSON.stringify(values.projectId)},
1328
+ publicKey: ${JSON.stringify(values.publicKey)},
1329
+ avatarId: ${JSON.stringify(values.avatarId)},
1330
+ position: "bottom-right",
1331
+ });
1332
+ ${MARKER_END}
1333
+ `;
1334
+ }
1335
+ const importLine = extension === "tsx" ? 'import * as React from "react";' : 'import React from "react";';
1336
+ return `${MARKER_START}
1337
+ ${importLine}
1338
+ import { Asiyst } from "@asiyst/sdk";
1339
+
1340
+ let asiystStarted = false;
1341
+
1342
+ export function AsiystAssistant() {
1343
+ React.useEffect(() => {
1344
+ if (asiystStarted) return;
1345
+ asiystStarted = true;
1346
+ void Asiyst.init({
1347
+ projectId: ${JSON.stringify(values.projectId)},
1348
+ publicKey: ${JSON.stringify(values.publicKey)},
1349
+ avatarId: ${JSON.stringify(values.avatarId)},
1350
+ position: "bottom-right",
1351
+ }).catch((error) => {
1352
+ asiystStarted = false;
1353
+ console.error("Asiyst failed to initialize.", error);
1354
+ });
1355
+ }, []);
1356
+
1357
+ return null;
1358
+ }
1359
+ ${MARKER_END}
1360
+ `;
1361
+ }
1362
+ function read(path) {
1363
+ return readFileSync6(path, "utf8");
1364
+ }
1365
+ function findFirst(cwd, paths) {
1366
+ return paths.map((path) => resolve5(cwd, path)).find((path) => existsSync5(path));
1367
+ }
1368
+ function isTypeScript(project) {
1369
+ return project.language === "TypeScript" || existsSync5(resolve5(project.cwd, "tsconfig.json"));
1370
+ }
1371
+ function pathsFor(project) {
1372
+ if (project.framework === "Vanilla TypeScript" || project.framework === "Vanilla JavaScript") {
1373
+ const extension2 = project.framework === "Vanilla TypeScript" ? "ts" : "js";
1374
+ return {
1375
+ component: join4("src", `asiyst.${extension2}`),
1376
+ entry: findFirst(project.cwd, [`src/index.${extension2}`, `index.${extension2}`]),
1377
+ extension: extension2
1378
+ };
1379
+ }
1380
+ const extension = isTypeScript(project) ? "tsx" : "jsx";
1381
+ const component = join4("src", "components", `AsiystAssistant.${extension}`);
1382
+ if (project.framework === "Next.js") {
1383
+ const appEntry = findFirst(project.cwd, ["src/app/layout.tsx", "src/app/layout.jsx", "app/layout.tsx", "app/layout.jsx"]);
1384
+ if (appEntry) return { component, entry: appEntry, extension: appEntry.endsWith(".tsx") ? "tsx" : "jsx" };
1385
+ const pagesEntry = findFirst(project.cwd, ["src/pages/_app.tsx", "src/pages/_app.jsx", "pages/_app.tsx", "pages/_app.jsx"]);
1386
+ if (pagesEntry) return { component, entry: pagesEntry, extension: pagesEntry.endsWith(".tsx") ? "tsx" : "jsx" };
1387
+ return { component, extension };
1388
+ }
1389
+ const entry = findFirst(project.cwd, [
1390
+ "src/main.tsx",
1391
+ "src/main.jsx",
1392
+ "src/main.ts",
1393
+ "src/main.js",
1394
+ "src/index.tsx",
1395
+ "src/index.jsx",
1396
+ "src/index.ts",
1397
+ "src/index.js"
1398
+ ]);
1399
+ return { component, entry, extension: entry?.endsWith(".tsx") || entry?.endsWith(".ts") ? "tsx" : "jsx" };
1400
+ }
1401
+ function updateComponent(path, source, values) {
1402
+ if (!existsSync5(path)) {
1403
+ mkdirSync4(dirname(path), { recursive: true });
1404
+ writeFileSync4(path, source, "utf8");
1405
+ return "create";
1406
+ }
1407
+ const current = read(path);
1408
+ const start = current.indexOf(MARKER_START);
1409
+ const end = current.indexOf(MARKER_END);
1410
+ if (start >= 0 && end >= start) {
1411
+ const next2 = current.slice(0, start) + source.trimEnd() + current.slice(end + MARKER_END.length);
1412
+ if (next2 === current) return "unchanged";
1413
+ writeFileSync4(path, next2, "utf8");
1414
+ return "update";
1415
+ }
1416
+ const replacements = {
1417
+ projectId: JSON.stringify(values.projectId),
1418
+ publicKey: JSON.stringify(values.publicKey),
1419
+ avatarId: JSON.stringify(values.avatarId)
1420
+ };
1421
+ let next = source;
1422
+ for (const [name, value] of Object.entries(replacements)) {
1423
+ const pattern = new RegExp(`(${name}\\s*:\\s*)["'\`][^"'\`]+["'\`]`);
1424
+ if (!pattern.test(next)) {
1425
+ throw new Error(`Existing Asiyst component is not safely updateable: ${path}`);
1426
+ }
1427
+ next = next.replace(pattern, `$1${value}`);
1428
+ }
1429
+ if (next === source) return "unchanged";
1430
+ writeFileSync4(path, next, "utf8");
1431
+ return "update";
1432
+ }
1433
+ function updateEntry(path, componentPath) {
1434
+ const current = read(path);
1435
+ const importPath = relative(dirname(path), resolve5(componentPath)).replace(/\\/g, "/").replace(/\.(tsx|jsx)$/, "").replace(/^\.\//, "");
1436
+ const importLine = componentPath.endsWith(".ts") || componentPath.endsWith(".js") ? `import "./${importPath}";` : `import { AsiystAssistant } from "./${importPath}";`;
1437
+ const hasImport = current.includes("AsiystAssistant") && current.includes("@asiyst/sdk");
1438
+ const nextImport = hasImport ? current : `${importLine}
1439
+ ${current}`;
1440
+ const componentPattern = /<AsiystAssistant\s*\/>/;
1441
+ let next = componentPattern.test(nextImport) ? nextImport : nextImport;
1442
+ if (!componentPattern.test(nextImport)) {
1443
+ if (/\{children\}/.test(nextImport)) {
1444
+ next = nextImport.replace(/\{children\}/, "<AsiystAssistant />\n {children}");
1445
+ } else if (/<App\s*\/>/.test(nextImport)) {
1446
+ next = nextImport.replace(/<App\s*\/>/, "<><AsiystAssistant /><App /></>");
1447
+ } else if (/<Component\s+\{\.\.\.pageProps\}\s*\/>/.test(nextImport)) {
1448
+ next = nextImport.replace(/<Component\s+\{\.\.\.pageProps\}\s*\/>/, "<><AsiystAssistant /><Component {...pageProps} /></>");
1449
+ } else if (componentPath.endsWith(".ts") || componentPath.endsWith(".js")) {
1450
+ next = nextImport;
1451
+ } else {
1452
+ throw new Error(`Could not find a safe render location in ${path}. Add <AsiystAssistant /> to this entry point.`);
1453
+ }
1454
+ }
1455
+ if (next === current) return "unchanged";
1456
+ writeFileSync4(path, next, "utf8");
1457
+ return "update";
1458
+ }
1459
+ function planIntegration(project, values) {
1460
+ if (!values.projectId || !values.publicKey || !values.avatarId) {
1461
+ throw new Error("Verified projectId, publicKey, and avatarId are required before configuring the SDK.");
1462
+ }
1463
+ const paths = pathsFor(project);
1464
+ const componentPath = resolve5(project.cwd, paths.component);
1465
+ const entryPath = paths.entry;
1466
+ if (!entryPath && !project.framework.startsWith("Vanilla") && project.framework !== "Next.js" && project.framework !== "React" && project.framework !== "Vite") {
1467
+ return {
1468
+ framework: project.framework,
1469
+ sdkInstalled: Boolean(project.sdkVersion),
1470
+ sdkVersion: project.sdkVersion,
1471
+ componentPath,
1472
+ componentAction: existsSync5(componentPath) ? "update" : "create",
1473
+ entryAction: "unsupported",
1474
+ reason: "No supported React entry point was detected. Add AsiystAssistant to the website entry point manually."
1475
+ };
1476
+ }
1477
+ return {
1478
+ framework: project.framework,
1479
+ sdkInstalled: Boolean(project.sdkVersion),
1480
+ sdkVersion: project.sdkVersion,
1481
+ componentPath,
1482
+ entryPath,
1483
+ componentAction: existsSync5(componentPath) ? "update" : "create",
1484
+ entryAction: entryPath ? "update" : "unsupported",
1485
+ reason: entryPath ? void 0 : "No supported application entry point was detected."
1486
+ };
1487
+ }
1488
+ async function installSdk(project) {
1489
+ const commands = {
1490
+ npm: { command: "npm", args: ["install", "@asiyst/sdk"] },
1491
+ pnpm: { command: "pnpm", args: ["add", "@asiyst/sdk"] },
1492
+ yarn: { command: "yarn", args: ["add", "@asiyst/sdk"] },
1493
+ bun: { command: "bun", args: ["add", "@asiyst/sdk"] }
1494
+ };
1495
+ const selected = commands[project.packageManager] ?? commands.npm;
1496
+ await execFileAsync3(selected.command, selected.args, { cwd: project.cwd, windowsHide: true });
1497
+ }
1498
+ function applyIntegration(project, values, plan = planIntegration(project, values)) {
1499
+ if (plan.entryAction === "unsupported" || !plan.entryPath) {
1500
+ throw new Error(plan.reason ?? "The project entry point could not be detected safely.");
1501
+ }
1502
+ const paths = pathsFor(project);
1503
+ const componentAction = updateComponent(plan.componentPath, componentSource(values, paths.extension), values);
1504
+ const entryAction = updateEntry(plan.entryPath, plan.componentPath);
1505
+ return { ...plan, componentAction, entryAction, installed: plan.sdkInstalled };
1506
+ }
1507
+ function printDryRun(plan) {
1508
+ console.log(`Framework: ${plan.framework}`);
1509
+ console.log(`SDK: ${plan.sdkInstalled ? `installed${plan.sdkVersion ? ` (${plan.sdkVersion})` : ""}` : "missing"}`);
1510
+ console.log(`Component: ${plan.componentAction} ${plan.componentPath}`);
1511
+ console.log(`Entry point: ${plan.entryPath ? `${plan.entryAction} ${plan.entryPath}` : "not detected"}`);
1512
+ if (plan.reason) console.log(`Note: ${plan.reason}`);
1513
+ }
1514
+ function readIntegrationValues(plan) {
1515
+ if (!existsSync5(plan.componentPath)) return {};
1516
+ const source = read(plan.componentPath);
1517
+ const value = (name) => {
1518
+ const match = source.match(new RegExp(`${name}\\s*:\\s*["'\`]([^"'\`]+)["'\`]`));
1519
+ return match?.[1];
1520
+ };
1521
+ return {
1522
+ projectId: value("projectId"),
1523
+ publicKey: value("publicKey"),
1524
+ avatarId: value("avatarId")
1525
+ };
1526
+ }
1527
+
1133
1528
  // src/commands/avatar.ts
1134
1529
  function parseAvatarId(argv) {
1135
1530
  const index = argv.findIndex((value) => value === "--avatar-id");
1136
1531
  if (index >= 0) return argv[index + 1];
1137
1532
  const inline = argv.find((value) => value.startsWith("--avatar-id="));
1138
- return inline?.slice("--avatar-id=".length);
1533
+ return inline?.slice("--avatar-id=".length) || argv.slice(1).find((value) => !value.startsWith("--"));
1534
+ }
1535
+ async function confirm2(message) {
1536
+ const result = await selectOption(message, [
1537
+ { label: "Continue", value: true },
1538
+ { label: "Cancel", value: false }
1539
+ ]);
1540
+ return result.type === "selected" && result.value;
1139
1541
  }
1140
1542
  async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(), suppliedAvatarId) {
1141
1543
  const stored = await loadConnection(cwd);
@@ -1148,24 +1550,112 @@ async function avatarImportCommand(cwd = process.cwd(), api = createApiClient(),
1148
1550
  console.log("Invalid Avatar ID. It must contain exactly 10 alphanumeric characters.");
1149
1551
  return;
1150
1552
  }
1553
+ console.log("Opening Asiyst Avatar Studio...");
1554
+ if (!await openBrowser(ASIIYST_WEB_URL)) {
1555
+ console.log(`Open this URL manually:
1556
+ ${ASIIYST_WEB_URL}`);
1557
+ }
1558
+ console.log("Configure and save your avatar in Avatar Studio, then return here.");
1559
+ const project = detectProject(cwd);
1560
+ const dryRun = process.argv.includes("--dry-run");
1561
+ const detectedProjectId = project.config.projectId;
1562
+ if (detectedProjectId && detectedProjectId !== stored.projectId) {
1563
+ console.log(`Existing projectId ${detectedProjectId} differs from verified project ${stored.projectId}.`);
1564
+ if (!await confirm2("Switch this website to the verified project?")) return;
1565
+ }
1566
+ const existingPlan = planIntegration(project, {
1567
+ projectId: stored.projectId,
1568
+ publicKey: stored.publicKey ?? "pending-public-key",
1569
+ avatarId: avatarId.trim()
1570
+ });
1571
+ const existing = readIntegrationValues(existingPlan);
1572
+ if (existing.projectId && existing.projectId !== stored.projectId) {
1573
+ console.log(`\u2717 Existing integration uses project ${existing.projectId}, but the verified project is ${stored.projectId}.`);
1574
+ if (!await confirm2("Replace the existing project configuration?")) return;
1575
+ }
1576
+ if (existing.avatarId && existing.avatarId !== avatarId.trim()) {
1577
+ console.log(`\u2717 Existing integration uses avatar ${existing.avatarId}, but the verified avatar is ${avatarId.trim()}.`);
1578
+ if (!await confirm2("Replace the existing avatar configuration?")) return;
1579
+ }
1151
1580
  try {
1581
+ if (dryRun) {
1582
+ const plan2 = planIntegration(project, {
1583
+ projectId: stored.projectId,
1584
+ publicKey: stored.publicKey ?? "",
1585
+ avatarId: avatarId.trim()
1586
+ });
1587
+ printDryRun(plan2);
1588
+ return;
1589
+ }
1590
+ const verifiedConnection = await verifyApiKeyRelationship(api, {
1591
+ userId: stored.userId,
1592
+ projectId: stored.projectId,
1593
+ apiKey: stored.apiKey
1594
+ });
1595
+ await verifyAvatar(api, {
1596
+ userId: stored.userId,
1597
+ projectId: stored.projectId,
1598
+ apiKey: stored.apiKey,
1599
+ avatarId
1600
+ });
1601
+ await createImportSession(api, {
1602
+ userId: stored.userId,
1603
+ projectId: stored.projectId,
1604
+ apiKey: stored.apiKey,
1605
+ avatarId
1606
+ });
1607
+ const shouldImport = await confirm2("Import this avatar into this website?");
1608
+ if (!shouldImport) {
1609
+ console.log("Avatar import cancelled. Your verified connection has been saved.");
1610
+ return;
1611
+ }
1152
1612
  const result = await importAvatar(api, {
1153
1613
  userId: stored.userId,
1154
1614
  projectId: stored.projectId,
1155
1615
  apiKey: stored.apiKey,
1156
1616
  avatarId
1157
1617
  });
1158
- await saveConnection(cwd, { ...stored, avatarId: result.avatarId });
1159
- console.log(`\u2713 Avatar ${result.avatarId} imported into project ${result.projectId}.`);
1618
+ const publicKey = stored.publicKey ?? verifiedConnection.publicKey ?? result.publicKey;
1619
+ if (!publicKey) {
1620
+ console.log("\u2717 The verified project did not return a public SDK key. No files were changed.");
1621
+ return;
1622
+ }
1623
+ const plan = planIntegration(detectProject(cwd), {
1624
+ projectId: result.projectId,
1625
+ publicKey,
1626
+ avatarId: result.avatarId
1627
+ });
1628
+ if (!plan.sdkInstalled) {
1629
+ console.log(`Installing @asiyst/sdk with ${project.packageManager}...`);
1630
+ await installSdk(project);
1631
+ }
1632
+ const configured = applyIntegration(detectProject(cwd), {
1633
+ projectId: result.projectId,
1634
+ publicKey,
1635
+ avatarId: result.avatarId
1636
+ });
1637
+ const next = { ...stored, avatarId: result.avatarId, avatarName: result.avatarName, publicKey };
1638
+ await saveConnection(cwd, next);
1639
+ writeProjectMetadata(cwd, next);
1640
+ console.log("\u2713 Avatar verified");
1641
+ console.log("\u2713 Project verified");
1642
+ console.log("\u2713 API key verified");
1643
+ console.log("\u2713 SDK installed");
1644
+ console.log(`\u2713 Asiyst integration ${configured.componentAction === "unchanged" ? "already " : ""}configured`);
1645
+ console.log(`\u2713 Avatar ${result.alreadyImported ? "already imported" : "imported"}`);
1646
+ console.log(`
1647
+ Your Asiyst avatar is ready.`);
1160
1648
  } catch (error) {
1161
1649
  if (error instanceof ApiError) {
1162
1650
  if (error.code === "FORBIDDEN") console.log("\u2717 You do not have permission to import an avatar into this project.");
1651
+ else if (error.code === "USER_MISMATCH") console.log("\u2717 The avatar is not owned by the verified Asiyst user.");
1652
+ else if (error.code === "PROJECT_MISMATCH") console.log("\u2717 The avatar is not assigned to the verified project.");
1163
1653
  else if (error.code === "NOT_FOUND") console.log("\u2717 Project or avatar was not found.");
1164
1654
  else if (error.code === "CONFLICT" || error.code === "AVATAR_ALREADY_IMPORTED") console.log("\u2717 This avatar is already imported into the project.");
1165
1655
  else console.log(`\u2717 ${error.message}`);
1166
1656
  return;
1167
1657
  }
1168
- console.log("\u2717 Unable to import the avatar.");
1658
+ console.log(`\u2717 ${error instanceof Error ? error.message : "Unable to import the avatar."}`);
1169
1659
  }
1170
1660
  }
1171
1661
  async function avatarCommand(cwd = process.cwd(), argv = process.argv.slice(2)) {
@@ -1188,55 +1678,79 @@ ${ASIIYST_WEB_URL}`);
1188
1678
  }
1189
1679
 
1190
1680
  // src/commands/interactive.ts
1191
- function interactiveHelp() {
1192
- console.log(`
1193
- Asiyst CLI
1194
-
1195
- Usage: asiyst [command]
1196
-
1197
- Commands
1198
- connect Connect this project to Asiyst
1199
- avatar import Import an avatar into the connected project
1200
- status Show connection status
1201
- health Check Asiyst API connectivity
1202
- disconnect Remove local connection information
1203
- help Show available commands
1204
- version Show CLI version
1205
-
1206
- Also available
1207
- trust Trust this project folder
1208
- revoke-trust Revoke folder trust
1209
- doctor Run diagnostics
1210
- update Check for CLI updates
1681
+ import { stdin as stdin3, stdout as stdout4 } from "process";
1682
+ import { emitKeypressEvents as emitKeypressEvents3 } from "readline";
1211
1683
 
1212
- Examples
1213
- asiyst connect
1214
- asiyst status
1215
- asiyst disconnect
1216
- `);
1217
- }
1218
- async function interactiveHome() {
1219
- homeStatus();
1220
- for (; ; ) {
1221
- const result = await selectOption("What would you like to do?", [
1222
- { label: "Connect", value: "connect" },
1223
- { label: "Status", value: "status" },
1224
- { label: "Help", value: "help" },
1225
- { label: "Exit", value: "exit" }
1226
- ]);
1227
- if (result.type !== "selected" || result.value === "exit") {
1228
- break;
1684
+ // src/commands/health.ts
1685
+ async function healthCommand(api = createApiClient(), cwd = process.cwd()) {
1686
+ printHeader("Asiyst Health Check");
1687
+ const project = detectProject(cwd);
1688
+ let healthy = true;
1689
+ ok("Project detected", project.framework);
1690
+ if (project.sdkVersion) ok("SDK installed", project.sdkVersion);
1691
+ else {
1692
+ fail("SDK installed", "Run the avatar import command to install it");
1693
+ healthy = false;
1694
+ }
1695
+ try {
1696
+ await api.health();
1697
+ ok("Asiyst API reachable");
1698
+ } catch {
1699
+ fail("Asiyst API reachable", "Unable to reach Asiyst API");
1700
+ console.log(`
1701
+ Health: ${warning(symbols.warning)} Action required`);
1702
+ return;
1703
+ }
1704
+ const stored = await loadConnection(cwd);
1705
+ if (!stored?.apiKey || !stored.userId || !stored.projectId) {
1706
+ console.log(`
1707
+ Health: ${warning(symbols.warning)} Action required`);
1708
+ console.log("Run `asiyst connect` to connect this project.");
1709
+ return;
1710
+ }
1711
+ ok("Authentication valid");
1712
+ try {
1713
+ await verifyApiKeyRelationship(api, {
1714
+ userId: stored.userId,
1715
+ projectId: stored.projectId,
1716
+ apiKey: stored.apiKey
1717
+ });
1718
+ ok("Project authorized");
1719
+ const metadata = readProjectMetadata(cwd);
1720
+ if (metadata?.avatarId) {
1721
+ await verifyAvatar(api, {
1722
+ userId: stored.userId,
1723
+ projectId: stored.projectId,
1724
+ apiKey: stored.apiKey,
1725
+ avatarId: metadata.avatarId
1726
+ });
1727
+ ok("Avatar verified", metadata.avatarName ?? metadata.avatarId);
1728
+ } else {
1729
+ fail("Avatar", "No imported avatar is recorded for this project");
1730
+ healthy = false;
1229
1731
  }
1230
- if (result.value === "connect") await connectCommand();
1231
- else if (result.value === "status") await statusCommand();
1232
- else if (result.value === "help") interactiveHelp();
1233
- else if (result.value === "version") console.log(readCurrentVersion());
1732
+ if (metadata?.projectId === stored.projectId && metadata.publicKey) ok("Local configuration", "Project and public key present");
1733
+ else {
1734
+ fail("Local configuration", "Project metadata is incomplete");
1735
+ healthy = false;
1736
+ }
1737
+ if (project.sdkVersion && metadata?.avatarId) ok("Website integration", "SDK and avatar are configured");
1738
+ else {
1739
+ fail("Website integration", "Run `asiyst avatar import`");
1740
+ healthy = false;
1741
+ }
1742
+ console.log(`
1743
+ Health: ${healthy ? `${success(symbols.connected)} Healthy` : `${warning(symbols.warning)} Action required`}`);
1744
+ } catch {
1745
+ fail("Project authorization", "Run `asiyst connect` to reconnect");
1746
+ console.log(`
1747
+ Health: ${warning(symbols.warning)} Action required`);
1234
1748
  }
1235
1749
  }
1236
1750
 
1237
1751
  // src/update/check.ts
1238
1752
  import { execFileSync } from "child_process";
1239
- import { readFileSync as readFileSync6 } from "fs";
1753
+ import { readFileSync as readFileSync7 } from "fs";
1240
1754
  var PACKAGE_NAME = "@asiyst/cli";
1241
1755
  var REGISTRY_URL = "https://registry.npmjs.org/%40asiyst%2Fcli/latest";
1242
1756
  var TIMEOUT_MS = 800;
@@ -1308,7 +1822,7 @@ function detectInstallKind(executablePath = process.argv[1] ?? "", env = process
1308
1822
  }
1309
1823
  function installedVersion(packageRoot) {
1310
1824
  try {
1311
- const value = JSON.parse(readFileSync6(`${packageRoot}/package.json`, "utf8"));
1825
+ const value = JSON.parse(readFileSync7(`${packageRoot}/package.json`, "utf8"));
1312
1826
  return value && typeof value === "object" && typeof value.version === "string" ? value.version : void 0;
1313
1827
  } catch {
1314
1828
  return void 0;
@@ -1331,18 +1845,18 @@ function installLatest(latestVersion, kind, executablePath = process.argv[1] ??
1331
1845
 
1332
1846
  // src/commands/update.ts
1333
1847
  async function updateCommand() {
1848
+ printHeader("Asiyst CLI Update");
1334
1849
  const result = await checkForUpdate();
1335
1850
  if (!result.latestVersion || compareVersions(result.latestVersion, result.currentVersion) <= 0) {
1336
- console.log(`\u2713 Asiyst CLI is already up to date.
1851
+ console.log(`${success(symbols.success)} Latest version
1337
1852
 
1338
1853
  Current version: ${result.currentVersion}`);
1339
1854
  return;
1340
1855
  }
1341
- console.log(`
1342
- Update available
1856
+ console.log(`${warning(symbols.warning)} Update available
1343
1857
 
1344
1858
  Current version: ${result.currentVersion}
1345
- New version: ${result.latestVersion}
1859
+ Latest version: ${result.latestVersion}
1346
1860
  `);
1347
1861
  if (!process.stdin.isTTY || !await confirm(`Update Asiyst CLI from ${result.currentVersion} to ${result.latestVersion}?`)) {
1348
1862
  console.log("Update cancelled.");
@@ -1359,7 +1873,7 @@ New version: ${result.latestVersion}
1359
1873
  if (!installed || compareVersions(installed, result.latestVersion) !== 0) {
1360
1874
  throw new Error("installed version could not be verified");
1361
1875
  }
1362
- console.log(`\u2713 Verifying installation
1876
+ console.log(`${success(symbols.success)} Verifying installation
1363
1877
 
1364
1878
  Asiyst CLI updated from ${result.currentVersion} to ${installed}.
1365
1879
  Restart Asiyst to use the new version.`);
@@ -1369,13 +1883,201 @@ ${error instanceof Error ? error.message : "Installation could not be verified."
1369
1883
  }
1370
1884
  }
1371
1885
 
1372
- // src/commands/health.ts
1373
- async function healthCommand(api = createApiClient()) {
1374
- try {
1375
- await api.health();
1376
- console.log("\u2713 Asiyst API is reachable.");
1377
- } catch {
1378
- console.log("\u2717 Unable to reach Asiyst API.");
1886
+ // src/commands/interactive.ts
1887
+ function interactiveHelp() {
1888
+ console.log(`
1889
+ Asiyst CLI
1890
+
1891
+ Usage: asiyst [command]
1892
+
1893
+ Commands
1894
+ connect Connect this project to Asiyst
1895
+ status Show connection status
1896
+ avatar Manage and import avatars
1897
+ health Check Asiyst API connectivity
1898
+ disconnect Remove local connection information
1899
+ help Show available commands
1900
+ version Show CLI version
1901
+
1902
+ Also available
1903
+ trust Trust this project folder
1904
+ revoke-trust Revoke folder trust
1905
+ doctor Run diagnostics
1906
+ update Check for CLI updates
1907
+
1908
+ Examples
1909
+ asiyst connect
1910
+ asiyst status
1911
+ asiyst health
1912
+ asiyst avatar import
1913
+ asiyst avatar import <avatar-id>
1914
+ asiyst disconnect
1915
+ `);
1916
+ }
1917
+ var COMMANDS = [
1918
+ { input: "connect", label: "Connect this project to Asiyst" },
1919
+ { input: "status", label: "Show connection status" },
1920
+ { input: "avatar", label: "Manage avatars" },
1921
+ { input: "avatar import", label: "Import an avatar" },
1922
+ { input: "health", label: "Check Asiyst API health" },
1923
+ { input: "disconnect", label: "Disconnect this project" },
1924
+ { input: "help", label: "Show available commands" },
1925
+ { input: "version", label: "Show CLI version" },
1926
+ { input: "update", label: "Check for CLI updates" },
1927
+ { input: "exit", label: "Exit the interactive CLI" }
1928
+ ];
1929
+ function suggestions(value) {
1930
+ const query = value.trim().toLowerCase();
1931
+ if (!query) return COMMANDS;
1932
+ return COMMANDS.filter((command) => command.input.startsWith(query));
1933
+ }
1934
+ function readCommand() {
1935
+ if (!stdin3.isTTY || !stdout4.isTTY) return Promise.resolve(void 0);
1936
+ return new Promise((resolve6) => {
1937
+ let value = "";
1938
+ let active = 0;
1939
+ let settled = false;
1940
+ let renderedLines = 0;
1941
+ const wasRaw = Boolean(stdin3.isRaw);
1942
+ const clear = () => {
1943
+ if (renderedLines === 0) return;
1944
+ stdout4.write(`\x1B[${renderedLines}A\x1B[0J`);
1945
+ renderedLines = 0;
1946
+ };
1947
+ const render = () => {
1948
+ clear();
1949
+ const matches = suggestions(value);
1950
+ if (active >= matches.length) active = Math.max(0, matches.length - 1);
1951
+ const lines = [
1952
+ `${section("Command")} ${value || muted("type a command")}`,
1953
+ "",
1954
+ ...matches.slice(0, 6).map((command, index) => `${index === active ? symbols.pointer : " "} ${command.input.padEnd(18)} ${muted(command.label)}`),
1955
+ "",
1956
+ muted("\u2191\u2193 select Enter run Esc cancel Ctrl+C exit")
1957
+ ];
1958
+ stdout4.write(`${lines.join("\n")}
1959
+ `);
1960
+ renderedLines = lines.length;
1961
+ };
1962
+ const finish = (result) => {
1963
+ if (settled) return;
1964
+ settled = true;
1965
+ clear();
1966
+ stdin3.off("keypress", onKeypress);
1967
+ if (stdin3.isTTY) stdin3.setRawMode?.(wasRaw);
1968
+ stdin3.pause();
1969
+ stdout4.write("\n");
1970
+ resolve6(result);
1971
+ };
1972
+ const onKeypress = (chunk, key) => {
1973
+ if (key.ctrl && key.name === "c") {
1974
+ finish(void 0);
1975
+ process.exit(130);
1976
+ }
1977
+ if (key.name === "escape") return finish(void 0);
1978
+ if (key.name === "up") {
1979
+ const matches = suggestions(value);
1980
+ active = Math.max(0, active - 1);
1981
+ if (matches[active]) value = matches[active].input;
1982
+ return render();
1983
+ }
1984
+ if (key.name === "down") {
1985
+ const matches = suggestions(value);
1986
+ active = Math.min(Math.max(0, matches.length - 1), active + 1);
1987
+ if (matches[active]) value = matches[active].input;
1988
+ return render();
1989
+ }
1990
+ if (key.name === "backspace") {
1991
+ value = value.slice(0, -1);
1992
+ active = 0;
1993
+ return render();
1994
+ }
1995
+ if (key.name === "return" || key.name === "enter") {
1996
+ const matches = suggestions(value);
1997
+ const selected = value.trim() || matches[active]?.input;
1998
+ return finish(selected);
1999
+ }
2000
+ if (key.name === "tab") {
2001
+ const matches = suggestions(value);
2002
+ if (matches[active]) value = matches[active].input;
2003
+ return render();
2004
+ }
2005
+ const sequence = key.sequence || chunk;
2006
+ if (sequence && /^[\x20-\x7e]+$/.test(sequence)) {
2007
+ value += sequence;
2008
+ active = 0;
2009
+ return render();
2010
+ }
2011
+ if (key.name === "left" || key.name === "right") return;
2012
+ };
2013
+ emitKeypressEvents3(stdin3);
2014
+ stdin3.setRawMode?.(true);
2015
+ stdin3.resume();
2016
+ stdin3.on("keypress", onKeypress);
2017
+ render();
2018
+ });
2019
+ }
2020
+ async function runInteractiveCommand(command) {
2021
+ const parts = command.trim().split(/\s+/).filter(Boolean);
2022
+ const root = parts[0];
2023
+ if (root === "exit" || root === "quit") return false;
2024
+ if (root === "connect") await connectCommand();
2025
+ else if (root === "status") await statusCommand();
2026
+ else if (root === "avatar") await avatarCommand(process.cwd(), parts.slice(1));
2027
+ else if (root === "health") await healthCommand();
2028
+ else if (root === "disconnect") await disconnectCommand();
2029
+ else if (root === "help") interactiveHelp();
2030
+ else if (root === "version") console.log(readCurrentVersion());
2031
+ else if (root === "update") await updateCommand();
2032
+ else {
2033
+ console.log(`
2034
+ ${symbols.error} Unknown command: ${command}`);
2035
+ console.log(`Try ${muted("help")} to see available commands.`);
2036
+ }
2037
+ return true;
2038
+ }
2039
+ function renderLogo() {
2040
+ const lines = [
2041
+ " \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
2042
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
2043
+ "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
2044
+ "\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ",
2045
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ",
2046
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
2047
+ ];
2048
+ console.log(title(lines.join("\n")));
2049
+ console.log();
2050
+ }
2051
+ function renderWelcome(project, connected) {
2052
+ console.log(muted("Developer Assistant CLI"));
2053
+ console.log();
2054
+ console.log("Welcome to Asiyst CLI.");
2055
+ console.log("Connect your project, manage your avatar, and verify your website integration.");
2056
+ console.log();
2057
+ console.log(section("Project"));
2058
+ console.log(` ${project && project.packageJson?.name ? String(project.packageJson.name) : process.cwd().split(/[\\/]/).pop() || "current-project"}`);
2059
+ console.log(` ${connected ? `${symbols.connected} Connected` : `${symbols.disconnected} Not connected`}`);
2060
+ console.log();
2061
+ console.log(section("Environment"));
2062
+ if (project) {
2063
+ console.log(` ${project.framework} \xB7 ${project.language} \xB7 ${project.packageManager}`);
2064
+ console.log(` SDK: ${project.sdkVersion ? project.sdkVersion : "not installed"}`);
2065
+ } else {
2066
+ console.log(" Unable to detect project metadata automatically.");
2067
+ }
2068
+ console.log();
2069
+ console.log(muted("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2070
+ console.log();
2071
+ }
2072
+ async function interactiveHome() {
2073
+ const project = detectProject();
2074
+ const initialConnection = await loadConnection(process.cwd());
2075
+ console.clear();
2076
+ renderLogo();
2077
+ renderWelcome(project, Boolean(initialConnection));
2078
+ for (; ; ) {
2079
+ const command = await readCommand();
2080
+ if (!command || !await runInteractiveCommand(command)) break;
1379
2081
  }
1380
2082
  }
1381
2083
 
@@ -1410,7 +2112,8 @@ async function main(argv = process.argv.slice(2)) {
1410
2112
  if (command === "revoke-trust") return revokeTrustCommand();
1411
2113
  if (command === "update") return updateCommand();
1412
2114
  if (command === "help") return help();
1413
- if (command === "version") return console.log(CLI_VERSION);
2115
+ if (command === "version") return console.log(`Asiyst CLI v${CLI_VERSION}
2116
+ \u2713 Latest version`);
1414
2117
  if (!process.stdin.isTTY) {
1415
2118
  help();
1416
2119
  return;