@flydocs/cli 0.5.0-beta.9 → 0.6.0-alpha.10

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 (74) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +1553 -414
  3. package/package.json +1 -1
  4. package/template/.claude/CLAUDE.md +11 -9
  5. package/template/.claude/agents/implementation-agent.md +0 -1
  6. package/template/.claude/agents/pm-agent.md +0 -1
  7. package/template/.claude/agents/research-agent.md +0 -1
  8. package/template/.claude/agents/review-agent.md +0 -1
  9. package/template/.claude/commands/flydocs-setup.md +202 -35
  10. package/template/.claude/commands/flydocs-upgrade.md +342 -0
  11. package/template/.claude/commands/knowledge.md +61 -0
  12. package/template/.claude/skills/flydocs-cloud/SKILL.md +66 -39
  13. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +5 -5
  14. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +17 -27
  15. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +14 -30
  16. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +10 -32
  17. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +15 -25
  18. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +42 -59
  19. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +26 -37
  20. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +24 -31
  21. package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +39 -0
  22. package/template/.claude/skills/flydocs-cloud/scripts/delete_milestone.py +21 -0
  23. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +17 -22
  24. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +113 -169
  25. package/template/.claude/skills/flydocs-cloud/scripts/get_estimate_scale.py +23 -0
  26. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +6 -59
  27. package/template/.claude/skills/flydocs-cloud/scripts/link.py +16 -35
  28. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +21 -28
  29. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +16 -77
  30. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +19 -0
  31. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +21 -33
  32. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +24 -38
  33. package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +19 -0
  34. package/template/.claude/skills/flydocs-cloud/scripts/list_statuses.py +19 -0
  35. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +19 -0
  36. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +10 -19
  37. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +36 -50
  38. package/template/.claude/skills/flydocs-cloud/scripts/refresh_labels.py +87 -0
  39. package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +38 -0
  40. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +68 -0
  41. package/template/.claude/skills/flydocs-cloud/scripts/set_preferences.py +49 -0
  42. package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +46 -0
  43. package/template/.claude/skills/flydocs-cloud/scripts/set_status_mapping.py +69 -0
  44. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +42 -0
  45. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +11 -52
  46. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +16 -27
  47. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +43 -54
  48. package/template/.claude/skills/flydocs-cloud/scripts/update_milestone.py +42 -0
  49. package/template/.claude/skills/flydocs-cloud/scripts/validate_setup.py +139 -0
  50. package/template/.claude/skills/flydocs-local/SKILL.md +1 -1
  51. package/template/.claude/skills/flydocs-local/scripts/assign.py +13 -4
  52. package/template/.claude/skills/flydocs-local/scripts/flydocs_api.py +5 -2
  53. package/template/.claude/skills/flydocs-workflow/SKILL.md +23 -18
  54. package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
  55. package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +105 -0
  56. package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
  57. package/template/.claude/skills/flydocs-workflow/session.md +24 -16
  58. package/template/.claude/skills/flydocs-workflow/stages/capture.md +8 -3
  59. package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
  60. package/template/.claude/skills/flydocs-workflow/stages/implement.md +28 -4
  61. package/template/.claude/skills/flydocs-workflow/stages/refine.md +20 -4
  62. package/template/.claude/skills/flydocs-workflow/stages/review.md +14 -2
  63. package/template/.env.example +16 -7
  64. package/template/.flydocs/config.json +4 -18
  65. package/template/.flydocs/hooks/prompt-submit.py +27 -4
  66. package/template/.flydocs/version +1 -1
  67. package/template/AGENTS.md +8 -8
  68. package/template/CHANGELOG.md +183 -0
  69. package/template/flydocs/knowledge/INDEX.md +38 -53
  70. package/template/flydocs/knowledge/README.md +60 -9
  71. package/template/flydocs/knowledge/templates/decision.md +47 -0
  72. package/template/flydocs/knowledge/templates/feature.md +35 -0
  73. package/template/flydocs/knowledge/templates/note.md +25 -0
  74. package/template/manifest.json +12 -4
package/dist/cli.js CHANGED
@@ -11,13 +11,14 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/lib/constants.ts
13
13
  import pc from "picocolors";
14
- var CLI_VERSION, CLI_NAME, PACKAGE_NAME;
14
+ var CLI_VERSION, CLI_NAME, PACKAGE_NAME, POSTHOG_API_KEY;
15
15
  var init_constants = __esm({
16
16
  "src/lib/constants.ts"() {
17
17
  "use strict";
18
- CLI_VERSION = "0.5.0-beta.9";
18
+ CLI_VERSION = "0.6.0-alpha.10";
19
19
  CLI_NAME = "flydocs";
20
20
  PACKAGE_NAME = "@flydocs/cli";
21
+ POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
21
22
  }
22
23
  });
23
24
 
@@ -42,15 +43,14 @@ 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",
51
50
  "flydocs/knowledge/decisions",
52
51
  "flydocs/knowledge/notes",
53
52
  "flydocs/knowledge/product",
53
+ "flydocs/knowledge/templates",
54
54
  "flydocs/design-system"
55
55
  ];
