@flydocs/cli 0.5.0-beta.8 → 0.6.0-alpha.1

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 (40) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +1258 -367
  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 +96 -26
  9. package/template/.claude/commands/flydocs-upgrade.md +330 -0
  10. package/template/.claude/skills/flydocs-cloud/SKILL.md +52 -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/estimate.py +10 -19
  20. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +103 -170
  21. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +6 -59
  22. package/template/.claude/skills/flydocs-cloud/scripts/link.py +16 -35
  23. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +21 -28
  24. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +16 -77
  25. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +19 -0
  26. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +21 -33
  27. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +24 -38
  28. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +19 -0
  29. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +10 -19
  30. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +36 -50
  31. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +68 -0
  32. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +41 -0
  33. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +11 -52
  34. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +16 -27
  35. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +23 -52
  36. package/template/.env.example +16 -7
  37. package/template/.flydocs/config.json +1 -1
  38. package/template/.flydocs/version +1 -1
  39. package/template/CHANGELOG.md +155 -0
  40. 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.8";
18
+ CLI_VERSION = "0.6.0-alpha.1";
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,6 +135,59 @@ var init_template = __esm({
136
135
 
137
136
  // src/lib/ui.ts
138
137
  import pc2 from "picocolors";
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
+ }
190
+ }
139
191
  function printStatus(message) {
140
192
  console.log(`${pc2.green("\u2714")} ${message}`);
141
193
  }
@@ -149,26 +201,13 @@ function printInfo(message) {
149
201
  console.log(`${pc2.cyan("\u2139")} ${message}`);
150
202
  }
