@yavydev/cli 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yavy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Yavy CLI
2
+
3
+ Generate AI skills from your indexed documentation on [Yavy](https://yavy.dev).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @yavydev/cli
9
+ ```
10
+
11
+ Requires Node.js >= 18.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Authenticate with your Yavy account
17
+ yavy login
18
+
19
+ # List your projects
20
+ yavy projects
21
+
22
+ # Generate a skill for a project
23
+ yavy generate my-org/my-project
24
+ ```
25
+
26
+ ## Commands
27
+
28
+ ### `yavy login`
29
+
30
+ Opens your browser to authenticate with your Yavy account using OAuth (PKCE). Credentials are stored in `~/.yavy/credentials.json`.
31
+
32
+ ### `yavy logout`
33
+
34
+ Clears stored credentials.
35
+
36
+ ### `yavy projects`
37
+
38
+ Lists all projects you have access to across your organizations.
39
+
40
+ | Flag | Description |
41
+ | -------- | -------------- |
42
+ | `--json` | Output as JSON |
43
+
44
+ ### `yavy generate <org/project>`
45
+
46
+ Downloads a skill from a project's indexed documentation.
47
+
48
+ | Flag | Description |
49
+ | ----------------- | ----------------------------------------------------- |
50
+ | `--global` | Save to global skills directory (`~/.claude/skills/`) |
51
+ | `--output <path>` | Custom output path |
52
+ | `--force` | Overwrite existing skill files |
53
+ | `--json` | Output as JSON |
54
+
55
+ By default, skills are saved to `.claude/skills/<project>/` in the current directory.
56
+
57
+ ## How It Works
58
+
59
+ 1. Yavy indexes your documentation sources (websites, GitHub repos, Confluence, Notion)
60
+ 2. The CLI calls the Yavy API to download a skill using the indexed content
61
+ 3. The skill file is saved locally for your AI coding tools to discover
62
+ 4. AI coding assistants automatically activate the skill when working with relevant code
63
+
64
+ ## Configuration
65
+
66
+ | Environment Variable | Description | Default |
67
+ | -------------------- | ------------------------ | ------------------ |
68
+ | `YAVY_BASE_URL` | Override API base URL | `https://yavy.dev` |
69
+ | `YAVY_CLIENT_ID` | Override OAuth client ID | (built-in) |
70
+
71
+ ## Related
72
+
73
+ - [Yavy Claude Code Plugin](https://github.com/yavydev/claude-code) — Claude Code plugin with interactive setup
74
+ - [Yavy](https://yavy.dev) — Index documentation, generate AI skills
75
+
76
+ ## License
77
+
78
+ [MIT](LICENSE)
package/bin/yavy.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,579 @@
1
+ // src/index.ts
2
+ import { Command as Command5 } from "commander";
3
+
4
+ // package.json
5
+ var package_default = {
6
+ name: "@yavydev/cli",
7
+ version: "0.1.0",
8
+ description: "Generate AI skills from your indexed documentation on Yavy",
9
+ type: "module",
10
+ bin: {
11
+ yavy: "./bin/yavy.js"
12
+ },
13
+ main: "./dist/index.js",
14
+ scripts: {
15
+ build: "tsup",
16
+ dev: "tsup --watch",
17
+ typecheck: "tsc --noEmit",
18
+ test: "vitest run",
19
+ "test:watch": "vitest",
20
+ format: 'prettier --write "src/**/*.{ts,js}" "*.{json,md}"',
21
+ "format:check": 'prettier --check "src/**/*.{ts,js}" "*.{json,md}"'
22
+ },
23
+ keywords: [
24
+ "yavy",
25
+ "ai",
26
+ "skills",
27
+ "documentation",
28
+ "cli"
29
+ ],
30
+ author: {
31
+ name: "Yavy",
32
+ url: "https://yavy.dev"
33
+ },
34
+ homepage: "https://github.com/yavydev/cli#readme",
35
+ repository: {
36
+ type: "git",
37
+ url: "git+https://github.com/yavydev/cli.git"
38
+ },
39
+ bugs: {
40
+ url: "https://github.com/yavydev/cli/issues"
41
+ },
42
+ dependencies: {
43
+ chalk: "^5.3.0",
44
+ commander: "^13.1.0",
45
+ fflate: "^0.8.2",
46
+ open: "^10.1.0",
47
+ ora: "^8.1.1"
48
+ },
49
+ devDependencies: {
50
+ "@types/node": "^22.0.0",
51
+ "@vitest/coverage-v8": "^3.2.4",
52
+ tsup: "^8.4.0",
53
+ typescript: "^5.7.0",
54
+ prettier: "^3.7.0",
55
+ vitest: "^3.0.0"
56
+ },
57
+ engines: {
58
+ node: ">=18"
59
+ },
60
+ publishConfig: {
61
+ access: "public"
62
+ },
63
+ files: [
64
+ "dist",
65
+ "bin"
66
+ ],
67
+ license: "MIT"
68
+ };
69
+
70
+ // src/commands/generate.ts
71
+ import chalk2 from "chalk";
72
+ import { Command } from "commander";
73
+ import { unzipSync } from "fflate";
74
+ import { existsSync as existsSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
75
+ import { dirname, join as join3, normalize } from "path";
76
+ import ora from "ora";
77
+
78
+ // src/auth/store.ts
79
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
80
+ import { homedir } from "os";
81
+ import { join } from "path";
82
+
83
+ // src/config.ts
84
+ var YAVY_BASE_URL = process.env.YAVY_BASE_URL ?? "https://yavy.dev";
85
+ var YAVY_CLIENT_ID = process.env.YAVY_CLIENT_ID ?? "01965e6a-0000-7000-8000-000000000001";
86
+ var YAVY_USER_AGENT = `@yavydev/cli`;
87
+ var REQUEST_TIMEOUT_MS = 3e4;
88
+ var MAX_RETRIES = 3;
89
+
90
+ // src/auth/store.ts
91
+ var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
92
+ function credentialsPath() {
93
+ return join(homedir(), ".yavy", "credentials.json");
94
+ }
95
+ function loadCredentials() {
96
+ const path = credentialsPath();
97
+ if (!existsSync(path)) return null;
98
+ try {
99
+ return JSON.parse(readFileSync(path, "utf-8"));
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ function saveCredentials(creds) {
105
+ const dir = join(homedir(), ".yavy");
106
+ mkdirSync(dir, { recursive: true });
107
+ writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2), { mode: 384 });
108
+ }
109
+ function clearCredentials() {
110
+ const path = credentialsPath();
111
+ if (existsSync(path)) unlinkSync(path);
112
+ }
113
+ function isExpired(creds) {
114
+ if (!creds.expires_at) return false;
115
+ return new Date(creds.expires_at).getTime() - Date.now() <= REFRESH_BUFFER_MS;
116
+ }
117
+ async function refreshToken(token) {
118
+ try {
119
+ const response = await fetch(`${YAVY_BASE_URL}/oauth/token`, {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
122
+ body: JSON.stringify({
123
+ grant_type: "refresh_token",
124
+ refresh_token: token,
125
+ client_id: YAVY_CLIENT_ID
126
+ })
127
+ });
128
+ if (!response.ok) return null;
129
+ const data = await response.json();
130
+ const newCreds = {
131
+ access_token: data.access_token,
132
+ refresh_token: data.refresh_token ?? token,
133
+ expires_at: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3).toISOString() : void 0
134
+ };
135
+ saveCredentials(newCreds);
136
+ return newCreds;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ async function getAccessToken() {
142
+ const creds = loadCredentials();
143
+ if (!creds) return null;
144
+ if (isExpired(creds)) {
145
+ if (!creds.refresh_token) return null;
146
+ const refreshed = await refreshToken(creds.refresh_token);
147
+ return refreshed?.access_token ?? null;
148
+ }
149
+ return creds.access_token;
150
+ }
151
+
152
+ // src/api/client.ts
153
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
154
+ function isRetryable(error2, status) {
155
+ if (status && RETRYABLE_STATUS_CODES.has(status)) return true;
156
+ if (error2 instanceof TypeError) return true;
157
+ if (error2 instanceof DOMException && error2.name === "AbortError") return false;
158
+ return false;
159
+ }
160
+ async function sleep(ms) {
161
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
162
+ }
163
+ var YavyApiClient = class _YavyApiClient {
164
+ token;
165
+ constructor(token) {
166
+ this.token = token;
167
+ }
168
+ static async create() {
169
+ const token = await getAccessToken();
170
+ if (!token) {
171
+ throw new Error("Not authenticated. Run `yavy login` first.");
172
+ }
173
+ return new _YavyApiClient(token);
174
+ }
175
+ baseHeaders(accept = "application/json") {
176
+ return {
177
+ Authorization: `Bearer ${this.token}`,
178
+ Accept: accept,
179
+ "User-Agent": YAVY_USER_AGENT
180
+ };
181
+ }
182
+ async fetchWithRetry(url, init) {
183
+ let lastError;
184
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
185
+ if (attempt > 0) {
186
+ const delay = Math.min(1e3 * 2 ** (attempt - 1), 1e4);
187
+ const jitter = Math.random() * delay * 0.1;
188
+ await sleep(delay + jitter);
189
+ }
190
+ const controller = new AbortController();
191
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
192
+ try {
193
+ const response = await fetch(url, { ...init, signal: controller.signal });
194
+ if (response.ok || response.status === 401 || !isRetryable(null, response.status)) {
195
+ return response;
196
+ }
197
+ lastError = new Error(`HTTP ${response.status}`);
198
+ } catch (err) {
199
+ if (!isRetryable(err)) throw err;
200
+ lastError = err;
201
+ } finally {
202
+ clearTimeout(timeoutId);
203
+ }
204
+ }
205
+ throw lastError;
206
+ }
207
+ async handleErrorResponse(response) {
208
+ if (response.status === 401) {
209
+ throw new Error("Authentication expired. Run `yavy login` to re-authenticate.");
210
+ }
211
+ const errorData = await response.json().catch(() => ({}));
212
+ throw new Error(errorData.error ?? `API request failed with status ${response.status}`);
213
+ }
214
+ async request(method, path, body) {
215
+ const url = `${YAVY_BASE_URL}/api/v1${path}`;
216
+ const headers = { ...this.baseHeaders() };
217
+ if (body) {
218
+ headers["Content-Type"] = "application/json";
219
+ }
220
+ const response = await this.fetchWithRetry(url, {
221
+ method,
222
+ headers,
223
+ body: body ? JSON.stringify(body) : void 0
224
+ });
225
+ if (!response.ok) {
226
+ await this.handleErrorResponse(response);
227
+ }
228
+ return response.json();
229
+ }
230
+ async listProjects() {
231
+ const result = await this.request("GET", "/projects");
232
+ return result.data;
233
+ }
234
+ async downloadSkill(orgSlug, projectSlug) {
235
+ const url = `${YAVY_BASE_URL}/api/v1/${orgSlug}/${projectSlug}/skill/download`;
236
+ const response = await this.fetchWithRetry(url, {
237
+ method: "GET",
238
+ headers: this.baseHeaders("application/zip")
239
+ });
240
+ if (!response.ok) {
241
+ await this.handleErrorResponse(response);
242
+ }
243
+ return response.arrayBuffer();
244
+ }
245
+ };
246
+
247
+ // src/utils/output.ts
248
+ import chalk from "chalk";
249
+ function success(message) {
250
+ console.log(chalk.green("\u2713") + " " + message);
251
+ }
252
+ function error(message) {
253
+ console.error(chalk.red("\u2717") + " " + message);
254
+ }
255
+ function warn(message) {
256
+ console.error(chalk.yellow("\u26A0") + " " + message);
257
+ }
258
+ function info(message) {
259
+ console.log(chalk.blue("\u2139") + " " + message);
260
+ }
261
+
262
+ // src/utils/paths.ts
263
+ import { mkdirSync as mkdirSync2 } from "fs";
264
+ import { homedir as homedir2 } from "os";
265
+ import { join as join2, resolve, relative } from "path";
266
+ function getSkillOutputDir(projectSlug, options) {
267
+ if (options.output) {
268
+ return options.output;
269
+ }
270
+ const baseDir = options.global ? join2(homedir2(), ".claude", "skills") : join2(process.cwd(), ".claude", "skills");
271
+ return join2(baseDir, projectSlug);
272
+ }
273
+ function ensureDir(dirPath) {
274
+ mkdirSync2(dirPath, { recursive: true });
275
+ }
276
+ function isPathSafe(filePath, rootDir) {
277
+ const resolvedPath = resolve(rootDir, filePath);
278
+ const rel = relative(rootDir, resolvedPath);
279
+ return !rel.startsWith("..") && !resolve(resolvedPath).includes("\0");
280
+ }
281
+
282
+ // src/commands/generate.ts
283
+ function generateCommand() {
284
+ return new Command("generate").description("Download an AI skill for a project").argument("<org/project>", "Organization and project slug (e.g., my-org/my-project)").option("--global", "Save to global skills directory (~/.claude/skills/)").option("--output <path>", "Custom output directory").option("--force", "Overwrite existing skill files").option("--json", "Output as JSON").action(async (slug, options) => {
285
+ const parts = slug.split("/");
286
+ if (parts.length !== 2) {
287
+ error("Invalid slug format. Use: org-slug/project-slug");
288
+ process.exit(1);
289
+ }
290
+ const [orgSlug, projectSlug] = parts;
291
+ const outputDir = getSkillOutputDir(projectSlug, options);
292
+ if (!options.force && existsSync2(join3(outputDir, "SKILL.md"))) {
293
+ warn(`Skill files already exist at ${outputDir}. Use --force to overwrite.`);
294
+ process.exit(1);
295
+ }
296
+ const spinner = options.json ? null : ora(`Downloading skill for ${chalk2.bold(slug)}...`).start();
297
+ try {
298
+ const client = await YavyApiClient.create();
299
+ const zipBuffer = await client.downloadSkill(orgSlug, projectSlug);
300
+ extractZip(new Uint8Array(zipBuffer), outputDir, projectSlug);
301
+ spinner?.stop();
302
+ const skillPath = join3(outputDir, "SKILL.md");
303
+ const refsDir = join3(outputDir, "references");
304
+ const refCount = existsSync2(refsDir) ? readdirSync(refsDir).length : 0;
305
+ if (options.json) {
306
+ console.log(
307
+ JSON.stringify(
308
+ {
309
+ path: outputDir,
310
+ skill_file: skillPath,
311
+ reference_count: refCount
312
+ },
313
+ null,
314
+ 2
315
+ )
316
+ );
317
+ return;
318
+ }
319
+ success(`Downloaded skill for ${chalk2.bold(slug)} (${refCount} reference files)`);
320
+ console.log(` ${chalk2.dim(skillPath)}`);
321
+ } catch (err) {
322
+ spinner?.stop();
323
+ error(err instanceof Error ? err.message : String(err));
324
+ process.exit(1);
325
+ }
326
+ });
327
+ }
328
+ function extractZip(zipData, outputDir, projectSlug) {
329
+ const files = unzipSync(zipData);
330
+ const prefix = `${projectSlug}/`;
331
+ ensureDir(outputDir);
332
+ for (const [rawPath, data] of Object.entries(files)) {
333
+ if (rawPath.endsWith("/")) continue;
334
+ const relativePath = rawPath.startsWith(prefix) ? rawPath.slice(prefix.length) : rawPath;
335
+ const normalizedPath = normalize(relativePath);
336
+ if (!isPathSafe(normalizedPath, outputDir)) {
337
+ throw new Error(`Zip contains unsafe path: ${rawPath}`);
338
+ }
339
+ const destPath = join3(outputDir, normalizedPath);
340
+ ensureDir(dirname(destPath));
341
+ writeFileSync2(destPath, data);
342
+ }
343
+ }
344
+
345
+ // src/commands/login.ts
346
+ import { Command as Command2 } from "commander";
347
+ import ora2 from "ora";
348
+
349
+ // src/auth/oauth.ts
350
+ import { createHash, randomBytes } from "crypto";
351
+ import { createServer } from "http";
352
+ import { URL } from "url";
353
+ import open from "open";
354
+ var PREFERRED_PORTS = [9876, 9877, 9878, 0];
355
+ var CALLBACK_PATH = "/callback";
356
+ function generateCodeVerifier() {
357
+ return randomBytes(32).toString("base64url");
358
+ }
359
+ function generateCodeChallenge(verifier) {
360
+ return createHash("sha256").update(verifier).digest("base64url");
361
+ }
362
+ function generateState() {
363
+ return randomBytes(16).toString("base64url");
364
+ }
365
+ function tryListen(server, port) {
366
+ return new Promise((resolve2, reject) => {
367
+ server.once("error", reject);
368
+ server.listen(port, () => {
369
+ server.removeListener("error", reject);
370
+ const addr = server.address();
371
+ resolve2(addr.port);
372
+ });
373
+ });
374
+ }
375
+ async function listenOnAvailablePort(server) {
376
+ for (const port of PREFERRED_PORTS) {
377
+ try {
378
+ return await tryListen(server, port);
379
+ } catch (err) {
380
+ if (err instanceof Error && "code" in err && err.code === "EADDRINUSE") {
381
+ continue;
382
+ }
383
+ throw err;
384
+ }
385
+ }
386
+ throw new Error("Could not find an available port for OAuth callback");
387
+ }
388
+ async function performOAuthLogin() {
389
+ const codeVerifier = generateCodeVerifier();
390
+ const codeChallenge = generateCodeChallenge(codeVerifier);
391
+ const state = generateState();
392
+ return new Promise((resolve2, reject) => {
393
+ let settled = false;
394
+ const settle = (value) => {
395
+ if (!settled) {
396
+ settled = true;
397
+ resolve2(value);
398
+ }
399
+ };
400
+ const server = createServer(async (req, res) => {
401
+ const actualPort = server.address().port;
402
+ const url = new URL(req.url ?? "/", `http://localhost:${actualPort}`);
403
+ if (url.pathname !== CALLBACK_PATH) {
404
+ res.writeHead(404);
405
+ res.end("Not found");
406
+ return;
407
+ }
408
+ const code = url.searchParams.get("code");
409
+ const returnedState = url.searchParams.get("state");
410
+ const error2 = url.searchParams.get("error");
411
+ if (error2 || !code) {
412
+ res.writeHead(200, { "Content-Type": "text/html" });
413
+ res.end("<html><body><h1>Authentication failed</h1><p>You can close this window.</p></body></html>");
414
+ server.close();
415
+ settle(false);
416
+ return;
417
+ }
418
+ if (returnedState !== state) {
419
+ res.writeHead(200, { "Content-Type": "text/html" });
420
+ res.end("<html><body><h1>Authentication failed</h1><p>Invalid state parameter. Possible CSRF attack.</p></body></html>");
421
+ server.close();
422
+ settle(false);
423
+ return;
424
+ }
425
+ try {
426
+ const redirectUri = `http://localhost:${actualPort}${CALLBACK_PATH}`;
427
+ const tokenResponse = await fetch(`${YAVY_BASE_URL}/oauth/token`, {
428
+ method: "POST",
429
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
430
+ body: JSON.stringify({
431
+ grant_type: "authorization_code",
432
+ code,
433
+ redirect_uri: redirectUri,
434
+ client_id: YAVY_CLIENT_ID,
435
+ code_verifier: codeVerifier
436
+ })
437
+ });
438
+ if (!tokenResponse.ok) {
439
+ throw new Error(`Token exchange failed: ${tokenResponse.status}`);
440
+ }
441
+ const data = await tokenResponse.json();
442
+ saveCredentials({
443
+ access_token: data.access_token,
444
+ refresh_token: data.refresh_token,
445
+ expires_at: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3).toISOString() : void 0
446
+ });
447
+ res.writeHead(200, { "Content-Type": "text/html" });
448
+ res.end("<html><body><h1>Logged in to Yavy!</h1><p>You can close this window and return to your terminal.</p></body></html>");
449
+ server.close();
450
+ settle(true);
451
+ } catch {
452
+ res.writeHead(200, { "Content-Type": "text/html" });
453
+ res.end("<html><body><h1>Authentication failed</h1><p>Something went wrong. Please try again.</p></body></html>");
454
+ server.close();
455
+ settle(false);
456
+ }
457
+ });
458
+ listenOnAvailablePort(server).then((actualPort) => {
459
+ const authUrl = new URL("/oauth/authorize", YAVY_BASE_URL);
460
+ authUrl.searchParams.set("client_id", YAVY_CLIENT_ID);
461
+ authUrl.searchParams.set("redirect_uri", `http://localhost:${actualPort}${CALLBACK_PATH}`);
462
+ authUrl.searchParams.set("response_type", "code");
463
+ authUrl.searchParams.set("state", state);
464
+ authUrl.searchParams.set("code_challenge", codeChallenge);
465
+ authUrl.searchParams.set("code_challenge_method", "S256");
466
+ open(authUrl.toString());
467
+ }).catch((err) => {
468
+ server.close();
469
+ if (!settled) {
470
+ settled = true;
471
+ reject(err);
472
+ }
473
+ });
474
+ const timeout = setTimeout(
475
+ () => {
476
+ server.close();
477
+ settle(false);
478
+ },
479
+ 5 * 60 * 1e3
480
+ );
481
+ timeout.unref();
482
+ });
483
+ }
484
+
485
+ // src/commands/login.ts
486
+ function loginCommand() {
487
+ return new Command2("login").description("Log in to your Yavy account").action(async () => {
488
+ const existing = loadCredentials();
489
+ if (existing?.access_token && !isExpired(existing)) {
490
+ info("You are already logged in. Use `yavy logout` first to switch accounts.");
491
+ return;
492
+ }
493
+ if (existing && isExpired(existing)) {
494
+ warn("Your session has expired. Re-authenticating...");
495
+ }
496
+ const spinner = ora2("Opening browser for authentication...").start();
497
+ try {
498
+ const result = await performOAuthLogin();
499
+ spinner.stop();
500
+ if (result) {
501
+ success("Successfully logged in to Yavy!");
502
+ process.exit(0);
503
+ } else {
504
+ error("Login failed. Please try again.");
505
+ process.exit(1);
506
+ }
507
+ } catch (err) {
508
+ spinner.stop();
509
+ error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
510
+ process.exit(1);
511
+ }
512
+ });
513
+ }
514
+
515
+ // src/commands/logout.ts
516
+ import { Command as Command3 } from "commander";
517
+ function logoutCommand() {
518
+ return new Command3("logout").description("Log out of your Yavy account").action(() => {
519
+ const existing = loadCredentials();
520
+ if (!existing) {
521
+ info("You are not logged in.");
522
+ return;
523
+ }
524
+ clearCredentials();
525
+ success("Logged out of Yavy.");
526
+ });
527
+ }
528
+
529
+ // src/commands/projects.ts
530
+ import chalk3 from "chalk";
531
+ import { Command as Command4 } from "commander";
532
+ import ora3 from "ora";
533
+ function projectsCommand() {
534
+ return new Command4("projects").description("List your Yavy projects").option("--json", "Output as JSON").action(async (options) => {
535
+ const spinner = options.json ? null : ora3("Fetching projects...").start();
536
+ try {
537
+ const client = await YavyApiClient.create();
538
+ const projects = await client.listProjects();
539
+ spinner?.stop();
540
+ if (options.json) {
541
+ console.log(JSON.stringify(projects, null, 2));
542
+ return;
543
+ }
544
+ if (projects.length === 0) {
545
+ console.log(`No projects found. Create one at ${YAVY_BASE_URL}`);
546
+ return;
547
+ }
548
+ console.log(chalk3.bold(`
549
+ Your Projects (${projects.length}):
550
+ `));
551
+ for (const project of projects) {
552
+ const org = chalk3.dim(`${project.organization.slug}/`);
553
+ const name = chalk3.bold(project.name);
554
+ const pages = chalk3.cyan(`${project.pages_count} pages`);
555
+ const indexed = project.last_indexed_at ? chalk3.dim(`indexed ${new Date(project.last_indexed_at).toLocaleDateString()}`) : chalk3.yellow("not indexed");
556
+ const content = project.has_indexed_content ? chalk3.green(" [indexed]") : "";
557
+ console.log(` ${org}${name} ${pages} ${indexed}${content}`);
558
+ }
559
+ console.log("");
560
+ } catch (err) {
561
+ spinner?.stop();
562
+ error(err instanceof Error ? err.message : String(err));
563
+ process.exit(1);
564
+ }
565
+ });
566
+ }
567
+
568
+ // src/index.ts
569
+ var program = new Command5();
570
+ program.name("yavy").description("Generate AI skills from your indexed documentation").version(package_default.version);
571
+ program.addCommand(loginCommand());
572
+ program.addCommand(logoutCommand());
573
+ program.addCommand(projectsCommand());
574
+ program.addCommand(generateCommand());
575
+ program.parseAsync().catch((err) => {
576
+ error(err instanceof Error ? err.message : String(err));
577
+ process.exit(1);
578
+ });
579
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../package.json","../src/commands/generate.ts","../src/auth/store.ts","../src/config.ts","../src/api/client.ts","../src/utils/output.ts","../src/utils/paths.ts","../src/commands/login.ts","../src/auth/oauth.ts","../src/commands/logout.ts","../src/commands/projects.ts"],"sourcesContent":["import { Command } from 'commander';\r\nimport pkg from '../package.json' with { type: 'json' };\r\nimport { generateCommand } from './commands/generate';\r\nimport { loginCommand } from './commands/login';\r\nimport { logoutCommand } from './commands/logout';\r\nimport { projectsCommand } from './commands/projects';\r\nimport { error } from './utils';\r\n\r\nconst program = new Command();\r\n\r\nprogram.name('yavy').description('Generate AI skills from your indexed documentation').version(pkg.version);\r\n\r\nprogram.addCommand(loginCommand());\r\nprogram.addCommand(logoutCommand());\r\nprogram.addCommand(projectsCommand());\r\nprogram.addCommand(generateCommand());\r\n\r\nprogram.parseAsync().catch((err: unknown) => {\r\n error(err instanceof Error ? err.message : String(err));\r\n process.exit(1);\r\n});\r\n","{\r\n \"name\": \"@yavydev/cli\",\r\n \"version\": \"0.1.0\",\r\n \"description\": \"Generate AI skills from your indexed documentation on Yavy\",\r\n \"type\": \"module\",\r\n \"bin\": {\r\n \"yavy\": \"./bin/yavy.js\"\r\n },\r\n \"main\": \"./dist/index.js\",\r\n \"scripts\": {\r\n \"build\": \"tsup\",\r\n \"dev\": \"tsup --watch\",\r\n \"typecheck\": \"tsc --noEmit\",\r\n \"test\": \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n \"format\": \"prettier --write \\\"src/**/*.{ts,js}\\\" \\\"*.{json,md}\\\"\",\r\n \"format:check\": \"prettier --check \\\"src/**/*.{ts,js}\\\" \\\"*.{json,md}\\\"\"\r\n },\r\n \"keywords\": [\r\n \"yavy\",\r\n \"ai\",\r\n \"skills\",\r\n \"documentation\",\r\n \"cli\"\r\n ],\r\n \"author\": {\r\n \"name\": \"Yavy\",\r\n \"url\": \"https://yavy.dev\"\r\n },\r\n \"homepage\": \"https://github.com/yavydev/cli#readme\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/yavydev/cli.git\"\r\n },\r\n \"bugs\": {\r\n \"url\": \"https://github.com/yavydev/cli/issues\"\r\n },\r\n \"dependencies\": {\r\n \"chalk\": \"^5.3.0\",\r\n \"commander\": \"^13.1.0\",\r\n \"fflate\": \"^0.8.2\",\r\n \"open\": \"^10.1.0\",\r\n \"ora\": \"^8.1.1\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/node\": \"^22.0.0\",\r\n \"@vitest/coverage-v8\": \"^3.2.4\",\r\n \"tsup\": \"^8.4.0\",\r\n \"typescript\": \"^5.7.0\",\r\n \"prettier\": \"^3.7.0\",\r\n \"vitest\": \"^3.0.0\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18\"\r\n },\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"files\": [\r\n \"dist\",\r\n \"bin\"\r\n ],\r\n \"license\": \"MIT\"\r\n}\r\n","import chalk from 'chalk';\r\nimport { Command } from 'commander';\r\nimport { unzipSync } from 'fflate';\r\nimport { existsSync, readdirSync, writeFileSync } from 'node:fs';\r\nimport { dirname, join, normalize } from 'node:path';\r\nimport ora from 'ora';\r\nimport { YavyApiClient } from '../api/client';\r\nimport { ensureDir, error, getSkillOutputDir, isPathSafe, success, warn } from '../utils';\r\n\r\nexport function generateCommand(): Command {\r\n return new Command('generate')\r\n .description('Download an AI skill for a project')\r\n .argument('<org/project>', 'Organization and project slug (e.g., my-org/my-project)')\r\n .option('--global', 'Save to global skills directory (~/.claude/skills/)')\r\n .option('--output <path>', 'Custom output directory')\r\n .option('--force', 'Overwrite existing skill files')\r\n .option('--json', 'Output as JSON')\r\n .action(async (slug: string, options: { global?: boolean; output?: string; force?: boolean; json?: boolean }) => {\r\n const parts = slug.split('/');\r\n if (parts.length !== 2) {\r\n error('Invalid slug format. Use: org-slug/project-slug');\r\n process.exit(1);\r\n }\r\n\r\n const [orgSlug, projectSlug] = parts;\r\n const outputDir = getSkillOutputDir(projectSlug, options);\r\n\r\n if (!options.force && existsSync(join(outputDir, 'SKILL.md'))) {\r\n warn(`Skill files already exist at ${outputDir}. Use --force to overwrite.`);\r\n process.exit(1);\r\n }\r\n\r\n const spinner = options.json ? null : ora(`Downloading skill for ${chalk.bold(slug)}...`).start();\r\n\r\n try {\r\n const client = await YavyApiClient.create();\r\n const zipBuffer = await client.downloadSkill(orgSlug, projectSlug);\r\n\r\n extractZip(new Uint8Array(zipBuffer), outputDir, projectSlug);\r\n\r\n spinner?.stop();\r\n\r\n const skillPath = join(outputDir, 'SKILL.md');\r\n const refsDir = join(outputDir, 'references');\r\n const refCount = existsSync(refsDir) ? readdirSync(refsDir).length : 0;\r\n\r\n if (options.json) {\r\n console.log(\r\n JSON.stringify(\r\n {\r\n path: outputDir,\r\n skill_file: skillPath,\r\n reference_count: refCount,\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n return;\r\n }\r\n\r\n success(`Downloaded skill for ${chalk.bold(slug)} (${refCount} reference files)`);\r\n console.log(` ${chalk.dim(skillPath)}`);\r\n } catch (err) {\r\n spinner?.stop();\r\n error(err instanceof Error ? err.message : String(err));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Extract a zip buffer to the output directory using fflate (pure JS, cross-platform).\r\n * Strips the top-level project-slug prefix from zip entries.\r\n * Validates all paths to prevent zip-slip attacks.\r\n */\r\nfunction extractZip(zipData: Uint8Array, outputDir: string, projectSlug: string): void {\r\n const files = unzipSync(zipData);\r\n const prefix = `${projectSlug}/`;\r\n\r\n ensureDir(outputDir);\r\n\r\n for (const [rawPath, data] of Object.entries(files)) {\r\n if (rawPath.endsWith('/')) continue;\r\n\r\n const relativePath = rawPath.startsWith(prefix) ? rawPath.slice(prefix.length) : rawPath;\r\n const normalizedPath = normalize(relativePath);\r\n\r\n if (!isPathSafe(normalizedPath, outputDir)) {\r\n throw new Error(`Zip contains unsafe path: ${rawPath}`);\r\n }\r\n\r\n const destPath = join(outputDir, normalizedPath);\r\n ensureDir(dirname(destPath));\r\n writeFileSync(destPath, data);\r\n }\r\n}\r\n","import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\r\nimport { homedir } from 'node:os';\r\nimport { join } from 'node:path';\r\nimport { YAVY_BASE_URL, YAVY_CLIENT_ID } from '../config';\r\n\r\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000; // Refresh 5 minutes before expiry\r\n\r\nexport interface Credentials {\r\n access_token: string;\r\n refresh_token?: string;\r\n expires_at?: string;\r\n}\r\n\r\nfunction credentialsPath(): string {\r\n return join(homedir(), '.yavy', 'credentials.json');\r\n}\r\n\r\nexport function loadCredentials(): Credentials | null {\r\n const path = credentialsPath();\r\n if (!existsSync(path)) return null;\r\n try {\r\n return JSON.parse(readFileSync(path, 'utf-8')) as Credentials;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function saveCredentials(creds: Credentials): void {\r\n const dir = join(homedir(), '.yavy');\r\n mkdirSync(dir, { recursive: true });\r\n writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2), { mode: 0o600 });\r\n}\r\n\r\nexport function clearCredentials(): void {\r\n const path = credentialsPath();\r\n if (existsSync(path)) unlinkSync(path);\r\n}\r\n\r\nexport function isExpired(creds: Credentials): boolean {\r\n if (!creds.expires_at) return false;\r\n return new Date(creds.expires_at).getTime() - Date.now() <= REFRESH_BUFFER_MS;\r\n}\r\n\r\nasync function refreshToken(token: string): Promise<Credentials | null> {\r\n try {\r\n const response = await fetch(`${YAVY_BASE_URL}/oauth/token`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\r\n body: JSON.stringify({\r\n grant_type: 'refresh_token',\r\n refresh_token: token,\r\n client_id: YAVY_CLIENT_ID,\r\n }),\r\n });\r\n\r\n if (!response.ok) return null;\r\n\r\n const data = (await response.json()) as { access_token: string; refresh_token?: string; expires_in?: number };\r\n const newCreds: Credentials = {\r\n access_token: data.access_token,\r\n refresh_token: data.refresh_token ?? token,\r\n expires_at: data.expires_in ? new Date(Date.now() + data.expires_in * 1000).toISOString() : undefined,\r\n };\r\n\r\n saveCredentials(newCreds);\r\n return newCreds;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport async function getAccessToken(): Promise<string | null> {\r\n const creds = loadCredentials();\r\n if (!creds) return null;\r\n\r\n if (isExpired(creds)) {\r\n if (!creds.refresh_token) return null;\r\n const refreshed = await refreshToken(creds.refresh_token);\r\n return refreshed?.access_token ?? null;\r\n }\r\n\r\n return creds.access_token;\r\n}\r\n","export const YAVY_BASE_URL = process.env.YAVY_BASE_URL ?? 'https://yavy.dev';\r\nexport const YAVY_CLIENT_ID = process.env.YAVY_CLIENT_ID ?? '01965e6a-0000-7000-8000-000000000001';\r\nexport const YAVY_USER_AGENT = `@yavydev/cli`;\r\nexport const REQUEST_TIMEOUT_MS = 30_000;\r\nexport const MAX_RETRIES = 3;\r\n","import { getAccessToken } from '../auth/store';\r\nimport { MAX_RETRIES, REQUEST_TIMEOUT_MS, YAVY_BASE_URL, YAVY_USER_AGENT } from '../config';\r\n\r\nexport interface ApiProject {\r\n id: number;\r\n name: string;\r\n slug: string;\r\n description: string | null;\r\n organization: {\r\n name: string;\r\n slug: string;\r\n };\r\n pages_count: number;\r\n last_indexed_at: string | null;\r\n has_indexed_content: boolean;\r\n}\r\n\r\nconst RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);\r\n\r\nfunction isRetryable(error: unknown, status?: number): boolean {\r\n if (status && RETRYABLE_STATUS_CODES.has(status)) return true;\r\n if (error instanceof TypeError) return true; // fetch network errors\r\n if (error instanceof DOMException && error.name === 'AbortError') return false;\r\n return false;\r\n}\r\n\r\nasync function sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\nexport class YavyApiClient {\r\n private token: string;\r\n\r\n constructor(token: string) {\r\n this.token = token;\r\n }\r\n\r\n static async create(): Promise<YavyApiClient> {\r\n const token = await getAccessToken();\r\n if (!token) {\r\n throw new Error('Not authenticated. Run `yavy login` first.');\r\n }\r\n return new YavyApiClient(token);\r\n }\r\n\r\n private baseHeaders(accept = 'application/json'): Record<string, string> {\r\n return {\r\n Authorization: `Bearer ${this.token}`,\r\n Accept: accept,\r\n 'User-Agent': YAVY_USER_AGENT,\r\n };\r\n }\r\n\r\n private async fetchWithRetry(url: string, init: RequestInit): Promise<Response> {\r\n let lastError: unknown;\r\n\r\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\r\n if (attempt > 0) {\r\n const delay = Math.min(1000 * 2 ** (attempt - 1), 10_000);\r\n const jitter = Math.random() * delay * 0.1;\r\n await sleep(delay + jitter);\r\n }\r\n\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\r\n\r\n try {\r\n const response = await fetch(url, { ...init, signal: controller.signal });\r\n\r\n if (response.ok || response.status === 401 || !isRetryable(null, response.status)) {\r\n return response;\r\n }\r\n\r\n lastError = new Error(`HTTP ${response.status}`);\r\n } catch (err) {\r\n if (!isRetryable(err)) throw err;\r\n lastError = err;\r\n } finally {\r\n clearTimeout(timeoutId);\r\n }\r\n }\r\n\r\n throw lastError;\r\n }\r\n\r\n private async handleErrorResponse(response: Response): Promise<never> {\r\n if (response.status === 401) {\r\n throw new Error('Authentication expired. Run `yavy login` to re-authenticate.');\r\n }\r\n\r\n const errorData = (await response.json().catch(() => ({}))) as { error?: string };\r\n throw new Error(errorData.error ?? `API request failed with status ${response.status}`);\r\n }\r\n\r\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\r\n const url = `${YAVY_BASE_URL}/api/v1${path}`;\r\n const headers: Record<string, string> = { ...this.baseHeaders() };\r\n\r\n if (body) {\r\n headers['Content-Type'] = 'application/json';\r\n }\r\n\r\n const response = await this.fetchWithRetry(url, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n\r\n if (!response.ok) {\r\n await this.handleErrorResponse(response);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n }\r\n\r\n async listProjects(): Promise<ApiProject[]> {\r\n const result = await this.request<{ data: ApiProject[] }>('GET', '/projects');\r\n return result.data;\r\n }\r\n\r\n async downloadSkill(orgSlug: string, projectSlug: string): Promise<ArrayBuffer> {\r\n const url = `${YAVY_BASE_URL}/api/v1/${orgSlug}/${projectSlug}/skill/download`;\r\n\r\n const response = await this.fetchWithRetry(url, {\r\n method: 'GET',\r\n headers: this.baseHeaders('application/zip'),\r\n });\r\n\r\n if (!response.ok) {\r\n await this.handleErrorResponse(response);\r\n }\r\n\r\n return response.arrayBuffer();\r\n }\r\n}\r\n","import chalk from 'chalk';\r\n\r\nexport function success(message: string): void {\r\n console.log(chalk.green('✓') + ' ' + message);\r\n}\r\n\r\nexport function error(message: string): void {\r\n console.error(chalk.red('✗') + ' ' + message);\r\n}\r\n\r\nexport function warn(message: string): void {\r\n console.error(chalk.yellow('⚠') + ' ' + message);\r\n}\r\n\r\nexport function info(message: string): void {\r\n console.log(chalk.blue('ℹ') + ' ' + message);\r\n}\r\n","import { mkdirSync } from 'node:fs';\r\nimport { homedir } from 'node:os';\r\nimport { join, resolve, relative } from 'node:path';\r\n\r\nexport function getSkillOutputDir(projectSlug: string, options: { global?: boolean; output?: string }): string {\r\n if (options.output) {\r\n return options.output;\r\n }\r\n\r\n const baseDir = options.global ? join(homedir(), '.claude', 'skills') : join(process.cwd(), '.claude', 'skills');\r\n\r\n return join(baseDir, projectSlug);\r\n}\r\n\r\nexport function ensureDir(dirPath: string): void {\r\n mkdirSync(dirPath, { recursive: true });\r\n}\r\n\r\n/**\r\n * Validates that a resolved file path is contained within the expected root directory.\r\n * Prevents zip-slip / path traversal attacks.\r\n */\r\nexport function isPathSafe(filePath: string, rootDir: string): boolean {\r\n const resolvedPath = resolve(rootDir, filePath);\r\n const rel = relative(rootDir, resolvedPath);\r\n return !rel.startsWith('..') && !resolve(resolvedPath).includes('\\0');\r\n}\r\n","import { Command } from 'commander';\r\nimport ora from 'ora';\r\nimport { performOAuthLogin } from '../auth/oauth';\r\nimport { isExpired, loadCredentials } from '../auth/store';\r\nimport { error, info, success, warn } from '../utils';\r\n\r\nexport function loginCommand(): Command {\r\n return new Command('login').description('Log in to your Yavy account').action(async () => {\r\n const existing = loadCredentials();\r\n if (existing?.access_token && !isExpired(existing)) {\r\n info('You are already logged in. Use `yavy logout` first to switch accounts.');\r\n return;\r\n }\r\n\r\n if (existing && isExpired(existing)) {\r\n warn('Your session has expired. Re-authenticating...');\r\n }\r\n\r\n const spinner = ora('Opening browser for authentication...').start();\r\n\r\n try {\r\n const result = await performOAuthLogin();\r\n spinner.stop();\r\n\r\n if (result) {\r\n success('Successfully logged in to Yavy!');\r\n process.exit(0);\r\n } else {\r\n error('Login failed. Please try again.');\r\n process.exit(1);\r\n }\r\n } catch (err) {\r\n spinner.stop();\r\n error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n","import { createHash, randomBytes } from 'node:crypto';\r\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\r\nimport type { AddressInfo } from 'node:net';\r\nimport { URL } from 'node:url';\r\nimport open from 'open';\r\nimport { YAVY_BASE_URL, YAVY_CLIENT_ID } from '../config';\r\nimport { saveCredentials } from './store';\r\n\r\nconst PREFERRED_PORTS = [9876, 9877, 9878, 0]; // 0 = OS-assigned fallback\r\nconst CALLBACK_PATH = '/callback';\r\n\r\ninterface TokenResponse {\r\n access_token: string;\r\n refresh_token?: string;\r\n expires_in?: number;\r\n token_type: string;\r\n}\r\n\r\nfunction generateCodeVerifier(): string {\r\n return randomBytes(32).toString('base64url');\r\n}\r\n\r\nfunction generateCodeChallenge(verifier: string): string {\r\n return createHash('sha256').update(verifier).digest('base64url');\r\n}\r\n\r\nfunction generateState(): string {\r\n return randomBytes(16).toString('base64url');\r\n}\r\n\r\nfunction tryListen(server: Server, port: number): Promise<number> {\r\n return new Promise((resolve, reject) => {\r\n server.once('error', reject);\r\n server.listen(port, () => {\r\n server.removeListener('error', reject);\r\n const addr = server.address() as AddressInfo;\r\n resolve(addr.port);\r\n });\r\n });\r\n}\r\n\r\nasync function listenOnAvailablePort(server: Server): Promise<number> {\r\n for (const port of PREFERRED_PORTS) {\r\n try {\r\n return await tryListen(server, port);\r\n } catch (err: unknown) {\r\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EADDRINUSE') {\r\n continue;\r\n }\r\n throw err;\r\n }\r\n }\r\n throw new Error('Could not find an available port for OAuth callback');\r\n}\r\n\r\nexport async function performOAuthLogin(): Promise<boolean> {\r\n const codeVerifier = generateCodeVerifier();\r\n const codeChallenge = generateCodeChallenge(codeVerifier);\r\n const state = generateState();\r\n\r\n return new Promise((resolve, reject) => {\r\n let settled = false;\r\n const settle = (value: boolean) => {\r\n if (!settled) {\r\n settled = true;\r\n resolve(value);\r\n }\r\n };\r\n\r\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\r\n const actualPort = (server.address() as AddressInfo).port;\r\n const url = new URL(req.url ?? '/', `http://localhost:${actualPort}`);\r\n\r\n if (url.pathname !== CALLBACK_PATH) {\r\n res.writeHead(404);\r\n res.end('Not found');\r\n return;\r\n }\r\n\r\n const code = url.searchParams.get('code');\r\n const returnedState = url.searchParams.get('state');\r\n const error = url.searchParams.get('error');\r\n\r\n if (error || !code) {\r\n res.writeHead(200, { 'Content-Type': 'text/html' });\r\n res.end('<html><body><h1>Authentication failed</h1><p>You can close this window.</p></body></html>');\r\n server.close();\r\n settle(false);\r\n return;\r\n }\r\n\r\n if (returnedState !== state) {\r\n res.writeHead(200, { 'Content-Type': 'text/html' });\r\n res.end('<html><body><h1>Authentication failed</h1><p>Invalid state parameter. Possible CSRF attack.</p></body></html>');\r\n server.close();\r\n settle(false);\r\n return;\r\n }\r\n\r\n try {\r\n const redirectUri = `http://localhost:${actualPort}${CALLBACK_PATH}`;\r\n const tokenResponse = await fetch(`${YAVY_BASE_URL}/oauth/token`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\r\n body: JSON.stringify({\r\n grant_type: 'authorization_code',\r\n code,\r\n redirect_uri: redirectUri,\r\n client_id: YAVY_CLIENT_ID,\r\n code_verifier: codeVerifier,\r\n }),\r\n });\r\n\r\n if (!tokenResponse.ok) {\r\n throw new Error(`Token exchange failed: ${tokenResponse.status}`);\r\n }\r\n\r\n const data = (await tokenResponse.json()) as TokenResponse;\r\n\r\n saveCredentials({\r\n access_token: data.access_token,\r\n refresh_token: data.refresh_token,\r\n expires_at: data.expires_in ? new Date(Date.now() + data.expires_in * 1000).toISOString() : undefined,\r\n });\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html' });\r\n res.end('<html><body><h1>Logged in to Yavy!</h1><p>You can close this window and return to your terminal.</p></body></html>');\r\n server.close();\r\n settle(true);\r\n } catch {\r\n res.writeHead(200, { 'Content-Type': 'text/html' });\r\n res.end('<html><body><h1>Authentication failed</h1><p>Something went wrong. Please try again.</p></body></html>');\r\n server.close();\r\n settle(false);\r\n }\r\n });\r\n\r\n listenOnAvailablePort(server)\r\n .then((actualPort) => {\r\n const authUrl = new URL('/oauth/authorize', YAVY_BASE_URL);\r\n authUrl.searchParams.set('client_id', YAVY_CLIENT_ID);\r\n authUrl.searchParams.set('redirect_uri', `http://localhost:${actualPort}${CALLBACK_PATH}`);\r\n authUrl.searchParams.set('response_type', 'code');\r\n authUrl.searchParams.set('state', state);\r\n authUrl.searchParams.set('code_challenge', codeChallenge);\r\n authUrl.searchParams.set('code_challenge_method', 'S256');\r\n\r\n open(authUrl.toString());\r\n })\r\n .catch((err) => {\r\n server.close();\r\n if (!settled) {\r\n settled = true;\r\n reject(err);\r\n }\r\n });\r\n\r\n // Timeout after 5 minutes\r\n const timeout = setTimeout(\r\n () => {\r\n server.close();\r\n settle(false);\r\n },\r\n 5 * 60 * 1000,\r\n );\r\n timeout.unref();\r\n });\r\n}\r\n","import { Command } from 'commander';\r\nimport { clearCredentials, loadCredentials } from '../auth/store';\r\nimport { info, success } from '../utils';\r\n\r\nexport function logoutCommand(): Command {\r\n return new Command('logout').description('Log out of your Yavy account').action(() => {\r\n const existing = loadCredentials();\r\n if (!existing) {\r\n info('You are not logged in.');\r\n return;\r\n }\r\n\r\n clearCredentials();\r\n success('Logged out of Yavy.');\r\n });\r\n}\r\n","import chalk from 'chalk';\r\nimport { Command } from 'commander';\r\nimport ora from 'ora';\r\nimport { YavyApiClient } from '../api/client';\r\nimport { YAVY_BASE_URL } from '../config';\r\nimport { error } from '../utils';\r\n\r\nexport function projectsCommand(): Command {\r\n return new Command('projects')\r\n .description('List your Yavy projects')\r\n .option('--json', 'Output as JSON')\r\n .action(async (options: { json?: boolean }) => {\r\n const spinner = options.json ? null : ora('Fetching projects...').start();\r\n\r\n try {\r\n const client = await YavyApiClient.create();\r\n const projects = await client.listProjects();\r\n\r\n spinner?.stop();\r\n\r\n if (options.json) {\r\n console.log(JSON.stringify(projects, null, 2));\r\n return;\r\n }\r\n\r\n if (projects.length === 0) {\r\n console.log(`No projects found. Create one at ${YAVY_BASE_URL}`);\r\n return;\r\n }\r\n\r\n console.log(chalk.bold(`\\nYour Projects (${projects.length}):\\n`));\r\n\r\n for (const project of projects) {\r\n const org = chalk.dim(`${project.organization.slug}/`);\r\n const name = chalk.bold(project.name);\r\n const pages = chalk.cyan(`${project.pages_count} pages`);\r\n const indexed = project.last_indexed_at\r\n ? chalk.dim(`indexed ${new Date(project.last_indexed_at).toLocaleDateString()}`)\r\n : chalk.yellow('not indexed');\r\n const content = project.has_indexed_content ? chalk.green(' [indexed]') : '';\r\n\r\n console.log(` ${org}${name} ${pages} ${indexed}${content}`);\r\n }\r\n\r\n console.log('');\r\n } catch (err) {\r\n spinner?.stop();\r\n error(err instanceof Error ? err.message : String(err));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n"],"mappings":"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","names":["Command","chalk","existsSync","writeFileSync","join","error","resolve","mkdirSync","homedir","join","existsSync","join","chalk","writeFileSync","Command","ora","resolve","error","Command","ora","Command","Command","chalk","Command","ora","Command","ora","chalk","Command"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@yavydev/cli",
3
+ "version": "0.1.0",
4
+ "description": "Generate AI skills from your indexed documentation on Yavy",
5
+ "type": "module",
6
+ "bin": {
7
+ "yavy": "./bin/yavy.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "typecheck": "tsc --noEmit",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "format": "prettier --write \"src/**/*.{ts,js}\" \"*.{json,md}\"",
17
+ "format:check": "prettier --check \"src/**/*.{ts,js}\" \"*.{json,md}\""
18
+ },
19
+ "keywords": [
20
+ "yavy",
21
+ "ai",
22
+ "skills",
23
+ "documentation",
24
+ "cli"
25
+ ],
26
+ "author": {
27
+ "name": "Yavy",
28
+ "url": "https://yavy.dev"
29
+ },
30
+ "homepage": "https://github.com/yavydev/cli#readme",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/yavydev/cli.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/yavydev/cli/issues"
37
+ },
38
+ "dependencies": {
39
+ "chalk": "^5.3.0",
40
+ "commander": "^13.1.0",
41
+ "fflate": "^0.8.2",
42
+ "open": "^10.1.0",
43
+ "ora": "^8.1.1"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "@vitest/coverage-v8": "^3.2.4",
48
+ "tsup": "^8.4.0",
49
+ "typescript": "^5.7.0",
50
+ "prettier": "^3.7.0",
51
+ "vitest": "^3.0.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "bin"
62
+ ],
63
+ "license": "MIT"
64
+ }