@flydocs/cli 0.5.0-beta.9 → 0.6.0-alpha.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.
Files changed (41) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +1259 -370
  3. package/package.json +1 -1
  4. package/template/.claude/agents/implementation-agent.md +0 -1
  5. package/template/.claude/agents/pm-agent.md +0 -1
  6. package/template/.claude/agents/research-agent.md +0 -1
  7. package/template/.claude/agents/review-agent.md +0 -1
  8. package/template/.claude/commands/flydocs-setup.md +109 -26
  9. package/template/.claude/commands/flydocs-upgrade.md +330 -0
  10. package/template/.claude/skills/flydocs-cloud/SKILL.md +53 -38
  11. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +5 -5
  12. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +8 -24
  13. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +14 -30
  14. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +10 -32
  15. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +15 -25
  16. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +21 -58
  17. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +26 -37
  18. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +24 -31
  19. package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +39 -0
  20. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +10 -19
  21. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +103 -170
  22. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +6 -59
  23. package/template/.claude/skills/flydocs-cloud/scripts/link.py +16 -35
  24. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +21 -28
  25. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +16 -77
  26. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +19 -0
  27. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +21 -33
  28. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +24 -38
  29. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +19 -0
  30. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +10 -19
  31. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +36 -50
  32. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +68 -0
  33. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +41 -0
  34. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +11 -52
  35. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +16 -27
  36. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +23 -52
  37. package/template/.env.example +16 -7
  38. package/template/.flydocs/config.json +1 -1
  39. package/template/.flydocs/version +1 -1
  40. package/template/CHANGELOG.md +144 -0
  41. package/template/manifest.json +5 -3
package/dist/cli.js CHANGED
@@ -11,13 +11,14 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/lib/constants.ts
13
13
  import pc from "picocolors";
14
- var CLI_VERSION, CLI_NAME, PACKAGE_NAME;
14
+ var CLI_VERSION, CLI_NAME, PACKAGE_NAME, POSTHOG_API_KEY;
15
15
  var init_constants = __esm({
16
16
  "src/lib/constants.ts"() {
17
17
  "use strict";
18
- CLI_VERSION = "0.5.0-beta.9";
18
+ CLI_VERSION = "0.6.0-alpha.2";
19
19
  CLI_NAME = "flydocs";
20
20
  PACKAGE_NAME = "@flydocs/cli";
21
+ POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
21
22
  }
22
23
  });
23
24
 