151
203
  function printBanner(version) {
152
- const pink = (t) => pc2.bold(pc2.magenta(t));
153
- const purple = (t) => pc2.bold(pc2.cyan(t));
154
- const dim = pc2.dim;
155
- const bold = pc2.bold;
156
- const pinkBlock12 = pink("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
157
- const pinkBlock4 = pink("\u2588\u2588\u2588\u2588");
158
- const purpleBlock12 = purple("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
159
- const purpleBlock4 = purple("\u2588\u2588\u2588\u2588");
160
204
  console.log();
161
- console.log(` ${pinkBlock12}`);
162
- console.log(` ${pinkBlock12}`);
163
- console.log(` ${pinkBlock4}`);
164
- console.log(
165
- ` ${pinkBlock4} ${purpleBlock12} ${bold("Fly")}${bold("Docs")} ${bold("Core")}`
166
- );
205
+ renderBannerBlock();
206
+ console.log();
167
207
  console.log(
168
- ` ${purpleBlock12} ${dim("Context-as-a-Service Platform Built for Engineering at Altitude")}`
208
+ ` ${pc2.bold(pc2.white("Structured context for AI coding tools"))} ${pc2.dim("(Beta)")}`
169
209
  );
170
- console.log(` ${purpleBlock4}`);
171
- console.log(` ${purpleBlock4} ${dim(`v${version}`)}`);
210
+ console.log(` ${pc2.dim(`Version: ${version}`)}`);
172
211
  console.log();
173
212
  }
174
213
  function printCompletionBox(title, lines) {
@@ -189,23 +228,42 @@ function printCompletionBox(title, lines) {
189
228
  }
190
229
  function printBetaCta() {
191
230
  const dim = pc2.dim;
231
+ const discordUrl = "https://discord.com/invite/YAkjePmZTQ";
232
+ const siteUrl = "https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta";
192
233
  console.log(
193
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")
194
235
  );
195
236
  console.log();
196
- console.log(` ${pc2.bold("Join the FlyDocs Closed Beta")}`);
197
237
  console.log(
198
- ` ${dim("Early access to cloud features, priority support, and more.")}`
238
+ ` ${pc2.bold(pc2.cyan("Join the Discord"))} for upcoming features, support, and early access to what's next.`
199
239
  );
240
+ console.log(` ${dim("Invite link:")}`);
241
+ console.log(` ${pc2.cyan(discordUrl)}`);
242
+ console.log();
200
243
  console.log(
201
- ` ${pc2.cyan("https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta")}`
244
+ ` ${dim("Docs and updates:")} ${pc2.cyan("https://www.flydocs.ai")}`
202
245
  );
203
246
  console.log();
204
247
  }
248
+ var GRADIENT, BANNER_ROWS;
205
249
  var init_ui = __esm({
206
250
  "src/lib/ui.ts"() {
207
251
  "use strict";
208
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
+ ];
209
267
  }
210
268
  });
211
269
 
@@ -303,18 +361,26 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
303
361
  join4(templateSkillsDir, activeMech),
304
362
  join4(skillsDir, activeMech)
305
363
  );
306
- const { rm: rm5 } = await import("fs/promises");
364
+ const { rm: rm6 } = await import("fs/promises");
307
365
  const inactivePath = join4(skillsDir, inactiveMech);
308
366
  if (await pathExists(inactivePath)) {
309
- await rm5(inactivePath, { recursive: true, force: true });
367
+ await rm6(inactivePath, { recursive: true, force: true });
310
368
  }
311
- for (const skill of OWNED_SKILLS) {
369
+ for (const skill of CORE_SKILLS) {
312
370
  if (skill === "flydocs-workflow") continue;
313
371
  const src = join4(templateSkillsDir, skill);
314
372
  if (await pathExists(src)) {
315
373
  await replaceDirectory(src, join4(skillsDir, skill));
316
374
  }
317
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
+ }
318
384
  const readmeSrc = join4(templateSkillsDir, "README.md");
319
385
  if (await pathExists(readmeSrc)) {
320
386
  await copyFile(readmeSrc, join4(skillsDir, "README.md"));
@@ -323,18 +389,29 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
323
389
  async function replaceOwnedSkills(templateDir, targetDir, tier) {
324
390
  const skillsDir = join4(targetDir, ".claude", "skills");
325
391
  const templateSkillsDir = join4(templateDir, ".claude", "skills");
326
- for (const skill of OWNED_SKILLS) {
392
+ const { rm: rm6 } = await import("fs/promises");
393
+ for (const skill of CORE_SKILLS) {
327
394
  const src = join4(templateSkillsDir, skill);
328
395
  if (await pathExists(src)) {
329
396
  await replaceDirectory(src, join4(skillsDir, skill));
330
397
  }
331
398
  }
332
- 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
+ }
333
410
  const activeMech = MECHANISM_SKILLS[tier];
334
411
  const inactiveMech = tier === "local" ? MECHANISM_SKILLS.cloud : MECHANISM_SKILLS.local;
335
412
  const inactivePath = join4(skillsDir, inactiveMech);
336
413
  if (await pathExists(inactivePath)) {
337
- await rm5(inactivePath, { recursive: true, force: true });
414
+ await rm6(inactivePath, { recursive: true, force: true });
338
415
  }
339
416
  await replaceDirectory(
340
417
  join4(templateSkillsDir, activeMech),
@@ -346,9 +423,9 @@ async function replaceOwnedSkills(templateDir, targetDir, tier) {
346
423
  }
347
424
  }
348
425
  async function copyCursorRules(targetDir) {
349
- const { mkdir: mkdir6 } = await import("fs/promises");
426
+ const { mkdir: mkdir9 } = await import("fs/promises");
350
427
  const rulesDir = join4(targetDir, ".cursor", "rules");
351
- await mkdir6(rulesDir, { recursive: true });
428
+ await mkdir9(rulesDir, { recursive: true });
352
429
  const workflowRule = join4(
353
430
  targetDir,
354
431
  ".claude",
@@ -382,18 +459,17 @@ async function copyCursorRules(targetDir) {
382
459
  await copyFile(context7Rule, join4(rulesDir, "flydocs-context7.mdc"));
383
460
  }
384
461
  }
385
- var OWNED_SKILLS, MECHANISM_SKILLS;
462
+ var CORE_SKILLS, CLOUD_ONLY_SKILLS, MECHANISM_SKILLS;
386
463
  var init_skills = __esm({
387
464
  "src/lib/skills.ts"() {
388
465
  "use strict";
389
466
  init_fs_ops();
390
- OWNED_SKILLS = [
467
+ CORE_SKILLS = [
391
468
  "flydocs-workflow",
392
- "flydocs-figma",
393
- "flydocs-estimates",
394
469
  "flydocs-context-graph",
395
470
  "flydocs-context7"
396
471
  ];
472
+ CLOUD_ONLY_SKILLS = ["flydocs-figma", "flydocs-estimates"];
397
473
  MECHANISM_SKILLS = {
398
474
  local: "flydocs-local",
399
475
  cloud: "flydocs-cloud"
@@ -401,14 +477,77 @@ var init_skills = __esm({
401
477
  }
402
478
  });
403
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
+
404
543
  // src/lib/stack.ts
405
- import { readFile as readFile3 } from "fs/promises";
406
- import { join as join5 } from "path";
544
+ import { readFile as readFile4 } from "fs/promises";
545
+ import { join as join6 } from "path";
407
546
  async function parseProjectMdStack(targetDir) {
408
547
  const detected = /* @__PURE__ */ new Set();
409
- const projectMdPath = join5(targetDir, "flydocs", "context", "project.md");
548
+ const projectMdPath = join6(targetDir, "flydocs", "context", "project.md");
410
549
  try {
411
- const content = await readFile3(projectMdPath, "utf-8");
550
+ const content = await readFile4(projectMdPath, "utf-8");
412
551
  const stackMatch = content.match(/## Stack\n([\s\S]*?)(?=\n## |\n---|\z)/);
413
552
  if (!stackMatch) return detected;
414
553
  const stackSection = stackMatch[1].toLowerCase();
@@ -451,10 +590,10 @@ async function parseProjectMdStack(targetDir) {
451
590
  }
452
591
  async function detectStack(targetDir) {
453
592
  const detected = /* @__PURE__ */ new Set();
454
- const pkgPath = join5(targetDir, "package.json");
593
+ const pkgPath = join6(targetDir, "package.json");
455
594
  let pkg;
456
595
  try {
457
- const content = await readFile3(pkgPath, "utf-8");
596
+ const content = await readFile4(pkgPath, "utf-8");
458
597
  pkg = JSON.parse(content);
459
598
  } catch {
460
599
  }
@@ -482,39 +621,39 @@ async function detectStack(targetDir) {
482
621
  detected.add("testing-library");
483
622
  }
484
623
  for (const f of ["convex/schema.ts", "convex/schema.js"]) {
485
- if (await pathExists(join5(targetDir, f))) detected.add("convex");
624
+ if (await pathExists(join6(targetDir, f))) detected.add("convex");
486
625
  }
487
626
  for (const f of ["next.config.ts", "next.config.js", "next.config.mjs"]) {
488
- if (await pathExists(join5(targetDir, f))) detected.add("nextjs");
627
+ if (await pathExists(join6(targetDir, f))) detected.add("nextjs");
489
628
  }
490
- const appJsonPath = join5(targetDir, "app.json");
629
+ const appJsonPath = join6(targetDir, "app.json");
491
630
  if (await pathExists(appJsonPath)) {
492
631
  try {
493
- const appContent = await readFile3(appJsonPath, "utf-8");
632
+ const appContent = await readFile4(appJsonPath, "utf-8");
494
633
  if (appContent.includes('"expo"')) detected.add("expo");
495
634
  } catch {
496
635
  }
497
636
  }
498
- if (await pathExists(join5(targetDir, "tsconfig.json"))) {
637
+ if (await pathExists(join6(targetDir, "tsconfig.json"))) {
499
638
  detected.add("typescript");
500
639
  }
501
- 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"))) {
502
641
  detected.add("nuxt");
503
642
  detected.add("vue");
504
643
  }
505
- if (await pathExists(join5(targetDir, "angular.json"))) {
644
+ if (await pathExists(join6(targetDir, "angular.json"))) {
506
645
  detected.add("angular");
507
646
  }
508
- 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"))) {
509
648
  detected.add("svelte");
510
649
  }
511
- 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"))) {
512
651
  detected.add("python");
513
652
  }
514
- if (await pathExists(join5(targetDir, "go.mod"))) {
653
+ if (await pathExists(join6(targetDir, "go.mod"))) {
515
654
  detected.add("go");
516
655
  }
517
- if (await pathExists(join5(targetDir, "Cargo.toml"))) {
656
+ if (await pathExists(join6(targetDir, "Cargo.toml"))) {
518
657
  detected.add("rust");
519
658
  }
520
659
  const projectMdStack = await parseProjectMdStack(targetDir);
@@ -560,9 +699,9 @@ var init_stack = __esm({
560
699
 
561
700
  // src/lib/post-install.ts
562
701
  import { execFileSync } from "child_process";
563
- import { join as join6 } from "path";
702
+ import { join as join7 } from "path";
564
703
  async function runManifestGeneration(targetDir) {
565
- const scriptPath = join6(
704
+ const scriptPath = join7(
566
705
  targetDir,
567
706
  ".flydocs",
568
707
  "scripts",
@@ -580,7 +719,7 @@ async function runManifestGeneration(targetDir) {
580
719
  }
581
720
  }
582
721
  async function runContextGraphBuild(targetDir) {
583
- const scriptPath = join6(
722
+ const scriptPath = join7(
584
723
  targetDir,
585
724
  ".claude",
586
725
  "skills",
@@ -608,8 +747,8 @@ var init_post_install = __esm({
608
747
  });
609
748
 
610
749
  // src/lib/skill-manager.ts
611
- import { readFile as readFile4, readdir, rm as rm2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
612
- 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";
613
752
  import pc3 from "picocolors";
614
753
  function flushFrontmatterValue(mode, lines) {
615
754
  switch (mode) {
@@ -736,13 +875,13 @@ async function downloadFileContent(downloadUrl) {
736
875
  async function downloadRecursive(url, destDir) {
737
876
  const items = await fetchGitHubJson(url);
738
877
  if (items === null) return false;
739
- await mkdir2(destDir, { recursive: true });
878
+ await mkdir3(destDir, { recursive: true });
740
879
  for (const item of items) {
741
- const destPath = join7(destDir, item.name);
880
+ const destPath = join8(destDir, item.name);
742
881
  if (item.type === "file") {
743
882
  if (item.download_url) {
744
883
  const content = await downloadFileContent(item.download_url);
745
- await writeFile2(destPath, content);
884
+ await writeFile3(destPath, content);
746
885
  }
747
886
  } else if (item.type === "dir") {
748
887
  await downloadRecursive(item.url, destPath);
@@ -755,7 +894,7 @@ async function downloadSkillTree(repo, skillName, targetDir) {
755
894
  return downloadRecursive(apiUrl, targetDir);
756
895
  }
757
896
  async function listSkills(targetDir) {
758
- const skillsDir = join7(targetDir, ".claude", "skills");
897
+ const skillsDir = join8(targetDir, ".claude", "skills");
759
898
  const platform = [];
760
899
  const community = [];
761
900
  if (!await pathExists(skillsDir)) {
@@ -768,11 +907,11 @@ async function listSkills(targetDir) {
768
907
  return { platform, community };
769
908
  }
770
909
  for (const entry of entries.sort()) {
771
- const skillFile = join7(skillsDir, entry, "SKILL.md");
910
+ const skillFile = join8(skillsDir, entry, "SKILL.md");
772
911
  if (!await pathExists(skillFile)) continue;
773
912
  let content;
774
913
  try {
775
- content = await readFile4(skillFile, "utf-8");
914
+ content = await readFile5(skillFile, "utf-8");
776
915
  } catch {
777
916
  continue;
778
917
  }
@@ -820,7 +959,7 @@ async function addSkill(targetDir, source) {
820
959
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
821
960
  return;
822
961
  }
823
- const skillsDir = join7(targetDir, ".claude", "skills", skillName);
962
+ const skillsDir = join8(targetDir, ".claude", "skills", skillName);
824
963
  if (await pathExists(skillsDir)) {
825
964
  printWarning(`Skill '${skillName}' is already installed.`);
826
965
  console.log(` Remove first: flydocs skills remove ${skillName}`);
@@ -849,13 +988,13 @@ async function addSkill(targetDir, source) {
849
988
  printError(`Skill not found at ${repo}/skills/${skillName}`);
850
989
  return;
851
990
  }
852
- const skillMdPath = join7(skillsDir, "SKILL.md");
991
+ const skillMdPath = join8(skillsDir, "SKILL.md");
853
992
  if (!await pathExists(skillMdPath)) {
854
993
  await rm2(skillsDir, { recursive: true, force: true });
855
994
  printError("Invalid skill: SKILL.md not found.");
856
995
  return;
857
996
  }
858
- const skillMdContent = await readFile4(skillMdPath, "utf-8");
997
+ const skillMdContent = await readFile5(skillMdPath, "utf-8");
859
998
  const fm = parseFrontmatter(skillMdContent);
860
999
  if (fm === null || !fm["name"] || !fm["description"]) {
861
1000
  await rm2(skillsDir, { recursive: true, force: true });
@@ -870,11 +1009,11 @@ async function addSkill(targetDir, source) {
870
1009
  );
871
1010
  }
872
1011
  printStatus("Downloaded skill files");
873
- const cursorRuleSrc = join7(skillsDir, "cursor-rule.mdc");
1012
+ const cursorRuleSrc = join8(skillsDir, "cursor-rule.mdc");
874
1013
  if (await pathExists(cursorRuleSrc)) {
875
- const cursorRulesDir = join7(targetDir, ".cursor", "rules");
876
- await mkdir2(cursorRulesDir, { recursive: true });
877
- 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`);
878
1017
  await copyFile(cursorRuleSrc, cursorRuleDest);
879
1018
  printStatus("Installed cursor rule");
880
1019
  }
@@ -901,14 +1040,14 @@ async function removeSkill(targetDir, name) {
901
1040
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
902
1041
  return;
903
1042
  }
904
- const skillDir = join7(targetDir, ".claude", "skills", name);
1043
+ const skillDir = join8(targetDir, ".claude", "skills", name);
905
1044
  if (!await pathExists(skillDir)) {
906
1045
  printError(`Skill '${name}' is not installed.`);
907
1046
  return;
908
1047
  }
909
1048
  await rm2(skillDir, { recursive: true, force: true });
910
1049
  printStatus("Removed skill directory");
911
- const cursorRule = join7(targetDir, ".cursor", "rules", `${name}.mdc`);
1050
+ const cursorRule = join8(targetDir, ".cursor", "rules", `${name}.mdc`);
912
1051
  if (await pathExists(cursorRule)) {
913
1052
  await rm2(cursorRule, { force: true });
914
1053
  printStatus("Removed cursor rule");
@@ -973,7 +1112,7 @@ var init_skill_manager = __esm({
973
1112
  });
974
1113
 
975
1114
  // src/lib/community-skills.ts
976
- import { join as join8 } from "path";
1115
+ import { join as join9 } from "path";
977
1116
  import { multiselect, isCancel, cancel } from "@clack/prompts";
978
1117
  import pc4 from "picocolors";
979
1118
  function suggestSkills(stack) {
@@ -992,10 +1131,10 @@ function suggestSkills(stack) {
992
1131
  }
993
1132
  async function promptCommunitySkills(targetDir, stack, autoYes) {
994
1133
  const suggestions = suggestSkills(stack);
995
- const skillsDir = join8(targetDir, ".claude", "skills");
1134
+ const skillsDir = join9(targetDir, ".claude", "skills");
996
1135
  const filtered = [];
997
1136
  for (const skill of suggestions) {
998
- if (!await pathExists(join8(skillsDir, skill.name))) {
1137
+ if (!await pathExists(join9(skillsDir, skill.name))) {
999
1138
  filtered.push(skill);
1000
1139
  }
1001
1140
  }
@@ -1152,48 +1291,48 @@ var init_community_skills = __esm({
1152
1291
  });
1153
1292
 
1154
1293
  // src/lib/deprecated.ts
1155
- import { readdir as readdir2, mkdir as mkdir3, rename, rm as rm3 } from "fs/promises";
1156
- 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";
1157
1296
  import { confirm, isCancel as isCancel2 } from "@clack/prompts";
1158
1297
  async function scanDeprecated(targetDir) {
1159
1298
  const found = [];
1160
1299
  for (const item of [...DEPRECATED_DIRS, ...DEPRECATED_FILES]) {
1161
- if (await pathExists(join9(targetDir, item))) {
1300
+ if (await pathExists(join10(targetDir, item))) {
1162
1301
  found.push(item);
1163
1302
  }
1164
1303
  }
1165
1304
  for (const skill of DEPRECATED_SKILLS) {
1166
- const p = join9(targetDir, ".claude", "skills", skill);
1305
+ const p = join10(targetDir, ".claude", "skills", skill);
1167
1306
  if (await pathExists(p)) {
1168
1307
  found.push(`.claude/skills/${skill}`);
1169
1308
  }
1170
1309
  }
1171
1310
  for (const dir of DEPRECATED_RULES_DIR) {
1172
- if (await pathExists(join9(targetDir, dir))) {
1311
+ if (await pathExists(join10(targetDir, dir))) {
1173
1312
  found.push(dir);
1174
1313
  }
1175
1314
  }
1176
1315
  for (const hook of DEPRECATED_HOOKS) {
1177
- const p = join9(targetDir, ".flydocs", "hooks", hook);
1316
+ const p = join10(targetDir, ".flydocs", "hooks", hook);
1178
1317
  if (await pathExists(p)) {
1179
1318
  found.push(`.flydocs/hooks/${hook}`);
1180
1319
  }
1181
1320
  }
1182
1321
  for (const rule of DEPRECATED_CURSOR_RULES) {
1183
- const p = join9(targetDir, ".cursor", "rules", rule);
1322
+ const p = join10(targetDir, ".cursor", "rules", rule);
1184
1323
  if (await pathExists(p)) {
1185
1324
  found.push(`.cursor/rules/${rule}`);
1186
1325
  }
1187
1326
  }
1188
1327
  for (const dir of DEPRECATED_CURSOR_RULE_DIRS) {
1189
- const p = join9(targetDir, ".cursor", "rules", dir);
1328
+ const p = join10(targetDir, ".cursor", "rules", dir);
1190
1329
  if (await pathExists(p)) {
1191
1330
  found.push(`.cursor/rules/${dir}`);
1192
1331
  }
1193
1332
  }
1194
1333
  for (const cmd of DEPRECATED_COMMANDS) {
1195
1334
  for (const prefix of [".cursor/commands", ".claude/commands"]) {
1196
- const p = join9(targetDir, prefix, cmd);
1335
+ const p = join10(targetDir, prefix, cmd);
1197
1336
  if (await pathExists(p)) {
1198
1337
  found.push(`${prefix}/${cmd}`);
1199
1338
  }
@@ -1202,22 +1341,22 @@ async function scanDeprecated(targetDir) {
1202
1341
  return found;
1203
1342
  }
1204
1343
  async function handleLegacyContext(targetDir) {
1205
- const contextDir = join9(targetDir, "flydocs", "context");
1206
- const legacyDir = join9(contextDir, "legacy");
1344
+ const contextDir = join10(targetDir, "flydocs", "context");
1345
+ const legacyDir = join10(contextDir, "legacy");
1207
1346
  const oldFiles = ["overview.md", "stack.md", "standards.md"];
1208
1347
  let hasOld = false;
1209
1348
  for (const f of oldFiles) {
1210
- if (await pathExists(join9(contextDir, f))) {
1349
+ if (await pathExists(join10(contextDir, f))) {
1211
1350
  hasOld = true;
1212
1351
  break;
1213
1352
  }
1214
1353
  }
1215
1354
  if (hasOld) {
1216
- await mkdir3(legacyDir, { recursive: true });
1355
+ await mkdir4(legacyDir, { recursive: true });
1217
1356
  for (const f of oldFiles) {
1218
- const src = join9(contextDir, f);
1357
+ const src = join10(contextDir, f);
1219
1358
  if (await pathExists(src)) {
1220
- await rename(src, join9(legacyDir, f));
1359
+ await rename(src, join10(legacyDir, f));
1221
1360
  printStatus(`Moved flydocs/context/${f} \u2192 legacy/`);
1222
1361
  }
1223
1362
  }
@@ -1227,7 +1366,7 @@ async function handleLegacyContext(targetDir) {
1227
1366
  }
1228
1367
  }
1229
1368
  async function checkLegacyFolder(targetDir) {
1230
- const legacyDir = join9(targetDir, "flydocs", "context", "legacy");
1369
+ const legacyDir = join10(targetDir, "flydocs", "context", "legacy");
1231
1370
  if (!await pathExists(legacyDir)) return null;
1232
1371
  try {
1233
1372
  const entries = await readdir2(legacyDir);
@@ -1257,7 +1396,7 @@ async function promptCleanup(targetDir, paths) {
1257
1396
  return;
1258
1397
  }
1259
1398
  for (const p of paths) {
1260
- await rm3(join9(targetDir, p), { recursive: true, force: true });
1399
+ await rm3(join10(targetDir, p), { recursive: true, force: true });
1261
1400
  printStatus(`Deleted: ${p}`);
1262
1401
  }
1263
1402
  }
@@ -1308,26 +1447,26 @@ var init_deprecated = __esm({
1308
1447
  });
1309
1448
 
1310
1449
  // src/lib/gitignore.ts
1311
- import { readFile as readFile5, writeFile as writeFile3, appendFile } from "fs/promises";
1312
- 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";
1313
1452
  async function ensureGitignore(targetDir) {
1314
- const gitignorePath = join10(targetDir, ".gitignore");
1453
+ const gitignorePath = join11(targetDir, ".gitignore");
1315
1454
  if (await pathExists(gitignorePath)) {
1316
- const content = await readFile5(gitignorePath, "utf-8");
1455
+ const content = await readFile6(gitignorePath, "utf-8");
1317
1456
  if (!content.includes("# FlyDocs")) {
1318
1457
  const section = "\n# FlyDocs\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
1319
1458
  await appendFile(gitignorePath, section, "utf-8");
1320
1459
  printStatus("Added FlyDocs entries to .gitignore");
1321
1460
  }
1322
1461
  } else {
1323
- await writeFile3(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1462
+ await writeFile4(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1324
1463
  printStatus(".gitignore (new)");
1325
1464
  }
1326
1465
  }
1327
1466
  async function migrateGitignore(targetDir) {
1328
- const gitignorePath = join10(targetDir, ".gitignore");
1467
+ const gitignorePath = join11(targetDir, ".gitignore");
1329
1468
  if (!await pathExists(gitignorePath)) return;
1330
- const content = await readFile5(gitignorePath, "utf-8");
1469
+ const content = await readFile6(gitignorePath, "utf-8");
1331
1470
  if (!content.includes("flydocs/context/graph.json")) {
1332
1471
  if (content.includes("# FlyDocs")) {
1333
1472
  const lines = content.split("\n");
@@ -1338,7 +1477,7 @@ async function migrateGitignore(targetDir) {
1338
1477
  insertIdx++;
1339
1478
  }
1340
1479
  lines.splice(insertIdx, 0, "flydocs/context/graph.json");
1341
- await writeFile3(gitignorePath, lines.join("\n"), "utf-8");
1480
+ await writeFile4(gitignorePath, lines.join("\n"), "utf-8");
1342
1481
  } else {
1343
1482
  await appendFile(
1344
1483
  gitignorePath,
@@ -1407,7 +1546,7 @@ __pycache__/
1407
1546
  });
1408
1547
 
1409
1548
  // src/lib/version.ts
1410
- import { readFile as readFile6 } from "fs/promises";
1549
+ import { readFile as readFile7 } from "fs/promises";
1411
1550
  function compareVersions(v1, v2) {
1412
1551
  const [core1, pre1] = v1.split("-", 2);
1413
1552
  const [core2, pre2] = v2.split("-", 2);
@@ -1446,7 +1585,7 @@ function compareVersions(v1, v2) {
1446
1585
  async function getWhatsNew(changelogPath, fromVersion, toVersion) {
1447
1586
  let content;
1448
1587
  try {
1449
- content = await readFile6(changelogPath, "utf-8");
1588
+ content = await readFile7(changelogPath, "utf-8");
1450
1589
  } catch {
1451
1590
  return [];
1452
1591
  }
@@ -1475,13 +1614,13 @@ var init_version = __esm({
1475
1614
  });
1476
1615
 
1477
1616
  // src/lib/update-check.ts
1478
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1479
- 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";
1480
1619
  import { homedir } from "os";
1481
1620
  import pc5 from "picocolors";
1482
1621
  async function readCache() {
1483
1622
  try {
1484
- const raw = await readFile7(CACHE_FILE, "utf-8");
1623
+ const raw = await readFile8(CACHE_FILE, "utf-8");
1485
1624
  const parsed = JSON.parse(raw);
1486
1625
  if (typeof parsed === "object" && parsed !== null && "checkedAt" in parsed && "latestVersion" in parsed && typeof parsed.checkedAt === "number" && typeof parsed.latestVersion === "string") {
1487
1626
  return parsed;
@@ -1493,8 +1632,8 @@ async function readCache() {
1493
1632
  }
1494
1633
  async function writeCache(cache) {
1495
1634
  try {
1496
- await mkdir4(join11(homedir(), ".flydocs"), { recursive: true });
1497
- 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");
1498
1637
  } catch {
1499
1638
  }
1500
1639
  }
@@ -1562,12 +1701,145 @@ var init_update_check = __esm({
1562
1701
  "use strict";
1563
1702
  init_constants();
1564
1703
  init_version();
1565
- CACHE_FILE = join11(homedir(), ".flydocs", "update-check.json");
1704
+ CACHE_FILE = join12(homedir(), ".flydocs", "update-check.json");
1566
1705
  CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1567
1706
  FETCH_TIMEOUT_MS = 5e3;
1568
1707
  }
1569
1708
  });
1570
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
+
1571
1843
  // src/commands/install.ts
1572
1844
  var install_exports = {};
1573
1845
  __export(install_exports, {
@@ -1575,8 +1847,10 @@ __export(install_exports, {
1575
1847
  });
1576
1848
  import { defineCommand } from "citty";
1577
1849
  import { resolve as resolve2 } from "path";
1578
- import { join as join12 } from "path";
1850
+ import { join as join14 } from "path";
1851
+ import { mkdir as mkdir7 } from "fs/promises";
1579
1852
  import { confirm as confirm2, select, isCancel as isCancel3, cancel as cancel2 } from "@clack/prompts";
1853
+ import pc6 from "picocolors";
1580
1854
  var install_default;
1581
1855
  var init_install = __esm({
1582
1856
  "src/commands/install.ts"() {
@@ -1586,12 +1860,14 @@ var init_install = __esm({
1586
1860
  init_ui();
1587
1861
  init_config();
1588
1862
  init_skills();
1863
+ init_user_content();
1589
1864
  init_stack();
1590
1865
  init_community_skills();
1591
1866
  init_deprecated();
1592
1867
  init_gitignore();
1593
1868
  init_post_install();
1594
1869
  init_update_check();
1870
+ init_telemetry();
1595
1871
  install_default = defineCommand({
1596
1872
  meta: {
1597
1873
  name: "install",
@@ -1600,7 +1876,7 @@ var init_install = __esm({
1600
1876
  args: {
1601
1877
  tier: {
1602
1878
  type: "string",
1603
- description: "Set tier: 'local' (free) or 'cloud' (Linear)"
1879
+ description: "Set tier: 'local' (free) or 'cloud' (managed)"
1604
1880
  },
1605
1881
  path: {
1606
1882
  type: "string",
@@ -1627,6 +1903,7 @@ var init_install = __esm({
1627
1903
  const templateDir = await resolveTemplatePath(args["local-source"]);
1628
1904
  const version = await readTemplateVersion(templateDir);
1629
1905
  printBanner(version);
1906
+ await capture("install_started", { template_version: version });
1630
1907
  let targetDir;
1631
1908
  if (args.path) {
1632
1909
  targetDir = resolve2(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -1652,60 +1929,95 @@ var init_install = __esm({
1652
1929
  process.exit(1);
1653
1930
  }
1654
1931
  tier = args.tier;
1655
- printInfo(`Tier set via flag: ${tier}`);
1656
- } else if (await pathExists(join12(targetDir, ".flydocs", "config.json"))) {
1932
+ } else if (await pathExists(join14(targetDir, ".flydocs", "config.json"))) {
1657
1933
  try {
1658
1934
  const existing = await readConfig(targetDir);
1659
1935
  if (existing.tier) {
1660
1936
  tier = existing.tier;
1661
- printInfo(`Tier from config: ${tier}`);
1662
1937
  }
1663
1938
  } catch {
1664
1939
  }
1665
1940
  }
1666
- if (!tier) {
1667
- const shouldInstall = await confirm2({
1668
- 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)
1669
1967
  Directory: ${targetDir}`
1670
- });
1671
- if (isCancel3(shouldInstall) || !shouldInstall) {
1672
- cancel2("Installation cancelled.");
1673
- process.exit(0);
1968
+ });
1969
+ if (isCancel3(shouldInstall) || !shouldInstall) {
1970
+ cancel2("Installation cancelled.");
1971
+ process.exit(0);
1972
+ }
1973
+ console.log();
1674
1974
  }
1975
+ }
1976
+ if (!tier) {
1675
1977
  tier = "local";
1676
- console.log();
1677
1978
  }
1678
- if (!await pathExists(join12(targetDir, ".git"))) {
1979
+ printInfo(`Tier: ${tier}`);
1980
+ await capture("install_tier_selected", { tier });
1981
+ if (!await pathExists(join14(targetDir, ".git"))) {
1679
1982
  printWarning("No git repository detected. Run git init when ready.");
1680
1983
  }
1681
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
+ }
1682
1994
  console.log("Installing framework files...");
1683
1995
  await replaceDirectory(
1684
- join12(templateDir, ".flydocs", "templates"),
1685
- join12(targetDir, ".flydocs", "templates")
1996
+ join14(templateDir, ".flydocs", "templates"),
1997
+ join14(targetDir, ".flydocs", "templates")
1686
1998
  );
1687
1999
  await replaceDirectory(
1688
- join12(templateDir, ".flydocs", "hooks"),
1689
- join12(targetDir, ".flydocs", "hooks")
2000
+ join14(templateDir, ".flydocs", "hooks"),
2001
+ join14(targetDir, ".flydocs", "hooks")
1690
2002
  );
1691
2003
  await replaceDirectory(
1692
- join12(templateDir, ".flydocs", "scripts"),
1693
- join12(targetDir, ".flydocs", "scripts")
2004
+ join14(templateDir, ".flydocs", "scripts"),
2005
+ join14(targetDir, ".flydocs", "scripts")
1694
2006
  );
1695
2007
  await copyFile(
1696
- join12(templateDir, ".flydocs", "version"),
1697
- join12(targetDir, ".flydocs", "version")
2008
+ join14(templateDir, ".flydocs", "version"),
2009
+ join14(targetDir, ".flydocs", "version")
1698
2010
  );
1699
- const manifestSrc = join12(templateDir, "manifest.json");
2011
+ const manifestSrc = join14(templateDir, "manifest.json");
1700
2012
  if (await pathExists(manifestSrc)) {
1701
- await copyFile(manifestSrc, join12(targetDir, ".flydocs", "manifest.json"));
2013
+ await copyFile(manifestSrc, join14(targetDir, ".flydocs", "manifest.json"));
1702
2014
  }
1703
- const changelogSrc = join12(templateDir, "CHANGELOG.md");
2015
+ const changelogSrc = join14(templateDir, "CHANGELOG.md");
1704
2016
  if (await pathExists(changelogSrc)) {
1705
- await copyFile(changelogSrc, join12(targetDir, ".flydocs", "CHANGELOG.md"));
2017
+ await copyFile(changelogSrc, join14(targetDir, ".flydocs", "CHANGELOG.md"));
1706
2018
  }
1707
2019
  printStatus(".flydocs/templates, hooks, version, manifest, changelog");
1708
- const configPath = join12(targetDir, ".flydocs", "config.json");
2020
+ const configPath = join14(targetDir, ".flydocs", "config.json");
1709
2021
  if (!await pathExists(configPath)) {
1710
2022
  const config = await createFreshConfig(templateDir, version, tier);
1711
2023
  await writeConfig(targetDir, config);
@@ -1730,48 +2042,84 @@ var init_install = __esm({
1730
2042
  await installOwnedSkills(templateDir, targetDir, tier);
1731
2043
  printStatus(`Skills installed (tier: ${tier})`);
1732
2044
  console.log();
1733
- console.log("Installing agents and commands...");
1734
- const claudeAgentsSrc = join12(templateDir, ".claude", "agents");
1735
- if (await pathExists(claudeAgentsSrc)) {
1736
- await copyDirectoryContents(
1737
- claudeAgentsSrc,
1738
- join12(targetDir, ".claude", "agents")
1739
- );
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
+ }
1740
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...");
1741
2096
  await copyDirectoryContents(
1742
- join12(templateDir, ".claude", "commands"),
1743
- join12(targetDir, ".claude", "commands")
2097
+ join14(templateDir, ".claude", "commands"),
2098
+ join14(targetDir, ".claude", "commands")
1744
2099
  );
1745
2100
  await copyFile(
1746
- join12(templateDir, ".claude", "CLAUDE.md"),
1747
- join12(targetDir, ".claude", "CLAUDE.md")
2101
+ join14(templateDir, ".claude", "CLAUDE.md"),
2102
+ join14(targetDir, ".claude", "CLAUDE.md")
1748
2103
  );
1749
2104
  await copyFile(
1750
- join12(templateDir, ".claude", "settings.json"),
1751
- join12(targetDir, ".claude", "settings.json")
2105
+ join14(templateDir, ".claude", "settings.json"),
2106
+ join14(targetDir, ".claude", "settings.json")
1752
2107
  );
1753
- printStatus(".claude/ (agents, commands, CLAUDE.md, settings)");
1754
- const cursorAgentsSrc = join12(templateDir, ".cursor", "agents");
1755
- if (await pathExists(cursorAgentsSrc)) {
1756
- await copyDirectoryContents(
1757
- cursorAgentsSrc,
1758
- join12(targetDir, ".cursor", "agents")
1759
- );
1760
- }
2108
+ printStatus(".claude/ (commands, CLAUDE.md, settings)");
1761
2109
  await copyDirectoryContents(
1762
- join12(templateDir, ".claude", "commands"),
1763
- join12(targetDir, ".cursor", "commands")
2110
+ join14(templateDir, ".claude", "commands"),
2111
+ join14(targetDir, ".cursor", "commands")
1764
2112
  );
1765
2113
  await copyFile(
1766
- join12(templateDir, ".cursor", "hooks.json"),
1767
- join12(targetDir, ".cursor", "hooks.json")
2114
+ join14(templateDir, ".cursor", "hooks.json"),
2115
+ join14(targetDir, ".cursor", "hooks.json")
1768
2116
  );
1769
- printStatus(".cursor/ (agents, commands, hooks)");
2117
+ printStatus(".cursor/ (commands, hooks)");
1770
2118
  await copyCursorRules(targetDir);
1771
2119
  printStatus(".cursor/rules/");
1772
2120
  await copyFile(
1773
- join12(templateDir, "AGENTS.md"),
1774
- join12(targetDir, "AGENTS.md")
2121
+ join14(templateDir, "AGENTS.md"),
2122
+ join14(targetDir, "AGENTS.md")
1775
2123
  );
1776
2124
  printStatus("AGENTS.md");
1777
2125
  await runManifestGeneration(targetDir);
@@ -1780,40 +2128,40 @@ var init_install = __esm({
1780
2128
  console.log("Installing project templates...");
1781
2129
  const userFiles = [
1782
2130
  {
1783
- src: join12(templateDir, "flydocs", "context", "project.md"),
1784
- dest: join12(targetDir, "flydocs", "context", "project.md"),
2131
+ src: join14(templateDir, "flydocs", "context", "project.md"),
2132
+ dest: join14(targetDir, "flydocs", "context", "project.md"),
1785
2133
  label: "flydocs/context/project.md"
1786
2134
  },
1787
2135
  {
1788
- src: join12(templateDir, "flydocs", "knowledge", "INDEX.md"),
1789
- dest: join12(targetDir, "flydocs", "knowledge", "INDEX.md"),
2136
+ src: join14(templateDir, "flydocs", "knowledge", "INDEX.md"),
2137
+ dest: join14(targetDir, "flydocs", "knowledge", "INDEX.md"),
1790
2138
  label: "flydocs/knowledge/INDEX.md"
1791
2139
  },
1792
2140
  {
1793
- src: join12(templateDir, "flydocs", "knowledge", "README.md"),
1794
- dest: join12(targetDir, "flydocs", "knowledge", "README.md"),
2141
+ src: join14(templateDir, "flydocs", "knowledge", "README.md"),
2142
+ dest: join14(targetDir, "flydocs", "knowledge", "README.md"),
1795
2143
  label: "flydocs/knowledge/README.md"
1796
2144
  },
1797
2145
  {
1798
- src: join12(
2146
+ src: join14(
1799
2147
  templateDir,
1800
2148
  "flydocs",
1801
2149
  "knowledge",
1802
2150
  "product",
1803
2151
  "personas.md"
1804
2152
  ),
1805
- dest: join12(targetDir, "flydocs", "knowledge", "product", "personas.md"),
2153
+ dest: join14(targetDir, "flydocs", "knowledge", "product", "personas.md"),
1806
2154
  label: "flydocs/knowledge/product/personas.md"
1807
2155
  },
1808
2156
  {
1809
- src: join12(
2157
+ src: join14(
1810
2158
  templateDir,
1811
2159
  "flydocs",
1812
2160
  "knowledge",
1813
2161
  "product",
1814
2162
  "user-flows.md"
1815
2163
  ),
1816
- dest: join12(
2164
+ dest: join14(
1817
2165
  targetDir,
1818
2166
  "flydocs",
1819
2167
  "knowledge",
@@ -1823,18 +2171,18 @@ var init_install = __esm({
1823
2171
  label: "flydocs/knowledge/product/user-flows.md"
1824
2172
  },
1825
2173
  {
1826
- src: join12(templateDir, "flydocs", "design-system", "README.md"),
1827
- 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"),
1828
2176
  label: "flydocs/design-system/README.md"
1829
2177
  },
1830
2178
  {
1831
- src: join12(
2179
+ src: join14(
1832
2180
  templateDir,
1833
2181
  "flydocs",
1834
2182
  "design-system",
1835
2183
  "component-patterns.md"
1836
2184
  ),
1837
- dest: join12(
2185
+ dest: join14(
1838
2186
  targetDir,
1839
2187
  "flydocs",
1840
2188
  "design-system",
@@ -1843,13 +2191,13 @@ var init_install = __esm({
1843
2191
  label: "flydocs/design-system/component-patterns.md"
1844
2192
  },
1845
2193
  {
1846
- src: join12(templateDir, "flydocs", "design-system", "token-mapping.md"),
1847
- 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"),
1848
2196
  label: "flydocs/design-system/token-mapping.md"
1849
2197
  },
1850
2198
  {
1851
- src: join12(templateDir, "flydocs", "README.md"),
1852
- dest: join12(targetDir, "flydocs", "README.md"),
2199
+ src: join14(templateDir, "flydocs", "README.md"),
2200
+ dest: join14(targetDir, "flydocs", "README.md"),
1853
2201
  label: "flydocs/README.md"
1854
2202
  }
1855
2203
  ];
@@ -1863,14 +2211,10 @@ var init_install = __esm({
1863
2211
  }
1864
2212
  }
1865
2213
  }
1866
- const envExampleSrc = join12(templateDir, ".env.example");
2214
+ const envExampleSrc = join14(templateDir, ".env.example");
1867
2215
  if (await pathExists(envExampleSrc)) {
1868
- const hasEnv = await pathExists(join12(targetDir, ".env"));
1869
- const hasEnvExample = await pathExists(join12(targetDir, ".env.example"));
1870
- if (!hasEnv && !hasEnvExample) {
1871
- await copyFile(envExampleSrc, join12(targetDir, ".env.example"));
1872
- printStatus(".env.example (new)");
1873
- }
2216
+ await copyFile(envExampleSrc, join14(targetDir, ".env.example"));
2217
+ printStatus(".env.example");
1874
2218
  }
1875
2219
  await ensureGitignore(targetDir);
1876
2220
  console.log();
@@ -1888,6 +2232,9 @@ var init_install = __esm({
1888
2232
  } else {
1889
2233
  printInfo("No framework detected in package.json");
1890
2234
  }
2235
+ await capture("install_skills_chosen", {
2236
+ stack_detected: stack.raw
2237
+ });
1891
2238
  console.log();
1892
2239
  console.log("Checking for deprecated files...");
1893
2240
  const deprecated = await scanDeprecated(targetDir);
@@ -1900,17 +2247,17 @@ var init_install = __esm({
1900
2247
  " 1. Run /flydocs-setup to configure your project",
1901
2248
  " 2. Start working with /start-session",
1902
2249
  "",
1903
- "Documentation: flydocs/README.md"
2250
+ "Docs: https://www.flydocs.ai/docs"
1904
2251
  ] : [
1905
2252
  `Tier: ${tier}`,
1906
2253
  `Version: ${version}`,
1907
2254
  "",
1908
2255
  "Next steps:",
1909
- " 1. Copy your LINEAR_API_KEY to .env",
1910
- " 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",
1911
2258
  " 3. Start working with /start-session",
1912
2259
  "",
1913
- "Documentation: flydocs/README.md"
2260
+ "Docs: https://www.flydocs.ai/docs"
1914
2261
  ];
1915
2262
  let copiedToClipboard = false;
1916
2263
  try {
@@ -1929,6 +2276,7 @@ var init_install = __esm({
1929
2276
  }
1930
2277
  printCompletionBox("Installation Complete!", nextSteps);
1931
2278
  printBetaCta();
2279
+ const detectedIdes = [];
1932
2280
  try {
1933
2281
  const { execSync: execSync2, spawn } = await import("child_process");
1934
2282
  const isInstalled = (cmd) => {
@@ -1964,6 +2312,7 @@ var init_install = __esm({
1964
2312
  passCommand: false
1965
2313
  });
1966
2314
  }
2315
+ detectedIdes.push(...ideOptions.map((o) => o.cmd));
1967
2316
  if (ideOptions.length === 1) {
1968
2317
  const ide = ideOptions[0];
1969
2318
  const launchConfirm = await confirm2({
@@ -2022,6 +2371,9 @@ var init_install = __esm({
2022
2371
  }
2023
2372
  } catch {
2024
2373
  }
2374
+ await capture("install_ide_detected", { ides: detectedIdes });
2375
+ await capture("install_completed", { tier, version });
2376
+ await flush();
2025
2377
  try {
2026
2378
  const updateResult = await checkForUpdate();
2027
2379
  if (updateResult) {
@@ -2040,10 +2392,10 @@ __export(update_exports, {
2040
2392
  default: () => update_default
2041
2393
  });
2042
2394
  import { defineCommand as defineCommand2 } from "citty";
2043
- import { resolve as resolve3, join as join13 } from "path";
2044
- 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";
2045
2397
  import { select as select2, text, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
2046
- import pc6 from "picocolors";
2398
+ import pc7 from "picocolors";
2047
2399
  var update_default;
2048
2400
  var init_update = __esm({
2049
2401
  "src/commands/update.ts"() {
@@ -2060,6 +2412,7 @@ var init_update = __esm({
2060
2412
  init_gitignore();
2061
2413
  init_post_install();
2062
2414
  init_update_check();
2415
+ init_telemetry();
2063
2416
  update_default = defineCommand2({
2064
2417
  meta: {
2065
2418
  name: "update",
@@ -2077,7 +2430,7 @@ var init_update = __esm({
2077
2430
  },
2078
2431
  tier: {
2079
2432
  type: "string",
2080
- description: "Change tier: 'local' (free) or 'cloud' (Linear)"
2433
+ description: "Change tier: 'local' (free) or 'cloud' (managed)"
2081
2434
  },
2082
2435
  force: {
2083
2436
  type: "boolean",
@@ -2102,6 +2455,7 @@ var init_update = __esm({
2102
2455
  printBanner(version);
2103
2456
  printInfo("Running in update mode \u2014 framework files will be refreshed");
2104
2457
  console.log();
2458
+ await capture("update_started", { template_version: version });
2105
2459
  let targetDir;
2106
2460
  if (args.path) {
2107
2461
  targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -2146,9 +2500,9 @@ var init_update = __esm({
2146
2500
  }
2147
2501
  targetDir = resolve3(targetDir);
2148
2502
  process.chdir(targetDir);
2149
- const hasVersion = await pathExists(join13(targetDir, ".flydocs", "version"));
2503
+ const hasVersion = await pathExists(join15(targetDir, ".flydocs", "version"));
2150
2504
  const hasConfig = await pathExists(
2151
- join13(targetDir, ".flydocs", "config.json")
2505
+ join15(targetDir, ".flydocs", "config.json")
2152
2506
  );
2153
2507
  if (!hasVersion && !hasConfig) {
2154
2508
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -2159,8 +2513,8 @@ var init_update = __esm({
2159
2513
  console.log();
2160
2514
  let currentVersion = "0.1.0";
2161
2515
  if (hasVersion) {
2162
- const vContent = await readFile8(
2163
- join13(targetDir, ".flydocs", "version"),
2516
+ const vContent = await readFile10(
2517
+ join15(targetDir, ".flydocs", "version"),
2164
2518
  "utf-8"
2165
2519
  );
2166
2520
  currentVersion = vContent.trim();
@@ -2186,12 +2540,18 @@ var init_update = __esm({
2186
2540
  process.exit(0);
2187
2541
  }
2188
2542
  }
2543
+ await capture("update_version_compared", {
2544
+ current_version: currentVersion,
2545
+ target_version: version,
2546
+ version_status: versionStatus,
2547
+ force: args.force
2548
+ });
2189
2549
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
2190
2550
  console.log();
2191
- const changelogPath = join13(templateDir, "CHANGELOG.md");
2551
+ const changelogPath = join15(templateDir, "CHANGELOG.md");
2192
2552
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
2193
2553
  if (whatsNew.length > 0) {
2194
- console.log(pc6.cyan("What's new:"));
2554
+ console.log(pc7.cyan("What's new:"));
2195
2555
  console.log();
2196
2556
  for (const entry of whatsNew) {
2197
2557
  console.log(` ${entry}`);
@@ -2200,23 +2560,23 @@ var init_update = __esm({
2200
2560
  }
2201
2561
  const now = /* @__PURE__ */ new Date();
2202
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")}`;
2203
- const backupDir = join13(targetDir, ".flydocs", `backup-${ts}`);
2204
- await mkdir5(backupDir, { recursive: true });
2563
+ const backupDir = join15(targetDir, ".flydocs", `backup-${ts}`);
2564
+ await mkdir8(backupDir, { recursive: true });
2205
2565
  if (hasConfig) {
2206
2566
  await cp2(
2207
- join13(targetDir, ".flydocs", "config.json"),
2208
- join13(backupDir, "config.json")
2567
+ join15(targetDir, ".flydocs", "config.json"),
2568
+ join15(backupDir, "config.json")
2209
2569
  );
2210
2570
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
2211
2571
  }
2212
2572
  try {
2213
- const flydocsDir = join13(targetDir, ".flydocs");
2573
+ const flydocsDir = join15(targetDir, ".flydocs");
2214
2574
  const entries = await readdir3(flydocsDir);
2215
2575
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
2216
2576
  if (backups.length > 3) {
2217
2577
  const toRemove = backups.slice(0, backups.length - 3);
2218
2578
  for (const old of toRemove) {
2219
- await rm4(join13(flydocsDir, old), { recursive: true, force: true });
2579
+ await rm4(join15(flydocsDir, old), { recursive: true, force: true });
2220
2580
  }
2221
2581
  }
2222
2582
  } catch {
@@ -2253,74 +2613,118 @@ var init_update = __esm({
2253
2613
  }
2254
2614
  console.log("Replacing framework directories...");
2255
2615
  await replaceDirectory(
2256
- join13(templateDir, ".flydocs", "templates"),
2257
- join13(targetDir, ".flydocs", "templates")
2616
+ join15(templateDir, ".flydocs", "templates"),
2617
+ join15(targetDir, ".flydocs", "templates")
2258
2618
  );
2259
2619
  await replaceDirectory(
2260
- join13(templateDir, ".flydocs", "hooks"),
2261
- join13(targetDir, ".flydocs", "hooks")
2620
+ join15(templateDir, ".flydocs", "hooks"),
2621
+ join15(targetDir, ".flydocs", "hooks")
2262
2622
  );
2263
2623
  await replaceDirectory(
2264
- join13(templateDir, ".flydocs", "scripts"),
2265
- join13(targetDir, ".flydocs", "scripts")
2624
+ join15(templateDir, ".flydocs", "scripts"),
2625
+ join15(targetDir, ".flydocs", "scripts")
2266
2626
  );
2267
2627
  printStatus(".flydocs/templates, hooks, scripts");
2268
- const claudeAgentsSrc = join13(templateDir, ".claude", "agents");
2269
- if (await pathExists(claudeAgentsSrc)) {
2270
- await copyDirectoryContents(
2271
- claudeAgentsSrc,
2272
- 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"
2273
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
+ }
2274
2659
  }
2275
- printStatus(".claude/agents");
2276
- await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2277
- printStatus(`.claude/skills (tier: ${effectiveTier})`);
2278
- const cursorAgentsSrc = join13(templateDir, ".cursor", "agents");
2279
- if (await pathExists(cursorAgentsSrc)) {
2280
- await copyDirectoryContents(
2281
- cursorAgentsSrc,
2282
- 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)"
2283
2679
  );
2680
+ } else {
2681
+ printInfo("Skipped sub-agents");
2284
2682
  }
2285
- printStatus(".cursor/agents");
2683
+ await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2684
+ printStatus(`.claude/skills (tier: ${effectiveTier})`);
2286
2685
  console.log();
2287
2686
  console.log("Replacing framework files...");
2288
2687
  await copyFile(
2289
- join13(templateDir, ".claude", "CLAUDE.md"),
2290
- join13(targetDir, ".claude", "CLAUDE.md")
2688
+ join15(templateDir, ".claude", "CLAUDE.md"),
2689
+ join15(targetDir, ".claude", "CLAUDE.md")
2291
2690
  );
2292
2691
  await copyFile(
2293
- join13(templateDir, ".claude", "settings.json"),
2294
- join13(targetDir, ".claude", "settings.json")
2692
+ join15(templateDir, ".claude", "settings.json"),
2693
+ join15(targetDir, ".claude", "settings.json")
2295
2694
  );
2296
2695
  printStatus(".claude/CLAUDE.md, settings.json");
2297
2696
  await copyDirectoryContents(
2298
- join13(templateDir, ".claude", "commands"),
2299
- join13(targetDir, ".claude", "commands")
2697
+ join15(templateDir, ".claude", "commands"),
2698
+ join15(targetDir, ".claude", "commands")
2300
2699
  );
2301
2700
  await copyDirectoryContents(
2302
- join13(templateDir, ".claude", "commands"),
2303
- join13(targetDir, ".cursor", "commands")
2701
+ join15(templateDir, ".claude", "commands"),
2702
+ join15(targetDir, ".cursor", "commands")
2304
2703
  );
2305
2704
  printStatus(".claude/commands, .cursor/commands");
2306
- const skillsReadmeSrc = join13(templateDir, ".claude", "skills", "README.md");
2705
+ const skillsReadmeSrc = join15(templateDir, ".claude", "skills", "README.md");
2307
2706
  if (await pathExists(skillsReadmeSrc)) {
2308
2707
  await copyFile(
2309
2708
  skillsReadmeSrc,
2310
- join13(targetDir, ".claude", "skills", "README.md")
2709
+ join15(targetDir, ".claude", "skills", "README.md")
2311
2710
  );
2312
2711
  }
2313
2712
  printStatus(".claude/skills/README.md");
2314
2713
  await copyFile(
2315
- join13(templateDir, ".cursor", "hooks.json"),
2316
- join13(targetDir, ".cursor", "hooks.json")
2714
+ join15(templateDir, ".cursor", "hooks.json"),
2715
+ join15(targetDir, ".cursor", "hooks.json")
2317
2716
  );
2318
2717
  printStatus(".cursor/hooks.json");
2319
2718
  await copyFile(
2320
- join13(templateDir, "AGENTS.md"),
2321
- join13(targetDir, "AGENTS.md")
2719
+ join15(templateDir, "AGENTS.md"),
2720
+ join15(targetDir, "AGENTS.md")
2322
2721
  );
2323
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
+ }
2324
2728
  await runManifestGeneration(targetDir);
2325
2729
  await runContextGraphBuild(targetDir);
2326
2730
  console.log();
@@ -2343,18 +2747,18 @@ var init_update = __esm({
2343
2747
  printWarning("Config merge failed \u2014 config.json preserved as-is");
2344
2748
  }
2345
2749
  await copyFile(
2346
- join13(templateDir, ".flydocs", "version"),
2347
- join13(targetDir, ".flydocs", "version")
2750
+ join15(templateDir, ".flydocs", "version"),
2751
+ join15(targetDir, ".flydocs", "version")
2348
2752
  );
2349
2753
  printStatus(`.flydocs/version \u2192 ${version}`);
2350
- const clSrc = join13(templateDir, "CHANGELOG.md");
2754
+ const clSrc = join15(templateDir, "CHANGELOG.md");
2351
2755
  if (await pathExists(clSrc)) {
2352
- await copyFile(clSrc, join13(targetDir, ".flydocs", "CHANGELOG.md"));
2756
+ await copyFile(clSrc, join15(targetDir, ".flydocs", "CHANGELOG.md"));
2353
2757
  printStatus(".flydocs/CHANGELOG.md");
2354
2758
  }
2355
- const mfSrc = join13(templateDir, "manifest.json");
2759
+ const mfSrc = join15(templateDir, "manifest.json");
2356
2760
  if (await pathExists(mfSrc)) {
2357
- await copyFile(mfSrc, join13(targetDir, ".flydocs", "manifest.json"));
2761
+ await copyFile(mfSrc, join15(targetDir, ".flydocs", "manifest.json"));
2358
2762
  printStatus(".flydocs/manifest.json");
2359
2763
  }
2360
2764
  console.log();
@@ -2390,6 +2794,12 @@ var init_update = __esm({
2390
2794
  `Backup: .flydocs/backup-${ts}/`
2391
2795
  ]);
2392
2796
  printBetaCta();
2797
+ await capture("update_completed", {
2798
+ current_version: currentVersion,
2799
+ version,
2800
+ tier: effectiveTier
2801
+ });
2802
+ await flush();
2393
2803
  try {
2394
2804
  const updateResult = await checkForUpdate();
2395
2805
  if (updateResult) {
@@ -2402,33 +2812,352 @@ var init_update = __esm({
2402
2812
  }
2403
2813
  });
2404
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
+
2405
3134
  // src/commands/setup.ts
2406
3135
  var setup_exports = {};
2407
3136
  __export(setup_exports, {
2408
3137
  default: () => setup_default
2409
3138
  });
2410
- import { defineCommand as defineCommand3 } from "citty";
2411
- import pc7 from "picocolors";
3139
+ import { defineCommand as defineCommand4 } from "citty";
3140
+ import pc9 from "picocolors";
2412
3141
  var setup_default;
2413
3142
  var init_setup = __esm({
2414
3143
  "src/commands/setup.ts"() {
2415
3144
  "use strict";
2416
- setup_default = defineCommand3({
3145
+ setup_default = defineCommand4({
2417
3146
  meta: {
2418
3147
  name: "setup",
2419
3148
  description: "Configure FlyDocs settings for this project"
2420
3149
  },
2421
3150
  run() {
2422
3151
  console.log();
2423
- console.log(` ${pc7.bold("FlyDocs Setup")}`);
3152
+ console.log(` ${pc9.bold("FlyDocs Setup")}`);
2424
3153
  console.log();
2425
3154
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
2426
3155
  console.log();
2427
3156
  console.log(
2428
- ` ${pc7.cyan("Claude Code:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3157
+ ` ${pc9.cyan("Claude Code:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2429
3158
  );
2430
3159
  console.log(
2431
- ` ${pc7.cyan("Cursor:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3160
+ ` ${pc9.cyan("Cursor:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2432
3161
  );
2433
3162
  console.log();
2434
3163
  console.log(` This configures your project context, detects your stack,`);
@@ -2444,14 +3173,14 @@ var skills_exports = {};
2444
3173
  __export(skills_exports, {
2445
3174
  default: () => skills_default
2446
3175
  });
2447
- import { defineCommand as defineCommand4 } from "citty";
2448
- import pc8 from "picocolors";
3176
+ import { defineCommand as defineCommand5 } from "citty";
3177
+ import pc10 from "picocolors";
2449
3178
  var list, search, add, remove, skills_default;
2450
3179
  var init_skills2 = __esm({
2451
3180
  "src/commands/skills.ts"() {
2452
3181
  "use strict";
2453
3182
  init_skill_manager();
2454
- list = defineCommand4({
3183
+ list = defineCommand5({
2455
3184
  meta: {
2456
3185
  name: "list",
2457
3186
  description: "List installed skills"
@@ -2467,26 +3196,26 @@ var init_skills2 = __esm({
2467
3196
  console.log(`${total} skill(s) installed:`);
2468
3197
  if (result.platform.length > 0) {
2469
3198
  console.log();
2470
- console.log(pc8.bold("Platform"));
3199
+ console.log(pc10.bold("Platform"));
2471
3200
  for (const skill of result.platform) {
2472
3201
  console.log(
2473
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3202
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2474
3203
  );
2475
3204
  }
2476
3205
  }
2477
3206
  if (result.community.length > 0) {
2478
3207
  console.log();
2479
- console.log(pc8.bold("Community"));
3208
+ console.log(pc10.bold("Community"));
2480
3209
  for (const skill of result.community) {
2481
3210
  console.log(
2482
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3211
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2483
3212
  );
2484
3213
  }
2485
3214
  }
2486
3215
  console.log();
2487
3216
  }
2488
3217
  });
2489
- search = defineCommand4({
3218
+ search = defineCommand5({
2490
3219
  meta: {
2491
3220
  name: "search",
2492
3221
  description: "Search community skills"
@@ -2502,24 +3231,24 @@ var init_skills2 = __esm({
2502
3231
  const results = await searchCatalog(args.keyword);
2503
3232
  if (results.length === 0) {
2504
3233
  console.log(`No skills found for "${args.keyword}".`);
2505
- console.log(` Browse the catalog at: ${pc8.cyan("https://skills.sh/")}`);
3234
+ console.log(` Browse the catalog at: ${pc10.cyan("https://skills.sh/")}`);
2506
3235
  return;
2507
3236
  }
2508
3237
  console.log();
2509
3238
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
2510
3239
  console.log();
2511
3240
  for (const skill of results) {
2512
- console.log(` ${pc8.bold(skill.name)}`);
3241
+ console.log(` ${pc10.bold(skill.name)}`);
2513
3242
  console.log(` ${skill.description}`);
2514
- console.log(` ${pc8.dim(skill.repo)}`);
3243
+ console.log(` ${pc10.dim(skill.repo)}`);
2515
3244
  if (skill.tags.length > 0) {
2516
- console.log(` ${pc8.dim(skill.tags.join(", "))}`);
3245
+ console.log(` ${pc10.dim(skill.tags.join(", "))}`);
2517
3246
  }
2518
3247
  console.log();
2519
3248
  }
2520
3249
  }
2521
3250
  });
2522
- add = defineCommand4({
3251
+ add = defineCommand5({
2523
3252
  meta: {
2524
3253
  name: "add",
2525
3254
  description: "Install a community skill"
@@ -2535,7 +3264,7 @@ var init_skills2 = __esm({
2535
3264
  await addSkill(process.cwd(), args.source);
2536
3265
  }
2537
3266
  });
2538
- remove = defineCommand4({
3267
+ remove = defineCommand5({
2539
3268
  meta: {
2540
3269
  name: "remove",
2541
3270
  description: "Remove an installed community skill"
@@ -2551,7 +3280,7 @@ var init_skills2 = __esm({
2551
3280
  await removeSkill(process.cwd(), args.name);
2552
3281
  }
2553
3282
  });
2554
- skills_default = defineCommand4({
3283
+ skills_default = defineCommand5({
2555
3284
  meta: {
2556
3285
  name: "skills",
2557
3286
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -2571,11 +3300,71 @@ var connect_exports = {};
2571
3300
  __export(connect_exports, {
2572
3301
  default: () => connect_default
2573
3302
  });
2574
- import { defineCommand as defineCommand5 } from "citty";
2575
- import { text as text2, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
2576
- import pc9 from "picocolors";
2577
- import { readFile as readFile9, writeFile as writeFile5, appendFile as appendFile2 } from "fs/promises";
2578
- 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
+ }
2579
3368
  var connect_default;
2580
3369
  var init_connect = __esm({
2581
3370
  "src/commands/connect.ts"() {
@@ -2584,10 +3373,10 @@ var init_connect = __esm({
2584
3373
  init_fs_ops();
2585
3374
  init_template();
2586
3375
  init_ui();
2587
- connect_default = defineCommand5({
3376
+ connect_default = defineCommand6({
2588
3377
  meta: {
2589
3378
  name: "connect",
2590
- description: "Connect FlyDocs to a cloud provider (Linear)"
3379
+ description: "Connect FlyDocs to a cloud provider"
2591
3380
  },
2592
3381
  args: {
2593
3382
  path: {
@@ -2604,16 +3393,16 @@ var init_connect = __esm({
2604
3393
  },
2605
3394
  key: {
2606
3395
  type: "string",
2607
- description: "Linear API key (skips prompt)"
3396
+ description: "API key (fdk_ for relay, lin_api_ for direct Linear)"
2608
3397
  }
2609
3398
  },
2610
3399
  async run({ args }) {
2611
3400
  const targetDir = args.path ?? process.cwd();
2612
- const configPath = join14(targetDir, ".flydocs", "config.json");
3401
+ const configPath = join17(targetDir, ".flydocs", "config.json");
2613
3402
  if (!await pathExists(configPath)) {
2614
3403
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
2615
3404
  console.log(
2616
- ` Run ${pc9.cyan("flydocs")} first to install FlyDocs in this project.`
3405
+ ` Run ${pc11.cyan("flydocs")} first to install FlyDocs in this project.`
2617
3406
  );
2618
3407
  process.exit(1);
2619
3408
  }
@@ -2621,87 +3410,85 @@ var init_connect = __esm({
2621
3410
  if (config.tier === "cloud") {
2622
3411
  printInfo("This project is already connected to the cloud tier.");
2623
3412
  console.log();
2624
- const reconnect = await confirm4({
3413
+ const reconnect = await confirm5({
2625
3414
  message: "Want to update your API key?"
2626
3415
  });
2627
- if (isCancel5(reconnect) || !reconnect) {
3416
+ if (isCancel6(reconnect) || !reconnect) {
2628
3417
  console.log(` No changes made.`);
2629
3418
  return;
2630
3419
  }
2631
3420
  }
2632
3421
  console.log();
2633
- console.log(` ${pc9.bold("Connect to Linear")}`);
3422
+ console.log(` ${pc11.bold("Connect to FlyDocs Cloud")}`);
2634
3423
  console.log();
2635
3424
  console.log(
2636
- ` ${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")}`
2637
3429
  );
2638
3430
  console.log();
2639
3431
  let apiKey = args.key ?? "";
2640
3432
  if (!apiKey) {
2641
3433
  const keyInput = await text2({
2642
- message: "Enter your Linear API key",
2643
- placeholder: "lin_api_...",
3434
+ message: "Enter your API key",
3435
+ placeholder: "fdk_... or lin_api_...",
2644
3436
  validate(value) {
2645
3437
  if (!value.trim()) return "API key is required";
2646
- if (!value.startsWith("lin_api_"))
2647
- 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)";
2648
3441
  return void 0;
2649
3442
  }
2650
3443
  });
2651
- if (isCancel5(keyInput)) {
2652
- cancel4("Connection cancelled.");
3444
+ if (isCancel6(keyInput)) {
3445
+ cancel5("Connection cancelled.");
2653
3446
  process.exit(0);
2654
3447
  }
2655
- apiKey = keyInput;
3448
+ apiKey = keyInput.trim();
2656
3449
  }
2657
- printInfo("Validating API key...");
2658
- try {
2659
- const response = await fetch("https://api.linear.app/graphql", {
2660
- method: "POST",
2661
- headers: {
2662
- Authorization: apiKey,
2663
- "Content-Type": "application/json"
2664
- },
2665
- body: JSON.stringify({ query: "{ viewer { id name email } }" }),
2666
- signal: AbortSignal.timeout(15e3)
2667
- });
2668
- if (!response.ok) {
2669
- throw new Error(`HTTP ${response.status}`);
2670
- }
2671
- const data = await response.json();
2672
- if (!data.data?.viewer) {
2673
- throw new Error("Invalid response");
2674
- }
2675
- const viewer = data.data.viewer;
2676
- printStatus(`Authenticated as ${pc9.bold(viewer.name)} (${viewer.email})`);
2677
- } catch {
2678
- printError("Invalid API key or network error.");
2679
- 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.");
2680
3453
  process.exit(1);
2681
3454
  }
2682
- const envPath = join14(targetDir, ".env");
2683
- const envLocalPath = join14(targetDir, ".env.local");
2684
- const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
2685
- if (await pathExists(targetEnvPath)) {
2686
- const envContent = await readFile9(targetEnvPath, "utf-8");
2687
- if (envContent.includes("LINEAR_API_KEY=")) {
2688
- const updated = envContent.replace(
2689
- /LINEAR_API_KEY=.*/,
2690
- `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."
2691
3468
  );
2692
- await writeFile5(targetEnvPath, updated, "utf-8");
2693
- } else {
2694
- await appendFile2(targetEnvPath, `
2695
- LINEAR_API_KEY=${apiKey}
2696
- `);
3469
+ process.exit(1);
2697
3470
  }
3471
+ const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
3472
+ printStatus(`API key stored in ${pc11.dim(envFile)}`);
2698
3473
  } else {
2699
- await writeFile5(targetEnvPath, `LINEAR_API_KEY=${apiKey}
2700
- `, "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)}`);
2701
3491
  }
2702
- printStatus(
2703
- `API key stored in ${pc9.dim(targetEnvPath === envLocalPath ? ".env.local" : ".env")}`
2704
- );
2705
3492
  const wasLocal = config.tier === "local";
2706
3493
  config.tier = "cloud";
2707
3494
  config.provider = config.provider ?? { type: "linear", teamId: null };
@@ -2713,16 +3500,16 @@ LINEAR_API_KEY=${apiKey}
2713
3500
  const templateDir = await resolveTemplatePath(
2714
3501
  args["local-source"] || void 0
2715
3502
  );
2716
- const templateSkillsDir = join14(templateDir, ".claude", "skills");
2717
- const skillsDir = join14(targetDir, ".claude", "skills");
3503
+ const templateSkillsDir = join17(templateDir, ".claude", "skills");
3504
+ const skillsDir = join17(targetDir, ".claude", "skills");
2718
3505
  await replaceDirectory(
2719
- join14(templateSkillsDir, "flydocs-cloud"),
2720
- join14(skillsDir, "flydocs-cloud")
3506
+ join17(templateSkillsDir, "flydocs-cloud"),
3507
+ join17(skillsDir, "flydocs-cloud")
2721
3508
  );
2722
- const { rm: rm5 } = await import("fs/promises");
2723
- const localSkillDir = join14(skillsDir, "flydocs-local");
3509
+ const { rm: rm6 } = await import("fs/promises");
3510
+ const localSkillDir = join17(skillsDir, "flydocs-local");
2724
3511
  if (await pathExists(localSkillDir)) {
2725
- await rm5(localSkillDir, { recursive: true, force: true });
3512
+ await rm6(localSkillDir, { recursive: true, force: true });
2726
3513
  }
2727
3514
  printStatus("Cloud mechanism skill installed");
2728
3515
  } catch {
@@ -2733,14 +3520,14 @@ LINEAR_API_KEY=${apiKey}
2733
3520
  }
2734
3521
  console.log();
2735
3522
  console.log(
2736
- ` ${pc9.bold("Connected!")} Your project now syncs with Linear.`
3523
+ ` ${pc11.bold("Connected!")} Your project is now on the cloud tier.`
2737
3524
  );
2738
3525
  console.log();
2739
3526
  console.log(` Next steps:`);
2740
3527
  console.log(
2741
- ` 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`
2742
3529
  );
2743
- console.log(` 2. Run ${pc9.cyan("/start-session")} to begin working`);
3530
+ console.log(` 2. Run ${pc11.cyan("/start-session")} to begin working`);
2744
3531
  console.log();
2745
3532
  }
2746
3533
  });
@@ -2752,15 +3539,15 @@ var upgrade_exports = {};
2752
3539
  __export(upgrade_exports, {
2753
3540
  default: () => upgrade_default
2754
3541
  });
2755
- import { defineCommand as defineCommand6 } from "citty";
2756
- import pc10 from "picocolors";
3542
+ import { defineCommand as defineCommand7 } from "citty";
3543
+ import pc12 from "picocolors";
2757
3544
  var upgrade_default;
2758
3545
  var init_upgrade = __esm({
2759
3546
  "src/commands/upgrade.ts"() {
2760
3547
  "use strict";
2761
3548
  init_config();
2762
3549
  init_fs_ops();
2763
- upgrade_default = defineCommand6({
3550
+ upgrade_default = defineCommand7({
2764
3551
  meta: {
2765
3552
  name: "upgrade",
2766
3553
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -2789,38 +3576,40 @@ var init_upgrade = __esm({
2789
3576
  console.log();
2790
3577
  if (currentTier === "cloud") {
2791
3578
  console.log(
2792
- ` ${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.`
2793
3580
  );
2794
3581
  console.log();
2795
3582
  console.log(
2796
3583
  ` Your issues sync with Linear via the cloud mechanism skill.`
2797
3584
  );
2798
3585
  console.log(
2799
- ` Run ${pc10.cyan("flydocs connect")} to update your connection settings.`
3586
+ ` Run ${pc12.cyan("flydocs connect")} to update your connection settings.`
2800
3587
  );
2801
3588
  console.log();
2802
3589
  return;
2803
3590
  }
2804
- console.log(` ${pc10.bold("FlyDocs Cloud Tier")}`);
3591
+ console.log(` ${pc12.bold("FlyDocs Cloud Tier")}`);
2805
3592
  console.log();
2806
- console.log(` You're currently on the ${pc10.yellow("local")} tier.`);
3593
+ console.log(` You're currently on the ${pc12.yellow("local")} tier.`);
2807
3594
  console.log(` Upgrade to cloud for:`);
2808
3595
  console.log();
2809
3596
  console.log(
2810
- ` ${pc10.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
3597
+ ` ${pc12.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
2811
3598
  );
2812
- console.log(` ${pc10.cyan("\u2192")} Project milestones and cycle management`);
2813
- console.log(` ${pc10.cyan("\u2192")} Team assignment and priority tracking`);
2814
- console.log(` ${pc10.cyan("\u2192")} Project health updates and dashboards`);
2815
- 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:")}`);
2816
3605
  console.log();
2817
- 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`);
2818
3608
  console.log();
2819
- console.log(` 1. Sign up at ${pc10.cyan("https://www.flydocs.ai")}`);
2820
- console.log(` 2. Get your Linear API key from Linear \u2192 Settings \u2192 API`);
2821
3609
  console.log(
2822
- ` 3. Run ${pc10.cyan("flydocs connect")} to connect your project`
3610
+ ` Option 2: Run ${pc12.cyan("flydocs connect")} from terminal`
2823
3611
  );
3612
+ console.log(` Quick tier swap (no issue migration)`);
2824
3613
  console.log();
2825
3614
  }
2826
3615
  });
@@ -2832,23 +3621,23 @@ var self_update_exports = {};
2832
3621
  __export(self_update_exports, {
2833
3622
  default: () => self_update_default
2834
3623
  });
2835
- import { defineCommand as defineCommand7 } from "citty";
3624
+ import { defineCommand as defineCommand8 } from "citty";
2836
3625
  import { execSync } from "child_process";
2837
- import pc11 from "picocolors";
3626
+ import pc13 from "picocolors";
2838
3627
  var self_update_default;
2839
3628
  var init_self_update = __esm({
2840
3629
  "src/commands/self-update.ts"() {
2841
3630
  "use strict";
2842
3631
  init_constants();
2843
3632
  init_ui();
2844
- self_update_default = defineCommand7({
3633
+ self_update_default = defineCommand8({
2845
3634
  meta: {
2846
3635
  name: "self-update",
2847
3636
  description: "Update FlyDocs CLI to the latest version"
2848
3637
  },
2849
3638
  async run() {
2850
3639
  console.log();
2851
- console.log(` Updating ${pc11.cyan(PACKAGE_NAME)}...`);
3640
+ console.log(` Updating ${pc13.cyan(PACKAGE_NAME)}...`);
2852
3641
  console.log();
2853
3642
  try {
2854
3643
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -2868,17 +3657,117 @@ var init_self_update = __esm({
2868
3657
  }
2869
3658
  });
2870
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
+
2871
3758
  // src/cli.ts
2872
3759
  init_constants();
2873
- import { defineCommand as defineCommand8, runMain } from "citty";
3760
+ import { defineCommand as defineCommand10, runMain } from "citty";
2874
3761
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
2875
3762
  "install",
2876
3763
  "update",
3764
+ "uninstall",
2877
3765
  "setup",
2878
3766
  "skills",
2879
3767
  "connect",
2880
3768
  "upgrade",
2881
- "self-update"
3769
+ "self-update",
3770
+ "telemetry"
2882
3771
  ]);
2883
3772
  var userArgs = process.argv.slice(2);
2884
3773
  var hasMetaFlag = userArgs.some(
@@ -2888,7 +3777,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
2888
3777
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
2889
3778
  process.argv.splice(2, 0, "install");
2890
3779
  }
2891
- var main = defineCommand8({
3780
+ var main = defineCommand10({
2892
3781
  meta: {
2893
3782
  name: CLI_NAME,
2894
3783
  version: CLI_VERSION,
@@ -2897,11 +3786,13 @@ var main = defineCommand8({
2897
3786
  subCommands: {
2898
3787
  install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
2899
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),
2900
3790
  setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
2901
3791
  skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
2902
3792
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
2903
3793
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
2904
- "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)
2905
3796
  }
2906
3797
  });
2907
3798
  runMain(main);