56
56
  if (tier === "local") {
@@ -136,8 +136,58 @@ var init_template = __esm({
136
136
 
137
137
  // src/lib/ui.ts
138
138
  import pc2 from "picocolors";
139
- function hyperlink(text3, url) {
140
- return `\x1B]8;;${url}\x1B\\${text3}\x1B]8;;\x1B\\`;
139
+ function shadow(text4) {
140
+ return `\x1B[38;2;55;45;70m${text4}\x1B[0m`;
141
+ }
142
+ function renderBannerBlock() {
143
+ const height = BANNER_ROWS.length;
144
+ const width = BANNER_ROWS[0].length;
145
+ const padded = BANNER_ROWS.map((r) => r.padEnd(width));
146
+ console.log(` ${GRADIENT[0](padded[0])}`);
147
+ for (let i = 1; i < height; i++) {
148
+ let line = "";
149
+ const shadowSrc2 = padded[i - 1];
150
+ const textSrc = padded[i];
151
+ for (let c = 0; c < width; c++) {
152
+ const textChar = textSrc[c];
153
+ const shadowChar = c > 0 ? shadowSrc2[c - 1] : " ";
154
+ if (textChar === "\u2588") {
155
+ line += "\u2588";
156
+ } else if (shadowChar === "\u2588") {
157
+ line += "\u2593";
158
+ } else {
159
+ line += " ";
160
+ }
161
+ }
162
+ let colored = " ";
163
+ let j = 0;
164
+ while (j < line.length) {
165
+ const ch = line[j];
166
+ let run = "";
167
+ while (j < line.length && line[j] === ch) {
168
+ run += line[j];
169
+ j++;
170
+ }
171
+ if (ch === "\u2588") {
172
+ colored += GRADIENT[i](run);
173
+ } else if (ch === "\u2593") {
174
+ colored += shadow(run);
175
+ } else {
176
+ colored += run;
177
+ }
178
+ }
179
+ console.log(colored);
180
+ }
181
+ const shadowSrc = padded[height - 1];
182
+ let bottomShadow = "";
183
+ for (let c = 0; c < width + 1; c++) {
184
+ const shadowChar = c > 0 ? shadowSrc[c - 1] : " ";
185
+ bottomShadow += shadowChar === "\u2588" ? "\u2593" : " ";
186
+ }
187
+ bottomShadow = bottomShadow.replace(/\s+$/, "");
188
+ if (bottomShadow.trim()) {
189
+ console.log(` ${shadow(" " + bottomShadow)}`);
190
+ }
141
191
  }
142
192
  function printStatus(message) {
143
193
  console.log(`${pc2.green("\u2714")} ${message}`);
@@ -152,26 +202,13 @@ function printInfo(message) {
152
202
  console.log(`${pc2.cyan("\u2139")} ${message}`);
153
203
  }
154
204
  function printBanner(version) {
155
- const pink = (t) => pc2.bold(pc2.magenta(t));
156
- const purple = (t) => pc2.bold(pc2.cyan(t));
157
- const dim = pc2.dim;
158
- const bold = pc2.bold;
159
- const pinkBlock12 = pink("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
160
- const pinkBlock4 = pink("\u2588\u2588\u2588\u2588");
161
- const purpleBlock12 = purple("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588");
162
- const purpleBlock4 = purple("\u2588\u2588\u2588\u2588");
163
205
  console.log();
164
- console.log(` ${pinkBlock12}`);
165
- console.log(` ${pinkBlock12}`);
166
- console.log(` ${pinkBlock4}`);
167
- console.log(
168
- ` ${pinkBlock4} ${purpleBlock12} ${bold("FlyDocs")} ${dim("(Beta)")}`
169
- );
206
+ renderBannerBlock();
207
+ console.log();
170
208
  console.log(
171
- ` ${purpleBlock12} ${dim("Spec-Driven Context That Helps Engineers Fly")}`
209
+ ` ${pc2.bold(pc2.white("Structured context for AI coding tools"))} ${pc2.dim("(Beta)")}`
172
210
  );
173
- console.log(` ${purpleBlock4}`);
174
- console.log(` ${purpleBlock4} ${dim(`v${version}`)}`);
211
+ console.log(` ${pc2.dim(`Version: ${version}`)}`);
175
212
  console.log();
176
213
  }
177
214
  function printCompletionBox(title, lines) {
@@ -192,22 +229,42 @@ function printCompletionBox(title, lines) {
192
229
  }
193
230
  function printBetaCta() {
194
231
  const dim = pc2.dim;
195
- const url = "https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta";
232
+ const discordUrl = "https://discord.com/invite/YAkjePmZTQ";
233
+ const siteUrl = "https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta";
196
234
  console.log(
197
235
  dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")
198
236
  );
199
237
  console.log();
200
- console.log(` ${pc2.bold("Join the FlyDocs Closed Beta")}`);
201
238
  console.log(
202
- ` ${dim("Early access to cloud features, web portal, and more.")}`
239
+ ` ${pc2.bold(pc2.cyan("Join the Discord"))} for upcoming features, support, and early access to what's next.`
240
+ );
241
+ console.log(` ${dim("Invite link:")}`);
242
+ console.log(` ${pc2.cyan(discordUrl)}`);
243
+ console.log();
244
+ console.log(
245
+ ` ${dim("Docs and updates:")} ${pc2.cyan("https://www.flydocs.ai")}`
203
246
  );
204
- console.log(` ${pc2.cyan(hyperlink("flydocs.ai", url))}`);
205
247
  console.log();
206
248
  }
249
+ var GRADIENT, BANNER_ROWS;
207
250
  var init_ui = __esm({
208
251
  "src/lib/ui.ts"() {
209
252
  "use strict";
210
253
  init_constants();
254
+ GRADIENT = [
255
+ (t) => `\x1B[1;38;2;255;0;128m${t}\x1B[0m`,
256
+ (t) => `\x1B[1;38;2;225;15;158m${t}\x1B[0m`,
257
+ (t) => `\x1B[1;38;2;190;30;190m${t}\x1B[0m`,
258
+ (t) => `\x1B[1;38;2;158;44;214m${t}\x1B[0m`,
259
+ (t) => `\x1B[1;38;2;124;58;237m${t}\x1B[0m`
260
+ ];
261
+ BANNER_ROWS = [
262
+ "\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",
263
+ "\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\u2588 \u2588\u2588\u2588\u2588 ",
265
+ "\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
266
+ "\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"
267
+ ];
211
268
  }
212
269
  });
213
270
 
@@ -236,10 +293,9 @@ function extractPreservedValues(config) {
236
293
  return {
237
294
  tier: config.tier,
238
295
  setupComplete: config.setupComplete ?? false,
239
- providerTeamId: config.provider?.teamId ?? null,
296
+ workspaceId: config.workspaceId ?? null,
240
297
  workspace: config.workspace ?? {},
241
298
  issueLabels: config.issueLabels ?? {},
242
- statusMapping: config.statusMapping ?? {},
243
299
  detectedStack: config.detectedStack ?? {},
244
300
  skills: config.skills ?? {},
245
301
  designSystem: config.designSystem ?? null,
@@ -255,21 +311,13 @@ async function mergeConfig(templateDir, version, tierFlag, preserved) {
255
311
  config.version = version;
256
312
  config.tier = tierFlag ?? preserved.tier;
257
313
  config.setupComplete = preserved.setupComplete;
258
- if (preserved.providerTeamId !== null) {
259
- if (!config.provider) {
260
- config.provider = { type: "linear", teamId: null };
261
- }
262
- config.provider.teamId = preserved.providerTeamId;
263
- }
314
+ config.workspaceId = preserved.workspaceId;
264
315
  if (Object.keys(preserved.workspace).length > 0) {
265
316
  config.workspace = preserved.workspace;
266
317
  }
267
318
  if (Object.keys(preserved.issueLabels).length > 0) {
268
319
  config.issueLabels = preserved.issueLabels;
269
320
  }
270
- if (Object.keys(preserved.statusMapping).length > 0) {
271
- config.statusMapping = preserved.statusMapping;
272
- }
273
321
  if (Object.keys(preserved.detectedStack).length > 0) {
274
322
  config.detectedStack = preserved.detectedStack;
275
323
  }
@@ -305,18 +353,26 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
305
353
  join4(templateSkillsDir, activeMech),
306
354
  join4(skillsDir, activeMech)
307
355
  );
308
- const { rm: rm5 } = await import("fs/promises");
356
+ const { rm: rm6 } = await import("fs/promises");
309
357
  const inactivePath = join4(skillsDir, inactiveMech);
310
358
  if (await pathExists(inactivePath)) {
311
- await rm5(inactivePath, { recursive: true, force: true });
359
+ await rm6(inactivePath, { recursive: true, force: true });
312
360
  }
313
- for (const skill of OWNED_SKILLS) {
361
+ for (const skill of CORE_SKILLS) {
314
362
  if (skill === "flydocs-workflow") continue;
315
363
  const src = join4(templateSkillsDir, skill);
316
364
  if (await pathExists(src)) {
317
365
  await replaceDirectory(src, join4(skillsDir, skill));
318
366
  }
319
367
  }
368
+ if (tier === "cloud") {
369
+ for (const skill of CLOUD_ONLY_SKILLS) {
370
+ const src = join4(templateSkillsDir, skill);
371
+ if (await pathExists(src)) {
372
+ await replaceDirectory(src, join4(skillsDir, skill));
373
+ }
374
+ }
375
+ }
320
376
  const readmeSrc = join4(templateSkillsDir, "README.md");
321
377
  if (await pathExists(readmeSrc)) {
322
378
  await copyFile(readmeSrc, join4(skillsDir, "README.md"));
@@ -325,18 +381,29 @@ async function installOwnedSkills(templateDir, targetDir, tier) {
325
381
  async function replaceOwnedSkills(templateDir, targetDir, tier) {
326
382
  const skillsDir = join4(targetDir, ".claude", "skills");
327
383
  const templateSkillsDir = join4(templateDir, ".claude", "skills");
328
- for (const skill of OWNED_SKILLS) {
384
+ const { rm: rm6 } = await import("fs/promises");
385
+ for (const skill of CORE_SKILLS) {
329
386
  const src = join4(templateSkillsDir, skill);
330
387
  if (await pathExists(src)) {
331
388
  await replaceDirectory(src, join4(skillsDir, skill));
332
389
  }
333
390
  }
334
- const { rm: rm5 } = await import("fs/promises");
391
+ for (const skill of CLOUD_ONLY_SKILLS) {
392
+ const dest = join4(skillsDir, skill);
393
+ if (tier === "cloud") {
394
+ const src = join4(templateSkillsDir, skill);
395
+ if (await pathExists(src)) {
396
+ await replaceDirectory(src, dest);
397
+ }
398
+ } else if (await pathExists(dest)) {
399
+ await rm6(dest, { recursive: true, force: true });
400
+ }
401
+ }
335
402
  const activeMech = MECHANISM_SKILLS[tier];
336
403
  const inactiveMech = tier === "local" ? MECHANISM_SKILLS.cloud : MECHANISM_SKILLS.local;
337
404
  const inactivePath = join4(skillsDir, inactiveMech);
338
405
  if (await pathExists(inactivePath)) {
339
- await rm5(inactivePath, { recursive: true, force: true });
406
+ await rm6(inactivePath, { recursive: true, force: true });
340
407
  }
341
408
  await replaceDirectory(
342
409
  join4(templateSkillsDir, activeMech),
@@ -348,9 +415,9 @@ async function replaceOwnedSkills(templateDir, targetDir, tier) {
348
415
  }
349
416
  }
350
417
  async function copyCursorRules(targetDir) {
351
- const { mkdir: mkdir6 } = await import("fs/promises");
418
+ const { mkdir: mkdir9 } = await import("fs/promises");
352
419
  const rulesDir = join4(targetDir, ".cursor", "rules");
353
- await mkdir6(rulesDir, { recursive: true });
420
+ await mkdir9(rulesDir, { recursive: true });
354
421
  const workflowRule = join4(
355
422
  targetDir,
356
423
  ".claude",
@@ -384,18 +451,17 @@ async function copyCursorRules(targetDir) {
384
451
  await copyFile(context7Rule, join4(rulesDir, "flydocs-context7.mdc"));
385
452
  }
386
453
  }
387
- var OWNED_SKILLS, MECHANISM_SKILLS;
454
+ var CORE_SKILLS, CLOUD_ONLY_SKILLS, MECHANISM_SKILLS;
388
455
  var init_skills = __esm({
389
456
  "src/lib/skills.ts"() {
390
457
  "use strict";
391
458
  init_fs_ops();
392
- OWNED_SKILLS = [
459
+ CORE_SKILLS = [
393
460
  "flydocs-workflow",
394
- "flydocs-figma",
395
- "flydocs-estimates",
396
461
  "flydocs-context-graph",
397
462
  "flydocs-context7"
398
463
  ];
464
+ CLOUD_ONLY_SKILLS = ["flydocs-figma", "flydocs-estimates"];
399
465
  MECHANISM_SKILLS = {
400
466
  local: "flydocs-local",
401
467
  cloud: "flydocs-cloud"
@@ -403,14 +469,89 @@ var init_skills = __esm({
403
469
  }
404
470
  });
405
471
 
472
+ // src/lib/user-content.ts
473
+ import {
474
+ copyFile as fsCopyFile2,
475
+ mkdir as mkdir2,
476
+ readFile as readFile3,
477
+ writeFile as writeFile2
478
+ } from "fs/promises";
479
+ import { join as join5, dirname as dirname2 } from "path";
480
+ async function detectExistingConfigs(targetDir) {
481
+ const existing = [];
482
+ const backupDir = join5(targetDir, BACKUP_ORIGINALS_DIR);
483
+ for (const relativePath of RESTORABLE_FILES) {
484
+ const filePath = join5(targetDir, relativePath);
485
+ const backupPath = join5(backupDir, relativePath);
486
+ if (await pathExists(filePath) && !await pathExists(backupPath)) {
487
+ existing.push(relativePath);
488
+ }
489
+ }
490
+ return existing;
491
+ }
492
+ async function backupOriginals(targetDir, files = RESTORABLE_FILES) {
493
+ const backedUp = [];
494
+ const backupDir = join5(targetDir, BACKUP_ORIGINALS_DIR);
495
+ for (const relativePath of files) {
496
+ const srcPath = join5(targetDir, relativePath);
497
+ const destPath = join5(backupDir, relativePath);
498
+ if (!await pathExists(srcPath)) {
499
+ continue;
500
+ }
501
+ if (await pathExists(destPath)) {
502
+ continue;
503
+ }
504
+ await mkdir2(dirname2(destPath), { recursive: true });
505
+ await fsCopyFile2(srcPath, destPath);
506
+ backedUp.push(relativePath);
507
+ }
508
+ return backedUp;
509
+ }
510
+ async function readBackupOriginals(targetDir) {
511
+ const backupDir = join5(targetDir, BACKUP_ORIGINALS_DIR);
512
+ if (!await pathExists(backupDir)) {
513
+ return [];
514
+ }
515
+ const files = [];
516
+ for (const relativePath of RESTORABLE_FILES) {
517
+ const backupPath = join5(backupDir, relativePath);
518
+ if (await pathExists(backupPath)) {
519
+ const content = await readFile3(backupPath);
520
+ files.push({ relativePath, content });
521
+ }
522
+ }
523
+ return files;
524
+ }
525
+ async function writeRestoredFiles(targetDir, files) {
526
+ for (const { relativePath, content } of files) {
527
+ const destPath = join5(targetDir, relativePath);
528
+ await mkdir2(dirname2(destPath), { recursive: true });
529
+ await writeFile2(destPath, content);
530
+ }
531
+ }
532
+ var BACKUP_ORIGINALS_DIR, RESTORABLE_FILES;
533
+ var init_user_content = __esm({
534
+ "src/lib/user-content.ts"() {
535
+ "use strict";
536
+ init_fs_ops();
537
+ BACKUP_ORIGINALS_DIR = ".flydocs/backup-originals";
538
+ RESTORABLE_FILES = [
539
+ ".claude/CLAUDE.md",
540
+ ".claude/settings.json",
541
+ ".cursor/hooks.json",
542
+ "AGENTS.md"
543
+ ];
544
+ }
545
+ });
546
+
406
547
  // src/lib/stack.ts
407
- import { readFile as readFile3 } from "fs/promises";
408
- import { join as join5 } from "path";
548
+ import { readFile as readFile4 } from "fs/promises";
549
+ import { join as join6 } from "path";
409
550
  async function parseProjectMdStack(targetDir) {
410
551
  const detected = /* @__PURE__ */ new Set();
411
- const projectMdPath = join5(targetDir, "flydocs", "context", "project.md");
552
+ const projectMdPath = join6(targetDir, "flydocs", "context", "project.md");
412
553
  try {
413
- const content = await readFile3(projectMdPath, "utf-8");
554
+ const content = await readFile4(projectMdPath, "utf-8");
414
555
  const stackMatch = content.match(/## Stack\n([\s\S]*?)(?=\n## |\n---|\z)/);
415
556
  if (!stackMatch) return detected;
416
557
  const stackSection = stackMatch[1].toLowerCase();
@@ -453,10 +594,10 @@ async function parseProjectMdStack(targetDir) {
453
594
  }
454
595
  async function detectStack(targetDir) {
455
596
  const detected = /* @__PURE__ */ new Set();
456
- const pkgPath = join5(targetDir, "package.json");
597
+ const pkgPath = join6(targetDir, "package.json");
457
598
  let pkg;
458
599
  try {
459
- const content = await readFile3(pkgPath, "utf-8");
600
+ const content = await readFile4(pkgPath, "utf-8");
460
601
  pkg = JSON.parse(content);
461
602
  } catch {
462
603
  }
@@ -484,39 +625,39 @@ async function detectStack(targetDir) {
484
625
  detected.add("testing-library");
485
626
  }
486
627
  for (const f of ["convex/schema.ts", "convex/schema.js"]) {
487
- if (await pathExists(join5(targetDir, f))) detected.add("convex");
628
+ if (await pathExists(join6(targetDir, f))) detected.add("convex");
488
629
  }
489
630
  for (const f of ["next.config.ts", "next.config.js", "next.config.mjs"]) {
490
- if (await pathExists(join5(targetDir, f))) detected.add("nextjs");
631
+ if (await pathExists(join6(targetDir, f))) detected.add("nextjs");
491
632
  }
492
- const appJsonPath = join5(targetDir, "app.json");
633
+ const appJsonPath = join6(targetDir, "app.json");
493
634
  if (await pathExists(appJsonPath)) {
494
635
  try {
495
- const appContent = await readFile3(appJsonPath, "utf-8");
636
+ const appContent = await readFile4(appJsonPath, "utf-8");
496
637
  if (appContent.includes('"expo"')) detected.add("expo");
497
638
  } catch {
498
639
  }
499
640
  }
500
- if (await pathExists(join5(targetDir, "tsconfig.json"))) {
641
+ if (await pathExists(join6(targetDir, "tsconfig.json"))) {
501
642
  detected.add("typescript");
502
643
  }
503
- if (await pathExists(join5(targetDir, "nuxt.config.ts")) || await pathExists(join5(targetDir, "nuxt.config.js"))) {
644
+ if (await pathExists(join6(targetDir, "nuxt.config.ts")) || await pathExists(join6(targetDir, "nuxt.config.js"))) {
504
645
  detected.add("nuxt");
505
646
  detected.add("vue");
506
647
  }
507
- if (await pathExists(join5(targetDir, "angular.json"))) {
648
+ if (await pathExists(join6(targetDir, "angular.json"))) {
508
649
  detected.add("angular");
509
650
  }
510
- if (await pathExists(join5(targetDir, "svelte.config.js")) || await pathExists(join5(targetDir, "svelte.config.ts"))) {
651
+ if (await pathExists(join6(targetDir, "svelte.config.js")) || await pathExists(join6(targetDir, "svelte.config.ts"))) {
511
652
  detected.add("svelte");
512
653
  }
513
- if (await pathExists(join5(targetDir, "pyproject.toml")) || await pathExists(join5(targetDir, "requirements.txt")) || await pathExists(join5(targetDir, "setup.py"))) {
654
+ if (await pathExists(join6(targetDir, "pyproject.toml")) || await pathExists(join6(targetDir, "requirements.txt")) || await pathExists(join6(targetDir, "setup.py"))) {
514
655
  detected.add("python");
515
656
  }
516
- if (await pathExists(join5(targetDir, "go.mod"))) {
657
+ if (await pathExists(join6(targetDir, "go.mod"))) {
517
658
  detected.add("go");
518
659
  }
519
- if (await pathExists(join5(targetDir, "Cargo.toml"))) {
660
+ if (await pathExists(join6(targetDir, "Cargo.toml"))) {
520
661
  detected.add("rust");
521
662
  }
522
663
  const projectMdStack = await parseProjectMdStack(targetDir);
@@ -562,9 +703,9 @@ var init_stack = __esm({
562
703
 
563
704
  // src/lib/post-install.ts
564
705
  import { execFileSync } from "child_process";
565
- import { join as join6 } from "path";
706
+ import { join as join7 } from "path";
566
707
  async function runManifestGeneration(targetDir) {
567
- const scriptPath = join6(
708
+ const scriptPath = join7(
568
709
  targetDir,
569
710
  ".flydocs",
570
711
  "scripts",
@@ -582,7 +723,7 @@ async function runManifestGeneration(targetDir) {
582
723
  }
583
724
  }
584
725
  async function runContextGraphBuild(targetDir) {
585
- const scriptPath = join6(
726
+ const scriptPath = join7(
586
727
  targetDir,
587
728
  ".claude",
588
729
  "skills",
@@ -610,8 +751,8 @@ var init_post_install = __esm({
610
751
  });
611
752
 
612
753
  // src/lib/skill-manager.ts
613
- import { readFile as readFile4, readdir, rm as rm2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
614
- import { join as join7 } from "path";
754
+ import { readFile as readFile5, readdir, rm as rm2, mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
755
+ import { join as join8 } from "path";
615
756
  import pc3 from "picocolors";
616
757
  function flushFrontmatterValue(mode, lines) {
617
758
  switch (mode) {
@@ -623,8 +764,8 @@ function flushFrontmatterValue(mode, lines) {
623
764
  return lines.join("\n").trim();
624
765
  }
625
766
  }
626
- function parseFrontmatter(text3) {
627
- const match = text3.match(/^---\s*\n([\s\S]*?)\n---/);
767
+ function parseFrontmatter(text4) {
768
+ const match = text4.match(/^---\s*\n([\s\S]*?)\n---/);
628
769
  if (!match) return null;
629
770
  const block = match[1];
630
771
  const result = {};
@@ -738,13 +879,13 @@ async function downloadFileContent(downloadUrl) {
738
879
  async function downloadRecursive(url, destDir) {
739
880
  const items = await fetchGitHubJson(url);
740
881
  if (items === null) return false;
741
- await mkdir2(destDir, { recursive: true });
882
+ await mkdir3(destDir, { recursive: true });
742
883
  for (const item of items) {
743
- const destPath = join7(destDir, item.name);
884
+ const destPath = join8(destDir, item.name);
744
885
  if (item.type === "file") {
745
886
  if (item.download_url) {
746
887
  const content = await downloadFileContent(item.download_url);
747
- await writeFile2(destPath, content);
888
+ await writeFile3(destPath, content);
748
889
  }
749
890
  } else if (item.type === "dir") {
750
891
  await downloadRecursive(item.url, destPath);
@@ -757,7 +898,7 @@ async function downloadSkillTree(repo, skillName, targetDir) {
757
898
  return downloadRecursive(apiUrl, targetDir);
758
899
  }
759
900
  async function listSkills(targetDir) {
760
- const skillsDir = join7(targetDir, ".claude", "skills");
901
+ const skillsDir = join8(targetDir, ".claude", "skills");
761
902
  const platform = [];
762
903
  const community = [];
763
904
  if (!await pathExists(skillsDir)) {
@@ -770,11 +911,11 @@ async function listSkills(targetDir) {
770
911
  return { platform, community };
771
912
  }
772
913
  for (const entry of entries.sort()) {
773
- const skillFile = join7(skillsDir, entry, "SKILL.md");
914
+ const skillFile = join8(skillsDir, entry, "SKILL.md");
774
915
  if (!await pathExists(skillFile)) continue;
775
916
  let content;
776
917
  try {
777
- content = await readFile4(skillFile, "utf-8");
918
+ content = await readFile5(skillFile, "utf-8");
778
919
  } catch {
779
920
  continue;
780
921
  }
@@ -810,7 +951,8 @@ function searchCatalog(keyword) {
810
951
  return searchable.includes(lower);
811
952
  });
812
953
  }
813
- async function addSkill(targetDir, source) {
954
+ async function addSkill(targetDir, source, options) {
955
+ const quiet = options?.quiet ?? false;
814
956
  if (isPlatformSkill(source)) {
815
957
  printError(`Cannot install platform skill '${source}'.`);
816
958
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
@@ -822,17 +964,19 @@ async function addSkill(targetDir, source) {
822
964
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
823
965
  return;
824
966
  }
825
- const skillsDir = join7(targetDir, ".claude", "skills", skillName);
967
+ const skillsDir = join8(targetDir, ".claude", "skills", skillName);
826
968
  if (await pathExists(skillsDir)) {
827
969
  printWarning(`Skill '${skillName}' is already installed.`);
828
970
  console.log(` Remove first: flydocs skills remove ${skillName}`);
829
971
  return;
830
972
  }
831
- console.log();
832
- console.log(
833
- `${pc3.blue("->")} Installing ${pc3.cyan(skillName)} from ${repo}...`
834
- );
835
- console.log();
973
+ if (!quiet) {
974
+ console.log();
975
+ console.log(
976
+ `${pc3.blue("->")} Installing ${pc3.cyan(skillName)} from ${repo}...`
977
+ );
978
+ console.log();
979
+ }
836
980
  let success;
837
981
  try {
838
982
  success = await downloadSkillTree(repo, skillName, skillsDir);
@@ -851,13 +995,13 @@ async function addSkill(targetDir, source) {
851
995
  printError(`Skill not found at ${repo}/skills/${skillName}`);
852
996
  return;
853
997
  }
854
- const skillMdPath = join7(skillsDir, "SKILL.md");
998
+ const skillMdPath = join8(skillsDir, "SKILL.md");
855
999
  if (!await pathExists(skillMdPath)) {
856
1000
  await rm2(skillsDir, { recursive: true, force: true });
857
1001
  printError("Invalid skill: SKILL.md not found.");
858
1002
  return;
859
1003
  }
860
- const skillMdContent = await readFile4(skillMdPath, "utf-8");
1004
+ const skillMdContent = await readFile5(skillMdPath, "utf-8");
861
1005
  const fm = parseFrontmatter(skillMdContent);
862
1006
  if (fm === null || !fm["name"] || !fm["description"]) {
863
1007
  await rm2(skillsDir, { recursive: true, force: true });
@@ -866,19 +1010,23 @@ async function addSkill(targetDir, source) {
866
1010
  );
867
1011
  return;
868
1012
  }
869
- if (!fm["triggers"]) {
1013
+ if (!fm["triggers"] && !quiet) {
870
1014
  printWarning(
871
1015
  "SKILL.md has no triggers -- skill won't appear in manifest index."
872
1016
  );
873
1017
  }
874
- printStatus("Downloaded skill files");
875
- const cursorRuleSrc = join7(skillsDir, "cursor-rule.mdc");
1018
+ if (!quiet) {
1019
+ printStatus("Downloaded skill files");
1020
+ }
1021
+ const cursorRuleSrc = join8(skillsDir, "cursor-rule.mdc");
876
1022
  if (await pathExists(cursorRuleSrc)) {
877
- const cursorRulesDir = join7(targetDir, ".cursor", "rules");
878
- await mkdir2(cursorRulesDir, { recursive: true });
879
- const cursorRuleDest = join7(cursorRulesDir, `${skillName}.mdc`);
1023
+ const cursorRulesDir = join8(targetDir, ".cursor", "rules");
1024
+ await mkdir3(cursorRulesDir, { recursive: true });
1025
+ const cursorRuleDest = join8(cursorRulesDir, `${skillName}.mdc`);
880
1026
  await copyFile(cursorRuleSrc, cursorRuleDest);
881
- printStatus("Installed cursor rule");
1027
+ if (!quiet) {
1028
+ printStatus("Installed cursor rule");
1029
+ }
882
1030
  }
883
1031
  const config = await readConfig(targetDir);
884
1032
  const installed = config.skills?.installed ?? [];
@@ -890,12 +1038,16 @@ async function addSkill(targetDir, source) {
890
1038
  }
891
1039
  config.skills.installed = installed;
892
1040
  await writeConfig(targetDir, config);
893
- printStatus("Updated config.json");
1041
+ if (!quiet) {
1042
+ printStatus("Updated config.json");
1043
+ }
894
1044
  }
895
1045
  await runManifestGeneration(targetDir);
896
- console.log();
897
- printStatus(`Installed ${pc3.cyan(skillName)}`);
898
- console.log();
1046
+ if (!quiet) {
1047
+ console.log();
1048
+ printStatus(`Installed ${pc3.cyan(skillName)}`);
1049
+ console.log();
1050
+ }
899
1051
  }
900
1052
  async function removeSkill(targetDir, name) {
901
1053
  if (isPlatformSkill(name)) {
@@ -903,14 +1055,14 @@ async function removeSkill(targetDir, name) {
903
1055
  console.log(" Platform skills (flydocs-*) are managed by the installer.");
904
1056
  return;
905
1057
  }
906
- const skillDir = join7(targetDir, ".claude", "skills", name);
1058
+ const skillDir = join8(targetDir, ".claude", "skills", name);
907
1059
  if (!await pathExists(skillDir)) {
908
1060
  printError(`Skill '${name}' is not installed.`);
909
1061
  return;
910
1062
  }
911
1063
  await rm2(skillDir, { recursive: true, force: true });
912
1064
  printStatus("Removed skill directory");
913
- const cursorRule = join7(targetDir, ".cursor", "rules", `${name}.mdc`);
1065
+ const cursorRule = join8(targetDir, ".cursor", "rules", `${name}.mdc`);
914
1066
  if (await pathExists(cursorRule)) {
915
1067
  await rm2(cursorRule, { force: true });
916
1068
  printStatus("Removed cursor rule");
@@ -975,7 +1127,7 @@ var init_skill_manager = __esm({
975
1127
  });
976
1128
 
977
1129
  // src/lib/community-skills.ts
978
- import { join as join8 } from "path";
1130
+ import { join as join9 } from "path";
979
1131
  import { multiselect, isCancel, cancel } from "@clack/prompts";
980
1132
  import pc4 from "picocolors";
981
1133
  function suggestSkills(stack) {
@@ -994,10 +1146,10 @@ function suggestSkills(stack) {
994
1146
  }
995
1147
  async function promptCommunitySkills(targetDir, stack, autoYes) {
996
1148
  const suggestions = suggestSkills(stack);
997
- const skillsDir = join8(targetDir, ".claude", "skills");
1149
+ const skillsDir = join9(targetDir, ".claude", "skills");
998
1150
  const filtered = [];
999
1151
  for (const skill of suggestions) {
1000
- if (!await pathExists(join8(skillsDir, skill.name))) {
1152
+ if (!await pathExists(join9(skillsDir, skill.name))) {
1001
1153
  filtered.push(skill);
1002
1154
  }
1003
1155
  }
@@ -1050,7 +1202,8 @@ async function promptCommunitySkills(targetDir, stack, autoYes) {
1050
1202
  let successCount = 0;
1051
1203
  for (const skill of selected) {
1052
1204
  try {
1053
- await addSkill(targetDir, `${skill.repo}:${skill.name}`);
1205
+ await addSkill(targetDir, `${skill.repo}:${skill.name}`, { quiet: true });
1206
+ printStatus(`Installed ${pc4.cyan(skill.name)}`);
1054
1207
  successCount++;
1055
1208
  } catch {
1056
1209
  printError(`${skill.name} (failed)`);
@@ -1154,48 +1307,48 @@ var init_community_skills = __esm({
1154
1307
  });
1155
1308
 
1156
1309
  // src/lib/deprecated.ts
1157
- import { readdir as readdir2, mkdir as mkdir3, rename, rm as rm3 } from "fs/promises";
1158
- import { join as join9 } from "path";
1310
+ import { readdir as readdir2, mkdir as mkdir4, rename, rm as rm3 } from "fs/promises";
1311
+ import { join as join10 } from "path";
1159
1312
  import { confirm, isCancel as isCancel2 } from "@clack/prompts";
1160
1313
  async function scanDeprecated(targetDir) {
1161
1314
  const found = [];
1162
1315
  for (const item of [...DEPRECATED_DIRS, ...DEPRECATED_FILES]) {
1163
- if (await pathExists(join9(targetDir, item))) {
1316
+ if (await pathExists(join10(targetDir, item))) {
1164
1317
  found.push(item);
1165
1318
  }
1166
1319
  }
1167
1320
  for (const skill of DEPRECATED_SKILLS) {
1168
- const p = join9(targetDir, ".claude", "skills", skill);
1321
+ const p = join10(targetDir, ".claude", "skills", skill);
1169
1322
  if (await pathExists(p)) {
1170
1323
  found.push(`.claude/skills/${skill}`);
1171
1324
  }
1172
1325
  }
1173
1326
  for (const dir of DEPRECATED_RULES_DIR) {
1174
- if (await pathExists(join9(targetDir, dir))) {
1327
+ if (await pathExists(join10(targetDir, dir))) {
1175
1328
  found.push(dir);
1176
1329
  }
1177
1330
  }
1178
1331
  for (const hook of DEPRECATED_HOOKS) {
1179
- const p = join9(targetDir, ".flydocs", "hooks", hook);
1332
+ const p = join10(targetDir, ".flydocs", "hooks", hook);
1180
1333
  if (await pathExists(p)) {
1181
1334
  found.push(`.flydocs/hooks/${hook}`);
1182
1335
  }
1183
1336
  }
1184
1337
  for (const rule of DEPRECATED_CURSOR_RULES) {
1185
- const p = join9(targetDir, ".cursor", "rules", rule);
1338
+ const p = join10(targetDir, ".cursor", "rules", rule);
1186
1339
  if (await pathExists(p)) {
1187
1340
  found.push(`.cursor/rules/${rule}`);
1188
1341
  }
1189
1342
  }
1190
1343
  for (const dir of DEPRECATED_CURSOR_RULE_DIRS) {
1191
- const p = join9(targetDir, ".cursor", "rules", dir);
1344
+ const p = join10(targetDir, ".cursor", "rules", dir);
1192
1345
  if (await pathExists(p)) {
1193
1346
  found.push(`.cursor/rules/${dir}`);
1194
1347
  }
1195
1348
  }
1196
1349
  for (const cmd of DEPRECATED_COMMANDS) {
1197
1350
  for (const prefix of [".cursor/commands", ".claude/commands"]) {
1198
- const p = join9(targetDir, prefix, cmd);
1351
+ const p = join10(targetDir, prefix, cmd);
1199
1352
  if (await pathExists(p)) {
1200
1353
  found.push(`${prefix}/${cmd}`);
1201
1354
  }
@@ -1204,22 +1357,22 @@ async function scanDeprecated(targetDir) {
1204
1357
  return found;
1205
1358
  }
1206
1359
  async function handleLegacyContext(targetDir) {
1207
- const contextDir = join9(targetDir, "flydocs", "context");
1208
- const legacyDir = join9(contextDir, "legacy");
1360
+ const contextDir = join10(targetDir, "flydocs", "context");
1361
+ const legacyDir = join10(contextDir, "legacy");
1209
1362
  const oldFiles = ["overview.md", "stack.md", "standards.md"];
1210
1363
  let hasOld = false;
1211
1364
  for (const f of oldFiles) {
1212
- if (await pathExists(join9(contextDir, f))) {
1365
+ if (await pathExists(join10(contextDir, f))) {
1213
1366
  hasOld = true;
1214
1367
  break;
1215
1368
  }
1216
1369
  }
1217
1370
  if (hasOld) {
1218
- await mkdir3(legacyDir, { recursive: true });
1371
+ await mkdir4(legacyDir, { recursive: true });
1219
1372
  for (const f of oldFiles) {
1220
- const src = join9(contextDir, f);
1373
+ const src = join10(contextDir, f);
1221
1374
  if (await pathExists(src)) {
1222
- await rename(src, join9(legacyDir, f));
1375
+ await rename(src, join10(legacyDir, f));
1223
1376
  printStatus(`Moved flydocs/context/${f} \u2192 legacy/`);
1224
1377
  }
1225
1378
  }
@@ -1229,7 +1382,7 @@ async function handleLegacyContext(targetDir) {
1229
1382
  }
1230
1383
  }
1231
1384
  async function checkLegacyFolder(targetDir) {
1232
- const legacyDir = join9(targetDir, "flydocs", "context", "legacy");
1385
+ const legacyDir = join10(targetDir, "flydocs", "context", "legacy");
1233
1386
  if (!await pathExists(legacyDir)) return null;
1234
1387
  try {
1235
1388
  const entries = await readdir2(legacyDir);
@@ -1259,7 +1412,7 @@ async function promptCleanup(targetDir, paths) {
1259
1412
  return;
1260
1413
  }
1261
1414
  for (const p of paths) {
1262
- await rm3(join9(targetDir, p), { recursive: true, force: true });
1415
+ await rm3(join10(targetDir, p), { recursive: true, force: true });
1263
1416
  printStatus(`Deleted: ${p}`);
1264
1417
  }
1265
1418
  }
@@ -1310,26 +1463,26 @@ var init_deprecated = __esm({
1310
1463
  });
1311
1464
 
1312
1465
  // src/lib/gitignore.ts
1313
- import { readFile as readFile5, writeFile as writeFile3, appendFile } from "fs/promises";
1314
- import { join as join10 } from "path";
1466
+ import { readFile as readFile6, writeFile as writeFile4, appendFile } from "fs/promises";
1467
+ import { join as join11 } from "path";
1315
1468
  async function ensureGitignore(targetDir) {
1316
- const gitignorePath = join10(targetDir, ".gitignore");
1469
+ const gitignorePath = join11(targetDir, ".gitignore");
1317
1470
  if (await pathExists(gitignorePath)) {
1318
- const content = await readFile5(gitignorePath, "utf-8");
1471
+ const content = await readFile6(gitignorePath, "utf-8");
1319
1472
  if (!content.includes("# FlyDocs")) {
1320
1473
  const section = "\n# FlyDocs\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
1321
1474
  await appendFile(gitignorePath, section, "utf-8");
1322
1475
  printStatus("Added FlyDocs entries to .gitignore");
1323
1476
  }
1324
1477
  } else {
1325
- await writeFile3(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1478
+ await writeFile4(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
1326
1479
  printStatus(".gitignore (new)");
1327
1480
  }
1328
1481
  }
1329
1482
  async function migrateGitignore(targetDir) {
1330
- const gitignorePath = join10(targetDir, ".gitignore");
1483
+ const gitignorePath = join11(targetDir, ".gitignore");
1331
1484
  if (!await pathExists(gitignorePath)) return;
1332
- const content = await readFile5(gitignorePath, "utf-8");
1485
+ const content = await readFile6(gitignorePath, "utf-8");
1333
1486
  if (!content.includes("flydocs/context/graph.json")) {
1334
1487
  if (content.includes("# FlyDocs")) {
1335
1488
  const lines = content.split("\n");
@@ -1340,7 +1493,7 @@ async function migrateGitignore(targetDir) {
1340
1493
  insertIdx++;
1341
1494
  }
1342
1495
  lines.splice(insertIdx, 0, "flydocs/context/graph.json");
1343
- await writeFile3(gitignorePath, lines.join("\n"), "utf-8");
1496
+ await writeFile4(gitignorePath, lines.join("\n"), "utf-8");
1344
1497
  } else {
1345
1498
  await appendFile(
1346
1499
  gitignorePath,
@@ -1409,7 +1562,7 @@ __pycache__/
1409
1562
  });
1410
1563
 
1411
1564
  // src/lib/version.ts
1412
- import { readFile as readFile6 } from "fs/promises";
1565
+ import { readFile as readFile7 } from "fs/promises";
1413
1566
  function compareVersions(v1, v2) {
1414
1567
  const [core1, pre1] = v1.split("-", 2);
1415
1568
  const [core2, pre2] = v2.split("-", 2);
@@ -1448,7 +1601,7 @@ function compareVersions(v1, v2) {
1448
1601
  async function getWhatsNew(changelogPath, fromVersion, toVersion) {
1449
1602
  let content;
1450
1603
  try {
1451
- content = await readFile6(changelogPath, "utf-8");
1604
+ content = await readFile7(changelogPath, "utf-8");
1452
1605
  } catch {
1453
1606
  return [];
1454
1607
  }
@@ -1477,13 +1630,13 @@ var init_version = __esm({
1477
1630
  });
1478
1631
 
1479
1632
  // src/lib/update-check.ts
1480
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1481
- import { join as join11 } from "path";
1633
+ import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
1634
+ import { join as join12 } from "path";
1482
1635
  import { homedir } from "os";
1483
1636
  import pc5 from "picocolors";
1484
1637
  async function readCache() {
1485
1638
  try {
1486
- const raw = await readFile7(CACHE_FILE, "utf-8");
1639
+ const raw = await readFile8(CACHE_FILE, "utf-8");
1487
1640
  const parsed = JSON.parse(raw);
1488
1641
  if (typeof parsed === "object" && parsed !== null && "checkedAt" in parsed && "latestVersion" in parsed && typeof parsed.checkedAt === "number" && typeof parsed.latestVersion === "string") {
1489
1642
  return parsed;
@@ -1495,8 +1648,8 @@ async function readCache() {
1495
1648
  }
1496
1649
  async function writeCache(cache) {
1497
1650
  try {
1498
- await mkdir4(join11(homedir(), ".flydocs"), { recursive: true });
1499
- await writeFile4(CACHE_FILE, JSON.stringify(cache), "utf-8");
1651
+ await mkdir5(join12(homedir(), ".flydocs"), { recursive: true });
1652
+ await writeFile5(CACHE_FILE, JSON.stringify(cache), "utf-8");
1500
1653
  } catch {
1501
1654
  }
1502
1655
  }
@@ -1564,12 +1717,230 @@ var init_update_check = __esm({
1564
1717
  "use strict";
1565
1718
  init_constants();
1566
1719
  init_version();
1567
- CACHE_FILE = join11(homedir(), ".flydocs", "update-check.json");
1720
+ CACHE_FILE = join12(homedir(), ".flydocs", "update-check.json");
1568
1721
  CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1569
1722
  FETCH_TIMEOUT_MS = 5e3;
1570
1723
  }
1571
1724
  });
1572
1725
 
1726
+ // src/lib/telemetry.ts
1727
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
1728
+ import { randomUUID } from "crypto";
1729
+ import { join as join13 } from "path";
1730
+ import { homedir as homedir2 } from "os";
1731
+ async function readConfig2() {
1732
+ try {
1733
+ const raw = await readFile9(CONFIG_FILE, "utf-8");
1734
+ const parsed = JSON.parse(raw);
1735
+ if (typeof parsed === "object" && parsed !== null && "enabled" in parsed && "anonymousId" in parsed && typeof parsed.enabled === "boolean" && typeof parsed.anonymousId === "string") {
1736
+ return parsed;
1737
+ }
1738
+ return null;
1739
+ } catch {
1740
+ return null;
1741
+ }
1742
+ }
1743
+ async function writeConfig2(config) {
1744
+ try {
1745
+ await mkdir6(CONFIG_DIR, { recursive: true });
1746
+ await writeFile6(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
1747
+ } catch {
1748
+ }
1749
+ }
1750
+ async function ensureConfig() {
1751
+ const existing = await readConfig2();
1752
+ if (existing) {
1753
+ return existing;
1754
+ }
1755
+ const config = {
1756
+ enabled: false,
1757
+ anonymousId: randomUUID()
1758
+ };
1759
+ await writeConfig2(config);
1760
+ return config;
1761
+ }
1762
+ async function isEnabled() {
1763
+ try {
1764
+ if (process.env.FLYDOCS_TELEMETRY === "0") {
1765
+ return false;
1766
+ }
1767
+ const config = await readConfig2();
1768
+ if (config) {
1769
+ return config.enabled;
1770
+ }
1771
+ return false;
1772
+ } catch {
1773
+ return false;
1774
+ }
1775
+ }
1776
+ function baseProperties() {
1777
+ return {
1778
+ cli_version: CLI_VERSION,
1779
+ os: process.platform,
1780
+ os_arch: process.arch,
1781
+ node_version: process.version,
1782
+ is_ci: Boolean(
1783
+ process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.BUILD_NUMBER
1784
+ ),
1785
+ $ip: null,
1786
+ $process_person_profile: false
1787
+ };
1788
+ }
1789
+ async function capture(event, properties = {}) {
1790
+ try {
1791
+ if (!await isEnabled()) {
1792
+ return;
1793
+ }
1794
+ const config = await ensureConfig();
1795
+ eventQueue.push({
1796
+ event,
1797
+ properties: {
1798
+ distinct_id: config.anonymousId,
1799
+ ...baseProperties(),
1800
+ ...properties
1801
+ },
1802
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1803
+ });
1804
+ } catch {
1805
+ }
1806
+ }
1807
+ async function flush() {
1808
+ try {
1809
+ if (eventQueue.length === 0) {
1810
+ return;
1811
+ }
1812
+ if (!await isEnabled()) {
1813
+ eventQueue.length = 0;
1814
+ return;
1815
+ }
1816
+ const batch = eventQueue.splice(0, eventQueue.length);
1817
+ const controller = new AbortController();
1818
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
1819
+ await fetch(POSTHOG_BATCH_URL, {
1820
+ method: "POST",
1821
+ headers: { "Content-Type": "application/json" },
1822
+ body: JSON.stringify({
1823
+ api_key: POSTHOG_API_KEY,
1824
+ batch
1825
+ }),
1826
+ signal: controller.signal
1827
+ });
1828
+ clearTimeout(timeout);
1829
+ } catch {
1830
+ }
1831
+ }
1832
+ async function setEnabled(enabled) {
1833
+ const config = await ensureConfig();
1834
+ config.enabled = enabled;
1835
+ await writeConfig2(config);
1836
+ }
1837
+ async function getStatus() {
1838
+ const envOverride = process.env.FLYDOCS_TELEMETRY === "0";
1839
+ const config = await readConfig2();
1840
+ return {
1841
+ enabled: config?.enabled ?? false,
1842
+ envOverride,
1843
+ anonymousId: config?.anonymousId ?? null
1844
+ };
1845
+ }
1846
+ var CONFIG_DIR, CONFIG_FILE, POSTHOG_BATCH_URL, FETCH_TIMEOUT_MS2, eventQueue;
1847
+ var init_telemetry = __esm({
1848
+ "src/lib/telemetry.ts"() {
1849
+ "use strict";
1850
+ init_constants();
1851
+ CONFIG_DIR = join13(homedir2(), ".flydocs");
1852
+ CONFIG_FILE = join13(CONFIG_DIR, "telemetry.json");
1853
+ POSTHOG_BATCH_URL = "https://us.i.posthog.com/batch/";
1854
+ FETCH_TIMEOUT_MS2 = 5e3;
1855
+ eventQueue = [];
1856
+ }
1857
+ });
1858
+
1859
+ // src/lib/api-key.ts
1860
+ import { readFile as readFile10, writeFile as writeFile7, appendFile as appendFile2 } from "fs/promises";
1861
+ import { join as join14 } from "path";
1862
+ function detectKeyType(key) {
1863
+ if (key.startsWith("fdk_")) return "relay";
1864
+ if (key.startsWith("lin_api_")) return "direct";
1865
+ return "unknown";
1866
+ }
1867
+ async function validateRelayKey(apiKey) {
1868
+ const baseUrl = process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? "https://app.flydocs.ai/api/relay";
1869
+ const response = await fetch(`${baseUrl}/auth/validate`, {
1870
+ method: "POST",
1871
+ headers: {
1872
+ Authorization: `Bearer ${apiKey}`,
1873
+ "Content-Type": "application/json"
1874
+ },
1875
+ signal: AbortSignal.timeout(15e3)
1876
+ });
1877
+ if (!response.ok) return { valid: false };
1878
+ const data = await response.json();
1879
+ if (!data.valid) return { valid: false };
1880
+ return { valid: true, org: data.org ?? "your organization" };
1881
+ }
1882
+ async function fetchWorkspaces(apiKey) {
1883
+ const baseUrl = process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? "https://app.flydocs.ai/api/relay";
1884
+ const response = await fetch(`${baseUrl}/auth/workspaces`, {
1885
+ method: "GET",
1886
+ headers: {
1887
+ Authorization: `Bearer ${apiKey}`
1888
+ },
1889
+ signal: AbortSignal.timeout(15e3)
1890
+ });
1891
+ if (!response.ok) {
1892
+ throw new Error(`Failed to fetch workspaces: ${response.status}`);
1893
+ }
1894
+ const data = await response.json();
1895
+ return data;
1896
+ }
1897
+ async function validateLinearKey(apiKey) {
1898
+ const response = await fetch("https://api.linear.app/graphql", {
1899
+ method: "POST",
1900
+ headers: {
1901
+ Authorization: apiKey,
1902
+ "Content-Type": "application/json"
1903
+ },
1904
+ body: JSON.stringify({ query: "{ viewer { id name email } }" }),
1905
+ signal: AbortSignal.timeout(15e3)
1906
+ });
1907
+ if (!response.ok) return { valid: false };
1908
+ const data = await response.json();
1909
+ if (!data.data?.viewer) return { valid: false };
1910
+ return {
1911
+ valid: true,
1912
+ name: data.data.viewer.name,
1913
+ email: data.data.viewer.email
1914
+ };
1915
+ }
1916
+ async function storeEnvKey(targetDir, envVarName, value) {
1917
+ const envPath = join14(targetDir, ".env");
1918
+ const envLocalPath = join14(targetDir, ".env.local");
1919
+ const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
1920
+ const pattern = new RegExp(`${envVarName}=.*`);
1921
+ if (await pathExists(targetEnvPath)) {
1922
+ const envContent = await readFile10(targetEnvPath, "utf-8");
1923
+ if (pattern.test(envContent)) {
1924
+ const updated = envContent.replace(pattern, `${envVarName}=${value}`);
1925
+ await writeFile7(targetEnvPath, updated, "utf-8");
1926
+ } else {
1927
+ await appendFile2(targetEnvPath, `
1928
+ ${envVarName}=${value}
1929
+ `);
1930
+ }
1931
+ } else {
1932
+ await writeFile7(targetEnvPath, `${envVarName}=${value}
1933
+ `, "utf-8");
1934
+ }
1935
+ return targetEnvPath === envLocalPath ? ".env.local" : ".env";
1936
+ }
1937
+ var init_api_key = __esm({
1938
+ "src/lib/api-key.ts"() {
1939
+ "use strict";
1940
+ init_fs_ops();
1941
+ }
1942
+ });
1943
+
1573
1944
  // src/commands/install.ts
1574
1945
  var install_exports = {};
1575
1946
  __export(install_exports, {
@@ -1577,8 +1948,10 @@ __export(install_exports, {
1577
1948
  });
1578
1949
  import { defineCommand } from "citty";
1579
1950
  import { resolve as resolve2 } from "path";
1580
- import { join as join12 } from "path";
1581
- import { confirm as confirm2, select, isCancel as isCancel3, cancel as cancel2 } from "@clack/prompts";
1951
+ import { join as join15 } from "path";
1952
+ import { mkdir as mkdir7 } from "fs/promises";
1953
+ import { confirm as confirm2, select, text, isCancel as isCancel3, cancel as cancel2 } from "@clack/prompts";
1954
+ import pc6 from "picocolors";
1582
1955
  var install_default;
1583
1956
  var init_install = __esm({
1584
1957
  "src/commands/install.ts"() {
@@ -1588,12 +1961,15 @@ var init_install = __esm({
1588
1961
  init_ui();
1589
1962
  init_config();
1590
1963
  init_skills();
1964
+ init_user_content();
1591
1965
  init_stack();
1592
1966
  init_community_skills();
1593
1967
  init_deprecated();
1594
1968
  init_gitignore();
1595
1969
  init_post_install();
1596
1970
  init_update_check();
1971
+ init_telemetry();
1972
+ init_api_key();
1597
1973
  install_default = defineCommand({
1598
1974
  meta: {
1599
1975
  name: "install",
@@ -1602,7 +1978,7 @@ var init_install = __esm({
1602
1978
  args: {
1603
1979
  tier: {
1604
1980
  type: "string",
1605
- description: "Set tier: 'local' (free) or 'cloud' (Linear)"
1981
+ description: "Set tier: 'local' (free) or 'cloud' (managed)"
1606
1982
  },
1607
1983
  path: {
1608
1984
  type: "string",
@@ -1629,6 +2005,7 @@ var init_install = __esm({
1629
2005
  const templateDir = await resolveTemplatePath(args["local-source"]);
1630
2006
  const version = await readTemplateVersion(templateDir);
1631
2007
  printBanner(version);
2008
+ await capture("install_started", { template_version: version });
1632
2009
  let targetDir;
1633
2010
  if (args.path) {
1634
2011
  targetDir = resolve2(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -1654,62 +2031,222 @@ var init_install = __esm({
1654
2031
  process.exit(1);
1655
2032
  }
1656
2033
  tier = args.tier;
1657
- printInfo(`Tier set via flag: ${tier}`);
1658
- } else if (await pathExists(join12(targetDir, ".flydocs", "config.json"))) {
2034
+ } else if (await pathExists(join15(targetDir, ".flydocs", "config.json"))) {
1659
2035
  try {
1660
2036
  const existing = await readConfig(targetDir);
1661
2037
  if (existing.tier) {
1662
2038
  tier = existing.tier;
1663
- printInfo(`Tier from config: ${tier}`);
1664
2039
  }
1665
2040
  } catch {
1666
2041
  }
1667
2042
  }
1668
- if (!tier) {
1669
- const shouldInstall = await confirm2({
1670
- message: `Install FlyDocs here? (local tier)
2043
+ if (!args.tier) {
2044
+ if (!tier) {
2045
+ const tierChoice = await select({
2046
+ message: `Install FlyDocs in: ${targetDir}`,
2047
+ options: [
2048
+ {
2049
+ value: "local",
2050
+ label: "Local (free)",
2051
+ hint: "File-based issues, no account needed"
2052
+ },
2053
+ {
2054
+ value: "cloud",
2055
+ label: "Cloud (managed)",
2056
+ hint: "Sync with Linear, Jira, and more"
2057
+ }
2058
+ ]
2059
+ });
2060
+ if (isCancel3(tierChoice)) {
2061
+ cancel2("Installation cancelled.");
2062
+ process.exit(0);
2063
+ }
2064
+ tier = tierChoice;
2065
+ console.log();
2066
+ } else {
2067
+ const shouldInstall = await confirm2({
2068
+ message: `Install FlyDocs here? (${tier} tier)
1671
2069
  Directory: ${targetDir}`
2070
+ });
2071
+ if (isCancel3(shouldInstall) || !shouldInstall) {
2072
+ cancel2("Installation cancelled.");
2073
+ process.exit(0);
2074
+ }
2075
+ console.log();
2076
+ }
2077
+ }
2078
+ if (!tier) {
2079
+ tier = "local";
2080
+ }
2081
+ printInfo(`Tier: ${tier}`);
2082
+ await capture("install_tier_selected", { tier });
2083
+ let selectedWorkspaceId = null;
2084
+ let selectedWorkspaceName = null;
2085
+ if (tier === "cloud") {
2086
+ console.log();
2087
+ console.log(` ${pc6.bold("Connect to FlyDocs Cloud")}`);
2088
+ console.log();
2089
+ console.log(
2090
+ ` ${pc6.dim("Get your API key from your FlyDocs dashboard (fdk_...)")}`
2091
+ );
2092
+ console.log();
2093
+ const keyInput = await text({
2094
+ message: "Enter your FlyDocs API key",
2095
+ placeholder: "fdk_...",
2096
+ validate(value) {
2097
+ if (!value.trim()) return "API key is required";
2098
+ const type = detectKeyType(value.trim());
2099
+ if (type !== "relay")
2100
+ return "Cloud tier requires a FlyDocs API key (fdk_...)";
2101
+ return void 0;
2102
+ }
1672
2103
  });
1673
- if (isCancel3(shouldInstall) || !shouldInstall) {
2104
+ if (isCancel3(keyInput)) {
1674
2105
  cancel2("Installation cancelled.");
1675
2106
  process.exit(0);
1676
2107
  }
1677
- tier = "local";
2108
+ const apiKey = keyInput.trim();
2109
+ printInfo("Validating API key...");
2110
+ try {
2111
+ const result = await validateRelayKey(apiKey);
2112
+ if (!result.valid) {
2113
+ printError("Invalid API key or relay API unreachable.");
2114
+ console.log(` Check your key and try again.`);
2115
+ process.exit(1);
2116
+ }
2117
+ printStatus(`Connected to ${pc6.bold(result.org)}`);
2118
+ } catch {
2119
+ printError(
2120
+ "Could not reach relay API. Check your network and try again."
2121
+ );
2122
+ process.exit(1);
2123
+ }
2124
+ const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
2125
+ printStatus(`API key stored in ${pc6.dim(envFile)}`);
1678
2126
  console.log();
2127
+ printInfo("Fetching workspaces...");
2128
+ try {
2129
+ const workspaces = await fetchWorkspaces(apiKey);
2130
+ if (workspaces.length === 0) {
2131
+ printError(
2132
+ "No workspaces found. Create a workspace in the FlyDocs dashboard first, then re-run install."
2133
+ );
2134
+ process.exit(1);
2135
+ }
2136
+ if (workspaces.length === 1) {
2137
+ selectedWorkspaceId = workspaces[0].id;
2138
+ selectedWorkspaceName = workspaces[0].name;
2139
+ printStatus(
2140
+ `Workspace: ${pc6.bold(selectedWorkspaceName)} (auto-selected)`
2141
+ );
2142
+ } else {
2143
+ const workspaceChoice = await select({
2144
+ message: "Select a workspace",
2145
+ options: workspaces.map((ws) => ({
2146
+ value: ws.id,
2147
+ label: `${ws.name} (${ws.provider.type})`,
2148
+ hint: ws.team.name
2149
+ }))
2150
+ });
2151
+ if (isCancel3(workspaceChoice)) {
2152
+ cancel2("Installation cancelled.");
2153
+ process.exit(0);
2154
+ }
2155
+ selectedWorkspaceId = workspaceChoice;
2156
+ const chosen = workspaces.find((ws) => ws.id === selectedWorkspaceId);
2157
+ selectedWorkspaceName = chosen?.name ?? "Unknown";
2158
+ printStatus(`Workspace: ${pc6.bold(selectedWorkspaceName)}`);
2159
+ }
2160
+ } catch {
2161
+ printError(
2162
+ "Could not fetch workspaces. Check your network and try again."
2163
+ );
2164
+ process.exit(1);
2165
+ }
1679
2166
  }
1680
- if (!await pathExists(join12(targetDir, ".git"))) {
2167
+ let skipConfigOverwrite = false;
2168
+ if (!await pathExists(join15(targetDir, ".git"))) {
1681
2169
  printWarning("No git repository detected. Run git init when ready.");
1682
2170
  }
1683
2171
  await ensureDirectories(targetDir, tier);
2172
+ const existingFiles = await detectExistingConfigs(targetDir);
2173
+ if (existingFiles.length > 0) {
2174
+ console.log();
2175
+ printWarning(
2176
+ `Found ${existingFiles.length} existing config file(s) that FlyDocs will overwrite:`
2177
+ );
2178
+ for (const f of existingFiles) {
2179
+ console.log(` ${pc6.dim(f)}`);
2180
+ }
2181
+ console.log();
2182
+ if (!args.yes) {
2183
+ const overwriteChoice = await select({
2184
+ message: "How should FlyDocs handle these files?",
2185
+ options: [
2186
+ {
2187
+ value: "backup",
2188
+ label: "Overwrite (backed up)",
2189
+ hint: "Files are saved to .flydocs/backup-originals/ and restored on uninstall"
2190
+ },
2191
+ {
2192
+ value: "skip",
2193
+ label: "Skip overwriting",
2194
+ hint: "Keep your existing files \u2014 FlyDocs config may be incomplete"
2195
+ }
2196
+ ]
2197
+ });
2198
+ if (isCancel3(overwriteChoice)) {
2199
+ cancel2("Installation cancelled.");
2200
+ process.exit(0);
2201
+ }
2202
+ if (overwriteChoice === "skip") {
2203
+ printInfo(
2204
+ "Keeping existing files. Run /flydocs-setup to merge your config manually."
2205
+ );
2206
+ skipConfigOverwrite = true;
2207
+ }
2208
+ }
2209
+ if (!skipConfigOverwrite) {
2210
+ const backedUp = await backupOriginals(targetDir);
2211
+ if (backedUp.length > 0) {
2212
+ printStatus(
2213
+ `Backed up ${backedUp.length} file(s) to .flydocs/backup-originals/`
2214
+ );
2215
+ }
2216
+ }
2217
+ }
1684
2218
  console.log("Installing framework files...");
1685
2219
  await replaceDirectory(
1686
- join12(templateDir, ".flydocs", "templates"),
1687
- join12(targetDir, ".flydocs", "templates")
2220
+ join15(templateDir, ".flydocs", "templates"),
2221
+ join15(targetDir, ".flydocs", "templates")
1688
2222
  );
1689
2223
  await replaceDirectory(
1690
- join12(templateDir, ".flydocs", "hooks"),
1691
- join12(targetDir, ".flydocs", "hooks")
2224
+ join15(templateDir, ".flydocs", "hooks"),
2225
+ join15(targetDir, ".flydocs", "hooks")
1692
2226
  );
1693
2227
  await replaceDirectory(
1694
- join12(templateDir, ".flydocs", "scripts"),
1695
- join12(targetDir, ".flydocs", "scripts")
2228
+ join15(templateDir, ".flydocs", "scripts"),
2229
+ join15(targetDir, ".flydocs", "scripts")
1696
2230
  );
1697
2231
  await copyFile(
1698
- join12(templateDir, ".flydocs", "version"),
1699
- join12(targetDir, ".flydocs", "version")
2232
+ join15(templateDir, ".flydocs", "version"),
2233
+ join15(targetDir, ".flydocs", "version")
1700
2234
  );
1701
- const manifestSrc = join12(templateDir, "manifest.json");
2235
+ const manifestSrc = join15(templateDir, "manifest.json");
1702
2236
  if (await pathExists(manifestSrc)) {
1703
- await copyFile(manifestSrc, join12(targetDir, ".flydocs", "manifest.json"));
2237
+ await copyFile(manifestSrc, join15(targetDir, ".flydocs", "manifest.json"));
1704
2238
  }
1705
- const changelogSrc = join12(templateDir, "CHANGELOG.md");
2239
+ const changelogSrc = join15(templateDir, "CHANGELOG.md");
1706
2240
  if (await pathExists(changelogSrc)) {
1707
- await copyFile(changelogSrc, join12(targetDir, ".flydocs", "CHANGELOG.md"));
2241
+ await copyFile(changelogSrc, join15(targetDir, ".flydocs", "CHANGELOG.md"));
1708
2242
  }
1709
2243
  printStatus(".flydocs/templates, hooks, version, manifest, changelog");
1710
- const configPath = join12(targetDir, ".flydocs", "config.json");
2244
+ const configPath = join15(targetDir, ".flydocs", "config.json");
1711
2245
  if (!await pathExists(configPath)) {
1712
2246
  const config = await createFreshConfig(templateDir, version, tier);
2247
+ if (selectedWorkspaceId) {
2248
+ config.workspaceId = selectedWorkspaceId;
2249
+ }
1713
2250
  await writeConfig(targetDir, config);
1714
2251
  printStatus(`.flydocs/config.json (new, tier: ${tier})`);
1715
2252
  } else if (args.tier) {
@@ -1717,105 +2254,172 @@ var init_install = __esm({
1717
2254
  const existing = await readConfig(targetDir);
1718
2255
  existing.version = version;
1719
2256
  existing.tier = tier;
2257
+ if (selectedWorkspaceId) {
2258
+ existing.workspaceId = selectedWorkspaceId;
2259
+ }
1720
2260
  await writeConfig(targetDir, existing);
1721
2261
  printStatus(`.flydocs/config.json (tier updated: ${tier})`);
1722
2262
  } catch {
1723
2263
  const config = await createFreshConfig(templateDir, version, tier);
2264
+ if (selectedWorkspaceId) {
2265
+ config.workspaceId = selectedWorkspaceId;
2266
+ }
1724
2267
  await writeConfig(targetDir, config);
1725
2268
  printStatus(`.flydocs/config.json (recreated, tier: ${tier})`);
1726
2269
  }
1727
2270
  } else {
1728
- printWarning(".flydocs/config.json exists, preserving");
2271
+ if (selectedWorkspaceId) {
2272
+ try {
2273
+ const existing = await readConfig(targetDir);
2274
+ existing.workspaceId = selectedWorkspaceId;
2275
+ await writeConfig(targetDir, existing);
2276
+ printWarning(
2277
+ ".flydocs/config.json exists, preserving (workspace updated)"
2278
+ );
2279
+ } catch {
2280
+ printWarning(".flydocs/config.json exists, preserving");
2281
+ }
2282
+ } else {
2283
+ printWarning(".flydocs/config.json exists, preserving");
2284
+ }
1729
2285
  }
1730
2286
  console.log();
1731
2287
  console.log(`Installing skills (tier: ${tier})...`);
1732
2288
  await installOwnedSkills(templateDir, targetDir, tier);
1733
2289
  printStatus(`Skills installed (tier: ${tier})`);
1734
2290
  console.log();
1735
- console.log("Installing agents and commands...");
1736
- const claudeAgentsSrc = join12(templateDir, ".claude", "agents");
1737
- if (await pathExists(claudeAgentsSrc)) {
1738
- await copyDirectoryContents(
1739
- claudeAgentsSrc,
1740
- join12(targetDir, ".claude", "agents")
1741
- );
1742
- }
1743
- await copyDirectoryContents(
1744
- join12(templateDir, ".claude", "commands"),
1745
- join12(targetDir, ".claude", "commands")
2291
+ console.log(` ${pc6.bold(pc6.yellow("Sub-Agents (Recommended)"))}`);
2292
+ console.log();
2293
+ console.log(
2294
+ " Sub-agents are specialized roles (PM, implementation, review,"
1746
2295
  );
1747
- await copyFile(
1748
- join12(templateDir, ".claude", "CLAUDE.md"),
1749
- join12(targetDir, ".claude", "CLAUDE.md")
2296
+ console.log(
2297
+ " research) that your AI tool can delegate tasks to. They help"
1750
2298
  );
1751
- await copyFile(
1752
- join12(templateDir, ".claude", "settings.json"),
1753
- join12(targetDir, ".claude", "settings.json")
2299
+ console.log(
2300
+ " structure work but are not required \u2014 everything works without them."
1754
2301
  );
1755
- printStatus(".claude/ (agents, commands, CLAUDE.md, settings)");
1756
- const cursorAgentsSrc = join12(templateDir, ".cursor", "agents");
1757
- if (await pathExists(cursorAgentsSrc)) {
1758
- await copyDirectoryContents(
1759
- cursorAgentsSrc,
1760
- join12(targetDir, ".cursor", "agents")
1761
- );
2302
+ console.log();
2303
+ let installAgents;
2304
+ if (args.yes) {
2305
+ installAgents = true;
2306
+ console.log(" Auto-accepting (--yes)");
2307
+ } else {
2308
+ const agentConfirm = await confirm2({
2309
+ message: "Install sub-agents?",
2310
+ initialValue: true
2311
+ });
2312
+ if (isCancel3(agentConfirm)) {
2313
+ installAgents = false;
2314
+ } else {
2315
+ installAgents = agentConfirm;
2316
+ }
2317
+ }
2318
+ await capture("install_agents_chosen", { install_agents: installAgents });
2319
+ if (installAgents) {
2320
+ const claudeAgentsSrc = join15(templateDir, ".claude", "agents");
2321
+ if (await pathExists(claudeAgentsSrc)) {
2322
+ await mkdir7(join15(targetDir, ".claude", "agents"), { recursive: true });
2323
+ await copyDirectoryContents(
2324
+ claudeAgentsSrc,
2325
+ join15(targetDir, ".claude", "agents")
2326
+ );
2327
+ }
2328
+ const cursorAgentsSrc = join15(templateDir, ".cursor", "agents");
2329
+ if (await pathExists(cursorAgentsSrc)) {
2330
+ await mkdir7(join15(targetDir, ".cursor", "agents"), { recursive: true });
2331
+ await copyDirectoryContents(
2332
+ cursorAgentsSrc,
2333
+ join15(targetDir, ".cursor", "agents")
2334
+ );
2335
+ }
2336
+ printStatus("Sub-agents installed (.claude/agents, .cursor/agents)");
2337
+ } else {
2338
+ printInfo("Skipped sub-agents");
1762
2339
  }
2340
+ console.log();
2341
+ console.log("Installing commands and settings...");
1763
2342
  await copyDirectoryContents(
1764
- join12(templateDir, ".claude", "commands"),
1765
- join12(targetDir, ".cursor", "commands")
2343
+ join15(templateDir, ".claude", "commands"),
2344
+ join15(targetDir, ".claude", "commands")
1766
2345
  );
1767
- await copyFile(
1768
- join12(templateDir, ".cursor", "hooks.json"),
1769
- join12(targetDir, ".cursor", "hooks.json")
2346
+ if (!skipConfigOverwrite) {
2347
+ await copyFile(
2348
+ join15(templateDir, ".claude", "CLAUDE.md"),
2349
+ join15(targetDir, ".claude", "CLAUDE.md")
2350
+ );
2351
+ await copyFile(
2352
+ join15(templateDir, ".claude", "settings.json"),
2353
+ join15(targetDir, ".claude", "settings.json")
2354
+ );
2355
+ printStatus(".claude/ (commands, CLAUDE.md, settings)");
2356
+ } else {
2357
+ printStatus(".claude/ (commands only \u2014 existing config preserved)");
2358
+ }
2359
+ await copyDirectoryContents(
2360
+ join15(templateDir, ".claude", "commands"),
2361
+ join15(targetDir, ".cursor", "commands")
1770
2362
  );
1771
- printStatus(".cursor/ (agents, commands, hooks)");
2363
+ if (!skipConfigOverwrite) {
2364
+ await copyFile(
2365
+ join15(templateDir, ".cursor", "hooks.json"),
2366
+ join15(targetDir, ".cursor", "hooks.json")
2367
+ );
2368
+ printStatus(".cursor/ (commands, hooks)");
2369
+ } else {
2370
+ printStatus(".cursor/ (commands only \u2014 existing hooks preserved)");
2371
+ }
1772
2372
  await copyCursorRules(targetDir);
1773
2373
  printStatus(".cursor/rules/");
1774
- await copyFile(
1775
- join12(templateDir, "AGENTS.md"),
1776
- join12(targetDir, "AGENTS.md")
1777
- );
1778
- printStatus("AGENTS.md");
2374
+ if (!skipConfigOverwrite) {
2375
+ await copyFile(
2376
+ join15(templateDir, "AGENTS.md"),
2377
+ join15(targetDir, "AGENTS.md")
2378
+ );
2379
+ printStatus("AGENTS.md");
2380
+ } else {
2381
+ printInfo("AGENTS.md preserved (existing)");
2382
+ }
1779
2383
  await runManifestGeneration(targetDir);
1780
2384
  await runContextGraphBuild(targetDir);
1781
2385
  console.log();
1782
2386
  console.log("Installing project templates...");
1783
2387
  const userFiles = [
1784
2388
  {
1785
- src: join12(templateDir, "flydocs", "context", "project.md"),
1786
- dest: join12(targetDir, "flydocs", "context", "project.md"),
2389
+ src: join15(templateDir, "flydocs", "context", "project.md"),
2390
+ dest: join15(targetDir, "flydocs", "context", "project.md"),
1787
2391
  label: "flydocs/context/project.md"
1788
2392
  },
1789
2393
  {
1790
- src: join12(templateDir, "flydocs", "knowledge", "INDEX.md"),
1791
- dest: join12(targetDir, "flydocs", "knowledge", "INDEX.md"),
2394
+ src: join15(templateDir, "flydocs", "knowledge", "INDEX.md"),
2395
+ dest: join15(targetDir, "flydocs", "knowledge", "INDEX.md"),
1792
2396
  label: "flydocs/knowledge/INDEX.md"
1793
2397
  },
1794
2398
  {
1795
- src: join12(templateDir, "flydocs", "knowledge", "README.md"),
1796
- dest: join12(targetDir, "flydocs", "knowledge", "README.md"),
2399
+ src: join15(templateDir, "flydocs", "knowledge", "README.md"),
2400
+ dest: join15(targetDir, "flydocs", "knowledge", "README.md"),
1797
2401
  label: "flydocs/knowledge/README.md"
1798
2402
  },
1799
2403
  {
1800
- src: join12(
2404
+ src: join15(
1801
2405
  templateDir,
1802
2406
  "flydocs",
1803
2407
  "knowledge",
1804
2408
  "product",
1805
2409
  "personas.md"
1806
2410
  ),
1807
- dest: join12(targetDir, "flydocs", "knowledge", "product", "personas.md"),
2411
+ dest: join15(targetDir, "flydocs", "knowledge", "product", "personas.md"),
1808
2412
  label: "flydocs/knowledge/product/personas.md"
1809
2413
  },
1810
2414
  {
1811
- src: join12(
2415
+ src: join15(
1812
2416
  templateDir,
1813
2417
  "flydocs",
1814
2418
  "knowledge",
1815
2419
  "product",
1816
2420
  "user-flows.md"
1817
2421
  ),
1818
- dest: join12(
2422
+ dest: join15(
1819
2423
  targetDir,
1820
2424
  "flydocs",
1821
2425
  "knowledge",
@@ -1825,18 +2429,57 @@ var init_install = __esm({
1825
2429
  label: "flydocs/knowledge/product/user-flows.md"
1826
2430
  },
1827
2431
  {
1828
- src: join12(templateDir, "flydocs", "design-system", "README.md"),
1829
- dest: join12(targetDir, "flydocs", "design-system", "README.md"),
2432
+ src: join15(
2433
+ templateDir,
2434
+ "flydocs",
2435
+ "knowledge",
2436
+ "templates",
2437
+ "decision.md"
2438
+ ),
2439
+ dest: join15(
2440
+ targetDir,
2441
+ "flydocs",
2442
+ "knowledge",
2443
+ "templates",
2444
+ "decision.md"
2445
+ ),
2446
+ label: "flydocs/knowledge/templates/decision.md"
2447
+ },
2448
+ {
2449
+ src: join15(
2450
+ templateDir,
2451
+ "flydocs",
2452
+ "knowledge",
2453
+ "templates",
2454
+ "feature.md"
2455
+ ),
2456
+ dest: join15(
2457
+ targetDir,
2458
+ "flydocs",
2459
+ "knowledge",
2460
+ "templates",
2461
+ "feature.md"
2462
+ ),
2463
+ label: "flydocs/knowledge/templates/feature.md"
2464
+ },
2465
+ {
2466
+ src: join15(templateDir, "flydocs", "knowledge", "templates", "note.md"),
2467
+ dest: join15(targetDir, "flydocs", "knowledge", "templates", "note.md"),
2468
+ label: "flydocs/knowledge/templates/note.md"
2469
+ },
2470
+ {
2471
+ src: join15(templateDir, "flydocs", "design-system", "README.md"),
2472
+ dest: join15(targetDir, "flydocs", "design-system", "README.md"),
1830
2473
  label: "flydocs/design-system/README.md"
1831
2474
  },
1832
2475
  {
1833
- src: join12(
2476
+ src: join15(
1834
2477
  templateDir,
1835
2478
  "flydocs",
1836
2479
  "design-system",
1837
2480
  "component-patterns.md"
1838
2481
  ),
1839
- dest: join12(
2482
+ dest: join15(
1840
2483
  targetDir,
1841
2484
  "flydocs",
1842
2485
  "design-system",
@@ -1845,13 +2488,13 @@ var init_install = __esm({
1845
2488
  label: "flydocs/design-system/component-patterns.md"
1846
2489
  },
1847
2490
  {
1848
- src: join12(templateDir, "flydocs", "design-system", "token-mapping.md"),
1849
- dest: join12(targetDir, "flydocs", "design-system", "token-mapping.md"),
2491
+ src: join15(templateDir, "flydocs", "design-system", "token-mapping.md"),
2492
+ dest: join15(targetDir, "flydocs", "design-system", "token-mapping.md"),
1850
2493
  label: "flydocs/design-system/token-mapping.md"
1851
2494
  },
1852
2495
  {
1853
- src: join12(templateDir, "flydocs", "README.md"),
1854
- dest: join12(targetDir, "flydocs", "README.md"),
2496
+ src: join15(templateDir, "flydocs", "README.md"),
2497
+ dest: join15(targetDir, "flydocs", "README.md"),
1855
2498
  label: "flydocs/README.md"
1856
2499
  }
1857
2500
  ];
@@ -1865,14 +2508,10 @@ var init_install = __esm({
1865
2508
  }
1866
2509
  }
1867
2510
  }
1868
- const envExampleSrc = join12(templateDir, ".env.example");
2511
+ const envExampleSrc = join15(templateDir, ".env.example");
1869
2512
  if (await pathExists(envExampleSrc)) {
1870
- const hasEnv = await pathExists(join12(targetDir, ".env"));
1871
- const hasEnvExample = await pathExists(join12(targetDir, ".env.example"));
1872
- if (!hasEnv && !hasEnvExample) {
1873
- await copyFile(envExampleSrc, join12(targetDir, ".env.example"));
1874
- printStatus(".env.example (new)");
1875
- }
2513
+ await copyFile(envExampleSrc, join15(targetDir, ".env.example"));
2514
+ printStatus(".env.example");
1876
2515
  }
1877
2516
  await ensureGitignore(targetDir);
1878
2517
  console.log();
@@ -1890,6 +2529,9 @@ var init_install = __esm({
1890
2529
  } else {
1891
2530
  printInfo("No framework detected in package.json");
1892
2531
  }
2532
+ await capture("install_skills_chosen", {
2533
+ stack_detected: stack.raw
2534
+ });
1893
2535
  console.log();
1894
2536
  console.log("Checking for deprecated files...");
1895
2537
  const deprecated = await scanDeprecated(targetDir);
@@ -1902,17 +2544,16 @@ var init_install = __esm({
1902
2544
  " 1. Run /flydocs-setup to configure your project",
1903
2545
  " 2. Start working with /start-session",
1904
2546
  "",
1905
- "Documentation: flydocs/README.md"
2547
+ "Docs: https://www.flydocs.ai/docs"
1906
2548
  ] : [
1907
2549
  `Tier: ${tier}`,
1908
2550
  `Version: ${version}`,
1909
2551
  "",
1910
2552
  "Next steps:",
1911
- " 1. Copy your LINEAR_API_KEY to .env",
1912
- " 2. Run /flydocs-setup to configure your Linear workspace",
1913
- " 3. Start working with /start-session",
2553
+ " 1. Run /flydocs-setup to configure your workspace",
2554
+ " 2. Start working with /start-session",
1914
2555
  "",
1915
- "Documentation: flydocs/README.md"
2556
+ "Docs: https://www.flydocs.ai/docs"
1916
2557
  ];
1917
2558
  let copiedToClipboard = false;
1918
2559
  try {
@@ -1931,6 +2572,7 @@ var init_install = __esm({
1931
2572
  }
1932
2573
  printCompletionBox("Installation Complete!", nextSteps);
1933
2574
  printBetaCta();
2575
+ const detectedIdes = [];
1934
2576
  try {
1935
2577
  const { execSync: execSync2, spawn } = await import("child_process");
1936
2578
  const isInstalled = (cmd) => {
@@ -1966,6 +2608,7 @@ var init_install = __esm({
1966
2608
  passCommand: false
1967
2609
  });
1968
2610
  }
2611
+ detectedIdes.push(...ideOptions.map((o) => o.cmd));
1969
2612
  if (ideOptions.length === 1) {
1970
2613
  const ide = ideOptions[0];
1971
2614
  const launchConfirm = await confirm2({
@@ -2024,6 +2667,9 @@ var init_install = __esm({
2024
2667
  }
2025
2668
  } catch {
2026
2669
  }
2670
+ await capture("install_ide_detected", { ides: detectedIdes });
2671
+ await capture("install_completed", { tier, version });
2672
+ await flush();
2027
2673
  try {
2028
2674
  const updateResult = await checkForUpdate();
2029
2675
  if (updateResult) {
@@ -2042,10 +2688,10 @@ __export(update_exports, {
2042
2688
  default: () => update_default
2043
2689
  });
2044
2690
  import { defineCommand as defineCommand2 } from "citty";
2045
- import { resolve as resolve3, join as join13 } from "path";
2046
- import { mkdir as mkdir5, cp as cp2, readFile as readFile8, readdir as readdir3, rm as rm4 } from "fs/promises";
2047
- import { select as select2, text, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
2048
- import pc6 from "picocolors";
2691
+ import { resolve as resolve3, join as join16 } from "path";
2692
+ import { mkdir as mkdir8, cp as cp2, readFile as readFile11, readdir as readdir3, rm as rm4 } from "fs/promises";
2693
+ import { select as select2, text as text2, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
2694
+ import pc7 from "picocolors";
2049
2695
  var update_default;
2050
2696
  var init_update = __esm({
2051
2697
  "src/commands/update.ts"() {
@@ -2062,6 +2708,7 @@ var init_update = __esm({
2062
2708
  init_gitignore();
2063
2709
  init_post_install();
2064
2710
  init_update_check();
2711
+ init_telemetry();
2065
2712
  update_default = defineCommand2({
2066
2713
  meta: {
2067
2714
  name: "update",
@@ -2079,7 +2726,7 @@ var init_update = __esm({
2079
2726
  },
2080
2727
  tier: {
2081
2728
  type: "string",
2082
- description: "Change tier: 'local' (free) or 'cloud' (Linear)"
2729
+ description: "Change tier: 'local' (free) or 'cloud' (managed)"
2083
2730
  },
2084
2731
  force: {
2085
2732
  type: "boolean",
@@ -2104,6 +2751,7 @@ var init_update = __esm({
2104
2751
  printBanner(version);
2105
2752
  printInfo("Running in update mode \u2014 framework files will be refreshed");
2106
2753
  console.log();
2754
+ await capture("update_started", { template_version: version });
2107
2755
  let targetDir;
2108
2756
  if (args.path) {
2109
2757
  targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
@@ -2130,7 +2778,7 @@ var init_update = __esm({
2130
2778
  if (choice === "cwd") {
2131
2779
  targetDir = process.cwd();
2132
2780
  } else {
2133
- const enteredPath = await text({
2781
+ const enteredPath = await text2({
2134
2782
  message: "Enter project path:"
2135
2783
  });
2136
2784
  if (isCancel4(enteredPath)) {
@@ -2148,9 +2796,9 @@ var init_update = __esm({
2148
2796
  }
2149
2797
  targetDir = resolve3(targetDir);
2150
2798
  process.chdir(targetDir);
2151
- const hasVersion = await pathExists(join13(targetDir, ".flydocs", "version"));
2799
+ const hasVersion = await pathExists(join16(targetDir, ".flydocs", "version"));
2152
2800
  const hasConfig = await pathExists(
2153
- join13(targetDir, ".flydocs", "config.json")
2801
+ join16(targetDir, ".flydocs", "config.json")
2154
2802
  );
2155
2803
  if (!hasVersion && !hasConfig) {
2156
2804
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -2161,8 +2809,8 @@ var init_update = __esm({
2161
2809
  console.log();
2162
2810
  let currentVersion = "0.1.0";
2163
2811
  if (hasVersion) {
2164
- const vContent = await readFile8(
2165
- join13(targetDir, ".flydocs", "version"),
2812
+ const vContent = await readFile11(
2813
+ join16(targetDir, ".flydocs", "version"),
2166
2814
  "utf-8"
2167
2815
  );
2168
2816
  currentVersion = vContent.trim();
@@ -2188,12 +2836,18 @@ var init_update = __esm({
2188
2836
  process.exit(0);
2189
2837
  }
2190
2838
  }
2839
+ await capture("update_version_compared", {
2840
+ current_version: currentVersion,
2841
+ target_version: version,
2842
+ version_status: versionStatus,
2843
+ force: args.force
2844
+ });
2191
2845
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
2192
2846
  console.log();
2193
- const changelogPath = join13(templateDir, "CHANGELOG.md");
2847
+ const changelogPath = join16(templateDir, "CHANGELOG.md");
2194
2848
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
2195
2849
  if (whatsNew.length > 0) {
2196
- console.log(pc6.cyan("What's new:"));
2850
+ console.log(pc7.cyan("What's new:"));
2197
2851
  console.log();
2198
2852
  for (const entry of whatsNew) {
2199
2853
  console.log(` ${entry}`);
@@ -2202,23 +2856,23 @@ var init_update = __esm({
2202
2856
  }
2203
2857
  const now = /* @__PURE__ */ new Date();
2204
2858
  const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
2205
- const backupDir = join13(targetDir, ".flydocs", `backup-${ts}`);
2206
- await mkdir5(backupDir, { recursive: true });
2859
+ const backupDir = join16(targetDir, ".flydocs", `backup-${ts}`);
2860
+ await mkdir8(backupDir, { recursive: true });
2207
2861
  if (hasConfig) {
2208
2862
  await cp2(
2209
- join13(targetDir, ".flydocs", "config.json"),
2210
- join13(backupDir, "config.json")
2863
+ join16(targetDir, ".flydocs", "config.json"),
2864
+ join16(backupDir, "config.json")
2211
2865
  );
2212
2866
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
2213
2867
  }
2214
2868
  try {
2215
- const flydocsDir = join13(targetDir, ".flydocs");
2869
+ const flydocsDir = join16(targetDir, ".flydocs");
2216
2870
  const entries = await readdir3(flydocsDir);
2217
2871
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
2218
2872
  if (backups.length > 3) {
2219
2873
  const toRemove = backups.slice(0, backups.length - 3);
2220
2874
  for (const old of toRemove) {
2221
- await rm4(join13(flydocsDir, old), { recursive: true, force: true });
2875
+ await rm4(join16(flydocsDir, old), { recursive: true, force: true });
2222
2876
  }
2223
2877
  }
2224
2878
  } catch {
@@ -2226,10 +2880,9 @@ var init_update = __esm({
2226
2880
  let preserved = {
2227
2881
  tier: "cloud",
2228
2882
  setupComplete: false,
2229
- providerTeamId: null,
2883
+ workspaceId: null,
2230
2884
  workspace: {},
2231
2885
  issueLabels: {},
2232
- statusMapping: {},
2233
2886
  detectedStack: {},
2234
2887
  skills: {},
2235
2888
  designSystem: null,
@@ -2253,76 +2906,137 @@ var init_update = __esm({
2253
2906
  } else {
2254
2907
  effectiveTier = preserved.tier;
2255
2908
  }
2909
+ await ensureDirectories(targetDir, effectiveTier);
2256
2910
  console.log("Replacing framework directories...");
2257
2911
  await replaceDirectory(
2258
- join13(templateDir, ".flydocs", "templates"),
2259
- join13(targetDir, ".flydocs", "templates")
2912
+ join16(templateDir, ".flydocs", "templates"),
2913
+ join16(targetDir, ".flydocs", "templates")
2260
2914
  );
2261
2915
  await replaceDirectory(
2262
- join13(templateDir, ".flydocs", "hooks"),
2263
- join13(targetDir, ".flydocs", "hooks")
2916
+ join16(templateDir, ".flydocs", "hooks"),
2917
+ join16(targetDir, ".flydocs", "hooks")
2264
2918
  );
2265
2919
  await replaceDirectory(
2266
- join13(templateDir, ".flydocs", "scripts"),
2267
- join13(targetDir, ".flydocs", "scripts")
2920
+ join16(templateDir, ".flydocs", "scripts"),
2921
+ join16(targetDir, ".flydocs", "scripts")
2268
2922
  );
2269
2923
  printStatus(".flydocs/templates, hooks, scripts");
2270
- const claudeAgentsSrc = join13(templateDir, ".claude", "agents");
2271
- if (await pathExists(claudeAgentsSrc)) {
2272
- await copyDirectoryContents(
2273
- claudeAgentsSrc,
2274
- join13(targetDir, ".claude", "agents")
2924
+ const hasExistingAgents = await pathExists(
2925
+ join16(targetDir, ".claude", "agents")
2926
+ );
2927
+ let installAgents;
2928
+ if (args.yes) {
2929
+ installAgents = hasExistingAgents ? true : true;
2930
+ } else if (hasExistingAgents) {
2931
+ installAgents = true;
2932
+ } else {
2933
+ console.log();
2934
+ console.log(` ${pc7.bold(pc7.yellow("Sub-Agents (Recommended)"))}`);
2935
+ console.log();
2936
+ console.log(
2937
+ " Sub-agents are specialized roles (PM, implementation, review,"
2275
2938
  );
2939
+ console.log(
2940
+ " research) that your AI tool can delegate tasks to. They help"
2941
+ );
2942
+ console.log(
2943
+ " structure work but are not required \u2014 everything works without them."
2944
+ );
2945
+ console.log();
2946
+ const agentConfirm = await confirm3({
2947
+ message: "Install sub-agents?",
2948
+ initialValue: true
2949
+ });
2950
+ if (isCancel4(agentConfirm)) {
2951
+ installAgents = false;
2952
+ } else {
2953
+ installAgents = agentConfirm;
2954
+ }
2276
2955
  }
2277
- printStatus(".claude/agents");
2278
- await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2279
- printStatus(`.claude/skills (tier: ${effectiveTier})`);
2280
- const cursorAgentsSrc = join13(templateDir, ".cursor", "agents");
2281
- if (await pathExists(cursorAgentsSrc)) {
2282
- await copyDirectoryContents(
2283
- cursorAgentsSrc,
2284
- join13(targetDir, ".cursor", "agents")
2956
+ if (installAgents) {
2957
+ const claudeAgentsSrc = join16(templateDir, ".claude", "agents");
2958
+ if (await pathExists(claudeAgentsSrc)) {
2959
+ await mkdir8(join16(targetDir, ".claude", "agents"), { recursive: true });
2960
+ await copyDirectoryContents(
2961
+ claudeAgentsSrc,
2962
+ join16(targetDir, ".claude", "agents")
2963
+ );
2964
+ }
2965
+ const cursorAgentsSrc = join16(templateDir, ".cursor", "agents");
2966
+ if (await pathExists(cursorAgentsSrc)) {
2967
+ await mkdir8(join16(targetDir, ".cursor", "agents"), { recursive: true });
2968
+ await copyDirectoryContents(
2969
+ cursorAgentsSrc,
2970
+ join16(targetDir, ".cursor", "agents")
2971
+ );
2972
+ }
2973
+ printStatus(
2974
+ hasExistingAgents ? "Sub-agents updated (.claude/agents, .cursor/agents)" : "Sub-agents installed (.claude/agents, .cursor/agents)"
2285
2975
  );
2976
+ } else {
2977
+ printInfo("Skipped sub-agents");
2286
2978
  }
2287
- printStatus(".cursor/agents");
2979
+ await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
2980
+ printStatus(`.claude/skills (tier: ${effectiveTier})`);
2288
2981
  console.log();
2289
2982
  console.log("Replacing framework files...");
2290
2983
  await copyFile(
2291
- join13(templateDir, ".claude", "CLAUDE.md"),
2292
- join13(targetDir, ".claude", "CLAUDE.md")
2984
+ join16(templateDir, ".claude", "CLAUDE.md"),
2985
+ join16(targetDir, ".claude", "CLAUDE.md")
2293
2986
  );
2294
2987
  await copyFile(
2295
- join13(templateDir, ".claude", "settings.json"),
2296
- join13(targetDir, ".claude", "settings.json")
2988
+ join16(templateDir, ".claude", "settings.json"),
2989
+ join16(targetDir, ".claude", "settings.json")
2297
2990
  );
2298
2991
  printStatus(".claude/CLAUDE.md, settings.json");
2299
2992
  await copyDirectoryContents(
2300
- join13(templateDir, ".claude", "commands"),
2301
- join13(targetDir, ".claude", "commands")
2993
+ join16(templateDir, ".claude", "commands"),
2994
+ join16(targetDir, ".claude", "commands")
2302
2995
  );
2303
2996
  await copyDirectoryContents(
2304
- join13(templateDir, ".claude", "commands"),
2305
- join13(targetDir, ".cursor", "commands")
2997
+ join16(templateDir, ".claude", "commands"),
2998
+ join16(targetDir, ".cursor", "commands")
2306
2999
  );
2307
3000
  printStatus(".claude/commands, .cursor/commands");
2308
- const skillsReadmeSrc = join13(templateDir, ".claude", "skills", "README.md");
3001
+ const skillsReadmeSrc = join16(templateDir, ".claude", "skills", "README.md");
2309
3002
  if (await pathExists(skillsReadmeSrc)) {
2310
3003
  await copyFile(
2311
3004
  skillsReadmeSrc,
2312
- join13(targetDir, ".claude", "skills", "README.md")
3005
+ join16(targetDir, ".claude", "skills", "README.md")
2313
3006
  );
2314
3007
  }
2315
3008
  printStatus(".claude/skills/README.md");
2316
3009
  await copyFile(
2317
- join13(templateDir, ".cursor", "hooks.json"),
2318
- join13(targetDir, ".cursor", "hooks.json")
3010
+ join16(templateDir, ".cursor", "hooks.json"),
3011
+ join16(targetDir, ".cursor", "hooks.json")
2319
3012
  );
2320
3013
  printStatus(".cursor/hooks.json");
2321
3014
  await copyFile(
2322
- join13(templateDir, "AGENTS.md"),
2323
- join13(targetDir, "AGENTS.md")
3015
+ join16(templateDir, "AGENTS.md"),
3016
+ join16(targetDir, "AGENTS.md")
2324
3017
  );
2325
3018
  printStatus("AGENTS.md");
3019
+ const envExampleSrc = join16(templateDir, ".env.example");
3020
+ if (await pathExists(envExampleSrc)) {
3021
+ await copyFile(envExampleSrc, join16(targetDir, ".env.example"));
3022
+ printStatus(".env.example");
3023
+ }
3024
+ const knowledgeTemplatesDir = join16(
3025
+ targetDir,
3026
+ "flydocs",
3027
+ "knowledge",
3028
+ "templates"
3029
+ );
3030
+ if (!await pathExists(knowledgeTemplatesDir)) {
3031
+ await mkdir8(knowledgeTemplatesDir, { recursive: true });
3032
+ }
3033
+ for (const tmpl of ["decision.md", "feature.md", "note.md"]) {
3034
+ const src = join16(templateDir, "flydocs", "knowledge", "templates", tmpl);
3035
+ const dest = join16(knowledgeTemplatesDir, tmpl);
3036
+ if (await pathExists(src) && !await pathExists(dest)) {
3037
+ await copyFile(src, dest);
3038
+ }
3039
+ }
2326
3040
  await runManifestGeneration(targetDir);
2327
3041
  await runContextGraphBuild(targetDir);
2328
3042
  console.log();
@@ -2345,18 +3059,18 @@ var init_update = __esm({
2345
3059
  printWarning("Config merge failed \u2014 config.json preserved as-is");
2346
3060
  }
2347
3061
  await copyFile(
2348
- join13(templateDir, ".flydocs", "version"),
2349
- join13(targetDir, ".flydocs", "version")
3062
+ join16(templateDir, ".flydocs", "version"),
3063
+ join16(targetDir, ".flydocs", "version")
2350
3064
  );
2351
3065
  printStatus(`.flydocs/version \u2192 ${version}`);
2352
- const clSrc = join13(templateDir, "CHANGELOG.md");
3066
+ const clSrc = join16(templateDir, "CHANGELOG.md");
2353
3067
  if (await pathExists(clSrc)) {
2354
- await copyFile(clSrc, join13(targetDir, ".flydocs", "CHANGELOG.md"));
3068
+ await copyFile(clSrc, join16(targetDir, ".flydocs", "CHANGELOG.md"));
2355
3069
  printStatus(".flydocs/CHANGELOG.md");
2356
3070
  }
2357
- const mfSrc = join13(templateDir, "manifest.json");
3071
+ const mfSrc = join16(templateDir, "manifest.json");
2358
3072
  if (await pathExists(mfSrc)) {
2359
- await copyFile(mfSrc, join13(targetDir, ".flydocs", "manifest.json"));
3073
+ await copyFile(mfSrc, join16(targetDir, ".flydocs", "manifest.json"));
2360
3074
  printStatus(".flydocs/manifest.json");
2361
3075
  }
2362
3076
  console.log();
@@ -2392,6 +3106,12 @@ var init_update = __esm({
2392
3106
  `Backup: .flydocs/backup-${ts}/`
2393
3107
  ]);
2394
3108
  printBetaCta();
3109
+ await capture("update_completed", {
3110
+ current_version: currentVersion,
3111
+ version,
3112
+ tier: effectiveTier
3113
+ });
3114
+ await flush();
2395
3115
  try {
2396
3116
  const updateResult = await checkForUpdate();
2397
3117
  if (updateResult) {
@@ -2404,33 +3124,352 @@ var init_update = __esm({
2404
3124
  }
2405
3125
  });
2406
3126
 
3127
+ // src/commands/uninstall.ts
3128
+ var uninstall_exports = {};
3129
+ __export(uninstall_exports, {
3130
+ default: () => uninstall_default
3131
+ });
3132
+ import { defineCommand as defineCommand3 } from "citty";
3133
+ import { resolve as resolve4, join as join17 } from "path";
3134
+ import { readdir as readdir4, rm as rm5, rename as rename2 } from "fs/promises";
3135
+ import { confirm as confirm4, select as select3, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3136
+ import pc8 from "picocolors";
3137
+ async function removeOwnedSkills(targetDir) {
3138
+ const skillsDir = join17(targetDir, ".claude", "skills");
3139
+ const removed = [];
3140
+ if (!await pathExists(skillsDir)) {
3141
+ return removed;
3142
+ }
3143
+ try {
3144
+ const entries = await readdir4(skillsDir);
3145
+ for (const entry of entries) {
3146
+ if (entry.startsWith(OWNED_SKILL_PREFIX)) {
3147
+ await rm5(join17(skillsDir, entry), { recursive: true, force: true });
3148
+ removed.push(`.claude/skills/${entry}`);
3149
+ }
3150
+ }
3151
+ } catch {
3152
+ }
3153
+ return removed;
3154
+ }
3155
+ async function removeOwnedCursorRules(targetDir) {
3156
+ const rulesDir = join17(targetDir, ".cursor", "rules");
3157
+ const removed = [];
3158
+ if (!await pathExists(rulesDir)) {
3159
+ return removed;
3160
+ }
3161
+ try {
3162
+ const entries = await readdir4(rulesDir);
3163
+ for (const entry of entries) {
3164
+ if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
3165
+ await rm5(join17(rulesDir, entry), { force: true });
3166
+ removed.push(`.cursor/rules/${entry}`);
3167
+ }
3168
+ }
3169
+ } catch {
3170
+ }
3171
+ return removed;
3172
+ }
3173
+ async function isEmptyDir(dirPath) {
3174
+ try {
3175
+ const entries = await readdir4(dirPath);
3176
+ return entries.length === 0;
3177
+ } catch {
3178
+ return false;
3179
+ }
3180
+ }
3181
+ async function cleanupEmptyParents(targetDir, dirs) {
3182
+ const cleaned = [];
3183
+ for (const dir of dirs) {
3184
+ const fullPath = join17(targetDir, dir);
3185
+ if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
3186
+ await rm5(fullPath, { recursive: true, force: true });
3187
+ cleaned.push(dir);
3188
+ }
3189
+ }
3190
+ return cleaned;
3191
+ }
3192
+ var ALWAYS_REMOVED, OWNED_SKILL_PREFIX, OWNED_RULE_PREFIX, uninstall_default;
3193
+ var init_uninstall = __esm({
3194
+ "src/commands/uninstall.ts"() {
3195
+ "use strict";
3196
+ init_fs_ops();
3197
+ init_user_content();
3198
+ init_ui();
3199
+ init_constants();
3200
+ ALWAYS_REMOVED = [
3201
+ [".claude/CLAUDE.md", "file"],
3202
+ [".claude/settings.json", "file"],
3203
+ [".claude/agents", "dir"],
3204
+ [".claude/commands", "dir"],
3205
+ [".claude/skills/README.md", "file"],
3206
+ [".cursor/hooks.json", "file"],
3207
+ [".cursor/agents", "dir"],
3208
+ [".flydocs", "dir"],
3209
+ ["AGENTS.md", "file"],
3210
+ [".env.example", "file"]
3211
+ ];
3212
+ OWNED_SKILL_PREFIX = "flydocs-";
3213
+ OWNED_RULE_PREFIX = "flydocs-";
3214
+ uninstall_default = defineCommand3({
3215
+ meta: {
3216
+ name: "uninstall",
3217
+ description: "Remove FlyDocs from a project directory"
3218
+ },
3219
+ args: {
3220
+ path: {
3221
+ type: "string",
3222
+ description: "Uninstall from the specified directory"
3223
+ },
3224
+ here: {
3225
+ type: "boolean",
3226
+ description: "Uninstall from the current directory",
3227
+ default: false
3228
+ },
3229
+ all: {
3230
+ type: "boolean",
3231
+ description: "Remove everything including flydocs/ user content",
3232
+ default: false
3233
+ },
3234
+ yes: {
3235
+ type: "boolean",
3236
+ alias: ["y"],
3237
+ description: "Skip confirmation prompts",
3238
+ default: false
3239
+ },
3240
+ force: {
3241
+ type: "boolean",
3242
+ description: "Alias for --all --yes (complete removal, no prompts)",
3243
+ default: false
3244
+ }
3245
+ },
3246
+ async run({ args }) {
3247
+ printBanner(CLI_VERSION);
3248
+ let targetDir;
3249
+ if (args.path) {
3250
+ targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
3251
+ } else if (args.here) {
3252
+ targetDir = process.cwd();
3253
+ } else {
3254
+ targetDir = process.cwd();
3255
+ }
3256
+ if (!await pathExists(targetDir)) {
3257
+ printError(`Directory does not exist: ${targetDir}`);
3258
+ process.exit(1);
3259
+ }
3260
+ targetDir = resolve4(targetDir);
3261
+ const hasFlydocs = await pathExists(join17(targetDir, ".flydocs"));
3262
+ const hasAgentsMd = await pathExists(join17(targetDir, "AGENTS.md"));
3263
+ if (!hasFlydocs && !hasAgentsMd) {
3264
+ printError(`Not a FlyDocs project: ${targetDir}`);
3265
+ printInfo("No .flydocs/ directory or AGENTS.md found.");
3266
+ process.exit(1);
3267
+ }
3268
+ printInfo(`Project: ${targetDir}`);
3269
+ console.log();
3270
+ const forceAll = args.force;
3271
+ const removeAll = forceAll || args.all;
3272
+ const skipPrompts = forceAll || args.yes;
3273
+ let contentAction = "preserve";
3274
+ const hasUserContent = await pathExists(join17(targetDir, "flydocs"));
3275
+ if (hasUserContent) {
3276
+ if (removeAll) {
3277
+ contentAction = "remove";
3278
+ } else if (!skipPrompts) {
3279
+ const choice = await select3({
3280
+ message: "What should happen to your flydocs/ content (project docs, knowledge base)?",
3281
+ options: [
3282
+ {
3283
+ value: "archive",
3284
+ label: "Archive",
3285
+ hint: "Rename to flydocs-archive/ (safe, reversible)"
3286
+ },
3287
+ {
3288
+ value: "remove",
3289
+ label: "Remove completely",
3290
+ hint: "Permanently delete flydocs/ and all contents"
3291
+ },
3292
+ {
3293
+ value: "preserve",
3294
+ label: "Keep as-is",
3295
+ hint: "Leave flydocs/ untouched"
3296
+ }
3297
+ ]
3298
+ });
3299
+ if (isCancel5(choice)) {
3300
+ cancel4("Uninstall cancelled.");
3301
+ process.exit(0);
3302
+ }
3303
+ contentAction = choice;
3304
+ }
3305
+ }
3306
+ if (!skipPrompts) {
3307
+ console.log();
3308
+ console.log(pc8.bold("The following will be removed:"));
3309
+ console.log();
3310
+ console.log(" Framework files:");
3311
+ for (const [path] of ALWAYS_REMOVED) {
3312
+ console.log(` ${pc8.dim(path)}`);
3313
+ }
3314
+ console.log(` ${pc8.dim(".claude/skills/flydocs-*")}`);
3315
+ console.log(` ${pc8.dim(".cursor/rules/flydocs-*.mdc")}`);
3316
+ if (hasUserContent) {
3317
+ if (contentAction === "archive") {
3318
+ console.log();
3319
+ console.log(
3320
+ ` User content: ${pc8.yellow("flydocs/ -> flydocs-archive/")}`
3321
+ );
3322
+ } else if (contentAction === "remove") {
3323
+ console.log();
3324
+ console.log(` User content: ${pc8.red("flydocs/ (deleted)")}`);
3325
+ } else {
3326
+ console.log();
3327
+ console.log(` User content: ${pc8.green("flydocs/ (preserved)")}`);
3328
+ }
3329
+ }
3330
+ console.log();
3331
+ console.log(pc8.bold("Preserved:"));
3332
+ console.log(
3333
+ ` ${pc8.dim(".claude/skills/ (non-flydocs community skills)")}`
3334
+ );
3335
+ console.log(` ${pc8.dim(".env, .env.local")}`);
3336
+ console.log();
3337
+ const shouldContinue = await confirm4({
3338
+ message: "Proceed with uninstall?"
3339
+ });
3340
+ if (isCancel5(shouldContinue) || !shouldContinue) {
3341
+ cancel4("Uninstall cancelled.");
3342
+ process.exit(0);
3343
+ }
3344
+ }
3345
+ const originals = await readBackupOriginals(targetDir);
3346
+ const result = {
3347
+ removed: [],
3348
+ skipped: [],
3349
+ archived: [],
3350
+ restored: []
3351
+ };
3352
+ const removedSkills = await removeOwnedSkills(targetDir);
3353
+ result.removed.push(...removedSkills);
3354
+ const removedRules = await removeOwnedCursorRules(targetDir);
3355
+ result.removed.push(...removedRules);
3356
+ for (const [relativePath, type] of ALWAYS_REMOVED) {
3357
+ const fullPath = join17(targetDir, relativePath);
3358
+ if (!await pathExists(fullPath)) {
3359
+ result.skipped.push(relativePath);
3360
+ continue;
3361
+ }
3362
+ try {
3363
+ if (type === "dir") {
3364
+ await rm5(fullPath, { recursive: true, force: true });
3365
+ } else {
3366
+ await rm5(fullPath, { force: true });
3367
+ }
3368
+ result.removed.push(relativePath);
3369
+ } catch {
3370
+ printWarning(`Could not remove: ${relativePath}`);
3371
+ result.skipped.push(relativePath);
3372
+ }
3373
+ }
3374
+ if (hasUserContent) {
3375
+ const flydocsPath = join17(targetDir, "flydocs");
3376
+ if (contentAction === "archive") {
3377
+ const archivePath = join17(targetDir, "flydocs-archive");
3378
+ if (await pathExists(archivePath)) {
3379
+ await rm5(archivePath, { recursive: true, force: true });
3380
+ }
3381
+ await rename2(flydocsPath, archivePath);
3382
+ result.archived.push("flydocs/ -> flydocs-archive/");
3383
+ } else if (contentAction === "remove") {
3384
+ await rm5(flydocsPath, { recursive: true, force: true });
3385
+ result.removed.push("flydocs/");
3386
+ }
3387
+ }
3388
+ const emptyCandidates = [
3389
+ ".claude/skills",
3390
+ ".claude",
3391
+ ".cursor/rules",
3392
+ ".cursor"
3393
+ ];
3394
+ const cleaned = await cleanupEmptyParents(targetDir, emptyCandidates);
3395
+ for (const dir of cleaned) {
3396
+ result.removed.push(`${dir}/ (empty, cleaned up)`);
3397
+ }
3398
+ if (originals.length > 0) {
3399
+ await writeRestoredFiles(targetDir, originals);
3400
+ result.restored = originals.map((f) => f.relativePath);
3401
+ }
3402
+ console.log();
3403
+ console.log(pc8.bold("Uninstall Summary"));
3404
+ console.log();
3405
+ if (result.removed.length > 0) {
3406
+ console.log(` ${pc8.green("Removed")} (${result.removed.length}):`);
3407
+ for (const item of result.removed) {
3408
+ printStatus(item);
3409
+ }
3410
+ }
3411
+ if (result.archived.length > 0) {
3412
+ console.log();
3413
+ console.log(` ${pc8.yellow("Archived")} (${result.archived.length}):`);
3414
+ for (const item of result.archived) {
3415
+ printInfo(item);
3416
+ }
3417
+ }
3418
+ if (result.restored.length > 0) {
3419
+ console.log();
3420
+ console.log(
3421
+ ` ${pc8.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
3422
+ );
3423
+ for (const item of result.restored) {
3424
+ printInfo(item);
3425
+ }
3426
+ }
3427
+ if (result.skipped.length > 0) {
3428
+ console.log();
3429
+ console.log(
3430
+ ` ${pc8.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
3431
+ );
3432
+ for (const item of result.skipped) {
3433
+ console.log(` ${pc8.dim(item)}`);
3434
+ }
3435
+ }
3436
+ console.log();
3437
+ printStatus("FlyDocs has been removed from this project.");
3438
+ console.log();
3439
+ printInfo(`To reinstall: ${pc8.cyan("npx @flydocs/cli install --here")}`);
3440
+ console.log();
3441
+ }
3442
+ });
3443
+ }
3444
+ });
3445
+
2407
3446
  // src/commands/setup.ts
2408
3447
  var setup_exports = {};
2409
3448
  __export(setup_exports, {
2410
3449
  default: () => setup_default
2411
3450
  });
2412
- import { defineCommand as defineCommand3 } from "citty";
2413
- import pc7 from "picocolors";
3451
+ import { defineCommand as defineCommand4 } from "citty";
3452
+ import pc9 from "picocolors";
2414
3453
  var setup_default;
2415
3454
  var init_setup = __esm({
2416
3455
  "src/commands/setup.ts"() {
2417
3456
  "use strict";
2418
- setup_default = defineCommand3({
3457
+ setup_default = defineCommand4({
2419
3458
  meta: {
2420
3459
  name: "setup",
2421
3460
  description: "Configure FlyDocs settings for this project"
2422
3461
  },
2423
3462
  run() {
2424
3463
  console.log();
2425
- console.log(` ${pc7.bold("FlyDocs Setup")}`);
3464
+ console.log(` ${pc9.bold("FlyDocs Setup")}`);
2426
3465
  console.log();
2427
3466
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
2428
3467
  console.log();
2429
3468
  console.log(
2430
- ` ${pc7.cyan("Claude Code:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3469
+ ` ${pc9.cyan("Claude Code:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2431
3470
  );
2432
3471
  console.log(
2433
- ` ${pc7.cyan("Cursor:")} Type ${pc7.bold("/flydocs-setup")} in chat`
3472
+ ` ${pc9.cyan("Cursor:")} Type ${pc9.bold("/flydocs-setup")} in chat`
2434
3473
  );
2435
3474
  console.log();
2436
3475
  console.log(` This configures your project context, detects your stack,`);
@@ -2446,14 +3485,14 @@ var skills_exports = {};
2446
3485
  __export(skills_exports, {
2447
3486
  default: () => skills_default
2448
3487
  });
2449
- import { defineCommand as defineCommand4 } from "citty";
2450
- import pc8 from "picocolors";
3488
+ import { defineCommand as defineCommand5 } from "citty";
3489
+ import pc10 from "picocolors";
2451
3490
  var list, search, add, remove, skills_default;
2452
3491
  var init_skills2 = __esm({
2453
3492
  "src/commands/skills.ts"() {
2454
3493
  "use strict";
2455
3494
  init_skill_manager();
2456
- list = defineCommand4({
3495
+ list = defineCommand5({
2457
3496
  meta: {
2458
3497
  name: "list",
2459
3498
  description: "List installed skills"
@@ -2469,26 +3508,26 @@ var init_skills2 = __esm({
2469
3508
  console.log(`${total} skill(s) installed:`);
2470
3509
  if (result.platform.length > 0) {
2471
3510
  console.log();
2472
- console.log(pc8.bold("Platform"));
3511
+ console.log(pc10.bold("Platform"));
2473
3512
  for (const skill of result.platform) {
2474
3513
  console.log(
2475
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3514
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2476
3515
  );
2477
3516
  }
2478
3517
  }
2479
3518
  if (result.community.length > 0) {
2480
3519
  console.log();
2481
- console.log(pc8.bold("Community"));
3520
+ console.log(pc10.bold("Community"));
2482
3521
  for (const skill of result.community) {
2483
3522
  console.log(
2484
- ` ${skill.name} ${pc8.dim(`(${skill.triggers} triggers)`)}`
3523
+ ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
2485
3524
  );
2486
3525
  }
2487
3526
  }
2488
3527
  console.log();
2489
3528
  }
2490
3529
  });
2491
- search = defineCommand4({
3530
+ search = defineCommand5({
2492
3531
  meta: {
2493
3532
  name: "search",
2494
3533
  description: "Search community skills"
@@ -2504,24 +3543,24 @@ var init_skills2 = __esm({
2504
3543
  const results = await searchCatalog(args.keyword);
2505
3544
  if (results.length === 0) {
2506
3545
  console.log(`No skills found for "${args.keyword}".`);
2507
- console.log(` Browse the catalog at: ${pc8.cyan("https://skills.sh/")}`);
3546
+ console.log(` Browse the catalog at: ${pc10.cyan("https://skills.sh/")}`);
2508
3547
  return;
2509
3548
  }
2510
3549
  console.log();
2511
3550
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
2512
3551
  console.log();
2513
3552
  for (const skill of results) {
2514
- console.log(` ${pc8.bold(skill.name)}`);
3553
+ console.log(` ${pc10.bold(skill.name)}`);
2515
3554
  console.log(` ${skill.description}`);
2516
- console.log(` ${pc8.dim(skill.repo)}`);
3555
+ console.log(` ${pc10.dim(skill.repo)}`);
2517
3556
  if (skill.tags.length > 0) {
2518
- console.log(` ${pc8.dim(skill.tags.join(", "))}`);
3557
+ console.log(` ${pc10.dim(skill.tags.join(", "))}`);
2519
3558
  }
2520
3559
  console.log();
2521
3560
  }
2522
3561
  }
2523
3562
  });
2524
- add = defineCommand4({
3563
+ add = defineCommand5({
2525
3564
  meta: {
2526
3565
  name: "add",
2527
3566
  description: "Install a community skill"
@@ -2537,7 +3576,7 @@ var init_skills2 = __esm({
2537
3576
  await addSkill(process.cwd(), args.source);
2538
3577
  }
2539
3578
  });
2540
- remove = defineCommand4({
3579
+ remove = defineCommand5({
2541
3580
  meta: {
2542
3581
  name: "remove",
2543
3582
  description: "Remove an installed community skill"
@@ -2553,7 +3592,7 @@ var init_skills2 = __esm({
2553
3592
  await removeSkill(process.cwd(), args.name);
2554
3593
  }
2555
3594
  });
2556
- skills_default = defineCommand4({
3595
+ skills_default = defineCommand5({
2557
3596
  meta: {
2558
3597
  name: "skills",
2559
3598
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -2573,11 +3612,10 @@ var connect_exports = {};
2573
3612
  __export(connect_exports, {
2574
3613
  default: () => connect_default
2575
3614
  });
2576
- import { defineCommand as defineCommand5 } from "citty";
2577
- import { text as text2, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
2578
- import pc9 from "picocolors";
2579
- import { readFile as readFile9, writeFile as writeFile5, appendFile as appendFile2 } from "fs/promises";
2580
- import { join as join14 } from "path";
3615
+ import { defineCommand as defineCommand6 } from "citty";
3616
+ import { text as text3, confirm as confirm5, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
3617
+ import pc11 from "picocolors";
3618
+ import { join as join18 } from "path";
2581
3619
  var connect_default;
2582
3620
  var init_connect = __esm({
2583
3621
  "src/commands/connect.ts"() {
@@ -2586,10 +3624,11 @@ var init_connect = __esm({
2586
3624
  init_fs_ops();
2587
3625
  init_template();
2588
3626
  init_ui();
2589
- connect_default = defineCommand5({
3627
+ init_api_key();
3628
+ connect_default = defineCommand6({
2590
3629
  meta: {
2591
3630
  name: "connect",
2592
- description: "Connect FlyDocs to a cloud provider (Linear)"
3631
+ description: "Connect FlyDocs to a cloud provider"
2593
3632
  },
2594
3633
  args: {
2595
3634
  path: {
@@ -2606,16 +3645,16 @@ var init_connect = __esm({
2606
3645
  },
2607
3646
  key: {
2608
3647
  type: "string",
2609
- description: "Linear API key (skips prompt)"
3648
+ description: "FlyDocs API key (fdk_...)"
2610
3649
  }
2611
3650
  },
2612
3651
  async run({ args }) {
2613
3652
  const targetDir = args.path ?? process.cwd();
2614
- const configPath = join14(targetDir, ".flydocs", "config.json");
3653
+ const configPath = join18(targetDir, ".flydocs", "config.json");
2615
3654
  if (!await pathExists(configPath)) {
2616
3655
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
2617
3656
  console.log(
2618
- ` Run ${pc9.cyan("flydocs")} first to install FlyDocs in this project.`
3657
+ ` Run ${pc11.cyan("flydocs")} first to install FlyDocs in this project.`
2619
3658
  );
2620
3659
  process.exit(1);
2621
3660
  }
@@ -2623,91 +3662,89 @@ var init_connect = __esm({
2623
3662
  if (config.tier === "cloud") {
2624
3663
  printInfo("This project is already connected to the cloud tier.");
2625
3664
  console.log();
2626
- const reconnect = await confirm4({
3665
+ const reconnect = await confirm5({
2627
3666
  message: "Want to update your API key?"
2628
3667
  });
2629
- if (isCancel5(reconnect) || !reconnect) {
3668
+ if (isCancel6(reconnect) || !reconnect) {
2630
3669
  console.log(` No changes made.`);
2631
3670
  return;
2632
3671
  }
2633
3672
  }
2634
3673
  console.log();
2635
- console.log(` ${pc9.bold("Connect to Linear")}`);
3674
+ console.log(` ${pc11.bold("Connect to FlyDocs Cloud")}`);
2636
3675
  console.log();
2637
3676
  console.log(
2638
- ` ${pc9.dim("Get your API key from: Linear \u2192 Settings \u2192 API \u2192 Personal API keys")}`
3677
+ ` ${pc11.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
2639
3678
  );
2640
3679
  console.log();
2641
3680
  let apiKey = args.key ?? "";
2642
3681
  if (!apiKey) {
2643
- const keyInput = await text2({
2644
- message: "Enter your Linear API key",
2645
- placeholder: "lin_api_...",
3682
+ const keyInput = await text3({
3683
+ message: "Enter your API key",
3684
+ placeholder: "fdk_...",
2646
3685
  validate(value) {
2647
3686
  if (!value.trim()) return "API key is required";
2648
- if (!value.startsWith("lin_api_"))
2649
- return "Linear API keys start with lin_api_";
3687
+ const type = detectKeyType(value.trim());
3688
+ if (type === "unknown")
3689
+ return "Key must start with fdk_ (FlyDocs API key)";
2650
3690
  return void 0;
2651
3691
  }
2652
3692
  });
2653
- if (isCancel5(keyInput)) {
2654
- cancel4("Connection cancelled.");
3693
+ if (isCancel6(keyInput)) {
3694
+ cancel5("Connection cancelled.");
2655
3695
  process.exit(0);
2656
3696
  }
2657
- apiKey = keyInput;
3697
+ apiKey = keyInput.trim();
2658
3698
  }
2659
- printInfo("Validating API key...");
2660
- try {
2661
- const response = await fetch("https://api.linear.app/graphql", {
2662
- method: "POST",
2663
- headers: {
2664
- Authorization: apiKey,
2665
- "Content-Type": "application/json"
2666
- },
2667
- body: JSON.stringify({ query: "{ viewer { id name email } }" }),
2668
- signal: AbortSignal.timeout(15e3)
2669
- });
2670
- if (!response.ok) {
2671
- throw new Error(`HTTP ${response.status}`);
2672
- }
2673
- const data = await response.json();
2674
- if (!data.data?.viewer) {
2675
- throw new Error("Invalid response");
2676
- }
2677
- const viewer = data.data.viewer;
2678
- printStatus(`Authenticated as ${pc9.bold(viewer.name)} (${viewer.email})`);
2679
- } catch {
2680
- printError("Invalid API key or network error.");
2681
- console.log(` Check your key and try again.`);
3699
+ const keyType = detectKeyType(apiKey);
3700
+ if (keyType === "unknown") {
3701
+ printError(
3702
+ "Unrecognized key format. Expected fdk_ prefix (FlyDocs API key)."
3703
+ );
2682
3704
  process.exit(1);
2683
3705
  }
2684
- const envPath = join14(targetDir, ".env");
2685
- const envLocalPath = join14(targetDir, ".env.local");
2686
- const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
2687
- if (await pathExists(targetEnvPath)) {
2688
- const envContent = await readFile9(targetEnvPath, "utf-8");
2689
- if (envContent.includes("LINEAR_API_KEY=")) {
2690
- const updated = envContent.replace(
2691
- /LINEAR_API_KEY=.*/,
2692
- `LINEAR_API_KEY=${apiKey}`
3706
+ printInfo("Validating API key...");
3707
+ if (keyType === "relay") {
3708
+ try {
3709
+ const result = await validateRelayKey(apiKey);
3710
+ if (!result.valid) {
3711
+ printError("Invalid API key or relay API unreachable.");
3712
+ console.log(` Check your key and try again.`);
3713
+ process.exit(1);
3714
+ }
3715
+ printStatus(`Connected to ${pc11.bold(result.org)}`);
3716
+ } catch {
3717
+ printError(
3718
+ "Could not reach relay API. Check your network and try again."
2693
3719
  );
2694
- await writeFile5(targetEnvPath, updated, "utf-8");
2695
- } else {
2696
- await appendFile2(targetEnvPath, `
2697
- LINEAR_API_KEY=${apiKey}
2698
- `);
3720
+ process.exit(1);
2699
3721
  }
3722
+ const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
3723
+ printStatus(`API key stored in ${pc11.dim(envFile)}`);
2700
3724
  } else {
2701
- await writeFile5(targetEnvPath, `LINEAR_API_KEY=${apiKey}
2702
- `, "utf-8");
3725
+ try {
3726
+ const result = await validateLinearKey(apiKey);
3727
+ if (!result.valid) {
3728
+ printError("Invalid API key or network error.");
3729
+ console.log(` Check your key and try again.`);
3730
+ process.exit(1);
3731
+ }
3732
+ printStatus(
3733
+ `Authenticated as ${pc11.bold(result.name)} (${result.email})`
3734
+ );
3735
+ } catch {
3736
+ printError("Invalid API key or network error.");
3737
+ console.log(` Check your key and try again.`);
3738
+ process.exit(1);
3739
+ }
3740
+ const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
3741
+ printStatus(`API key stored in ${pc11.dim(envFile)}`);
2703
3742
  }
2704
- printStatus(
2705
- `API key stored in ${pc9.dim(targetEnvPath === envLocalPath ? ".env.local" : ".env")}`
2706
- );
2707
3743
  const wasLocal = config.tier === "local";
2708
3744
  config.tier = "cloud";
2709
- config.provider = config.provider ?? { type: "linear", teamId: null };
2710
- config.provider.type = "linear";
3745
+ const configRecord = config;
3746
+ delete configRecord.statusMapping;
3747
+ delete configRecord.provider;
2711
3748
  await writeConfig(targetDir, config);
2712
3749
  printStatus("Config updated to cloud tier");
2713
3750
  if (wasLocal) {
@@ -2715,16 +3752,16 @@ LINEAR_API_KEY=${apiKey}
2715
3752
  const templateDir = await resolveTemplatePath(
2716
3753
  args["local-source"] || void 0
2717
3754
  );
2718
- const templateSkillsDir = join14(templateDir, ".claude", "skills");
2719
- const skillsDir = join14(targetDir, ".claude", "skills");
3755
+ const templateSkillsDir = join18(templateDir, ".claude", "skills");
3756
+ const skillsDir = join18(targetDir, ".claude", "skills");
2720
3757
  await replaceDirectory(
2721
- join14(templateSkillsDir, "flydocs-cloud"),
2722
- join14(skillsDir, "flydocs-cloud")
3758
+ join18(templateSkillsDir, "flydocs-cloud"),
3759
+ join18(skillsDir, "flydocs-cloud")
2723
3760
  );
2724
- const { rm: rm5 } = await import("fs/promises");
2725
- const localSkillDir = join14(skillsDir, "flydocs-local");
3761
+ const { rm: rm6 } = await import("fs/promises");
3762
+ const localSkillDir = join18(skillsDir, "flydocs-local");
2726
3763
  if (await pathExists(localSkillDir)) {
2727
- await rm5(localSkillDir, { recursive: true, force: true });
3764
+ await rm6(localSkillDir, { recursive: true, force: true });
2728
3765
  }
2729
3766
  printStatus("Cloud mechanism skill installed");
2730
3767
  } catch {
@@ -2735,14 +3772,14 @@ LINEAR_API_KEY=${apiKey}
2735
3772
  }
2736
3773
  console.log();
2737
3774
  console.log(
2738
- ` ${pc9.bold("Connected!")} Your project now syncs with Linear.`
3775
+ ` ${pc11.bold("Connected!")} Your project is now on the cloud tier.`
2739
3776
  );
2740
3777
  console.log();
2741
3778
  console.log(` Next steps:`);
2742
3779
  console.log(
2743
- ` 1. Run ${pc9.cyan("/flydocs-setup")} in your IDE to configure your Linear project`
3780
+ ` 1. Run ${pc11.cyan("/flydocs-setup")} in your IDE to configure your project`
2744
3781
  );
2745
- console.log(` 2. Run ${pc9.cyan("/start-session")} to begin working`);
3782
+ console.log(` 2. Run ${pc11.cyan("/start-session")} to begin working`);
2746
3783
  console.log();
2747
3784
  }
2748
3785
  });
@@ -2754,15 +3791,15 @@ var upgrade_exports = {};
2754
3791
  __export(upgrade_exports, {
2755
3792
  default: () => upgrade_default
2756
3793
  });
2757
- import { defineCommand as defineCommand6 } from "citty";
2758
- import pc10 from "picocolors";
3794
+ import { defineCommand as defineCommand7 } from "citty";
3795
+ import pc12 from "picocolors";
2759
3796
  var upgrade_default;
2760
3797
  var init_upgrade = __esm({
2761
3798
  "src/commands/upgrade.ts"() {
2762
3799
  "use strict";
2763
3800
  init_config();
2764
3801
  init_fs_ops();
2765
- upgrade_default = defineCommand6({
3802
+ upgrade_default = defineCommand7({
2766
3803
  meta: {
2767
3804
  name: "upgrade",
2768
3805
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -2791,38 +3828,38 @@ var init_upgrade = __esm({
2791
3828
  console.log();
2792
3829
  if (currentTier === "cloud") {
2793
3830
  console.log(
2794
- ` ${pc10.green("\u2713")} You're already on the ${pc10.bold("cloud")} tier.`
3831
+ ` ${pc12.green("\u2713")} You're already on the ${pc12.bold("cloud")} tier.`
2795
3832
  );
2796
3833
  console.log();
2797
3834
  console.log(
2798
- ` Your issues sync with Linear via the cloud mechanism skill.`
3835
+ ` Your issues sync with your provider via the cloud mechanism skill.`
2799
3836
  );
2800
3837
  console.log(
2801
- ` Run ${pc10.cyan("flydocs connect")} to update your connection settings.`
3838
+ ` Run ${pc12.cyan("flydocs connect")} to update your connection settings.`
2802
3839
  );
2803
3840
  console.log();
2804
3841
  return;
2805
3842
  }
2806
- console.log(` ${pc10.bold("FlyDocs Cloud Tier")}`);
3843
+ console.log(` ${pc12.bold("FlyDocs Cloud Tier")}`);
2807
3844
  console.log();
2808
- console.log(` You're currently on the ${pc10.yellow("local")} tier.`);
3845
+ console.log(` You're currently on the ${pc12.yellow("local")} tier.`);
2809
3846
  console.log(` Upgrade to cloud for:`);
2810
3847
  console.log();
2811
- console.log(
2812
- ` ${pc10.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
2813
- );
2814
- console.log(` ${pc10.cyan("\u2192")} Project milestones and cycle management`);
2815
- console.log(` ${pc10.cyan("\u2192")} Team assignment and priority tracking`);
2816
- console.log(` ${pc10.cyan("\u2192")} Project health updates and dashboards`);
2817
- console.log(` ${pc10.cyan("\u2192")} Cross-project issue linking`);
3848
+ console.log(` ${pc12.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
3849
+ console.log(` ${pc12.cyan("\u2192")} Project milestones and cycle management`);
3850
+ console.log(` ${pc12.cyan("\u2192")} Team assignment and priority tracking`);
3851
+ console.log(` ${pc12.cyan("\u2192")} Project health updates and dashboards`);
3852
+ console.log(` ${pc12.cyan("\u2192")} Cross-project issue linking`);
2818
3853
  console.log();
2819
- console.log(` ${pc10.bold("How to upgrade:")}`);
3854
+ console.log(` ${pc12.bold("How to upgrade:")}`);
3855
+ console.log();
3856
+ console.log(` Option 1: Run ${pc12.cyan("/flydocs-upgrade")} in your IDE`);
3857
+ console.log(` Guided migration with issue transfer`);
2820
3858
  console.log();
2821
- console.log(` 1. Sign up at ${pc10.cyan("https://www.flydocs.ai")}`);
2822
- console.log(` 2. Get your Linear API key from Linear \u2192 Settings \u2192 API`);
2823
3859
  console.log(
2824
- ` 3. Run ${pc10.cyan("flydocs connect")} to connect your project`
3860
+ ` Option 2: Run ${pc12.cyan("flydocs connect")} from terminal`
2825
3861
  );
3862
+ console.log(` Quick tier swap (no issue migration)`);
2826
3863
  console.log();
2827
3864
  }
2828
3865
  });
@@ -2834,23 +3871,23 @@ var self_update_exports = {};
2834
3871
  __export(self_update_exports, {
2835
3872
  default: () => self_update_default
2836
3873
  });
2837
- import { defineCommand as defineCommand7 } from "citty";
3874
+ import { defineCommand as defineCommand8 } from "citty";
2838
3875
  import { execSync } from "child_process";
2839
- import pc11 from "picocolors";
3876
+ import pc13 from "picocolors";
2840
3877
  var self_update_default;
2841
3878
  var init_self_update = __esm({
2842
3879
  "src/commands/self-update.ts"() {
2843
3880
  "use strict";
2844
3881
  init_constants();
2845
3882
  init_ui();
2846
- self_update_default = defineCommand7({
3883
+ self_update_default = defineCommand8({
2847
3884
  meta: {
2848
3885
  name: "self-update",
2849
3886
  description: "Update FlyDocs CLI to the latest version"
2850
3887
  },
2851
3888
  async run() {
2852
3889
  console.log();
2853
- console.log(` Updating ${pc11.cyan(PACKAGE_NAME)}...`);
3890
+ console.log(` Updating ${pc13.cyan(PACKAGE_NAME)}...`);
2854
3891
  console.log();
2855
3892
  try {
2856
3893
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -2870,17 +3907,117 @@ var init_self_update = __esm({
2870
3907
  }
2871
3908
  });
2872
3909
 
3910
+ // src/commands/telemetry.ts
3911
+ var telemetry_exports = {};
3912
+ __export(telemetry_exports, {
3913
+ default: () => telemetry_default
3914
+ });
3915
+ import { defineCommand as defineCommand9 } from "citty";
3916
+ import pc14 from "picocolors";
3917
+ var enable, disable, status, telemetry_default;
3918
+ var init_telemetry2 = __esm({
3919
+ "src/commands/telemetry.ts"() {
3920
+ "use strict";
3921
+ init_telemetry();
3922
+ init_ui();
3923
+ enable = defineCommand9({
3924
+ meta: {
3925
+ name: "enable",
3926
+ description: "Enable anonymous usage analytics"
3927
+ },
3928
+ async run() {
3929
+ try {
3930
+ await setEnabled(true);
3931
+ printStatus("Telemetry enabled");
3932
+ } catch {
3933
+ printError("Failed to update telemetry settings");
3934
+ process.exit(1);
3935
+ }
3936
+ }
3937
+ });
3938
+ disable = defineCommand9({
3939
+ meta: {
3940
+ name: "disable",
3941
+ description: "Disable anonymous usage analytics"
3942
+ },
3943
+ async run() {
3944
+ try {
3945
+ await setEnabled(false);
3946
+ printStatus("Telemetry disabled");
3947
+ printInfo(
3948
+ "You can also set FLYDOCS_TELEMETRY=0 in your shell environment."
3949
+ );
3950
+ } catch {
3951
+ printError("Failed to update telemetry settings");
3952
+ process.exit(1);
3953
+ }
3954
+ }
3955
+ });
3956
+ status = defineCommand9({
3957
+ meta: {
3958
+ name: "status",
3959
+ description: "Show current telemetry status"
3960
+ },
3961
+ async run() {
3962
+ const info = await getStatus();
3963
+ console.log();
3964
+ console.log(pc14.bold("Telemetry Status"));
3965
+ console.log();
3966
+ const effectivelyEnabled = info.enabled && !info.envOverride;
3967
+ console.log(
3968
+ ` Enabled: ${effectivelyEnabled ? pc14.green("yes") : pc14.yellow("no")}`
3969
+ );
3970
+ if (info.envOverride) {
3971
+ console.log(
3972
+ ` Env override: ${pc14.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
3973
+ );
3974
+ }
3975
+ if (info.anonymousId) {
3976
+ console.log(` Anonymous ID: ${pc14.dim(info.anonymousId)}`);
3977
+ } else {
3978
+ console.log(
3979
+ ` Anonymous ID: ${pc14.dim("(not yet created \u2014 generated on first run)")}`
3980
+ );
3981
+ }
3982
+ console.log();
3983
+ console.log(
3984
+ pc14.dim(
3985
+ " FlyDocs collects anonymous usage analytics to improve the CLI."
3986
+ )
3987
+ );
3988
+ console.log(
3989
+ pc14.dim(" No personal data, file contents, or code is ever collected.")
3990
+ );
3991
+ console.log();
3992
+ }
3993
+ });
3994
+ telemetry_default = defineCommand9({
3995
+ meta: {
3996
+ name: "telemetry",
3997
+ description: "Manage anonymous usage analytics (enable, disable, status)"
3998
+ },
3999
+ subCommands: {
4000
+ enable,
4001
+ disable,
4002
+ status
4003
+ }
4004
+ });
4005
+ }
4006
+ });
4007
+
2873
4008
  // src/cli.ts
2874
4009
  init_constants();
2875
- import { defineCommand as defineCommand8, runMain } from "citty";
4010
+ import { defineCommand as defineCommand10, runMain } from "citty";
2876
4011
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
2877
4012
  "install",
2878
4013
  "update",
4014
+ "uninstall",
2879
4015
  "setup",
2880
4016
  "skills",
2881
4017
  "connect",
2882
4018
  "upgrade",
2883
- "self-update"
4019
+ "self-update",
4020
+ "telemetry"
2884
4021
  ]);
2885
4022
  var userArgs = process.argv.slice(2);
2886
4023
  var hasMetaFlag = userArgs.some(
@@ -2890,7 +4027,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
2890
4027
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
2891
4028
  process.argv.splice(2, 0, "install");
2892
4029
  }
2893
- var main = defineCommand8({
4030
+ var main = defineCommand10({
2894
4031
  meta: {
2895
4032
  name: CLI_NAME,
2896
4033
  version: CLI_VERSION,
@@ -2899,11 +4036,13 @@ var main = defineCommand8({
2899
4036
  subCommands: {
2900
4037
  install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
2901
4038
  update: () => Promise.resolve().then(() => (init_update(), update_exports)).then((m) => m.default),
4039
+ uninstall: () => Promise.resolve().then(() => (init_uninstall(), uninstall_exports)).then((m) => m.default),
2902
4040
  setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
2903
4041
  skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
2904
4042
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
2905
4043
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
2906
- "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default)
4044
+ "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default),
4045
+ telemetry: () => Promise.resolve().then(() => (init_telemetry2(), telemetry_exports)).then((m) => m.default)
2907
4046
  }
2908
4047
  });
2909
4048
  runMain(main);