@@ -42,9 +43,7 @@ async function ensureDirectories(targetDir, tier) {
42
43
  const dirs = [
43
44
  ".flydocs",
44
45
  ".claude/skills",
45
- ".claude/agents",
46
46
  ".claude/commands",
47
- ".cursor/agents",
48
47
  ".cursor/commands",
49
48
  ".cursor/rules",
50
49
  "flydocs/context",
@@ -136,8 +135,58 @@ var init_template = __esm({
136
135
 
137
136
  // src/lib/ui.ts
138
137
  import pc2 from "picocolors";
139
- function hyperlink(text3, url) {
140
- return `\x1B]8;;${url}\x1B\\${text3}\x1B]8;;\x1B\\`;
138
+ function shadow(text3) {
139
+ return `\x1B[38;2;55;45;70m${text3}\x1B[0m`;
140
+ }
141
+ function renderBannerBlock() {
142
+ const height = BANNER_ROWS.length;
143
+ const width = BANNER_ROWS[0].length;
144
+ const padded = BANNER_ROWS.map((r) => r.padEnd(width));
145
+ console.log(` ${GRADIENT[0](padded[0])}`);
146
+ for (let i = 1; i < height; i++) {
147
+ let line = "";
148
+ const shadowSrc2 = padded[i - 1];
149
+ const textSrc = padded[i];
150
+ for (let c = 0; c < width; c++) {
151
+ const textChar = textSrc[c];
152
+ const shadowChar = c > 0 ? shadowSrc2[c - 1] : " ";
153
+ if (textChar === "\u2588") {
154
+ line += "\u2588";
155
+ } else if (shadowChar === "\u2588") {
156
+ line += "\u2593";
157
+ } else {
158
+ line += " ";
159
+ }
160
+ }
161
+ let colored = " ";
162
+ let j = 0;
163
+ while (j < line.length) {
164
+ const ch = line[j];
165
+ let run = "";
166
+ while (j < line.length && line[j] === ch) {
167
+ run += line[j];
168
+ j++;
169
+ }
170
+ if (ch === "\u2588") {
171
+ colored += GRADIENT[i](run);
172
+ } else if (ch === "\u2593") {
173
+ colored += shadow(run);
174
+ } else {
175
+ colored += run;
176
+ }
177
+ }
178
+ console.log(colored);
179
+ }
180
+ const shadowSrc = padded[height - 1];
181
+ let bottomShadow = "";
182
+ for (let c = 0; c < width + 1; c++) {
183
+ const shadowChar = c > 0 ? shadowSrc[c - 1] : " ";
184
+ bottomShadow += shadowChar === "\u2588" ? "\u2593" : " ";
185
+ }
186
+ bottomShadow = bottomShadow.replace(/\s+$/, "");
187
+ if (bottomShadow.trim()) {
188
+ console.log(` ${shadow(" " + bottomShadow)}`);
189
+ }
141
190
  }
142
191
  function printStatus(message) {
143
192
  console.log(`${pc2.green("\u2714")} ${message}`);
@@ -152,26 +201,13 @@ function printInfo(message) {
152
201
  console.log(`${pc2.cyan("\u2139")} ${message}`);
153
202
  }
154
203
  function printBanner(version) {
155
- const pink = (t) => pc2.bold(pc2.magenta(t));
156
- const purple = (t) => pc2.bold(pc2.cyan(t));
157
- const dim = pc2.dim;
158
- const bold = pc2.bold;
159
- const pinkBlock12 = pink("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
160
- const pinkBlock4 = pink("\u2588\u2588\u2588\u2588");
161
- const purpleBlock12 = purple("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
162
- const purpleBlock4 = purple("\u2588\u2588\u2588\u2588");
163
204
  console.log();
164
- console.log(` ${pinkBlock12}`);
165
- console.log(` ${pinkBlock12}`);
166
- console.log(` ${pinkBlock4}`);
167
- console.log(
168
- ` ${pinkBlock4} ${purpleBlock12} ${bold("FlyDocs")} ${dim("(Beta)")}`
169
- );
205
+ renderBannerBlock();
206
+ console.log();
170
207
  console.log(
171
- ` ${purpleBlock12} ${dim("Spec-Driven Context That Helps Engineers Fly")}`
208
+ ` ${pc2.bold(pc2.white("Structured context for AI coding tools"))} ${pc2.dim("(Beta)")}`
172
209
  );
173
- console.log(` ${purpleBlock4}`);
174
- console.log(` ${purpleBlock4} ${dim(`v${version}`)}`);
210
+ console.log(` ${pc2.dim(`Version: ${version}`)}`);
175
211
  console.log();
176
212
  }
177
213
  function printCompletionBox(title, lines) {
@@ -192,22 +228,42 @@ function printCompletionBox(title, lines) {
192
228
  }
193
229
  function printBetaCta() {
194
230
  const dim = pc2.dim;
195
- const url = "https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta";
231
+ const discordUrl = "https://discord.com/invite/YAkjePmZTQ";
232
+ const siteUrl = "https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta";
196
233
  console.log(
197
234
  dim(" \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\u2500")
198
235
  );
199
236
  console.log();
200
- console.log(` ${pc2.bold("Join the FlyDocs Closed Beta")}`);
201
237
  console.log(
202
- ` ${dim("Early access to cloud features, web portal, and more.")}`
238
+ ` ${pc2.bold(pc2.cyan("Join the Discord"))} for upcoming features, support, and early access to what's next.`
239
+ );
240
+ console.log(` ${dim("Invite link:")}`);
241
+ console.log(` ${pc2.cyan(discordUrl)}`);
242
+ console.log();
243
+ console.log(
244
+ ` ${dim("Docs and updates:")} ${pc2.cyan("https://www.flydocs.ai")}`
203
245
  );
204
- console.log(` ${pc2.cyan(hyperlink("flydocs.ai", url))}`);
205
246
  console.log();
206
247
  }
248
+ var GRADIENT, BANNER_ROWS;
207
249
  var init_ui = __esm({
208
250
  "src/lib/ui.ts"() {
209
251
  "use strict";
210
252
  init_constants();
253
+ GRADIENT = [
254
+ (t) => `\x1B[1;38;2;255;0;128m${t}\x1B[0m`,
255
+ (t) => `\x1B[1;38;2;225;15;158m${t}\x1B[0m`,
256
+ (t) => `\x1B[1;38;2;190;30;190m${t}\x1B[0m`,
257
+ (t) => `\x1B[1;38;2;158;44;214m${t}\x1B[0m`,
258
+ (t) => `\x1B[1;38;2;124;58;237m${t}\x1B[0m`
259
+ ];
260
+ BANNER_ROWS = [
261
+ "\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588",
262
+ "\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
263
+ "\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 ",
264
+ "\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
265
+ "\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588"
266
+ ];
211
267
  }
212
268
  });
213
269
 
@@ -305,18 +361,26 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
305
361
  join4(templateSkillsDir, activeMech),
306
362
  join4(skillsDir, activeMech)
307
363
  );
308
- const { rm: rm5 } = await import("fs/promises");
364
+ const { rm: rm6 } = await import("fs/promises");
309
365
  const inactivePath = join4(skillsDir, inactiveMech);
310
366
  if (await pathExists(inactivePath)) {
311
- await rm5(inactivePath, { recursive: true, force: true });
367
+ await rm6(inactivePath, { recursive: true, force: true });
312
368
  }
313
- for (const skill of OWNED_SKILLS) {
369
+ for (const skill of CORE_SKILLS) {
314
370
  if (skill === "flydocs-workflow") continue;
315
371
  const src = join4(templateSkillsDir, skill);
316
372
  if (await pathExists(src)) {
317
373
  await replaceDirectory(src, join4(skillsDir, skill));
318
374
  }
319
375
  }
376
+ if (tier === "cloud") {
377
+ for (const skill of CLOUD_ONLY_SKILLS) {
378
+ const src = join4(templateSkillsDir, skill);
379
+ if (await pathExists(src)) {
380
+ await replaceDirectory(src, join4(skillsDir, skill));
381
+ }
382
+ }
383
+ }
320
384
  const readmeSrc = join4(templateSkillsDir, "README.md");
321
385
  if (await pathExists(readmeSrc)) {
322
386
  await copyFile(readmeSrc, join4(skillsDir, "README.md"));
@@ -325,18 +389,29 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
325
389
  async function replaceOwnedSkills(templateDir, targetDir, tier) {
326
390
  const skillsDir = join4(targetDir, ".claude", "skills");
327
391
  const templateSkillsDir = join4(templateDir, ".claude", "skills");
328
- for (const skill of OWNED_SKILLS) {
392
+ const { rm: rm6 } = await import("fs/promises");
393
+ for (const skill of CORE_SKILLS) {
329
394
  const src = join4(templateSkillsDir, skill);
330
395
  if (await pathExists(src)) {
331
396
  await replaceDirectory(src, join4(skillsDir, skill));
332
397
  }
333
398
  }
334
- const { rm: rm5 } = await import("fs/promises");
399
+ for (const skill of CLOUD_ONLY_SKILLS) {
400
+ const dest = join4(skillsDir, skill);
401
+ if (tier === "cloud") {
402
+ const src = join4(templateSkillsDir, skill);
403
+ if (await pathExists(src)) {
404
+ await replaceDirectory(src, dest);
405
+ }
406
+ } else if (await pathExists(dest)) {
407
+ await rm6(dest, { recursive: true, force: true });
408
+ }
409
+ }
335
410
  const activeMech = MECHANISM_SKILLS[tier];
336
411
  const inactiveMech = tier === "local" ? MECHANISM_SKILLS.cloud : MECHANISM_SKILLS.local;
337
412
  const inactivePath = join4(skillsDir, inactiveMech);
338
413
  if (await pathExists(inactivePath)) {
339
- await rm5(inactivePath, { recursive: true, force: true });
414
+ await rm6(inactivePath, { recursive: true, force: true });
340
415
  }
341
416
  await replaceDirectory(
342
417
  join4(templateSkillsDir, activeMech),
@@ -348,9 +423,9 @@ async function replaceOwnedSkills(templateDir, targetDir, tier) {
348
423
  }
349
424
  }
350
425
  async function copyCursorRules(targetDir) {
351
- const { mkdir: mkdir6 } = await import("fs/promises");
426
+ const { mkdir: mkdir9 } = await import("fs/promises");
352
427
  const rulesDir = join4(targetDir, ".cursor", "rules");
353
- await mkdir6(rulesDir, { recursive: true });
428
+ await mkdir9(rulesDir, { recursive: true });
354
429
  const workflowRule = join4(
355
430
  targetDir,
356
431
  ".claude",
@@ -384,18 +459,17 @@ async function copyCursorRules(targetDir) {
384
459
  await copyFile(context7Rule, join4(rulesDir, "flydocs-context7.mdc"));
385
460
  }
386
461
  }
387
- var OWNED_SKILLS, MECHANISM_SKILLS;
462
+ var CORE_SKILLS, CLOUD_ONLY_SKILLS, MECHANISM_SKILLS;
388
463
  var init_skills = __esm({
389
464
  "src/lib/skills.ts"() {
390
465
  "use strict";
391
466
  init_fs_ops();
392
- OWNED_SKILLS = [
467
+ CORE_SKILLS = [
393
468
  "flydocs-workflow",
394
- "flydocs-figma",
395
- "flydocs-estimates",
396
469
  "flydocs-context-graph",
397
470
  "flydocs-context7"
398
471
  ];
472
+ CLOUD_ONLY_SKILLS = ["flydocs-figma", "flydocs-estimates"];
399
473
  MECHANISM_SKILLS = {
400
474
  local: "flydocs-local",
401
475
  cloud: "flydocs-cloud"
@@ -403,14 +477,77 @@ var init_skills = __esm({
403
477
  }
404
478
  });
405
479
 
480
+ // src/lib/user-content.ts
481
+ import {
482
+ copyFile as fsCopyFile2,
483
+ mkdir as mkdir2,
484
+ readFile as readFile3,
485
+ writeFile as writeFile2
486
+ } from "fs/promises";
487
+ import { join as join5, dirname as dirname2 } from "path";
488
+ async function backupOriginals(targetDir, files = RESTORABLE_FILES) {
489
+ const backedUp = [];
490
+ const backupDir = join5(targetDir, BACKUP_ORIGINALS_DIR);
491
+ for (const relativePath of files) {
492
+ const srcPath = join5(targetDir, relativePath);
493
+ const destPath = join5(backupDir, relativePath);
494
+ if (!await pathExists(srcPath)) {
495
+ continue;
496
+ }
497
+ if (await pathExists(destPath)) {
498
+ continue;
499
+ }
500
+ await mkdir2(dirname2(destPath), { recursive: true });
501
+ await fsCopyFile2(srcPath, destPath);
502
+ backedUp.push(relativePath);
503
+ }
504
+ return backedUp;
505
+ }
506
+ async function readBackupOriginals(targetDir) {
507
+ const backupDir = join5(targetDir, BACKUP_ORIGINALS_DIR);
508
+ if (!await pathExists(backupDir)) {
509
+ return [];
510
+ }
511
+ const files = [];
512
+ for (const relativePath of RESTORABLE_FILES) {
513
+ const backupPath = join5(backupDir, relativePath);
514
+ if (await pathExists(backupPath)) {
515
+ const content = await readFile3(backupPath);
516
+ files.push({ relativePath, content });
517
+ }
518
+ }
519
+ return files;
520
+ }
521
+ async function writeRestoredFiles(targetDir, files) {
522
+ for (const { relativePath, content } of files) {
523
+ const destPath = join5(targetDir, relativePath);
524
+ await mkdir2(dirname2(destPath), { recursive: true });
525
+ await writeFile2(destPath, content);
526
+ }
527
+ }
528
+ var BACKUP_ORIGINALS_DIR, RESTORABLE_FILES;
529
+ var init_user_content = __esm({
530
+ "src/lib/user-content.ts"() {
531
+ "use strict";
532
+ init_fs_ops();
533
+ BACKUP_ORIGINALS_DIR = ".flydocs/backup-originals";
534
+ RESTORABLE_FILES = [
535
+ ".claude/CLAUDE.md",
536
+ ".claude/settings.json",
537
+ ".cursor/hooks.json",
538
+ "AGENTS.md"
539
+ ];
540
+ }
541
+ });
542
+
406
543
  // src/lib/stack.ts
407
- import { readFile as readFile3 } from "fs/promises";
408
- import { join as join5 } from "path";
544
+ import { readFile as readFile4 } from "fs/promises";
545
+ import { join as join6 } from "path";
409
546
  async function parseProjectMdStack(targetDir) {
410
547
  const detected = /* @__PURE__ */ new Set();
411
- const projectMdPath = join5(targetDir, "flydocs", "context", "project.md");
548
+ const projectMdPath = join6(targetDir, "flydocs", "context", "project.md");
412
549
  try {
413
- const content = await readFile3(projectMdPath, "utf-8");
550
+ const content = await readFile4(projectMdPath, "utf-8");
414
551
  const stackMatch = content.match(/## Stack\n([\s\S]*?)(?=\n## |\n---|\z)/);
415
552
  if (!stackMatch) return detected;
416
553
  const stackSection = stackMatch[1].toLowerCase();
@@ -453,10 +590,10 @@ async function parseProjectMdStack(targetDir) {
453
590
  }
454
591
  async function detectStack(targetDir) {
455
592
  const detected = /* @__PURE__ */ new Set();
456
- const pkgPath = join5(targetDir, "package.json");
593
+ const pkgPath = join6(targetDir, "package.json");
457
594
  let pkg;
458
595
  try {
459
- const content = await readFile3(pkgPath, "utf-8");
596
+ const content = await readFile4(pkgPath, "utf-8");
460
597
  pkg = JSON.parse(content);
461
598
  } catch {
462
599
  }
@@ -484,39 +621,39 @@ async function detectStack(targetDir) {
484
621
  detected.add("testing-library");
485
622
  }
486
623
  for (const f of ["convex/schema.ts", "convex/schema.js"]) {
487
- if (await pathExists(join5(targetDir, f))) detected.add("convex");
624
+ if (await pathExists(join6(targetDir, f))) detected.add("convex");
488
625
  }
489
626
  for (const f of ["next.config.ts", "next.config.js", "next.config.mjs"]) {
490
- if (await pathExists(join5(targetDir, f))) detected.add("nextjs");
627
+ if (await pathExists(join6(targetDir, f))) detected.add("nextjs");
491
628
  }
492
- const appJsonPath = join5(targetDir, "app.json");
629
+ const appJsonPath = join6(targetDir, "app.json");
493
630
  if (await pathExists(appJsonPath)) {
494
631
  try {
495
- const appContent = await readFile3(appJsonPath, "utf-8");
632
+ const appContent = await readFile4(appJsonPath, "utf-8");
496
633
  if (appContent.includes('"expo"')) detected.add("expo");
497
634
  } catch {
498
635
  }
499
636
  }
500
- if (await pathExists(join5(targetDir, "tsconfig.json"))) {
637
+ if (await pathExists(join6(targetDir, "tsconfig.json"))) {
501
638
  detected.add("typescript");
502
639
  }
503
- if (await pathExists(join5(targetDir, "nuxt.config.ts")) || await pathExists(join5(targetDir, "nuxt.config.js"))) {
640
+ if (await pathExists(join6(targetDir, "nuxt.config.ts")) || await pathExists(join6(targetDir, "nuxt.config.js"))) {
504
641
  detected.add("nuxt");
505
642
  detected.add("vue");
506
643
  }
507
- if (await pathExists(join5(targetDir, "angular.json"))) {
644
+ if (await pathExists(join6(targetDir, "angular.json"))) {
508
645
  detected.add("angular");
509
646
  }
510
- if (await pathExists(join5(targetDir, "svelte.config.js")) || await pathExists(join5(targetDir, "svelte.config.ts"))) {
647
+ if (await pathExists(join6(targetDir, "svelte.config.js")) || await pathExists(join6(targetDir, "svelte.config.ts"))) {
511
648
  detected.add("svelte");
512
649
  }
513
- if (await pathExists(join5(targetDir, "pyproject.toml")) || await pathExists(join5(targetDir, "requirements.txt")) || await pathExists(join5(targetDir, "setup.py"))) {
650
+ if (await pathExists(join6(targetDir, "pyproject.toml")) || await pathExists(join6(targetDir, "requirements.txt")) || await pathExists(join6(targetDir, "setup.py"))) {
514
651
  detected.add("python");
515
652
  }
516
- if (await pathExists(join5(targetDir, "go.mod"))) {
653
+ if (await pathExists(join6(targetDir, "go.mod"))) {
517
654
  detected.add("go");
518
655
  }
519
- if (await pathExists(join5(targetDir, "Cargo.toml"))) {
656
+ if (await pathExists(join6(targetDir, "Cargo.toml"))) {
520
657
  detected.add("rust");
521
658
  }
522
659
  const projectMdStack = await parseProjectMdStack(targetDir);
@@ -562,9 +699,9 @@ var init_stack = __esm({
562
699
 
563
700
  // src/lib/post-install.ts
564
701
  import { execFileSync } from "child_process";
565
- import { join as join6 } from "path";
702
+ import { join as join7 } from "path";
566
703
  async function runManifestGeneration(targetDir) {
567
- const scriptPath = join6(
704
+ const scriptPath = join7(
568
705
  targetDir,
569
706
  ".flydocs",
570
707
  "scripts",
@@ -582,7 +719,7 @@ async function runManifestGeneration(targetDir) {
582
719
  }
583
720
  }
584
721
  async function runContextGraphBuild(targetDir) {
585
- const scriptPath = join6(
722
+ const scriptPath = join7(
586
723
  targetDir,
587
724
  ".claude",
588
725
  "skills",
@@ -610,8 +747,8 @@ var init_post_install = __esm({
610
747
  });
611
748
 
612
749
  // src/lib/skill-manager.ts
613
- import { readFile as readFile4, readdir, rm as rm2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
614
- import { join as join7 } from "path";
750
+ import { readFile as readFile5, readdir, rm as rm2, mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
751
+ import { join as join8 } from "path";
615
752
  import pc3 from "picocolors";
616
753
  function flushFrontmatterValue(mode, lines) {
617
754
  switch (mode) {
@@ -738,13 +875,13 @@ async function downloadFileContent(downloadUrl) {
738
875
  async function downloadRecursive(url, destDir) {
739
876
  const items = await fetchGitHubJson(url);
740
877
  if (items === null) return false;
741
- await mkdir2(destDir, { recursive: true });
878
+ await mkdir3(destDir, { recursive: true });
742
879
  for (const item of items) {
743
- const destPath = join7(destDir, item.name);
880
+ const destPath = join8(destDir, item.name);
744
881
  if (item.type === "file") {
745
882
  if (item.download_url) {
746
883
  const content = await downloadFileContent(item.download_url);
747
- await writeFile2(destPath, content);
884
+ await writeFile3(destPath, content);
748
885
  }
749
886
  } else if (item.type === "dir") {
750
887
  await downloadRecursive(item.url, destPath);
@@ -757,7 +894,7 @@ async function downloadSkillTree(repo, skillName, targetDir) {
757
894
  return downloadRecursive(apiUrl, targetDir);
758
895
  }
759
896
  async function listSkills(targetDir) {
760
- const skillsDir = join7(targetDir, ".claude", "skills");
897
+ const skillsDir = join8(targetDir, ".claude", "skills");
761
898
  const platform = [];
762
899
  const community = [];
763
900
  if (!await pathExists(skillsDir)) {
@@ -770,11 +907,11 @@ async function listSkills(targetDir) {
770
907
  return { platform, community };
771
908
  }
772
909
  for (const entry of entries.sort()) {
773
- const skillFile = join7(skillsDir, entry, "SKILL.md");
910
+ const skillFile = join8(skillsDir, entry, "SKILL.md");
774
911
  if (!await pathExists(skillFile)) continue;
775
912
  let content;
776
913
  try {
777
- content = await readFile4(skillFile, "utf-8");
914
+ content = await readFile5(skillFile, "utf-8");
778
915
  } catch {
779
916
  continue;
780
917
  }
@@ -822,7 +959,7 @@ async function addSkill(targetDir, source) {
822
959
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
823
960
  return;
824
961
  }
825
- const skillsDir = join7(targetDir, ".claude", "skills", skillName);
962
+ const skillsDir = join8(targetDir, ".claude", "skills", skillName);
826
963
  if (await pathExists(skillsDir)) {
827
964
  printWarning(`Skill '${skillName}' is already installed.`);
828
965
  console.log(` Remove first: flydocs skills remove ${skillName}`);
@@ -851,13 +988,13 @@ async function addSkill(targetDir, source) {
851
988
  printError(`Skill not found at ${repo}/skills/${skillName}`);
852
989
  return;
853
990
  }
854
- const skillMdPath = join7(skillsDir, "SKILL.md");
991
+ const skillMdPath = join8(skillsDir, "SKILL.md");
855
992
  if (!await pathExists(skillMdPath)) {
856
993
  await rm2(skillsDir, { recursive: true, force: true });
857
994
  printError("Invalid skill: SKILL.md not found.");
858
995
  return;
859
996
  }
860
- const skillMdContent = await readFile4(skillMdPath, "utf-8");
997
+ const skillMdContent = await readFile5(skillMdPath, "utf-8");
861
998
  const fm = parseFrontmatter(skillMdContent);
862
999
  if (fm === null || !fm["name"] || !fm["description"]) {
863
1000
  await rm2(skillsDir, { recursive: true, force: true });
@@ -872,11 +1009,11 @@ async function addSkill(targetDir, source) {
872
1009
  );
873
1010
  }
874
1011
  printStatus("Downloaded skill files");
875
- const cursorRuleSrc = join7(skillsDir, "cursor-rule.mdc");
1012
+ const cursorRuleSrc = join8(skillsDir, "cursor-rule.mdc");
876
1013
  if (await pathExists(cursorRuleSrc)) {
877
- const cursorRulesDir = join7(targetDir, ".cursor", "rules");
878
- await mkdir2(cursorRulesDir, { recursive: true });
879
- const cursorRuleDest = join7(cursorRulesDir, `${skillName}.mdc`);
1014
+ const cursorRulesDir = join8(targetDir, ".cursor", "rules");
1015
+ await mkdir3(cursorRulesDir, { recursive: true });
1016
+ const cursorRuleDest = join8(cursorRulesDir, `${skillName}.mdc`);
880
1017
  await copyFile(cursorRuleSrc, cursorRuleDest);
881
1018
  printStatus("Installed cursor rule");
882
1019
  }
@@ -903,14 +1040,14 @@ async function removeSkill(targetDir, name) {
903
1040
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
904
1041
  return;
905
1042
  }
906
- const skillDir = join7(targetDir, ".claude", "skills", name);
1043
+ const skillDir = join8(targetDir, ".claude", "skills", name);
907
1044
  if (!await pathExists(skillDir)) {
908
1045
  printError(`Skill '${name}' is not installed.`);
909
1046
  return;
910
1047
  }
911
1048
  await rm2(skillDir, { recursive: true, force: true });
912
1049
  printStatus("Removed skill directory");
913
- const cursorRule = join7(targetDir, ".cursor", "rules", `${name}.mdc`);
1050
+ const cursorRule = join8(targetDir, ".cursor", "rules", `${name}.mdc`);
914
1051
  if (await pathExists(cursorRule)) {
915
1052
  await rm2(cursorRule, { force: true });
916
1053
  printStatus("Removed cursor rule");
@@ -975,7 +1112,7 @@ var init_skill_manager = __esm({
975
1112
  });
976
1113
 
977
1114
  // src/lib/community-skills.ts
978
- import { join as join8 } from "path";
1115
+ import { join as join9 } from "path";
979
1116
  import { multiselect, isCancel, cancel } from "@clack/prompts";
980
1117
  import pc4 from "picocolors";
981
1118
  function suggestSkills(stack) {
@@ -994,10 +1131,10 @@ function suggestSkills(stack) {
994
1131
  }
995
1132
  async function promptCommunitySkills(targetDir, stack, autoYes) {
996
1133
  const suggestions = suggestSkills(stack);
997
- const skillsDir = join8(targetDir, ".claude", "skills");
1134
+ const skillsDir = join9(targetDir, ".claude", "skills");
998
1135
  const filtered = [];
999
1136
  for (const skill of suggestions) {
1000
- if (!await pathExists(join8(skillsDir, skill.name))) {
1137
+ if (!await pathExists(join9(skillsDir, skill.name))) {
1001
1138
  filtered.push(skill);
1002
1139
  }
1003
1140
  }
@@ -1154,48 +1291,48 @@ var init_community_skills = __esm({
1154
1291
  });
1155
1292
 
1156
1293
  // src/lib/deprecated.ts
1157
- import { readdir as readdir2, mkdir as mkdir3, rename, rm as rm3 } from "fs/promises";
1158
- import { join as join9 } from "path";
1294
+ import { readdir as readdir2, mkdir as mkdir4, rename, rm as rm3 } from "fs/promises";
1295
+ import { join as join10 } from "path";
1159
1296
  import { confirm, isCancel as isCancel2 } from "@clack/prompts";
1160
1297
  async function scanDeprecated(targetDir) {
1161
1298
  const found = [];
1162
1299
  for (const item of [...DEPRECATED_DIRS, ...DEPRECATED_FILES]) {
1163
- if (await pathExists(join9(targetDir, item))) {
1300
+ if (await pathExists(join10(targetDir, item))) {
1164
1301
  found.push(item);
1165
1302
  }
1166
1303
  }
1167
1304
  for (const skill of DEPRECATED_SKILLS) {
1168
- const p = join9(targetDir, ".claude", "skills", skill);
1305
+ const p = join10(targetDir, ".claude", "skills", skill);
1169
1306
  if (await pathExists(p)) {
1170
1307
  found.push(`.claude/skills/${skill}`);
1171
1308
  }
1172
1309
  }
1173
1310
  for (const dir of DEPRECATED_RULES_DIR) {
1174
- if (await pathExists(join9(targetDir, dir))) {
1311
+ if (await pathExists(join10(targetDir, dir))) {
1175
1312
  found.push(dir);
1176
1313
  }
1177
1314
  }
1178
1315
  for (const hook of DEPRECATED_HOOKS) {
1179
- const p = join9(targetDir, ".flydocs", "hooks", hook);
1316
+ const p = join10(targetDir, ".flydocs", "hooks", hook);
1180
1317
  if (await pathExists(p)) {
1181
1318
  found.push(`.flydocs/hooks/${hook}`);
1182
1319
  }
1183
1320
  }
1184
1321
  for (const rule of DEPRECATED_CURSOR_RULES) {
1185
- const p = join9(targetDir, ".cursor", "rules", rule);
1322
+ const p = join10(targetDir, ".cursor", "rules", rule);
1186
1323
  if (await pathExists(p)) {
1187
1324
  found.push(`.cursor/rules/${rule}`);
1188
1325
  }
1189
1326
  }
1190
1327
  for (const dir of DEPRECATED_CURSOR_RULE_DIRS) {
1191
- const p = join9(targetDir, ".cursor", "rules", dir);
1328
+ const p = join10(targetDir, ".cursor", "rules", dir);
1192
1329
  if (await pathExists(p)) {
1193
1330
  found.push(`.cursor/rules/${dir}`);
1194
1331
  }
1195
1332
  }
1196
1333
  for (const cmd of DEPRECATED_COMMANDS) {
1197
1334
  for (const prefix of [".cursor/commands", ".claude/commands"]) {
1198
- const p = join9(targetDir, prefix, cmd);
1335
+ const p = join10(targetDir, prefix, cmd);
1199
1336
  if (await pathExists(p)) {
1200
1337
  found.push(`${prefix}/${cmd}`);
1201
1338
  }
@@ -1204,22 +1341,22 @@ async function scanDeprecated(targetDir) {
1204
1341
  return found;
1205
1342
  }
1206
1343
  async function handleLegacyContext(targetDir) {
1207
- const contextDir = join9(targetDir, "flydocs", "context");
1208
- const legacyDir = join9(contextDir, "legacy");
1344
+ const contextDir = join10(targetDir, "flydocs", "context");
1345
+ const legacyDir = join10(contextDir, "legacy");
1209
1346
  const oldFiles = ["overview.md", "stack.md", "standards.md"];
1210
1347
  let hasOld = false;
1211
1348
  for (const f of oldFiles) {
1212
- if (await pathExists(join9(contextDir, f))) {
1349
+ if (await pathExists(join10(contextDir, f))) {
1213
1350
  hasOld = true;
1214
1351
  break;
1215
1352
  }
1216
1353
  }
1217
1354
  if (hasOld) {
1218
- await mkdir3(legacyDir, { recursive: true });
1355
+ await mkdir4(legacyDir, { recursive: true });
1219
1356
  for (const f of oldFiles) {
1220
- const src = join9(contextDir, f);
1357
+ const src = join10(contextDir, f);
1221
1358
  if (await pathExists(src)) {
1222
- await rename(src, join9(legacyDir, f));
1359
+ await rename(src, join10(legacyDir, f));
1223
1360
  printStatus(`Moved flydocs/context/${f} \u2192 legacy/`);
1224
1361
  }
1225
1362
  }
@@ -1229,7 +1366,7 @@ async function handleLegacyContext(targetDir) {
1229
1366
  }
1230
1367
  }
1231
1368
  async function checkLegacyFolder(targetDir) {
1232
- const legacyDir = join9(targetDir, "flydocs", "context", "legacy");
1369
+ const legacyDir = join10(targetDir, "flydocs", "context", "legacy");
1233
1370
  if (!await pathExists(legacyDir)) return null;
1234
1371
  try {
1235
1372
  const entries = await readdir2(legacyDir);
@@ -1259,7 +1396,7 @@ async function promptCleanup(targetDir, paths) {
1259
1396
  return;
1260
1397
  }
1261
1398
  for (const p of paths) {
1262
- await rm3(join9(targetDir, p), { recursive: true, force: true });
1399
+ await rm3(join10(targetDir, p), { recursive: true, force: true });
1263
1400
  printStatus(`Deleted: ${p}`);
1264
1401
  }
1265
1402
  }
@@ -1310,26 +1447,26 @@ var init_deprecated = __esm({
1310
1447
  });
1311
1448
 
1312
1449
  // src/lib/gitignore.ts
1313
- import { readFile as readFile5, writeFile as writeFile3, appendFile } from "fs/promises";
1314
- import { join as join10 } from "path";
1450
+ import { readFile as readFile6, writeFile as writeFile4, appendFile } from "fs/promises";
1451
+ import { join as join11 } from "path";
1315
1452
  async function ensureGitignore(targetDir) {
1316
- const gitignorePath = join10(targetDir, ".gitignore");
1453
+ const gitignorePath = join11(targetDir, ".gitignore");
1317
1454
  if (await pathExists(gitignorePath)) {
1318
- const content = await readFile5(gitignorePath, "utf-8");
1455
+ const content = await readFile6(gitignorePath, "utf-8");
1319
1456
  if (!content.includes("# FlyDocs")) {
1320
1457
  const section = "\n# FlyDocs\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
1321
1458
  await appendFile(gitignorePath, section, "utf-8");
1322
1459
  printStatus("Added FlyDocs entries to .gitignore");
1323
1460
  }
1324
1461
  } else {
1325
- await writeFile3(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1462
+ await writeFile4(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1326
1463
  printStatus(".gitignore (new)");
1327
1464
  }
1328
1465
  }
1329
1466
  async function migrateGitignore(targetDir) {
1330
- const gitignorePath = join10(targetDir, ".gitignore");
1467
+ const gitignorePath = join11(targetDir, ".gitignore");
1331
1468
  if (!await pathExists(gitignorePath)) return;
1332
- const content = await readFile5(gitignorePath, "utf-8");
1469
+ const content = await readFile6(gitignorePath, "utf-8");
1333
1470
  if (!content.includes("flydocs/context/graph.json")) {
1334
1471
  if (content.includes("# FlyDocs")) {
1335
1472
  const lines = content.split("\n");
@@ -1340,7 +1477,7 @@ async function migrateGitignore(targetDir) {
1340
1477
  insertIdx++;
1341
1478
  }
1342
1479
  lines.splice(insertIdx, 0, "flydocs/context/graph.json");
1343
- await writeFile3(gitignorePath, lines.join("\n"), "utf-8");
1480
+ await writeFile4(gitignorePath, lines.join("\n"), "utf-8");
1344
1481
  } else {
1345
1482
  await appendFile(
1346
1483
  gitignorePath,
@@ -1409,7 +1546,7 @@ __pycache__/
1409
1546
  });
1410
1547
 
1411
1548
  // src/lib/version.ts
1412
- import { readFile as readFile6 } from "fs/promises";
1549
+ import { readFile as readFile7 } from "fs/promises";
1413
1550
  function compareVersions(v1, v2) {
1414
1551
  const [core1, pre1] = v1.split("-", 2);
1415
1552
  const [core2, pre2] = v2.split("-", 2);
@@ -1448,7 +1585,7 @@ function compareVersions(v1, v2) {
1448
1585
  async function getWhatsNew(changelogPath, fromVersion, toVersion) {
1449
1586
  let content;
1450
1587
  try {
1451
- content = await readFile6(changelogPath, "utf-8");
1588
+ content = await readFile7(changelogPath, "utf-8");
1452
1589
  } catch {
1453
1590
  return [];
1454
1591
  }
@@ -1477,13 +1614,13 @@ var init_version = __esm({
1477
1614
  });
1478
1615
 
1479
1616
  // src/lib/update-check.ts
1480
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1481
- import { join as join11 } from "path";
1617
+ import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
1618
+ import { join as join12 } from "path";
1482
1619
  import { homedir } from "os";
1483
1620
  import pc5 from "picocolors";
1484
1621
  async function readCache() {
1485
1622
  try {
1486
- const raw = await readFile7(CACHE_FILE, "utf-8");
1623
+ const raw = await readFile8(CACHE_FILE, "utf-8");
1487
1624
  const parsed = JSON.parse(raw);
1488
1625
  if (typeof parsed === "object" && parsed !== null && "checkedAt" in parsed && "latestVersion" in parsed && typeof parsed.checkedAt === "number" && typeof parsed.latestVersion === "string") {
1489
1626
  return parsed;
@@ -1495,8 +1632,8 @@ async function readCache() {
1495
1632
  }
1496
1633
  async function writeCache(cache) {
1497
1634
  try {
1498
- await mkdir4(join11(homedir(), ".flydocs"), { recursive: true });
1499
- await writeFile4(CACHE_FILE, JSON.stringify(cache), "utf-8");
1635
+ await mkdir5(join12(homedir(), ".flydocs"), { recursive: true });
1636
+ await writeFile5(CACHE_FILE, JSON.stringify(cache), "utf-8");
1500
1637
  } catch {
1501
1638
  }
1502
1639
  }
@@ -1564,12 +1701,145 @@ var init_update_check = __esm({
1564
1701
  "use strict";
1565
1702
  init_constants();
1566
1703
  init_version();
1567
- CACHE_FILE = join11(homedir(), ".flydocs", "update-check.json");
1704
+ CACHE_FILE = join12(homedir(), ".flydocs", "update-check.json");
1568
1705
  CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1569
1706
  FETCH_TIMEOUT_MS = 5e3;
1570
1707
  }
1571
1708
  });
1572
1709
 
1710
+ // src/lib/telemetry.ts
1711
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
1712
+ import { randomUUID } from "crypto";
1713
+ import { join as join13 } from "path";
1714
+ import { homedir as homedir2 } from "os";
1715
+ async function readConfig2() {
1716
+ try {
1717
+ const raw = await readFile9(CONFIG_FILE, "utf-8");
1718
+ const parsed = JSON.parse(raw);
1719
+ if (typeof parsed === "object" && parsed !== null && "enabled" in parsed && "anonymousId" in parsed && typeof parsed.enabled === "boolean" && typeof parsed.anonymousId === "string") {
1720
+ return parsed;
1721
+ }
1722
+ return null;
1723
+ } catch {
1724
+ return null;
1725
+ }
1726
+ }
1727
+ async function writeConfig2(config) {
1728
+ try {
1729
+ await mkdir6(CONFIG_DIR, { recursive: true });
1730
+ await writeFile6(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
1731
+ } catch {
1732
+ }
1733
+ }
1734
+ async function ensureConfig() {
1735
+ const existing = await readConfig2();
1736
+ if (existing) {
1737
+ return existing;
1738
+ }
1739
+ const config = {
1740
+ enabled: false,
1741
+ anonymousId: randomUUID()
1742
+ };
1743
+ await writeConfig2(config);
1744
+ return config;
1745
+ }
1746
+ async function isEnabled() {
1747
+ try {
1748
+ if (process.env.FLYDOCS_TELEMETRY === "0") {
1749
+ return false;
1750
+ }
1751
+ const config = await readConfig2();
1752
+ if (config) {
1753
+ return config.enabled;
1754
+ }
1755
+ return false;
1756
+ } catch {
1757
+ return false;
1758
+ }
1759
+ }
1760
+ function baseProperties() {
1761
+ return {
1762
+ cli_version: CLI_VERSION,
1763
+ os: process.platform,
1764
+ os_arch: process.arch,
1765
+ node_version: process.version,
1766
+ is_ci: Boolean(
1767
+ process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.BUILD_NUMBER
1768
+ ),
1769
+ $ip: null,
1770
+ $process_person_profile: false
1771
+ };
1772
+ }
1773
+ async function capture(event, properties = {}) {
1774
+ try {
1775
+ if (!await isEnabled()) {
1776
+ return;
1777
+ }
1778
+ const config = await ensureConfig();
1779
+ eventQueue.push({
1780
+ event,
1781
+ properties: {
1782
+ distinct_id: config.anonymousId,
1783
+ ...baseProperties(),
1784
+ ...properties
1785
+ },
1786
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1787
+ });
1788
+ } catch {
1789
+ }
1790
+ }
1791
+ async function flush() {
1792
+ try {
1793
+ if (eventQueue.length === 0) {
1794
+ return;
1795
+ }
1796
+ if (!await isEnabled()) {
1797
+ eventQueue.length = 0;
1798
+ return;
1799
+ }
1800
+ const batch = eventQueue.splice(0, eventQueue.length);
1801
+ const controller = new AbortController();
1802
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
1803
+ await fetch(POSTHOG_BATCH_URL, {
1804
+ method: "POST",
1805
+ headers: { "Content-Type": "application/json" },
1806
+ body: JSON.stringify({
1807
+ api_key: POSTHOG_API_KEY,
1808
+ batch
1809
+ }),
1810
+ signal: controller.signal
1811
+ });
1812
+ clearTimeout(timeout);
1813
+ } catch {
1814
+ }
1815
+ }
1816
+ async function setEnabled(enabled) {
1817
+ const config = await ensureConfig();
1818
+ config.enabled = enabled;
1819
+ await writeConfig2(config);
1820
+ }
1821
+ async function getStatus() {
1822
+ const envOverride = process.env.FLYDOCS_TELEMETRY === "0";
1823
+ const config = await readConfig2();
1824
+ return {
1825
+ enabled: config?.enabled ?? false,
1826
+ envOverride,
1827
+ anonymousId: config?.anonymousId ?? null
1828
+ };
1829
+ }
1830
+ var CONFIG_DIR, CONFIG_FILE, POSTHOG_BATCH_URL, FETCH_TIMEOUT_MS2, eventQueue;
1831
+ var init_telemetry = __esm({
1832
+ "src/lib/telemetry.ts"() {
1833
+ "use strict";
1834
+ init_constants();
1835
+ CONFIG_DIR = join13(homedir2(), ".flydocs");
1836
+ CONFIG_FILE = join13(CONFIG_DIR, "telemetry.json");
1837
+ POSTHOG_BATCH_URL = "https://us.i.posthog.com/batch/";
1838
+ FETCH_TIMEOUT_MS2 = 5e3;
1839
+ eventQueue = [];
1840
+ }
1841
+ });
1842
+
1573
1843
  // src/commands/install.ts
1574
1844
  var install_exports = {};
1575
1845
  __export(install_exports, {
@@ -1577,8 +1847,10 @@ __export(install_exports, {
1577
1847
  });
1578
1848
  import { defineCommand } from "citty";
1579
1849
  import { resolve as resolve2 } from "path";
1580
- import { join as join12 } from "path";
1850
+ import { join as join14 } from "path";
1851
+ import { mkdir as mkdir7 } from "fs/promises";
1581
1852
  import { confirm as confirm2, select, isCancel as isCancel3, cancel as cancel2 } from "@clack/prompts";
1853
+ import pc6 from "picocolors";
1582
1854
  var install_default;
1583
1855
  var init_install = __esm({
1584
1856
  "src/commands/install.ts"() {
@@ -1588,12 +1860,14 @@ var init_install = __esm({
1588
1860
  init_ui();
1589
1861
  init_config();
1590
1862
  init_skills();
1863
+ init_user_content();
1591
1864
  init_stack();
1592
1865
  init_community_skills();
1593
1866
  init_deprecated();
1594
1867
  init_gitignore();
1595
1868
  init_post_install();
1596
1869
  init_update_check();
1870
+ init_telemetry();
1597
1871
  install_default = defineCommand({
1598
1872
  meta: {
1599
1873
  name: "install",
@@ -1602,7 +1876,7 @@ var init_install = __esm({
1602
1876
  args: {
1603
1877
  tier: {
1604
1878
  type: "string",
1605
- description: "Set tier: 'local' (free) or 'cloud' (Linear)"
1879
+ description: "Set tier: 'local' (free) or 'cloud' (managed)"
1606
1880
  },
1607
1881
  path: {
1608
1882
  type: "string",
@@ -1629,6 +1903,7 @@ var init_install = __esm({
1629
1903
  const templateDir = await resolveTemplatePath(args["local-source"]);
1630
1904
  const version = await readTemplateVersion(templateDir);
1631
1905
  printBanner(version);
1906
+ await capture("install_started", { template_version: version });
1632
1907
  let targetDir;
1633
1908
  if (args.path) {
1634
1909
  targetDir = resolve2(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -1654,60 +1929,95 @@ var init_install = __esm({
1654
1929
  process.exit(1);
1655
1930
  }
1656
1931
  tier = args.tier;
1657
- printInfo(`Tier set via flag: ${tier}`);
1658
- } else if (await pathExists(join12(targetDir, ".flydocs", "config.json"))) {
1932
+ } else if (await pathExists(join14(targetDir, ".flydocs", "config.json"))) {
1659
1933
  try {
1660
1934
  const existing = await readConfig(targetDir);
1661
1935
  if (existing.tier) {
1662
1936
  tier = existing.tier;
1663
- printInfo(`Tier from config: ${tier}`);
1664
1937
  }
1665
1938
  } catch {
1666
1939
  }
1667
1940
  }
1668
- if (!tier) {
1669
- const shouldInstall = await confirm2({
1670
- message: `Install FlyDocs here? (local tier)
1941
+ if (!args.tier) {
1942
+ if (!tier) {
1943
+ const tierChoice = await select({
1944
+ message: `Install FlyDocs in: ${targetDir}`,
1945
+ options: [
1946
+ {
1947
+ value: "local",
1948
+ label: "Local (free)",
1949
+ hint: "File-based issues, no account needed"
1950
+ },
1951
+ {
1952
+ value: "cloud",
1953
+ label: "Cloud (managed)",
1954
+ hint: "Sync with Linear \u2014 run flydocs connect after install"
1955
+ }
1956
+ ]
1957
+ });
1958
+ if (isCancel3(tierChoice)) {
1959
+ cancel2("Installation cancelled.");
1960
+ process.exit(0);
1961
+ }
1962
+ tier = tierChoice;
1963
+ console.log();
1964
+ } else {
1965
+ const shouldInstall = await confirm2({
1966
+ message: `Install FlyDocs here? (${tier} tier)
1671
1967
  Directory: ${targetDir}`
1672
- });
1673
- if (isCancel3(shouldInstall) || !shouldInstall) {
1674
- cancel2("Installation cancelled.");
1675
- process.exit(0);
1968
+ });
1969
+ if (isCancel3(shouldInstall) || !shouldInstall) {
1970
+ cancel2("Installation cancelled.");
1971
+ process.exit(0);
1972
+ }
1973
+ console.log();
1676
1974
  }
1975
+ }
1976
+ if (!tier) {
1677
1977
  tier = "local";
1678
- console.log();
1679
1978
  }
1680
- if (!await pathExists(join12(targetDir, ".git"))) {
1979
+ printInfo(`Tier: ${tier}`);
1980
+ await capture("install_tier_selected", { tier });
1981
+ if (!await pathExists(join14(targetDir, ".git"))) {
1681
1982
  printWarning("No git repository detected. Run git init when ready.");
1682
1983
  }
1683
1984
  await ensureDirectories(targetDir, tier);
1985
+ const backedUp = await backupOriginals(targetDir);
1986
+ if (backedUp.length > 0) {
1987
+ printStatus(
1988
+ `Backed up ${backedUp.length} existing config file(s) to .flydocs/backup-originals/`
1989
+ );
1990
+ for (const f of backedUp) {
1991
+ printInfo(` ${f}`);
1992
+ }
1993
+ }
1684
1994
  console.log("Installing framework files...");
1685
1995
  await replaceDirectory(
1686
- join12(templateDir, ".flydocs", "templates"),
1687
- join12(targetDir, ".flydocs", "templates")
1996
+ join14(templateDir, ".flydocs", "templates"),
1997
+ join14(targetDir, ".flydocs", "templates")
1688
1998
  );
1689
1999
  await replaceDirectory(
1690
- join12(templateDir, ".flydocs", "hooks"),
1691
- join12(targetDir, ".flydocs", "hooks")
2000
+ join14(templateDir, ".flydocs", "hooks"),
2001
+ join14(targetDir, ".flydocs", "hooks")
1692
2002
  );
1693
2003
  await replaceDirectory(
1694
- join12(templateDir, ".flydocs", "scripts"),
1695
- join12(targetDir, ".flydocs", "scripts")
2004
+ join14(templateDir, ".flydocs", "scripts"),
2005
+ join14(targetDir, ".flydocs", "scripts")
1696
2006
  );
1697
2007
  await copyFile(
1698
- join12(templateDir, ".flydocs", "version"),
1699
- join12(targetDir, ".flydocs", "version")
2008
+ join14(templateDir, ".flydocs", "version"),
2009
+ join14(targetDir, ".flydocs", "version")
1700
2010
  );
1701
- const manifestSrc = join12(templateDir, "manifest.json");
2011
+ const manifestSrc = join14(templateDir, "manifest.json");
1702
2012
  if (await pathExists(manifestSrc)) {
1703
- await copyFile(manifestSrc, join12(targetDir, ".flydocs", "manifest.json"));
2013
+ await copyFile(manifestSrc, join14(targetDir, ".flydocs", "manifest.json"));
1704
2014
  }
1705
- const changelogSrc = join12(templateDir, "CHANGELOG.md");
2015
+ const changelogSrc = join14(templateDir, "CHANGELOG.md");
1706
2016
  if (await pathExists(changelogSrc)) {
1707
- await copyFile(changelogSrc, join12(targetDir, ".flydocs", "CHANGELOG.md"));
2017
+ await copyFile(changelogSrc, join14(targetDir, ".flydocs", "CHANGELOG.md"));
1708
2018
  }
1709
2019
  printStatus(".flydocs/templates, hooks, version, manifest, changelog");
1710
- const configPath = join12(targetDir, ".flydocs", "config.json");
2020
+ const configPath = join14(targetDir, ".flydocs", "config.json");
1711
2021
  if (!await pathExists(configPath)) {
1712
2022
  const config = await createFreshConfig(templateDir, version, tier);
1713
2023
  await writeConfig(targetDir, config);
@@ -1732,48 +2042,84 @@ var init_install = __esm({
1732
2042
  await installOwnedSkills(templateDir, targetDir, tier);
1733
2043
  printStatus(`Skills installed (tier: ${tier})`);
1734
2044
  console.log();
1735
- console.log("Installing agents and commands...");
1736
- const claudeAgentsSrc = join12(templateDir, ".claude", "agents");
1737
- if (await pathExists(claudeAgentsSrc)) {
1738
- await copyDirectoryContents(
1739
- claudeAgentsSrc,
1740
- join12(targetDir, ".claude", "agents")
1741
- );
2045
+ console.log(` ${pc6.bold(pc6.yellow("Sub-Agents (Recommended)"))}`);
2046
+ console.log();
2047
+ console.log(
2048
+ " Sub-agents are specialized roles (PM, implementation, review,"
2049
+ );
2050
+ console.log(
2051
+ " research) that your AI tool can delegate tasks to. They help"
2052
+ );
2053
+ console.log(
2054
+ " structure work but are not required \u2014 everything works without them."
2055
+ );
2056
+ console.log();
2057
+ let installAgents;
2058
+ if (args.yes) {
2059
+ installAgents = true;
2060
+ console.log(" Auto-accepting (--yes)");
2061
+ } else {
2062
+ const agentConfirm = await confirm2({
2063
+ message: "Install sub-agents?",
2064
+ initialValue: true
2065
+ });
2066
+ if (isCancel3(agentConfirm)) {
2067
+ installAgents = false;
2068
+ } else {
2069
+ installAgents = agentConfirm;
2070
+ }
1742
2071
  }
2072
+ await capture("install_agents_chosen", { install_agents: installAgents });
2073
+ if (installAgents) {
2074
+ const claudeAgentsSrc = join14(templateDir, ".claude", "agents");
2075
+ if (await pathExists(claudeAgentsSrc)) {
2076
+ await mkdir7(join14(targetDir, ".claude", "agents"), { recursive: true });
2077
+ await copyDirectoryContents(
2078
+ claudeAgentsSrc,
2079
+ join14(targetDir, ".claude", "agents")
2080
+ );
2081
+ }
2082
+ const cursorAgentsSrc = join14(templateDir, ".cursor", "agents");
2083
+ if (await pathExists(cursorAgentsSrc)) {
2084
+ await mkdir7(join14(targetDir, ".cursor", "agents"), { recursive: true });
2085
+ await copyDirectoryContents(
2086
+ cursorAgentsSrc,
2087
+ join14(targetDir, ".cursor", "agents")
2088
+ );
2089
+ }
2090
+ printStatus("Sub-agents installed (.claude/agents, .cursor/agents)");
2091
+ } else {
2092
+ printInfo("Skipped sub-agents");
2093
+ }
2094
+ console.log();
2095
+ console.log("Installing commands and settings...");
1743
2096
  await copyDirectoryContents(
1744
- join12(templateDir, ".claude", "commands"),
1745
- join12(targetDir, ".claude", "commands")
2097
+ join14(templateDir, ".claude", "commands"),
2098
+ join14(targetDir, ".claude", "commands")
1746
2099
  );
1747
2100
  await copyFile(
1748
- join12(templateDir, ".claude", "CLAUDE.md"),
1749
- join12(targetDir, ".claude", "CLAUDE.md")
2101
+ join14(templateDir, ".claude", "CLAUDE.md"),
2102
+ join14(targetDir, ".claude", "CLAUDE.md")
1750
2103
  );
1751
2104
  await copyFile(
1752
- join12(templateDir, ".claude", "settings.json"),
1753
- join12(targetDir, ".claude", "settings.json")
2105
+ join14(templateDir, ".claude", "settings.json"),
2106
+ join14(targetDir, ".claude", "settings.json")
1754
2107
  );
1755
- printStatus(".claude/ (agents, commands, CLAUDE.md, settings)");
1756
- const cursorAgentsSrc = join12(templateDir, ".cursor", "agents");
1757
- if (await pathExists(cursorAgentsSrc)) {
1758
- await copyDirectoryContents(
1759
- cursorAgentsSrc,
1760
- join12(targetDir, ".cursor", "agents")
1761
- );
1762
- }
2108
+ printStatus(".claude/ (commands, CLAUDE.md, settings)");
1763
2109
  await copyDirectoryContents(
1764
- join12(templateDir, ".claude", "commands"),
1765
- join12(targetDir, ".cursor", "commands")
2110
+ join14(templateDir, ".claude", "commands"),
2111
+ join14(targetDir, ".cursor", "commands")
1766
2112
  );
1767
2113
  await copyFile(
1768
- join12(templateDir, ".cursor", "hooks.json"),
1769
- join12(targetDir, ".cursor", "hooks.json")
2114
+ join14(templateDir, ".cursor", "hooks.json"),
2115
+ join14(targetDir, ".cursor", "hooks.json")
1770
2116
  );
1771
- printStatus(".cursor/ (agents, commands, hooks)");
2117
+ printStatus(".cursor/ (commands, hooks)");
1772
2118
  await copyCursorRules(targetDir);
1773
2119
  printStatus(".cursor/rules/");
1774
2120
  await copyFile(
1775
- join12(templateDir, "AGENTS.md"),
1776
- join12(targetDir, "AGENTS.md")
2121
+ join14(templateDir, "AGENTS.md"),
2122
+ join14(targetDir, "AGENTS.md")
1777
2123
  );
1778
2124
  printStatus("AGENTS.md");
1779
2125
  await runManifestGeneration(targetDir);
@@ -1782,40 +2128,40 @@ var init_install = __esm({
1782
2128
  console.log("Installing project templates...");
1783
2129
  const userFiles = [
1784
2130
  {
1785
- src: join12(templateDir, "flydocs", "context", "project.md"),
1786
- dest: join12(targetDir, "flydocs", "context", "project.md"),
2131
+ src: join14(templateDir, "flydocs", "context", "project.md"),
2132
+ dest: join14(targetDir, "flydocs", "context", "project.md"),
1787
2133
  label: "flydocs/context/project.md"
1788
2134
  },
1789
2135
  {
1790
- src: join12(templateDir, "flydocs", "knowledge", "INDEX.md"),
1791
- dest: join12(targetDir, "flydocs", "knowledge", "INDEX.md"),
2136
+ src: join14(templateDir, "flydocs", "knowledge", "INDEX.md"),
2137
+ dest: join14(targetDir, "flydocs", "knowledge", "INDEX.md"),
1792
2138
  label: "flydocs/knowledge/INDEX.md"
1793
2139
  },
1794
2140
  {
1795
- src: join12(templateDir, "flydocs", "knowledge", "README.md"),
1796
- dest: join12(targetDir, "flydocs", "knowledge", "README.md"),
2141
+ src: join14(templateDir, "flydocs", "knowledge", "README.md"),
2142
+ dest: join14(targetDir, "flydocs", "knowledge", "README.md"),
1797
2143
  label: "flydocs/knowledge/README.md"
1798
2144
  },
1799
2145
  {
1800
- src: join12(
2146
+ src: join14(
1801
2147
  templateDir,
1802
2148
  "flydocs",
1803
2149
  "knowledge",
1804
2150
  "product",
1805
2151
  "personas.md"
1806
2152
  ),
1807
- dest: join12(targetDir, "flydocs", "knowledge", "product", "personas.md"),
2153
+ dest: join14(targetDir, "flydocs", "knowledge", "product", "personas.md"),
1808
2154
  label: "flydocs/knowledge/product/personas.md"
1809
2155
  },
1810
2156
  {
1811
- src: join12(
2157
+ src: join14(
1812
2158
  templateDir,
1813
2159
  "flydocs",
1814
2160
  "knowledge",
1815
2161
  "product",
1816
2162
  "user-flows.md"
1817
2163
  ),
1818
- dest: join12(
2164
+ dest: join14(
1819
2165
  targetDir,
1820
2166
  "flydocs",
1821
2167
  "knowledge",
@@ -1825,18 +2171,18 @@ var init_install = __esm({
1825
2171
  label: "flydocs/knowledge/product/user-flows.md"
1826
2172
  },
1827
2173
  {
1828
- src: join12(templateDir, "flydocs", "design-system", "README.md"),
1829
- dest: join12(targetDir, "flydocs", "design-system", "README.md"),
2174
+ src: join14(templateDir, "flydocs", "design-system", "README.md"),
2175
+ dest: join14(targetDir, "flydocs", "design-system", "README.md"),
1830
2176
  label: "flydocs/design-system/README.md"
1831
2177
  },
1832
2178
  {
1833
- src: join12(
2179
+ src: join14(
1834
2180
  templateDir,
1835
2181
  "flydocs",
1836
2182
  "design-system",
1837
2183
  "component-patterns.md"
1838
2184
  ),
1839
- dest: join12(
2185
+ dest: join14(
1840
2186
  targetDir,
1841
2187
  "flydocs",
1842
2188
  "design-system",
@@ -1845,13 +2191,13 @@ var init_install = __esm({
1845
2191
  label: "flydocs/design-system/component-patterns.md"
1846
2192
  },
1847
2193
  {
1848
- src: join12(templateDir, "flydocs", "design-system", "token-mapping.md"),
1849
- dest: join12(targetDir, "flydocs", "design-system", "token-mapping.md"),
2194
+ src: join14(templateDir, "flydocs", "design-system", "token-mapping.md"),
2195
+ dest: join14(targetDir, "flydocs", "design-system", "token-mapping.md"),
1850
2196
  label: "flydocs/design-system/token-mapping.md"
1851
2197
  },
1852
2198
  {
1853
- src: join12(templateDir, "flydocs", "README.md"),
1854
- dest: join12(targetDir, "flydocs", "README.md"),
2199
+ src: join14(templateDir, "flydocs", "README.md"),
2200
+ dest: join14(targetDir, "flydocs", "README.md"),
1855
2201
  label: "flydocs/README.md"
1856
2202
  }
1857
2203
  ];
@@ -1865,14 +2211,10 @@ var init_install = __esm({
1865
2211
  }
1866
2212
  }
1867
2213
  }
1868
- const envExampleSrc = join12(templateDir, ".env.example");
2214
+ const envExampleSrc = join14(templateDir, ".env.example");
1869
2215
  if (await pathExists(envExampleSrc)) {
1870
- const hasEnv = await pathExists(join12(targetDir, ".env"));
1871
- const hasEnvExample = await pathExists(join12(targetDir, ".env.example"));
1872
- if (!hasEnv && !hasEnvExample) {
1873
- await copyFile(envExampleSrc, join12(targetDir, ".env.example"));
1874
- printStatus(".env.example (new)");
1875
- }
2216
+ await copyFile(envExampleSrc, join14(targetDir, ".env.example"));
2217
+ printStatus(".env.example");
1876
2218
  }
1877
2219
  await ensureGitignore(targetDir);
1878
2220
  console.log();
@@ -1890,6 +2232,9 @@ var init_install = __esm({
1890
2232
  } else {
1891
2233
  printInfo("No framework detected in package.json");
1892
2234
  }
2235
+ await capture("install_skills_chosen", {
2236
+ stack_detected: stack.raw
2237
+ });
1893
2238
  console.log();
1894
2239
  console.log("Checking for deprecated files...");
1895
2240
  const deprecated = await scanDeprecated(targetDir);
@@ -1902,17 +2247,17 @@ var init_install = __esm({
1902
2247
  " 1. Run /flydocs-setup to configure your project",
1903
2248
  " 2. Start working with /start-session",
1904
2249
  "",
1905
- "Documentation: flydocs/README.md"
2250
+ "Docs: https://www.flydocs.ai/docs"
1906
2251
  ] : [
1907
2252
  `Tier: ${tier}`,
1908
2253
  `Version: ${version}`,
1909
2254
  "",
1910
2255
  "Next steps:",
1911
- " 1. Copy your LINEAR_API_KEY to .env",
1912
- " 2. Run /flydocs-setup to configure your Linear workspace",
2256
+ " 1. Run flydocs connect to set up your API key",
2257
+ " 2. Run /flydocs-setup to configure your workspace",
1913
2258
  " 3. Start working with /start-session",
1914
2259
  "",
1915
- "Documentation: flydocs/README.md"
2260
+ "Docs: https://www.flydocs.ai/docs"
1916
2261
  ];
1917
2262
  let copiedToClipboard = false;
1918
2263
  try {
@@ -1931,6 +2276,7 @@ var init_install = __esm({
1931
2276
  }
1932
2277
  printCompletionBox("Installation Complete!", nextSteps);
1933
2278
  printBetaCta();
2279
+ const detectedIdes = [];
1934
2280
  try {
1935
2281
  const { execSync: execSync2, spawn } = await import("child_process");
1936
2282
  const isInstalled = (cmd) => {
@@ -1966,6 +2312,7 @@ var init_install = __esm({
1966
2312
  passCommand: false
1967
2313
  });
1968
2314
  }
2315
+ detectedIdes.push(...ideOptions.map((o) => o.cmd));
1969
2316
  if (ideOptions.length === 1) {
1970
2317
  const ide = ideOptions[0];
1971
2318
  const launchConfirm = await confirm2({
@@ -2024,6 +2371,9 @@ var init_install = __esm({
2024
2371
  }
2025
2372
  } catch {
2026
2373
  }
2374
+ await capture("install_ide_detected", { ides: detectedIdes });
2375
+ await capture("install_completed", { tier, version });
2376
+ await flush();
2027
2377
  try {
2028
2378
  const updateResult = await checkForUpdate();
2029
2379
  if (updateResult) {
@@ -2042,10 +2392,10 @@ __export(update_exports, {
2042
2392
  default: () => update_default
2043
2393
  });
2044
2394
  import { defineCommand as defineCommand2 } from "citty";
2045
- import { resolve as resolve3, join as join13 } from "path";
2046
- import { mkdir as mkdir5, cp as cp2, readFile as readFile8, readdir as readdir3, rm as rm4 } from "fs/promises";
2395
+ import { resolve as resolve3, join as join15 } from "path";
2396
+ import { mkdir as mkdir8, cp as cp2, readFile as readFile10, readdir as readdir3, rm as rm4 } from "fs/promises";
2047
2397
  import { select as select2, text, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
2048
- import pc6 from "picocolors";
2398
+ import pc7 from "picocolors";
2049
2399
  var update_default;
2050
2400
  var init_update = __esm({
2051
2401
  "src/commands/update.ts"() {
@@ -2062,6 +2412,7 @@ var init_update = __esm({
2062
2412
  init_gitignore();
2063
2413
  init_post_install();
2064
2414
  init_update_check();
2415
+ init_telemetry();
2065
2416
  update_default = defineCommand2({
2066
2417
  meta: {
2067
2418
  name: "update",
@@ -2079,7 +2430,7 @@ var init_update = __esm({
2079
2430
  },
2080
2431
  tier: {
2081
2432
  type: "string",
2082
- description: "Change tier: 'local' (free) or 'cloud' (Linear)"
2433
+ description: "Change tier: 'local' (free) or 'cloud' (managed)"
2083
2434
  },
2084
2435
  force: {
2085
2436
  type: "boolean",
@@ -2104,6 +2455,7 @@ var init_update = __esm({
2104
2455
  printBanner(version);
2105
2456
  printInfo("Running in update mode \u2014 framework files will be refreshed");
2106
2457
  console.log();
2458
+ await capture("update_started", { template_version: version });
2107
2459
  let targetDir;
2108
2460
  if (args.path) {
2109
2461
  targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -2148,9 +2500,9 @@ var init_update = __esm({
2148
2500
  }
2149
2501
  targetDir = resolve3(targetDir);
2150
2502
  process.chdir(targetDir);
2151
- const hasVersion = await pathExists(join13(targetDir, ".flydocs", "version"));
2503
+ const hasVersion = await pathExists(join15(targetDir, ".flydocs", "version"));
2152
2504
  const hasConfig = await pathExists(
2153
- join13(targetDir, ".flydocs", "config.json")
2505
+ join15(targetDir, ".flydocs", "config.json")
2154
2506
  );
2155
2507
  if (!hasVersion && !hasConfig) {
2156
2508
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -2161,8 +2513,8 @@ var init_update = __esm({
2161
2513
  console.log();
2162
2514
  let currentVersion = "0.1.0";
2163
2515
  if (hasVersion) {
2164
- const vContent = await readFile8(
2165
- join13(targetDir, ".flydocs", "version"),
2516
+ const vContent = await readFile10(
2517
+ join15(targetDir, ".flydocs", "version"),
2166
2518
  "utf-8"
2167
2519
  );
2168
2520
  currentVersion = vContent.trim();
@@ -2188,12 +2540,18 @@ var init_update = __esm({
2188
2540
  process.exit(0);
2189
2541
  }
2190
2542
  }
2543
+ await capture("update_version_compared", {
2544
+ current_version: currentVersion,
2545
+ target_version: version,
2546
+ version_status: versionStatus,
2547
+ force: args.force
2548
+ });
2191
2549
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
2192
2550
  console.log();
2193
- const changelogPath = join13(templateDir, "CHANGELOG.md");
2551
+ const changelogPath = join15(templateDir, "CHANGELOG.md");
2194
2552
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
2195
2553
  if (whatsNew.length > 0) {
2196
- console.log(pc6.cyan("What's new:"));
2554
+ console.log(pc7.cyan("What's new:"));
2197
2555
  console.log();
2198
2556
  for (const entry of whatsNew) {
2199
2557
  console.log(` ${entry}`);
@@ -2202,23 +2560,23 @@ var init_update = __esm({
2202
2560
  }
2203
2561
  const now = /* @__PURE__ */ new Date();
2204
2562
  const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
2205
- const backupDir = join13(targetDir, ".flydocs", `backup-${ts}`);
2206
- await mkdir5(backupDir, { recursive: true });
2563
+ const backupDir = join15(targetDir, ".flydocs", `backup-${ts}`);
2564
+ await mkdir8(backupDir, { recursive: true });
2207
2565
  if (hasConfig) {
2208
2566
  await cp2(
2209
- join13(targetDir, ".flydocs", "config.json"),
2210
- join13(backupDir, "config.json")
2567
+ join15(targetDir, ".flydocs", "config.json"),
2568
+ join15(backupDir, "config.json")
2211
2569
  );
2212
2570
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
2213
2571
  }
2214
2572
  try {
2215
- const flydocsDir = join13(targetDir, ".flydocs");
2573
+ const flydocsDir = join15(targetDir, ".flydocs");
2216
2574
  const entries = await readdir3(flydocsDir);
2217
2575
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
2218
2576
  if (backups.length > 3) {
2219
2577
  const toRemove = backups.slice(0, backups.length - 3);
2220
2578
  for (const old of toRemove) {
2221
- await rm4(join13(flydocsDir, old), { recursive: true, force: true });
2579
+ await rm4(join15(flydocsDir, old), { recursive: true, force: true });
2222
2580
  }
2223
2581
  }
2224
2582
  } catch {
@@ -2255,74 +2613,118 @@ var init_update = __esm({
2255
2613
  }
2256
2614
  console.log("Replacing framework directories...");
2257
2615
  await replaceDirectory(
2258
- join13(templateDir, ".flydocs", "templates"),
2259
- join13(targetDir, ".flydocs", "templates")
2616
+ join15(templateDir, ".flydocs", "templates"),
2617
+ join15(targetDir, ".flydocs", "templates")
2260
2618
  );
2261
2619
  await replaceDirectory(
2262
- join13(templateDir, ".flydocs", "hooks"),
2263
- join13(targetDir, ".flydocs", "hooks")
2620
+ join15(templateDir, ".flydocs", "hooks"),
2621
+ join15(targetDir, ".flydocs", "hooks")
2264
2622
  );
2265
2623
  await replaceDirectory(
2266
- join13(templateDir, ".flydocs", "scripts"),
2267
- join13(targetDir, ".flydocs", "scripts")
2624
+ join15(templateDir, ".flydocs", "scripts"),
2625
+ join15(targetDir, ".flydocs", "scripts")
2268
2626
  );
2269
2627
  printStatus(".flydocs/templates, hooks, scripts");
2270
- const claudeAgentsSrc = join13(templateDir, ".claude", "agents");
2271
- if (await pathExists(claudeAgentsSrc)) {
2272
- await copyDirectoryContents(
2273
- claudeAgentsSrc,
2274
- join13(targetDir, ".claude", "agents")
2628
+ const hasExistingAgents = await pathExists(
2629
+ join15(targetDir, ".claude", "agents")
2630
+ );
2631
+ let installAgents;
2632
+ if (args.yes) {
2633
+ installAgents = hasExistingAgents ? true : true;
2634
+ } else if (hasExistingAgents) {
2635
+ installAgents = true;
2636
+ } else {
2637
+ console.log();
2638
+ console.log(` ${pc7.bold(pc7.yellow("Sub-Agents (Recommended)"))}`);
2639
+ console.log();
2640
+ console.log(
2641
+ " Sub-agents are specialized roles (PM, implementation, review,"
2642
+ );
2643
+ console.log(
2644
+ " research) that your AI tool can delegate tasks to. They help"
2275
2645
  );
2646
+ console.log(
2647
+ " structure work but are not required \u2014 everything works without them."
2648
+ );
2649
+ console.log();
2650
+ const agentConfirm = await confirm3({
2651
+ message: "Install sub-agents?",
2652
+ initialValue: true
2653
+ });
2654
+ if (isCancel4(agentConfirm)) {
2655
+ installAgents = false;
2656
+ } else {
2657
+ installAgents = agentConfirm;
2658
+ }
2276
2659
  }
2277
- printStatus(".claude/agents");
2278
- await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2279
- printStatus(`.claude/skills (tier: ${effectiveTier})`);
2280
- const cursorAgentsSrc = join13(templateDir, ".cursor", "agents");
2281
- if (await pathExists(cursorAgentsSrc)) {
2282
- await copyDirectoryContents(
2283
- cursorAgentsSrc,
2284
- join13(targetDir, ".cursor", "agents")
2660
+ if (installAgents) {
2661
+ const claudeAgentsSrc = join15(templateDir, ".claude", "agents");
2662
+ if (await pathExists(claudeAgentsSrc)) {
2663
+ await mkdir8(join15(targetDir, ".claude", "agents"), { recursive: true });
2664
+ await copyDirectoryContents(
2665
+ claudeAgentsSrc,
2666
+ join15(targetDir, ".claude", "agents")
2667
+ );
2668
+ }
2669
+ const cursorAgentsSrc = join15(templateDir, ".cursor", "agents");
2670
+ if (await pathExists(cursorAgentsSrc)) {
2671
+ await mkdir8(join15(targetDir, ".cursor", "agents"), { recursive: true });
2672
+ await copyDirectoryContents(
2673
+ cursorAgentsSrc,
2674
+ join15(targetDir, ".cursor", "agents")
2675
+ );
2676
+ }
2677
+ printStatus(
2678
+ hasExistingAgents ? "Sub-agents updated (.claude/agents, .cursor/agents)" : "Sub-agents installed (.claude/agents, .cursor/agents)"
2285
2679
  );
2680
+ } else {
2681
+ printInfo("Skipped sub-agents");
2286
2682
  }
2287
- printStatus(".cursor/agents");
2683
+ await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2684
+ printStatus(`.claude/skills (tier: ${effectiveTier})`);
2288
2685
  console.log();
2289
2686
  console.log("Replacing framework files...");
2290
2687
  await copyFile(
2291
- join13(templateDir, ".claude", "CLAUDE.md"),
2292
- join13(targetDir, ".claude", "CLAUDE.md")
2688
+ join15(templateDir, ".claude", "CLAUDE.md"),
2689
+ join15(targetDir, ".claude", "CLAUDE.md")
2293
2690
  );
2294
2691
  await copyFile(
2295
- join13(templateDir, ".claude", "settings.json"),
2296
- join13(targetDir, ".claude", "settings.json")
2692
+ join15(templateDir, ".claude", "settings.json"),
2693
+ join15(targetDir, ".claude", "settings.json")
2297
2694
  );
2298
2695
  printStatus(".claude/CLAUDE.md, settings.json");
2299
2696
  await copyDirectoryContents(
2300
- join13(templateDir, ".claude", "commands"),
2301
- join13(targetDir, ".claude", "commands")
2697
+ join15(templateDir, ".claude", "commands"),
2698
+ join15(targetDir, ".claude", "commands")
2302
2699
  );
2303
2700
  await copyDirectoryContents(
2304
- join13(templateDir, ".claude", "commands"),
2305
- join13(targetDir, ".cursor", "commands")
2701
+ join15(templateDir, ".claude", "commands"),
2702
+ join15(targetDir, ".cursor", "commands")
2306
2703
  );
2307
2704
  printStatus(".claude/commands, .cursor/commands");
2308
- const skillsReadmeSrc = join13(templateDir, ".claude", "skills", "README.md");
2705
+ const skillsReadmeSrc = join15(templateDir, ".claude", "skills", "README.md");
2309
2706
  if (await pathExists(skillsReadmeSrc)) {
2310
2707
  await copyFile(
2311
2708
  skillsReadmeSrc,
2312
- join13(targetDir, ".claude", "skills", "README.md")
2709
+ join15(targetDir, ".claude", "skills", "README.md")
2313
2710
  );
2314
2711
  }
2315
2712
  printStatus(".claude/skills/README.md");
2316
2713
  await copyFile(
2317
- join13(templateDir, ".cursor", "hooks.json"),
2318
- join13(targetDir, ".cursor", "hooks.json")
2714
+ join15(templateDir, ".cursor", "hooks.json"),
2715
+ join15(targetDir, ".cursor", "hooks.json")
2319
2716
  );
2320
2717
  printStatus(".cursor/hooks.json");
2321
2718
  await copyFile(
2322
- join13(templateDir, "AGENTS.md"),
2323
- join13(targetDir, "AGENTS.md")
2719
+ join15(templateDir, "AGENTS.md"),
2720
+ join15(targetDir, "AGENTS.md")
2324
2721
  );
2325
2722
  printStatus("AGENTS.md");
2723
+ const envExampleSrc = join15(templateDir, ".env.example");
2724
+ if (await pathExists(envExampleSrc)) {
2725
+ await copyFile(envExampleSrc, join15(targetDir, ".env.example"));
2726
+ printStatus(".env.example");
2727
+ }
2326
2728
  await runManifestGeneration(targetDir);
2327
2729
  await runContextGraphBuild(targetDir);
2328
2730
  console.log();
@@ -2345,18 +2747,18 @@ var init_update = __esm({
2345
2747
  printWarning("Config merge failed \u2014 config.json preserved as-is");
2346
2748
  }
2347
2749
  await copyFile(
2348
- join13(templateDir, ".flydocs", "version"),
2349
- join13(targetDir, ".flydocs", "version")
2750
+ join15(templateDir, ".flydocs", "version"),
2751
+ join15(targetDir, ".flydocs", "version")
2350
2752
  );
2351
2753
  printStatus(`.flydocs/version \u2192 ${version}`);
2352
- const clSrc = join13(templateDir, "CHANGELOG.md");
2754
+ const clSrc = join15(templateDir, "CHANGELOG.md");
2353
2755
  if (await pathExists(clSrc)) {
2354
- await copyFile(clSrc, join13(targetDir, ".flydocs", "CHANGELOG.md"));
2756
+ await copyFile(clSrc, join15(targetDir, ".flydocs", "CHANGELOG.md"));
2355
2757
  printStatus(".flydocs/CHANGELOG.md");
2356
2758
  }
2357
- const mfSrc = join13(templateDir, "manifest.json");
2759
+ const mfSrc = join15(templateDir, "manifest.json");
2358
2760
  if (await pathExists(mfSrc)) {
2359
- await copyFile(mfSrc, join13(targetDir, ".flydocs", "manifest.json"));
2761
+ await copyFile(mfSrc, join15(targetDir, ".flydocs", "manifest.json"));
2360
2762
  printStatus(".flydocs/manifest.json");
2361
2763
  }
2362
2764
  console.log();
@@ -2392,6 +2794,12 @@ var init_update = __esm({
2392
2794
  `Backup: .flydocs/backup-${ts}/`
2393
2795
  ]);
2394
2796
  printBetaCta();
2797
+ await capture("update_completed", {
2798
+ current_version: currentVersion,
2799
+ version,
2800
+ tier: effectiveTier
2801
+ });
2802
+ await flush();
2395
2803
  try {
2396
2804
  const updateResult = await checkForUpdate();
2397
2805
  if (updateResult) {
@@ -2404,33 +2812,352 @@ var init_update = __esm({
2404
2812
  }
2405
2813
  });
2406
2814
 
2815
+ // src/commands/uninstall.ts
2816
+ var uninstall_exports = {};
2817
+ __export(uninstall_exports, {
2818
+ default: () => uninstall_default
2819
+ });
2820
+ import { defineCommand as defineCommand3 } from "citty";
2821
+ import { resolve as resolve4, join as join16 } from "path";
2822
+ import { readdir as readdir4, rm as rm5, rename as rename2 } from "fs/promises";
2823
+ import { confirm as confirm4, select as select3, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
2824
+ import pc8 from "picocolors";
2825
+ async function removeOwnedSkills(targetDir) {
2826
+ const skillsDir = join16(targetDir, ".claude", "skills");
2827
+ const removed = [];
2828
+ if (!await pathExists(skillsDir)) {
2829
+ return removed;
2830
+ }
2831
+ try {
2832
+ const entries = await readdir4(skillsDir);
2833
+ for (const entry of entries) {
2834
+ if (entry.startsWith(OWNED_SKILL_PREFIX)) {
2835
+ await rm5(join16(skillsDir, entry), { recursive: true, force: true });
2836
+ removed.push(`.claude/skills/${entry}`);
2837
+ }
2838
+ }
2839
+ } catch {
2840
+ }
2841
+ return removed;
2842
+ }
2843
+ async function removeOwnedCursorRules(targetDir) {
2844
+ const rulesDir = join16(targetDir, ".cursor", "rules");
2845
+ const removed = [];
2846
+ if (!await pathExists(rulesDir)) {
2847
+ return removed;
2848
+ }
2849
+ try {
2850
+ const entries = await readdir4(rulesDir);
2851
+ for (const entry of entries) {
2852
+ if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
2853
+ await rm5(join16(rulesDir, entry), { force: true });
2854
+ removed.push(`.cursor/rules/${entry}`);
2855
+ }
2856
+ }
2857
+ } catch {
2858
+ }
2859
+ return removed;
2860
+ }
2861
+ async function isEmptyDir(dirPath) {
2862
+ try {
2863
+ const entries = await readdir4(dirPath);
2864
+ return entries.length === 0;
2865
+ } catch {
2866
+ return false;
2867
+ }
2868
+ }
2869
+ async function cleanupEmptyParents(targetDir, dirs) {
2870
+ const cleaned = [];
2871
+ for (const dir of dirs) {
2872
+ const fullPath = join16(targetDir, dir);
2873
+ if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
2874
+ await rm5(fullPath, { recursive: true, force: true });
2875
+ cleaned.push(dir);
2876
+ }
2877
+ }
2878
+ return cleaned;
2879
+ }
2880
+ var ALWAYS_REMOVED, OWNED_SKILL_PREFIX, OWNED_RULE_PREFIX, uninstall_default;
2881
+ var init_uninstall = __esm({
2882
+ "src/commands/uninstall.ts"() {
2883
+ "use strict";
2884
+ init_fs_ops();
2885
+ init_user_content();
2886
+ init_ui();
2887
+ init_constants();
2888
+ ALWAYS_REMOVED = [
2889
+ [".claude/CLAUDE.md", "file"],
2890
+ [".claude/settings.json", "file"],
2891
+ [".claude/agents", "dir"],
2892
+ [".claude/commands", "dir"],
2893
+ [".claude/skills/README.md", "file"],
2894
+ [".cursor/hooks.json", "file"],
2895
+ [".cursor/agents", "dir"],
2896
+ [".flydocs", "dir"],
2897
+ ["AGENTS.md", "file"],
2898
+ [".env.example", "file"]
2899
+ ];
2900
+ OWNED_SKILL_PREFIX = "flydocs-";
2901
+ OWNED_RULE_PREFIX = "flydocs-";
2902
+ uninstall_default = defineCommand3({
2903
+ meta: {
2904
+ name: "uninstall",
2905
+ description: "Remove FlyDocs from a project directory"
2906
+ },
2907
+ args: {
2908
+ path: {
2909
+ type: "string",
2910
+ description: "Uninstall from the specified directory"
2911
+ },
2912
+ here: {
2913
+ type: "boolean",
2914
+ description: "Uninstall from the current directory",
2915
+ default: false
2916
+ },
2917
+ all: {
2918
+ type: "boolean",
2919
+ description: "Remove everything including flydocs/ user content",
2920
+ default: false
2921
+ },
2922
+ yes: {
2923
+ type: "boolean",
2924
+ alias: ["y"],
2925
+ description: "Skip confirmation prompts",
2926
+ default: false
2927
+ },
2928
+ force: {
2929
+ type: "boolean",
2930
+ description: "Alias for --all --yes (complete removal, no prompts)",
2931
+ default: false
2932
+ }
2933
+ },
2934
+ async run({ args }) {
2935
+ printBanner(CLI_VERSION);
2936
+ let targetDir;
2937
+ if (args.path) {
2938
+ targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
2939
+ } else if (args.here) {
2940
+ targetDir = process.cwd();
2941
+ } else {
2942
+ targetDir = process.cwd();
2943
+ }
2944
+ if (!await pathExists(targetDir)) {
2945
+ printError(`Directory does not exist: ${targetDir}`);
2946
+ process.exit(1);
2947
+ }
2948
+ targetDir = resolve4(targetDir);
2949
+ const hasFlydocs = await pathExists(join16(targetDir, ".flydocs"));
2950
+ const hasAgentsMd = await pathExists(join16(targetDir, "AGENTS.md"));
2951
+ if (!hasFlydocs && !hasAgentsMd) {
2952
+ printError(`Not a FlyDocs project: ${targetDir}`);
2953
+ printInfo("No .flydocs/ directory or AGENTS.md found.");
2954
+ process.exit(1);
2955
+ }
2956
+ printInfo(`Project: ${targetDir}`);
2957
+ console.log();
2958
+ const forceAll = args.force;
2959
+ const removeAll = forceAll || args.all;
2960
+ const skipPrompts = forceAll || args.yes;
2961
+ let contentAction = "preserve";
2962
+ const hasUserContent = await pathExists(join16(targetDir, "flydocs"));
2963
+ if (hasUserContent) {
2964
+ if (removeAll) {
2965
+ contentAction = "remove";
2966
+ } else if (!skipPrompts) {
2967
+ const choice = await select3({
2968
+ message: "What should happen to your flydocs/ content (project docs, knowledge base)?",
2969
+ options: [
2970
+ {
2971
+ value: "archive",
2972
+ label: "Archive",
2973
+ hint: "Rename to flydocs-archive/ (safe, reversible)"
2974
+ },
2975
+ {
2976
+ value: "remove",
2977
+ label: "Remove completely",
2978
+ hint: "Permanently delete flydocs/ and all contents"
2979
+ },
2980
+ {
2981
+ value: "preserve",
2982
+ label: "Keep as-is",
2983
+ hint: "Leave flydocs/ untouched"
2984
+ }
2985
+ ]
2986
+ });
2987
+ if (isCancel5(choice)) {
2988
+ cancel4("Uninstall cancelled.");
2989
+ process.exit(0);
2990
+ }
2991
+ contentAction = choice;
2992
+ }
2993
+ }
2994
+ if (!skipPrompts) {
2995
+ console.log();
2996
+ console.log(pc8.bold("The following will be removed:"));
2997
+ console.log();
2998
+ console.log(" Framework files:");
2999
+ for (const [path] of ALWAYS_REMOVED) {
3000
+ console.log(` ${pc8.dim(path)}`);
3001
+ }
3002
+ console.log(` ${pc8.dim(".claude/skills/flydocs-*")}`);
3003
+ console.log(` ${pc8.dim(".cursor/rules/flydocs-*.mdc")}`);
3004
+ if (hasUserContent) {
3005
+ if (contentAction === "archive") {
3006
+ console.log();
3007
+ console.log(
3008
+ ` User content: ${pc8.yellow("flydocs/ -> flydocs-archive/")}`
3009
+ );
3010
+ } else if (contentAction === "remove") {
3011
+ console.log();
3012
+ console.log(` User content: ${pc8.red("flydocs/ (deleted)")}`);
3013
+ } else {
3014
+ console.log();
3015
+ console.log(` User content: ${pc8.green("flydocs/ (preserved)")}`);
3016
+ }
3017
+ }
3018
+ console.log();
3019
+ console.log(pc8.bold("Preserved:"));
3020
+ console.log(
3021
+ ` ${pc8.dim(".claude/skills/ (non-flydocs community skills)")}`
3022
+ );
3023
+ console.log(` ${pc8.dim(".env, .env.local")}`);
3024
+ console.log();
3025
+ const shouldContinue = await confirm4({
3026
+ message: "Proceed with uninstall?"
3027
+ });
3028
+ if (isCancel5(shouldContinue) || !shouldContinue) {
3029
+ cancel4("Uninstall cancelled.");
3030
+ process.exit(0);
3031
+ }
3032
+ }
3033
+ const originals = await readBackupOriginals(targetDir);
3034
+ const result = {
3035
+ removed: [],
3036
+ skipped: [],
3037
+ archived: [],
3038
+ restored: []
3039
+ };
3040
+ const removedSkills = await removeOwnedSkills(targetDir);
3041
+ result.removed.push(...removedSkills);
3042
+ const removedRules = await removeOwnedCursorRules(targetDir);
3043
+ result.removed.push(...removedRules);
3044
+ for (const [relativePath, type] of ALWAYS_REMOVED) {
3045
+ const fullPath = join16(targetDir, relativePath);
3046
+ if (!await pathExists(fullPath)) {
3047
+ result.skipped.push(relativePath);
3048
+ continue;
3049
+ }
3050
+ try {
3051
+ if (type === "dir") {
3052
+ await rm5(fullPath, { recursive: true, force: true });
3053
+ } else {
3054
+ await rm5(fullPath, { force: true });
3055
+ }
3056
+ result.removed.push(relativePath);
3057
+ } catch {
3058
+ printWarning(`Could not remove: ${relativePath}`);
3059
+ result.skipped.push(relativePath);
3060
+ }
3061
+ }
3062
+ if (hasUserContent) {
3063
+ const flydocsPath = join16(targetDir, "flydocs");
3064
+ if (contentAction === "archive") {
3065
+ const archivePath = join16(targetDir, "flydocs-archive");
3066
+ if (await pathExists(archivePath)) {
3067
+ await rm5(archivePath, { recursive: true, force: true });
3068
+ }
3069
+ await rename2(flydocsPath, archivePath);
3070
+ result.archived.push("flydocs/ -> flydocs-archive/");
3071
+ } else if (contentAction === "remove") {
3072
+ await rm5(flydocsPath, { recursive: true, force: true });
3073
+ result.removed.push("flydocs/");
3074
+ }
3075
+ }
3076
+ const emptyCandidates = [
3077
+ ".claude/skills",
3078
+ ".claude",
3079
+ ".cursor/rules",
3080
+ ".cursor"
3081
+ ];
3082
+ const cleaned = await cleanupEmptyParents(targetDir, emptyCandidates);
3083
+ for (const dir of cleaned) {
3084
+ result.removed.push(`${dir}/ (empty, cleaned up)`);
3085
+ }
3086
+ if (originals.length > 0) {
3087
+ await writeRestoredFiles(targetDir, originals);
3088
+ result.restored = originals.map((f) => f.relativePath);
3089
+ }
3090
+ console.log();
3091
+ console.log(pc8.bold("Uninstall Summary"));
3092
+ console.log();
3093
+ if (result.removed.length > 0) {
3094
+ console.log(` ${pc8.green("Removed")} (${result.removed.length}):`);
3095
+ for (const item of result.removed) {
3096
+ printStatus(item);
3097
+ }
3098
+ }
3099
+ if (result.archived.length > 0) {
3100
+ console.log();
3101
+ console.log(` ${pc8.yellow("Archived")} (${result.archived.length}):`);
3102
+ for (const item of result.archived) {
3103
+ printInfo(item);
3104
+ }
3105
+ }
3106
+ if (result.restored.length > 0) {
3107
+ console.log();
3108
+ console.log(
3109
+ ` ${pc8.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
3110
+ );
3111
+ for (const item of result.restored) {
3112
+ printInfo(item);
3113
+ }
3114
+ }
3115
+ if (result.skipped.length > 0) {
3116
+ console.log();
3117
+ console.log(
3118
+ ` ${pc8.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
3119
+ );
3120
+ for (const item of result.skipped) {
3121
+ console.log(` ${pc8.dim(item)}`);
3122
+ }
3123
+ }
3124
+ console.log();
3125
+ printStatus("FlyDocs has been removed from this project.");
3126
+ console.log();
3127
+ printInfo(`To reinstall: ${pc8.cyan("npx @flydocs/cli install --here")}`);
3128
+ console.log();
3129
+ }
3130
+ });
3131
+ }
3132
+ });
3133
+
2407
3134
  // src/commands/setup.ts
2408
3135
  var setup_exports = {};
2409
3136
  __export(setup_exports, {
2410
3137
  default: () => setup_default
2411
3138
  });
2412
- import { defineCommand as defineCommand3 } from "citty";
2413
- import pc7 from "picocolors";
3139
+ import { defineCommand as defineCommand4 } from "citty";
3140
+ import pc9 from "picocolors";
2414
3141
  var setup_default;
2415
3142
  var init_setup = __esm({
2416
3143
  "src/commands/setup.ts"() {
2417
3144
  "use strict";
2418
- setup_default = defineCommand3({
3145
+ setup_default = defineCommand4({
2419
3146
  meta: {
2420
3147
  name: "setup",
2421
3148
  description: "Configure FlyDocs settings for this project"
2422
3149
  },
2423
3150
  run() {
2424
3151
  console.log();
2425
- console.log(` ${pc7.bold("FlyDocs Setup")}`);
3152
+ console.log(` ${pc9.bold("FlyDocs Setup")}`);
2426
3153
  console.log();
2427
3154
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
2428
3155
  console.log();
2429
3156
  console.log(
2430
- ` ${pc7.cyan("Claude Code:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3157
+ ` ${pc9.cyan("Claude Code:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2431
3158
  );
2432
3159
  console.log(
2433
- ` ${pc7.cyan("Cursor:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3160
+ ` ${pc9.cyan("Cursor:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2434
3161
  );
2435
3162
  console.log();
2436
3163
  console.log(` This configures your project context, detects your stack,`);
@@ -2446,14 +3173,14 @@ var skills_exports = {};
2446
3173
  __export(skills_exports, {
2447
3174
  default: () => skills_default
2448
3175
  });
2449
- import { defineCommand as defineCommand4 } from "citty";
2450
- import pc8 from "picocolors";
3176
+ import { defineCommand as defineCommand5 } from "citty";
3177
+ import pc10 from "picocolors";
2451
3178
  var list, search, add, remove, skills_default;
2452
3179
  var init_skills2 = __esm({
2453
3180
  "src/commands/skills.ts"() {
2454
3181
  "use strict";
2455
3182
  init_skill_manager();
2456
- list = defineCommand4({
3183
+ list = defineCommand5({
2457
3184
  meta: {
2458
3185
  name: "list",
2459
3186
  description: "List installed skills"
@@ -2469,26 +3196,26 @@ var init_skills2 = __esm({
2469
3196
  console.log(`${total} skill(s) installed:`);
2470
3197
  if (result.platform.length > 0) {
2471
3198
  console.log();
2472
- console.log(pc8.bold("Platform"));
3199
+ console.log(pc10.bold("Platform"));
2473
3200
  for (const skill of result.platform) {
2474
3201
  console.log(
2475
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3202
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2476
3203
  );
2477
3204
  }
2478
3205
  }
2479
3206
  if (result.community.length > 0) {
2480
3207
  console.log();
2481
- console.log(pc8.bold("Community"));
3208
+ console.log(pc10.bold("Community"));
2482
3209
  for (const skill of result.community) {
2483
3210
  console.log(
2484
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3211
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2485
3212
  );
2486
3213
  }
2487
3214
  }
2488
3215
  console.log();
2489
3216
  }
2490
3217
  });
2491
- search = defineCommand4({
3218
+ search = defineCommand5({
2492
3219
  meta: {
2493
3220
  name: "search",
2494
3221
  description: "Search community skills"
@@ -2504,24 +3231,24 @@ var init_skills2 = __esm({
2504
3231
  const results = await searchCatalog(args.keyword);
2505
3232
  if (results.length === 0) {
2506
3233
  console.log(`No skills found for "${args.keyword}".`);
2507
- console.log(` Browse the catalog at: ${pc8.cyan("https://skills.sh/")}`);
3234
+ console.log(` Browse the catalog at: ${pc10.cyan("https://skills.sh/")}`);
2508
3235
  return;
2509
3236
  }
2510
3237
  console.log();
2511
3238
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
2512
3239
  console.log();
2513
3240
  for (const skill of results) {
2514
- console.log(` ${pc8.bold(skill.name)}`);
3241
+ console.log(` ${pc10.bold(skill.name)}`);
2515
3242
  console.log(` ${skill.description}`);
2516
- console.log(` ${pc8.dim(skill.repo)}`);
3243
+ console.log(` ${pc10.dim(skill.repo)}`);
2517
3244
  if (skill.tags.length > 0) {
2518
- console.log(` ${pc8.dim(skill.tags.join(", "))}`);
3245
+ console.log(` ${pc10.dim(skill.tags.join(", "))}`);
2519
3246
  }
2520
3247
  console.log();
2521
3248
  }
2522
3249
  }
2523
3250
  });
2524
- add = defineCommand4({
3251
+ add = defineCommand5({
2525
3252
  meta: {
2526
3253
  name: "add",
2527
3254
  description: "Install a community skill"
@@ -2537,7 +3264,7 @@ var init_skills2 = __esm({
2537
3264
  await addSkill(process.cwd(), args.source);
2538
3265
  }
2539
3266
  });
2540
- remove = defineCommand4({
3267
+ remove = defineCommand5({
2541
3268
  meta: {
2542
3269
  name: "remove",
2543
3270
  description: "Remove an installed community skill"
@@ -2553,7 +3280,7 @@ var init_skills2 = __esm({
2553
3280
  await removeSkill(process.cwd(), args.name);
2554
3281
  }
2555
3282
  });
2556
- skills_default = defineCommand4({
3283
+ skills_default = defineCommand5({
2557
3284
  meta: {
2558
3285
  name: "skills",
2559
3286
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -2573,11 +3300,71 @@ var connect_exports = {};
2573
3300
  __export(connect_exports, {
2574
3301
  default: () => connect_default
2575
3302
  });
2576
- import { defineCommand as defineCommand5 } from "citty";
2577
- import { text as text2, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
2578
- import pc9 from "picocolors";
2579
- import { readFile as readFile9, writeFile as writeFile5, appendFile as appendFile2 } from "fs/promises";
2580
- import { join as join14 } from "path";
3303
+ import { defineCommand as defineCommand6 } from "citty";
3304
+ import { text as text2, confirm as confirm5, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
3305
+ import pc11 from "picocolors";
3306
+ import { readFile as readFile11, writeFile as writeFile7, appendFile as appendFile2 } from "fs/promises";
3307
+ import { join as join17 } from "path";
3308
+ function detectKeyType(key) {
3309
+ if (key.startsWith("fdk_")) return "relay";
3310
+ if (key.startsWith("lin_api_")) return "direct";
3311
+ return "unknown";
3312
+ }
3313
+ async function validateRelayKey(apiKey) {
3314
+ const baseUrl = process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? "https://app.flydocs.ai/api/relay";
3315
+ const response = await fetch(`${baseUrl}/auth/validate`, {
3316
+ method: "POST",
3317
+ headers: {
3318
+ Authorization: `Bearer ${apiKey}`,
3319
+ "Content-Type": "application/json"
3320
+ },
3321
+ signal: AbortSignal.timeout(15e3)
3322
+ });
3323
+ if (!response.ok) return { valid: false };
3324
+ const data = await response.json();
3325
+ if (!data.valid) return { valid: false };
3326
+ return { valid: true, org: data.org ?? "your organization" };
3327
+ }
3328
+ async function validateLinearKey(apiKey) {
3329
+ const response = await fetch("https://api.linear.app/graphql", {
3330
+ method: "POST",
3331
+ headers: {
3332
+ Authorization: apiKey,
3333
+ "Content-Type": "application/json"
3334
+ },
3335
+ body: JSON.stringify({ query: "{ viewer { id name email } }" }),
3336
+ signal: AbortSignal.timeout(15e3)
3337
+ });
3338
+ if (!response.ok) return { valid: false };
3339
+ const data = await response.json();
3340
+ if (!data.data?.viewer) return { valid: false };
3341
+ return {
3342
+ valid: true,
3343
+ name: data.data.viewer.name,
3344
+ email: data.data.viewer.email
3345
+ };
3346
+ }
3347
+ async function storeEnvKey(targetDir, envVarName, value) {
3348
+ const envPath = join17(targetDir, ".env");
3349
+ const envLocalPath = join17(targetDir, ".env.local");
3350
+ const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
3351
+ const pattern = new RegExp(`${envVarName}=.*`);
3352
+ if (await pathExists(targetEnvPath)) {
3353
+ const envContent = await readFile11(targetEnvPath, "utf-8");
3354
+ if (pattern.test(envContent)) {
3355
+ const updated = envContent.replace(pattern, `${envVarName}=${value}`);
3356
+ await writeFile7(targetEnvPath, updated, "utf-8");
3357
+ } else {
3358
+ await appendFile2(targetEnvPath, `
3359
+ ${envVarName}=${value}
3360
+ `);
3361
+ }
3362
+ } else {
3363
+ await writeFile7(targetEnvPath, `${envVarName}=${value}
3364
+ `, "utf-8");
3365
+ }
3366
+ return targetEnvPath === envLocalPath ? ".env.local" : ".env";
3367
+ }
2581
3368
  var connect_default;
2582
3369
  var init_connect = __esm({
2583
3370
  "src/commands/connect.ts"() {
@@ -2586,10 +3373,10 @@ var init_connect = __esm({
2586
3373
  init_fs_ops();
2587
3374
  init_template();
2588
3375
  init_ui();
2589
- connect_default = defineCommand5({
3376
+ connect_default = defineCommand6({
2590
3377
  meta: {
2591
3378
  name: "connect",
2592
- description: "Connect FlyDocs to a cloud provider (Linear)"
3379
+ description: "Connect FlyDocs to a cloud provider"
2593
3380
  },
2594
3381
  args: {
2595
3382
  path: {
@@ -2606,16 +3393,16 @@ var init_connect = __esm({
2606
3393
  },
2607
3394
  key: {
2608
3395
  type: "string",
2609
- description: "Linear API key (skips prompt)"
3396
+ description: "API key (fdk_ for relay, lin_api_ for direct Linear)"
2610
3397
  }
2611
3398
  },
2612
3399
  async run({ args }) {
2613
3400
  const targetDir = args.path ?? process.cwd();
2614
- const configPath = join14(targetDir, ".flydocs", "config.json");
3401
+ const configPath = join17(targetDir, ".flydocs", "config.json");
2615
3402
  if (!await pathExists(configPath)) {
2616
3403
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
2617
3404
  console.log(
2618
- ` Run ${pc9.cyan("flydocs")} first to install FlyDocs in this project.`
3405
+ ` Run ${pc11.cyan("flydocs")} first to install FlyDocs in this project.`
2619
3406
  );
2620
3407
  process.exit(1);
2621
3408
  }
@@ -2623,87 +3410,85 @@ var init_connect = __esm({
2623
3410
  if (config.tier === "cloud") {
2624
3411
  printInfo("This project is already connected to the cloud tier.");
2625
3412
  console.log();
2626
- const reconnect = await confirm4({
3413
+ const reconnect = await confirm5({
2627
3414
  message: "Want to update your API key?"
2628
3415
  });
2629
- if (isCancel5(reconnect) || !reconnect) {
3416
+ if (isCancel6(reconnect) || !reconnect) {
2630
3417
  console.log(` No changes made.`);
2631
3418
  return;
2632
3419
  }
2633
3420
  }
2634
3421
  console.log();
2635
- console.log(` ${pc9.bold("Connect to Linear")}`);
3422
+ console.log(` ${pc11.bold("Connect to FlyDocs Cloud")}`);
2636
3423
  console.log();
2637
3424
  console.log(
2638
- ` ${pc9.dim("Get your API key from: Linear \u2192 Settings \u2192 API \u2192 Personal API keys")}`
3425
+ ` ${pc11.dim("FlyDocs API key (fdk_...): Get from your FlyDocs dashboard")}`
3426
+ );
3427
+ console.log(
3428
+ ` ${pc11.dim("Linear API key (lin_api_...): Get from Linear \u2192 Settings \u2192 API")}`
2639
3429
  );
2640
3430
  console.log();
2641
3431
  let apiKey = args.key ?? "";
2642
3432
  if (!apiKey) {
2643
3433
  const keyInput = await text2({
2644
- message: "Enter your Linear API key",
2645
- placeholder: "lin_api_...",
3434
+ message: "Enter your API key",
3435
+ placeholder: "fdk_... or lin_api_...",
2646
3436
  validate(value) {
2647
3437
  if (!value.trim()) return "API key is required";
2648
- if (!value.startsWith("lin_api_"))
2649
- return "Linear API keys start with lin_api_";
3438
+ const type = detectKeyType(value.trim());
3439
+ if (type === "unknown")
3440
+ return "Key must start with fdk_ (FlyDocs) or lin_api_ (Linear)";
2650
3441
  return void 0;
2651
3442
  }
2652
3443
  });
2653
- if (isCancel5(keyInput)) {
2654
- cancel4("Connection cancelled.");
3444
+ if (isCancel6(keyInput)) {
3445
+ cancel5("Connection cancelled.");
2655
3446
  process.exit(0);
2656
3447
  }
2657
- apiKey = keyInput;
3448
+ apiKey = keyInput.trim();
2658
3449
  }
2659
- printInfo("Validating API key...");
2660
- try {
2661
- const response = await fetch("https://api.linear.app/graphql", {
2662
- method: "POST",
2663
- headers: {
2664
- Authorization: apiKey,
2665
- "Content-Type": "application/json"
2666
- },
2667
- body: JSON.stringify({ query: "{ viewer { id name email } }" }),
2668
- signal: AbortSignal.timeout(15e3)
2669
- });
2670
- if (!response.ok) {
2671
- throw new Error(`HTTP ${response.status}`);
2672
- }
2673
- const data = await response.json();
2674
- if (!data.data?.viewer) {
2675
- throw new Error("Invalid response");
2676
- }
2677
- const viewer = data.data.viewer;
2678
- printStatus(`Authenticated as ${pc9.bold(viewer.name)} (${viewer.email})`);
2679
- } catch {
2680
- printError("Invalid API key or network error.");
2681
- console.log(` Check your key and try again.`);
3450
+ const keyType = detectKeyType(apiKey);
3451
+ if (keyType === "unknown") {
3452
+ printError("Unrecognized key format. Expected fdk_ or lin_api_ prefix.");
2682
3453
  process.exit(1);
2683
3454
  }
2684
- const envPath = join14(targetDir, ".env");
2685
- const envLocalPath = join14(targetDir, ".env.local");
2686
- const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
2687
- if (await pathExists(targetEnvPath)) {
2688
- const envContent = await readFile9(targetEnvPath, "utf-8");
2689
- if (envContent.includes("LINEAR_API_KEY=")) {
2690
- const updated = envContent.replace(
2691
- /LINEAR_API_KEY=.*/,
2692
- `LINEAR_API_KEY=${apiKey}`
3455
+ printInfo("Validating API key...");
3456
+ if (keyType === "relay") {
3457
+ try {
3458
+ const result = await validateRelayKey(apiKey);
3459
+ if (!result.valid) {
3460
+ printError("Invalid API key or relay API unreachable.");
3461
+ console.log(` Check your key and try again.`);
3462
+ process.exit(1);
3463
+ }
3464
+ printStatus(`Connected to ${pc11.bold(result.org)}`);
3465
+ } catch {
3466
+ printError(
3467
+ "Could not reach relay API. Check your network and try again."
2693
3468
  );
2694
- await writeFile5(targetEnvPath, updated, "utf-8");
2695
- } else {
2696
- await appendFile2(targetEnvPath, `
2697
- LINEAR_API_KEY=${apiKey}
2698
- `);
3469
+ process.exit(1);
2699
3470
  }
3471
+ const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
3472
+ printStatus(`API key stored in ${pc11.dim(envFile)}`);
2700
3473
  } else {
2701
- await writeFile5(targetEnvPath, `LINEAR_API_KEY=${apiKey}
2702
- `, "utf-8");
3474
+ try {
3475
+ const result = await validateLinearKey(apiKey);
3476
+ if (!result.valid) {
3477
+ printError("Invalid API key or network error.");
3478
+ console.log(` Check your key and try again.`);
3479
+ process.exit(1);
3480
+ }
3481
+ printStatus(
3482
+ `Authenticated as ${pc11.bold(result.name)} (${result.email})`
3483
+ );
3484
+ } catch {
3485
+ printError("Invalid API key or network error.");
3486
+ console.log(` Check your key and try again.`);
3487
+ process.exit(1);
3488
+ }
3489
+ const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
3490
+ printStatus(`API key stored in ${pc11.dim(envFile)}`);
2703
3491
  }
2704
- printStatus(
2705
- `API key stored in ${pc9.dim(targetEnvPath === envLocalPath ? ".env.local" : ".env")}`
2706
- );
2707
3492
  const wasLocal = config.tier === "local";
2708
3493
  config.tier = "cloud";
2709
3494
  config.provider = config.provider ?? { type: "linear", teamId: null };
@@ -2715,16 +3500,16 @@ LINEAR_API_KEY=${apiKey}
2715
3500
  const templateDir = await resolveTemplatePath(
2716
3501
  args["local-source"] || void 0
2717
3502
  );
2718
- const templateSkillsDir = join14(templateDir, ".claude", "skills");
2719
- const skillsDir = join14(targetDir, ".claude", "skills");
3503
+ const templateSkillsDir = join17(templateDir, ".claude", "skills");
3504
+ const skillsDir = join17(targetDir, ".claude", "skills");
2720
3505
  await replaceDirectory(
2721
- join14(templateSkillsDir, "flydocs-cloud"),
2722
- join14(skillsDir, "flydocs-cloud")
3506
+ join17(templateSkillsDir, "flydocs-cloud"),
3507
+ join17(skillsDir, "flydocs-cloud")
2723
3508
  );
2724
- const { rm: rm5 } = await import("fs/promises");
2725
- const localSkillDir = join14(skillsDir, "flydocs-local");
3509
+ const { rm: rm6 } = await import("fs/promises");
3510
+ const localSkillDir = join17(skillsDir, "flydocs-local");
2726
3511
  if (await pathExists(localSkillDir)) {
2727
- await rm5(localSkillDir, { recursive: true, force: true });
3512
+ await rm6(localSkillDir, { recursive: true, force: true });
2728
3513
  }
2729
3514
  printStatus("Cloud mechanism skill installed");
2730
3515
  } catch {
@@ -2735,14 +3520,14 @@ LINEAR_API_KEY=${apiKey}
2735
3520
  }
2736
3521
  console.log();
2737
3522
  console.log(
2738
- ` ${pc9.bold("Connected!")} Your project now syncs with Linear.`
3523
+ ` ${pc11.bold("Connected!")} Your project is now on the cloud tier.`
2739
3524
  );
2740
3525
  console.log();
2741
3526
  console.log(` Next steps:`);
2742
3527
  console.log(
2743
- ` 1. Run ${pc9.cyan("/flydocs-setup")} in your IDE to configure your Linear project`
3528
+ ` 1. Run ${pc11.cyan("/flydocs-setup")} in your IDE to configure your project`
2744
3529
  );
2745
- console.log(` 2. Run ${pc9.cyan("/start-session")} to begin working`);
3530
+ console.log(` 2. Run ${pc11.cyan("/start-session")} to begin working`);
2746
3531
  console.log();
2747
3532
  }
2748
3533
  });
@@ -2754,15 +3539,15 @@ var upgrade_exports = {};
2754
3539
  __export(upgrade_exports, {
2755
3540
  default: () => upgrade_default
2756
3541
  });
2757
- import { defineCommand as defineCommand6 } from "citty";
2758
- import pc10 from "picocolors";
3542
+ import { defineCommand as defineCommand7 } from "citty";
3543
+ import pc12 from "picocolors";
2759
3544
  var upgrade_default;
2760
3545
  var init_upgrade = __esm({
2761
3546
  "src/commands/upgrade.ts"() {
2762
3547
  "use strict";
2763
3548
  init_config();
2764
3549
  init_fs_ops();
2765
- upgrade_default = defineCommand6({
3550
+ upgrade_default = defineCommand7({
2766
3551
  meta: {
2767
3552
  name: "upgrade",
2768
3553
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -2791,38 +3576,40 @@ var init_upgrade = __esm({
2791
3576
  console.log();
2792
3577
  if (currentTier === "cloud") {
2793
3578
  console.log(
2794
- ` ${pc10.green("\u2713")} You're already on the ${pc10.bold("cloud")} tier.`
3579
+ ` ${pc12.green("\u2713")} You're already on the ${pc12.bold("cloud")} tier.`
2795
3580
  );
2796
3581
  console.log();
2797
3582
  console.log(
2798
3583
  ` Your issues sync with Linear via the cloud mechanism skill.`
2799
3584
  );
2800
3585
  console.log(
2801
- ` Run ${pc10.cyan("flydocs connect")} to update your connection settings.`
3586
+ ` Run ${pc12.cyan("flydocs connect")} to update your connection settings.`
2802
3587
  );
2803
3588
  console.log();
2804
3589
  return;
2805
3590
  }
2806
- console.log(` ${pc10.bold("FlyDocs Cloud Tier")}`);
3591
+ console.log(` ${pc12.bold("FlyDocs Cloud Tier")}`);
2807
3592
  console.log();
2808
- console.log(` You're currently on the ${pc10.yellow("local")} tier.`);
3593
+ console.log(` You're currently on the ${pc12.yellow("local")} tier.`);
2809
3594
  console.log(` Upgrade to cloud for:`);
2810
3595
  console.log();
2811
3596
  console.log(
2812
- ` ${pc10.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
3597
+ ` ${pc12.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
2813
3598
  );
2814
- console.log(` ${pc10.cyan("\u2192")} Project milestones and cycle management`);
2815
- console.log(` ${pc10.cyan("\u2192")} Team assignment and priority tracking`);
2816
- console.log(` ${pc10.cyan("\u2192")} Project health updates and dashboards`);
2817
- console.log(` ${pc10.cyan("\u2192")} Cross-project issue linking`);
3599
+ console.log(` ${pc12.cyan("\u2192")} Project milestones and cycle management`);
3600
+ console.log(` ${pc12.cyan("\u2192")} Team assignment and priority tracking`);
3601
+ console.log(` ${pc12.cyan("\u2192")} Project health updates and dashboards`);
3602
+ console.log(` ${pc12.cyan("\u2192")} Cross-project issue linking`);
3603
+ console.log();
3604
+ console.log(` ${pc12.bold("How to upgrade:")}`);
2818
3605
  console.log();
2819
- console.log(` ${pc10.bold("How to upgrade:")}`);
3606
+ console.log(` Option 1: Run ${pc12.cyan("/flydocs-upgrade")} in your IDE`);
3607
+ console.log(` Guided migration with issue transfer`);
2820
3608
  console.log();
2821
- console.log(` 1. Sign up at ${pc10.cyan("https://www.flydocs.ai")}`);
2822
- console.log(` 2. Get your Linear API key from Linear \u2192 Settings \u2192 API`);
2823
3609
  console.log(
2824
- ` 3. Run ${pc10.cyan("flydocs connect")} to connect your project`
3610
+ ` Option 2: Run ${pc12.cyan("flydocs connect")} from terminal`
2825
3611
  );
3612
+ console.log(` Quick tier swap (no issue migration)`);
2826
3613
  console.log();
2827
3614
  }
2828
3615
  });
@@ -2834,23 +3621,23 @@ var self_update_exports = {};
2834
3621
  __export(self_update_exports, {
2835
3622
  default: () => self_update_default
2836
3623
  });
2837
- import { defineCommand as defineCommand7 } from "citty";
3624
+ import { defineCommand as defineCommand8 } from "citty";
2838
3625
  import { execSync } from "child_process";
2839
- import pc11 from "picocolors";
3626
+ import pc13 from "picocolors";
2840
3627
  var self_update_default;
2841
3628
  var init_self_update = __esm({
2842
3629
  "src/commands/self-update.ts"() {
2843
3630
  "use strict";
2844
3631
  init_constants();
2845
3632
  init_ui();
2846
- self_update_default = defineCommand7({
3633
+ self_update_default = defineCommand8({
2847
3634
  meta: {
2848
3635
  name: "self-update",
2849
3636
  description: "Update FlyDocs CLI to the latest version"
2850
3637
  },
2851
3638
  async run() {
2852
3639
  console.log();
2853
- console.log(` Updating ${pc11.cyan(PACKAGE_NAME)}...`);
3640
+ console.log(` Updating ${pc13.cyan(PACKAGE_NAME)}...`);
2854
3641
  console.log();
2855
3642
  try {
2856
3643
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -2870,17 +3657,117 @@ var init_self_update = __esm({
2870
3657
  }
2871
3658
  });
2872
3659
 
3660
+ // src/commands/telemetry.ts
3661
+ var telemetry_exports = {};
3662
+ __export(telemetry_exports, {
3663
+ default: () => telemetry_default
3664
+ });
3665
+ import { defineCommand as defineCommand9 } from "citty";
3666
+ import pc14 from "picocolors";
3667
+ var enable, disable, status, telemetry_default;
3668
+ var init_telemetry2 = __esm({
3669
+ "src/commands/telemetry.ts"() {
3670
+ "use strict";
3671
+ init_telemetry();
3672
+ init_ui();
3673
+ enable = defineCommand9({
3674
+ meta: {
3675
+ name: "enable",
3676
+ description: "Enable anonymous usage analytics"
3677
+ },
3678
+ async run() {
3679
+ try {
3680
+ await setEnabled(true);
3681
+ printStatus("Telemetry enabled");
3682
+ } catch {
3683
+ printError("Failed to update telemetry settings");
3684
+ process.exit(1);
3685
+ }
3686
+ }
3687
+ });
3688
+ disable = defineCommand9({
3689
+ meta: {
3690
+ name: "disable",
3691
+ description: "Disable anonymous usage analytics"
3692
+ },
3693
+ async run() {
3694
+ try {
3695
+ await setEnabled(false);
3696
+ printStatus("Telemetry disabled");
3697
+ printInfo(
3698
+ "You can also set FLYDOCS_TELEMETRY=0 in your shell environment."
3699
+ );
3700
+ } catch {
3701
+ printError("Failed to update telemetry settings");
3702
+ process.exit(1);
3703
+ }
3704
+ }
3705
+ });
3706
+ status = defineCommand9({
3707
+ meta: {
3708
+ name: "status",
3709
+ description: "Show current telemetry status"
3710
+ },
3711
+ async run() {
3712
+ const info = await getStatus();
3713
+ console.log();
3714
+ console.log(pc14.bold("Telemetry Status"));
3715
+ console.log();
3716
+ const effectivelyEnabled = info.enabled && !info.envOverride;
3717
+ console.log(
3718
+ ` Enabled: ${effectivelyEnabled ? pc14.green("yes") : pc14.yellow("no")}`
3719
+ );
3720
+ if (info.envOverride) {
3721
+ console.log(
3722
+ ` Env override: ${pc14.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
3723
+ );
3724
+ }
3725
+ if (info.anonymousId) {
3726
+ console.log(` Anonymous ID: ${pc14.dim(info.anonymousId)}`);
3727
+ } else {
3728
+ console.log(
3729
+ ` Anonymous ID: ${pc14.dim("(not yet created \u2014 generated on first run)")}`
3730
+ );
3731
+ }
3732
+ console.log();
3733
+ console.log(
3734
+ pc14.dim(
3735
+ " FlyDocs collects anonymous usage analytics to improve the CLI."
3736
+ )
3737
+ );
3738
+ console.log(
3739
+ pc14.dim(" No personal data, file contents, or code is ever collected.")
3740
+ );
3741
+ console.log();
3742
+ }
3743
+ });
3744
+ telemetry_default = defineCommand9({
3745
+ meta: {
3746
+ name: "telemetry",
3747
+ description: "Manage anonymous usage analytics (enable, disable, status)"
3748
+ },
3749
+ subCommands: {
3750
+ enable,
3751
+ disable,
3752
+ status
3753
+ }
3754
+ });
3755
+ }
3756
+ });
3757
+
2873
3758
  // src/cli.ts
2874
3759
  init_constants();
2875
- import { defineCommand as defineCommand8, runMain } from "citty";
3760
+ import { defineCommand as defineCommand10, runMain } from "citty";
2876
3761
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
2877
3762
  "install",
2878
3763
  "update",
3764
+ "uninstall",
2879
3765
  "setup",
2880
3766
  "skills",
2881
3767
  "connect",
2882
3768
  "upgrade",
2883
- "self-update"
3769
+ "self-update",
3770
+ "telemetry"
2884
3771
  ]);
2885
3772
  var userArgs = process.argv.slice(2);
2886
3773
  var hasMetaFlag = userArgs.some(
@@ -2890,7 +3777,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
2890
3777
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
2891
3778
  process.argv.splice(2, 0, "install");
2892
3779
  }
2893
- var main = defineCommand8({
3780
+ var main = defineCommand10({
2894
3781
  meta: {
2895
3782
  name: CLI_NAME,
2896
3783
  version: CLI_VERSION,
@@ -2899,11 +3786,13 @@ var main = defineCommand8({
2899
3786
  subCommands: {
2900
3787
  install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
2901
3788
  update: () => Promise.resolve().then(() => (init_update(), update_exports)).then((m) => m.default),
3789
+ uninstall: () => Promise.resolve().then(() => (init_uninstall(), uninstall_exports)).then((m) => m.default),
2902
3790
  setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
2903
3791
  skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
2904
3792
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
2905
3793
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
2906
- "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default)
3794
+ "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default),
3795
+ telemetry: () => Promise.resolve().then(() => (init_telemetry2(), telemetry_exports)).then((m) => m.default)
2907
3796
  }
2908
3797
  });
2909
3798
  runMain(main);