@flydocs/cli 0.6.0-alpha.24 → 0.6.0-alpha.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ 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.6.0-alpha.24";
18
+ CLI_VERSION = "0.6.0-alpha.27";
19
19
  CLI_NAME = "flydocs";
20
20
  PACKAGE_NAME = "@flydocs/cli";
21
21
  POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
@@ -136,8 +136,8 @@ var init_template = __esm({
136
136
 
137
137
  // src/lib/ui.ts
138
138
  import pc2 from "picocolors";
139
- function shadow(text4) {
140
- return `\x1B[38;2;55;45;70m${text4}\x1B[0m`;
139
+ function shadow(text6) {
140
+ return `\x1B[38;2;55;45;70m${text6}\x1B[0m`;
141
141
  }
142
142
  function renderBannerBlock() {
143
143
  const height = BANNER_ROWS.length;
@@ -268,12 +268,70 @@ var init_ui = __esm({
268
268
  }
269
269
  });
270
270
 
271
+ // src/lib/types.ts
272
+ function isConfigV2(config) {
273
+ return config.configFormat === 2;
274
+ }
275
+ var init_types = __esm({
276
+ "src/lib/types.ts"() {
277
+ "use strict";
278
+ }
279
+ });
280
+
281
+ // src/lib/config-integrity.ts
282
+ import { createHash } from "crypto";
283
+ function computeConfigHash(config) {
284
+ const { configHash: _, ...rest } = config;
285
+ const serialized = JSON.stringify(rest, Object.keys(rest).sort());
286
+ return `sha256:${createHash("sha256").update(serialized, "utf-8").digest("hex")}`;
287
+ }
288
+ function validateConfigIntegrity(config) {
289
+ if (!config.configHash) return false;
290
+ const computed = computeConfigHash(config);
291
+ return computed === config.configHash;
292
+ }
293
+ function checkConfigIntegrity(config) {
294
+ if (!config.configHash) {
295
+ return true;
296
+ }
297
+ const valid = validateConfigIntegrity(config);
298
+ if (!valid) {
299
+ printWarning(
300
+ "Config modified locally \u2014 will restore from server on next sync."
301
+ );
302
+ }
303
+ return valid;
304
+ }
305
+ function applyConfigHash(config) {
306
+ const hash = computeConfigHash(config);
307
+ return { ...config, configHash: hash };
308
+ }
309
+ var init_config_integrity = __esm({
310
+ "src/lib/config-integrity.ts"() {
311
+ "use strict";
312
+ init_ui();
313
+ }
314
+ });
315
+
271
316
  // src/lib/config.ts
272
317
  import { readFile as readFile2, writeFile } from "fs/promises";
273
318
  import { join as join3 } from "path";
274
- async function readConfig(dir) {
319
+ async function readAnyConfig(dir) {
275
320
  const content = await readFile2(join3(dir, ".flydocs", "config.json"), "utf-8");
276
- return JSON.parse(content);
321
+ const config = JSON.parse(content);
322
+ if (isConfigV2(config)) {
323
+ checkConfigIntegrity(config);
324
+ }
325
+ return config;
326
+ }
327
+ async function readConfig(dir) {
328
+ const config = await readAnyConfig(dir);
329
+ if (isConfigV2(config)) {
330
+ throw new Error(
331
+ "This command requires v1 config format. Run `flydocs sync` to use v2 commands."
332
+ );
333
+ }
334
+ return config;
277
335
  }
278
336
  async function writeConfig(dir, config) {
279
337
  const content = JSON.stringify(config, null, 2) + "\n";
@@ -343,6 +401,8 @@ async function mergeConfig(templateDir, version, tierFlag, preserved) {
343
401
  var init_config = __esm({
344
402
  "src/lib/config.ts"() {
345
403
  "use strict";
404
+ init_types();
405
+ init_config_integrity();
346
406
  }
347
407
  });
348
408
 
@@ -363,7 +423,7 @@ async function installOwnedSkills(templateDir, targetDir, _tier) {
363
423
  async function replaceOwnedSkills(templateDir, targetDir, _tier) {
364
424
  const skillsDir = join4(targetDir, ".claude", "skills");
365
425
  const templateSkillsDir = join4(templateDir, ".claude", "skills");
366
- const { rm: rm6 } = await import("fs/promises");
426
+ const { rm: rm7 } = await import("fs/promises");
367
427
  await replaceDirectory(
368
428
  join4(templateSkillsDir, OWNED_SKILL),
369
429
  join4(skillsDir, OWNED_SKILL)
@@ -371,7 +431,7 @@ async function replaceOwnedSkills(templateDir, targetDir, _tier) {
371
431
  for (const legacy of LEGACY_SKILLS) {
372
432
  const legacyPath = join4(skillsDir, legacy);
373
433
  if (await pathExists(legacyPath)) {
374
- await rm6(legacyPath, { recursive: true, force: true });
434
+ await rm7(legacyPath, { recursive: true, force: true });
375
435
  }
376
436
  }
377
437
  const readmeSrc = join4(templateSkillsDir, "README.md");
@@ -380,9 +440,9 @@ async function replaceOwnedSkills(templateDir, targetDir, _tier) {
380
440
  }
381
441
  }
382
442
  async function copyCursorRules(targetDir) {
383
- const { mkdir: mkdir9 } = await import("fs/promises");
443
+ const { mkdir: mkdir12 } = await import("fs/promises");
384
444
  const rulesDir = join4(targetDir, ".cursor", "rules");
385
- await mkdir9(rulesDir, { recursive: true });
445
+ await mkdir12(rulesDir, { recursive: true });
386
446
  const workflowRule = join4(
387
447
  targetDir,
388
448
  ".claude",
@@ -393,11 +453,11 @@ async function copyCursorRules(targetDir) {
393
453
  if (await pathExists(workflowRule)) {
394
454
  await copyFile(workflowRule, join4(rulesDir, "flydocs-workflow.mdc"));
395
455
  }
396
- const { rm: rm6 } = await import("fs/promises");
456
+ const { rm: rm7 } = await import("fs/promises");
397
457
  for (const legacy of ["flydocs-mechanism.mdc", "flydocs-context7.mdc"]) {
398
458
  const legacyRule = join4(rulesDir, legacy);
399
459
  if (await pathExists(legacyRule)) {
400
- await rm6(legacyRule, { force: true });
460
+ await rm7(legacyRule, { force: true });
401
461
  }
402
462
  }
403
463
  }
@@ -715,8 +775,8 @@ function flushFrontmatterValue(mode, lines) {
715
775
  return lines.join("\n").trim();
716
776
  }
717
777
  }
718
- function parseFrontmatter(text4) {
719
- const match = text4.match(/^---\s*\n([\s\S]*?)\n---/);
778
+ function parseFrontmatter(text6) {
779
+ const match = text6.match(/^---\s*\n([\s\S]*?)\n---/);
720
780
  if (!match) return null;
721
781
  const block = match[1];
722
782
  const result = {};
@@ -1481,7 +1541,7 @@ async function ensurePlatformIgnores(targetDir) {
1481
1541
  if (!await pathExists(filePath)) continue;
1482
1542
  const content = await readFile6(filePath, "utf-8");
1483
1543
  if (content.includes("# FlyDocs")) continue;
1484
- const section = "\n# FlyDocs \u2014 exclude from builds and deploys\n" + FLYDOCS_DEPLOY_EXCLUSIONS.join("\n") + "\n";
1544
+ const section = "\n# FlyDocs \u2014 managed by flydocs-cli, do not edit this section\n" + FLYDOCS_DEPLOY_EXCLUSIONS.join("\n") + "\n";
1485
1545
  await appendFile(filePath, section, "utf-8");
1486
1546
  printStatus(`Added FlyDocs exclusions to ${filename}`);
1487
1547
  }
@@ -1496,14 +1556,19 @@ var init_gitignore = __esm({
1496
1556
  ".gcloudignore",
1497
1557
  ".dockerignore",
1498
1558
  ".vercelignore",
1499
- ".npmignore"
1559
+ ".npmignore",
1560
+ ".cfignore",
1561
+ ".ebignore",
1562
+ ".funcignore"
1500
1563
  ];
1501
1564
  FLYDOCS_DEPLOY_EXCLUSIONS = [
1502
1565
  ".flydocs/",
1503
1566
  ".claude/",
1504
1567
  ".cursor/",
1505
1568
  "flydocs/",
1506
- "AGENTS.md"
1569
+ ".flydocs-workspace.json",
1570
+ "AGENTS.md",
1571
+ ".cursorrules"
1507
1572
  ];
1508
1573
  FLYDOCS_GITIGNORE_ENTRIES = [
1509
1574
  ".env",
@@ -2759,16 +2824,666 @@ var init_install = __esm({
2759
2824
  }
2760
2825
  });
2761
2826
 
2827
+ // src/lib/global-config.ts
2828
+ import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir8, stat, chmod } from "fs/promises";
2829
+ import { join as join17 } from "path";
2830
+ import { homedir as homedir3 } from "os";
2831
+ function globalDir() {
2832
+ return join17(homedir3(), ".flydocs");
2833
+ }
2834
+ function credentialsPath() {
2835
+ return join17(globalDir(), "credentials");
2836
+ }
2837
+ function globalMePath() {
2838
+ return join17(globalDir(), "me.json");
2839
+ }
2840
+ async function ensureGlobalDir() {
2841
+ const dir = globalDir();
2842
+ if (!await pathExists(dir)) {
2843
+ await mkdir8(dir, { recursive: true, mode: 448 });
2844
+ }
2845
+ return dir;
2846
+ }
2847
+ async function readGlobalCredential() {
2848
+ const path = credentialsPath();
2849
+ if (!await pathExists(path)) return null;
2850
+ try {
2851
+ const content = await readFile12(path, "utf-8");
2852
+ return JSON.parse(content);
2853
+ } catch {
2854
+ return null;
2855
+ }
2856
+ }
2857
+ async function writeGlobalCredential(credential) {
2858
+ await ensureGlobalDir();
2859
+ const path = credentialsPath();
2860
+ await writeFile10(path, JSON.stringify(credential, null, 2) + "\n", {
2861
+ encoding: "utf-8",
2862
+ mode: 384
2863
+ });
2864
+ await chmod(path, 384);
2865
+ }
2866
+ async function checkCredentialPermissions() {
2867
+ const path = credentialsPath();
2868
+ if (!await pathExists(path)) return;
2869
+ try {
2870
+ const stats = await stat(path);
2871
+ const mode = stats.mode & 511;
2872
+ if (mode !== 384) {
2873
+ printWarning(
2874
+ `Credential file permissions are too open (${mode.toString(8)}). Expected 0600. Run: chmod 600 ${path}`
2875
+ );
2876
+ }
2877
+ } catch {
2878
+ }
2879
+ }
2880
+ async function writeGlobalIdentity(identity) {
2881
+ await ensureGlobalDir();
2882
+ const path = globalMePath();
2883
+ await writeFile10(path, JSON.stringify(identity, null, 2) + "\n", "utf-8");
2884
+ }
2885
+ async function resolveApiKey(explicitKey, projectDir) {
2886
+ if (explicitKey) {
2887
+ return { key: explicitKey, source: "--key flag" };
2888
+ }
2889
+ const envKey = process.env.FLYDOCS_API_KEY;
2890
+ if (envKey) {
2891
+ return { key: envKey, source: "FLYDOCS_API_KEY env var" };
2892
+ }
2893
+ const globalCred = await readGlobalCredential();
2894
+ if (globalCred?.apiKey) {
2895
+ return { key: globalCred.apiKey, source: "~/.flydocs/credentials" };
2896
+ }
2897
+ if (projectDir) {
2898
+ const envLocalKey = await readEnvKey(projectDir, "FLYDOCS_API_KEY");
2899
+ if (envLocalKey) {
2900
+ return { key: envLocalKey, source: ".env.local (legacy)" };
2901
+ }
2902
+ }
2903
+ return null;
2904
+ }
2905
+ async function readEnvKey(dir, varName) {
2906
+ for (const filename of [".env.local", ".env"]) {
2907
+ const filePath = join17(dir, filename);
2908
+ if (!await pathExists(filePath)) continue;
2909
+ try {
2910
+ const content = await readFile12(filePath, "utf-8");
2911
+ const match = content.match(new RegExp(`^${varName}=(.+)$`, "m"));
2912
+ if (match) return match[1].trim();
2913
+ } catch {
2914
+ continue;
2915
+ }
2916
+ }
2917
+ return null;
2918
+ }
2919
+ var init_global_config = __esm({
2920
+ "src/lib/global-config.ts"() {
2921
+ "use strict";
2922
+ init_fs_ops();
2923
+ init_ui();
2924
+ }
2925
+ });
2926
+
2927
+ // src/lib/relay-client.ts
2928
+ function resolveRelayUrl() {
2929
+ return process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? DEFAULT_RELAY_URL;
2930
+ }
2931
+ function headers(apiKey, workspaceId) {
2932
+ const h = {
2933
+ Authorization: `Bearer ${apiKey}`,
2934
+ "Content-Type": "application/json",
2935
+ Accept: "application/json"
2936
+ };
2937
+ if (workspaceId) h["X-Workspace"] = workspaceId;
2938
+ return h;
2939
+ }
2940
+ async function fetchConfigV2(apiKey, options = {}) {
2941
+ const baseUrl = resolveRelayUrl();
2942
+ const params = new URLSearchParams({ format: "2" });
2943
+ if (options.includeContext) params.set("includeContext", "true");
2944
+ const response = await fetch(`${baseUrl}/config/generate?${params}`, {
2945
+ method: "GET",
2946
+ headers: headers(apiKey, options.workspaceId),
2947
+ signal: AbortSignal.timeout(3e4)
2948
+ });
2949
+ if (!response.ok) {
2950
+ const body = await response.text().catch(() => "");
2951
+ throw new RelayError(
2952
+ `config/generate failed (${response.status})`,
2953
+ response.status,
2954
+ body
2955
+ );
2956
+ }
2957
+ return await response.json();
2958
+ }
2959
+ async function fetchTemplates(apiKey, sinceVersion, workspaceId) {
2960
+ const baseUrl = resolveRelayUrl();
2961
+ const response = await fetch(`${baseUrl}/templates?since=${sinceVersion}`, {
2962
+ method: "GET",
2963
+ headers: headers(apiKey, workspaceId),
2964
+ signal: AbortSignal.timeout(15e3)
2965
+ });
2966
+ if (!response.ok) {
2967
+ const body = await response.text().catch(() => "");
2968
+ throw new RelayError(
2969
+ `templates fetch failed (${response.status})`,
2970
+ response.status,
2971
+ body
2972
+ );
2973
+ }
2974
+ return await response.json();
2975
+ }
2976
+ var DEFAULT_RELAY_URL, RelayError;
2977
+ var init_relay_client = __esm({
2978
+ "src/lib/relay-client.ts"() {
2979
+ "use strict";
2980
+ DEFAULT_RELAY_URL = "https://app.flydocs.ai/api/relay";
2981
+ RelayError = class extends Error {
2982
+ constructor(message, status2, body) {
2983
+ super(message);
2984
+ this.status = status2;
2985
+ this.body = body;
2986
+ this.name = "RelayError";
2987
+ }
2988
+ };
2989
+ }
2990
+ });
2991
+
2992
+ // src/lib/workspace.ts
2993
+ import { readFile as readFile13, writeFile as writeFile11, readdir as readdir4, stat as stat2 } from "fs/promises";
2994
+ import { join as join18, dirname as dirname3, relative, resolve as resolve3 } from "path";
2995
+ async function readWorkspaceFile(dir) {
2996
+ const filePath = join18(dir, WORKSPACE_FILENAME);
2997
+ if (!await pathExists(filePath)) return null;
2998
+ const content = await readFile13(filePath, "utf-8");
2999
+ const parsed = JSON.parse(content);
3000
+ validateWorkspaceFile(parsed);
3001
+ return parsed;
3002
+ }
3003
+ async function writeWorkspaceFile(dir, workspace) {
3004
+ const filePath = join18(dir, WORKSPACE_FILENAME);
3005
+ const content = JSON.stringify(workspace, null, 2) + "\n";
3006
+ await writeFile11(filePath, content, "utf-8");
3007
+ }
3008
+ async function detectSiblingRepos(parentDir) {
3009
+ const repos = {};
3010
+ const entries = await readdir4(parentDir);
3011
+ for (const entry of entries) {
3012
+ if (entry.startsWith(".")) continue;
3013
+ const entryPath = join18(parentDir, entry);
3014
+ const entryStat = await stat2(entryPath).catch(() => null);
3015
+ if (!entryStat?.isDirectory()) continue;
3016
+ const hasGit = await pathExists(join18(entryPath, ".git"));
3017
+ const hasFlydocs = await pathExists(
3018
+ join18(entryPath, ".flydocs", "config.json")
3019
+ );
3020
+ if (hasGit || hasFlydocs) {
3021
+ repos[entry] = { path: `./${entry}` };
3022
+ }
3023
+ }
3024
+ return repos;
3025
+ }
3026
+ function buildWorkspaceFile(workspaceId, repos) {
3027
+ return { workspaceId, repos };
3028
+ }
3029
+ async function readSiblingDescriptors(workspaceDir, repos) {
3030
+ const descriptors = [];
3031
+ for (const [name, entry] of Object.entries(repos)) {
3032
+ const repoDir = resolve3(workspaceDir, entry.path);
3033
+ const serviceJsonPath = join18(repoDir, "flydocs", "context", "service.json");
3034
+ if (!await pathExists(serviceJsonPath)) continue;
3035
+ try {
3036
+ const content = await readFile13(serviceJsonPath, "utf-8");
3037
+ const descriptor = JSON.parse(content);
3038
+ descriptors.push({ name, path: entry.path, descriptor });
3039
+ } catch {
3040
+ }
3041
+ }
3042
+ return descriptors;
3043
+ }
3044
+ async function generateWorkspaceMd(workspaceDir, workspace, productName) {
3045
+ const descriptors = await readSiblingDescriptors(
3046
+ workspaceDir,
3047
+ workspace.repos
3048
+ );
3049
+ const repoCount = Object.keys(workspace.repos).length;
3050
+ const lines = [];
3051
+ const title = productName ?? "Workspace";
3052
+ lines.push(`# Product: ${title}`);
3053
+ lines.push("");
3054
+ lines.push("## Overview");
3055
+ lines.push("");
3056
+ if (descriptors.length > 0) {
3057
+ const purposes = descriptors.map((d) => d.descriptor.purpose).filter(Boolean);
3058
+ if (purposes.length > 0) {
3059
+ lines.push(purposes.join(". ") + ".");
3060
+ } else {
3061
+ lines.push(`Multi-repo workspace with ${repoCount} repositories.`);
3062
+ }
3063
+ } else {
3064
+ lines.push(`Multi-repo workspace with ${repoCount} repositories.`);
3065
+ }
3066
+ lines.push("");
3067
+ lines.push("## Architecture");
3068
+ lines.push("");
3069
+ lines.push(`${repoCount}-repo sibling topology.`);
3070
+ lines.push("");
3071
+ lines.push("## Repos");
3072
+ lines.push("");
3073
+ for (const [name, entry] of Object.entries(workspace.repos)) {
3074
+ const desc = descriptors.find((d) => d.name === name);
3075
+ const purpose = desc?.descriptor.purpose ?? "";
3076
+ const stack = desc?.descriptor.stack?.join(", ") ?? "";
3077
+ const detail = [purpose, stack].filter(Boolean).join(". ");
3078
+ const projectMdLink = `${entry.path}/flydocs/context/project.md`;
3079
+ lines.push(
3080
+ `- **${name}** \u2014 ${detail || "No description"}. [Details](${projectMdLink})`
3081
+ );
3082
+ }
3083
+ lines.push("");
3084
+ const edges = [];
3085
+ for (const desc of descriptors) {
3086
+ for (const dep of desc.descriptor.dependencies ?? []) {
3087
+ const target = dep.service;
3088
+ const iface = dep.interface;
3089
+ edges.push(`- ${desc.name} \u2192 ${target} (${iface})`);
3090
+ }
3091
+ }
3092
+ lines.push("## Cross-Repo Dependencies");
3093
+ lines.push("");
3094
+ if (edges.length > 0) {
3095
+ lines.push(...edges);
3096
+ } else {
3097
+ lines.push("No cross-repo dependencies detected.");
3098
+ }
3099
+ lines.push("");
3100
+ return lines.join("\n");
3101
+ }
3102
+ function validateWorkspaceFile(value) {
3103
+ if (typeof value !== "object" || value === null) {
3104
+ throw new Error("Invalid workspace file: expected an object");
3105
+ }
3106
+ const obj = value;
3107
+ if (typeof obj.workspaceId !== "string" || obj.workspaceId.length === 0) {
3108
+ throw new Error(
3109
+ "Invalid workspace file: workspaceId must be a non-empty string"
3110
+ );
3111
+ }
3112
+ if (typeof obj.repos !== "object" || obj.repos === null) {
3113
+ throw new Error("Invalid workspace file: repos must be an object");
3114
+ }
3115
+ const repos = obj.repos;
3116
+ for (const [name, entry] of Object.entries(repos)) {
3117
+ if (typeof entry !== "object" || entry === null) {
3118
+ throw new Error(
3119
+ `Invalid workspace file: repos.${name} must be an object`
3120
+ );
3121
+ }
3122
+ const repoEntry = entry;
3123
+ if (typeof repoEntry.path !== "string" || repoEntry.path.length === 0) {
3124
+ throw new Error(
3125
+ `Invalid workspace file: repos.${name}.path must be a non-empty string`
3126
+ );
3127
+ }
3128
+ }
3129
+ }
3130
+ var WORKSPACE_FILENAME;
3131
+ var init_workspace = __esm({
3132
+ "src/lib/workspace.ts"() {
3133
+ "use strict";
3134
+ init_fs_ops();
3135
+ WORKSPACE_FILENAME = ".flydocs-workspace.json";
3136
+ }
3137
+ });
3138
+
3139
+ // src/commands/init.ts
3140
+ var init_exports = {};
3141
+ __export(init_exports, {
3142
+ default: () => init_default
3143
+ });
3144
+ import { defineCommand as defineCommand2 } from "citty";
3145
+ import { text as text2, confirm as confirm3, select as select2, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
3146
+ import pc7 from "picocolors";
3147
+ import { join as join19 } from "path";
3148
+ import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
3149
+ async function resolveAndValidateKey(keyArg, targetDir) {
3150
+ let resolved = await resolveApiKey(keyArg, targetDir);
3151
+ if (!resolved) {
3152
+ console.log(
3153
+ ` ${pc7.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3154
+ );
3155
+ console.log();
3156
+ const keyInput = await text2({
3157
+ message: "Enter your API key",
3158
+ placeholder: "fdk_...",
3159
+ validate(value) {
3160
+ if (!value.trim()) return "API key is required";
3161
+ if (detectKeyType(value.trim()) !== "relay") {
3162
+ return "Key must start with fdk_ (FlyDocs API key)";
3163
+ }
3164
+ return void 0;
3165
+ }
3166
+ });
3167
+ if (isCancel4(keyInput)) {
3168
+ cancel3("Init cancelled.");
3169
+ process.exit(0);
3170
+ }
3171
+ resolved = { key: keyInput.trim(), source: "prompt" };
3172
+ } else {
3173
+ printInfo(`Using key from ${resolved.source}`);
3174
+ }
3175
+ const apiKey = resolved.key;
3176
+ printInfo("Validating API key...");
3177
+ try {
3178
+ const result = await validateRelayKey(apiKey);
3179
+ if (!result.valid) {
3180
+ printError("Invalid API key. Check your key and try again.");
3181
+ process.exit(1);
3182
+ }
3183
+ printStatus(`Authenticated with ${pc7.bold(result.org)}`);
3184
+ } catch {
3185
+ printError(
3186
+ "Could not reach FlyDocs API. Check your network and try again."
3187
+ );
3188
+ console.log(
3189
+ ` ${pc7.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3190
+ );
3191
+ process.exit(1);
3192
+ }
3193
+ const workspaces = await fetchWorkspaces(apiKey);
3194
+ let workspaceId;
3195
+ if (workspaces.length === 0) {
3196
+ printError("No workspaces found. Create one at app.flydocs.ai first.");
3197
+ process.exit(1);
3198
+ } else if (workspaces.length === 1) {
3199
+ workspaceId = workspaces[0].id;
3200
+ printStatus(`Workspace: ${pc7.bold(workspaces[0].name)}`);
3201
+ } else {
3202
+ const choice = await select2({
3203
+ message: "Select workspace",
3204
+ options: workspaces.map((ws) => ({
3205
+ value: ws.id,
3206
+ label: ws.name
3207
+ }))
3208
+ });
3209
+ if (isCancel4(choice)) {
3210
+ cancel3("Init cancelled.");
3211
+ process.exit(0);
3212
+ }
3213
+ workspaceId = choice;
3214
+ }
3215
+ await writeGlobalCredential({
3216
+ apiKey,
3217
+ workspaceId,
3218
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3219
+ lastValidated: (/* @__PURE__ */ new Date()).toISOString()
3220
+ });
3221
+ await checkCredentialPermissions();
3222
+ return { apiKey, workspaceId };
3223
+ }
3224
+ async function pullServerConfig(apiKey, targetDir, workspaceId) {
3225
+ printInfo("Pulling config from server...");
3226
+ const isFirstInit = !await pathExists(
3227
+ join19(targetDir, ".flydocs", "config.json")
3228
+ );
3229
+ try {
3230
+ const response = await fetchConfigV2(apiKey, {
3231
+ includeContext: isFirstInit,
3232
+ workspaceId
3233
+ });
3234
+ if (!response.valid) {
3235
+ printError("Server returned invalid config. Contact support.");
3236
+ process.exit(1);
3237
+ }
3238
+ return response;
3239
+ } catch (err) {
3240
+ if (err instanceof RelayError) {
3241
+ printError(`Server error: ${err.message}`);
3242
+ if (err.status === 401) {
3243
+ console.log(
3244
+ ` ${pc7.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3245
+ );
3246
+ }
3247
+ } else {
3248
+ printError("Could not reach server. Check your network.");
3249
+ }
3250
+ process.exit(1);
3251
+ }
3252
+ }
3253
+ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3254
+ const actions = [];
3255
+ const skipped = [];
3256
+ await mkdir9(join19(targetDir, ".flydocs"), { recursive: true });
3257
+ const configWithHash = applyConfigHash(serverResponse.config);
3258
+ await writeConfig(targetDir, configWithHash);
3259
+ actions.push("Wrote .flydocs/config.json (from server)");
3260
+ if (serverResponse.identity) {
3261
+ await writeGlobalIdentity({
3262
+ name: serverResponse.identity.name,
3263
+ email: serverResponse.identity.email,
3264
+ providerIdentities: serverResponse.identity.providerIdentities
3265
+ });
3266
+ actions.push("Wrote ~/.flydocs/me.json");
3267
+ }
3268
+ if (serverResponse.context) {
3269
+ const contextDir = join19(targetDir, "flydocs", "context");
3270
+ await mkdir9(contextDir, { recursive: true });
3271
+ const projectMdPath = join19(contextDir, "project.md");
3272
+ if (!await pathExists(projectMdPath)) {
3273
+ await writeFile12(projectMdPath, serverResponse.context.projectMd, "utf-8");
3274
+ actions.push("Wrote flydocs/context/project.md");
3275
+ } else {
3276
+ skipped.push("flydocs/context/project.md (already exists)");
3277
+ }
3278
+ const serviceJsonPath = join19(contextDir, "service.json");
3279
+ if (serverResponse.context.serviceJson && !await pathExists(serviceJsonPath)) {
3280
+ await writeFile12(
3281
+ serviceJsonPath,
3282
+ JSON.stringify(serverResponse.context.serviceJson, null, 2) + "\n",
3283
+ "utf-8"
3284
+ );
3285
+ actions.push("Wrote flydocs/context/service.json");
3286
+ } else if (await pathExists(serviceJsonPath)) {
3287
+ skipped.push("flydocs/context/service.json (already exists)");
3288
+ }
3289
+ }
3290
+ await ensureGitignore(targetDir);
3291
+ await ensurePlatformIgnores(targetDir);
3292
+ actions.push("Updated ignore files");
3293
+ return { actions, skipped };
3294
+ }
3295
+ async function isParentDirectory(dir) {
3296
+ const hasGit = await pathExists(join19(dir, ".git"));
3297
+ if (hasGit) return false;
3298
+ const repos = await detectSiblingRepos(dir);
3299
+ return Object.keys(repos).length >= 2;
3300
+ }
3301
+ async function runMultiRepoInit(parentDir, keyArg) {
3302
+ const repos = await detectSiblingRepos(parentDir);
3303
+ const repoNames = Object.keys(repos).sort();
3304
+ printInfo(`Detected ${repoNames.length} repos:`);
3305
+ for (const name of repoNames) {
3306
+ console.log(` ${pc7.cyan(name)}`);
3307
+ }
3308
+ console.log();
3309
+ const shouldContinue = await confirm3({
3310
+ message: `Initialize all ${repoNames.length} repos?`
3311
+ });
3312
+ if (isCancel4(shouldContinue) || !shouldContinue) {
3313
+ cancel3("Init cancelled.");
3314
+ process.exit(0);
3315
+ }
3316
+ const firstRepoDir = join19(parentDir, repoNames[0]);
3317
+ const { apiKey, workspaceId } = await resolveAndValidateKey(
3318
+ keyArg,
3319
+ firstRepoDir
3320
+ );
3321
+ const allActions = [
3322
+ "Stored credential globally (~/.flydocs/credentials)"
3323
+ ];
3324
+ const allSkipped = [];
3325
+ let allWarnings = [];
3326
+ let firstResponse;
3327
+ for (const repoName of repoNames) {
3328
+ const repoDir = join19(parentDir, repoName);
3329
+ console.log();
3330
+ printInfo(`Initializing ${pc7.bold(repoName)}...`);
3331
+ const serverResponse = await pullServerConfig(apiKey, repoDir, workspaceId);
3332
+ if (!firstResponse) firstResponse = serverResponse;
3333
+ const { actions, skipped } = await initSingleRepo(
3334
+ repoDir,
3335
+ apiKey,
3336
+ serverResponse
3337
+ );
3338
+ for (const action of actions) {
3339
+ allActions.push(`[${repoName}] ${action}`);
3340
+ }
3341
+ for (const skip of skipped) {
3342
+ allSkipped.push(`[${repoName}] ${skip}`);
3343
+ }
3344
+ allWarnings = [...allWarnings, ...serverResponse.warnings];
3345
+ printStatus(`${repoName} initialized`);
3346
+ }
3347
+ if (firstResponse) {
3348
+ const existing = await readWorkspaceFile(parentDir);
3349
+ if (existing) {
3350
+ allSkipped.push(".flydocs-workspace.json (already exists)");
3351
+ } else {
3352
+ const workspaceFile = buildWorkspaceFile(
3353
+ firstResponse.workspaceId,
3354
+ repos
3355
+ );
3356
+ await writeWorkspaceFile(parentDir, workspaceFile);
3357
+ allActions.push(`.flydocs-workspace.json (${repoNames.length} repos)`);
3358
+ }
3359
+ const contextDir = join19(parentDir, "flydocs", "context");
3360
+ const workspaceMdPath = join19(contextDir, "workspace.md");
3361
+ if (await pathExists(workspaceMdPath)) {
3362
+ allSkipped.push("flydocs/context/workspace.md (already exists)");
3363
+ } else {
3364
+ await mkdir9(contextDir, { recursive: true });
3365
+ const workspaceJson = await readWorkspaceFile(parentDir) ?? buildWorkspaceFile(firstResponse.workspaceId, repos);
3366
+ const content = firstResponse.context?.workspaceMd ?? await generateWorkspaceMd(parentDir, workspaceJson);
3367
+ await writeFile12(workspaceMdPath, content, "utf-8");
3368
+ const source = firstResponse.context?.workspaceMd ? "from server" : "generated locally";
3369
+ allActions.push(`Wrote flydocs/context/workspace.md (${source})`);
3370
+ }
3371
+ }
3372
+ console.log();
3373
+ printInitReport(allActions, allSkipped, allWarnings);
3374
+ }
3375
+ function printInitReport(actions, skipped, warnings) {
3376
+ console.log();
3377
+ const lines = [];
3378
+ for (const action of actions) {
3379
+ lines.push(`${pc7.green("+")} ${action}`);
3380
+ }
3381
+ for (const skip of skipped) {
3382
+ lines.push(`${pc7.dim("-")} ${skip}`);
3383
+ }
3384
+ if (warnings.length > 0) {
3385
+ lines.push("");
3386
+ for (const warning of warnings) {
3387
+ lines.push(`${pc7.yellow("!")} ${warning}`);
3388
+ }
3389
+ }
3390
+ printCompletionBox("FlyDocs Initialized", lines);
3391
+ console.log();
3392
+ console.log(` Next steps:`);
3393
+ console.log(
3394
+ ` ${pc7.cyan("flydocs sync")} \u2014 pull latest config and templates`
3395
+ );
3396
+ console.log(` ${pc7.cyan("/start-session")} \u2014 begin working`);
3397
+ console.log();
3398
+ }
3399
+ var init_default;
3400
+ var init_init = __esm({
3401
+ "src/commands/init.ts"() {
3402
+ "use strict";
3403
+ init_ui();
3404
+ init_api_key();
3405
+ init_global_config();
3406
+ init_config();
3407
+ init_config_integrity();
3408
+ init_gitignore();
3409
+ init_fs_ops();
3410
+ init_relay_client();
3411
+ init_workspace();
3412
+ init_default = defineCommand2({
3413
+ meta: {
3414
+ name: "init",
3415
+ description: "Initialize FlyDocs in this project (unified setup)"
3416
+ },
3417
+ args: {
3418
+ key: {
3419
+ type: "string",
3420
+ description: "FlyDocs API key (fdk_...)"
3421
+ },
3422
+ path: {
3423
+ type: "string",
3424
+ description: "Path to project directory"
3425
+ }
3426
+ },
3427
+ async run({ args }) {
3428
+ const targetDir = args.path ?? process.cwd();
3429
+ console.log();
3430
+ console.log(` ${pc7.bold("FlyDocs Init")}`);
3431
+ console.log();
3432
+ if (await isParentDirectory(targetDir)) {
3433
+ await runMultiRepoInit(targetDir, args.key);
3434
+ return;
3435
+ }
3436
+ const { apiKey, workspaceId } = await resolveAndValidateKey(
3437
+ args.key,
3438
+ targetDir
3439
+ );
3440
+ const serverResponse = await pullServerConfig(
3441
+ apiKey,
3442
+ targetDir,
3443
+ workspaceId
3444
+ );
3445
+ const { actions, skipped } = await initSingleRepo(
3446
+ targetDir,
3447
+ apiKey,
3448
+ serverResponse
3449
+ );
3450
+ actions.unshift("Stored credential globally (~/.flydocs/credentials)");
3451
+ const topology = serverResponse.config.topology;
3452
+ if (topology?.type === 4 && topology.label === "sibling-repos") {
3453
+ const parentDir = join19(targetDir, "..");
3454
+ const existing = await readWorkspaceFile(parentDir);
3455
+ if (existing) {
3456
+ skipped.push(".flydocs-workspace.json (already exists)");
3457
+ } else {
3458
+ const repos = await detectSiblingRepos(parentDir);
3459
+ if (Object.keys(repos).length > 0) {
3460
+ const workspaceFile = buildWorkspaceFile(
3461
+ serverResponse.workspaceId,
3462
+ repos
3463
+ );
3464
+ await writeWorkspaceFile(parentDir, workspaceFile);
3465
+ actions.push(
3466
+ `.flydocs-workspace.json (${Object.keys(repos).length} repos detected)`
3467
+ );
3468
+ }
3469
+ }
3470
+ }
3471
+ printInitReport(actions, skipped, serverResponse.warnings);
3472
+ }
3473
+ });
3474
+ }
3475
+ });
3476
+
2762
3477
  // src/commands/update.ts
2763
3478
  var update_exports = {};
2764
3479
  __export(update_exports, {
2765
3480
  default: () => update_default
2766
3481
  });
2767
- import { defineCommand as defineCommand2 } from "citty";
2768
- import { resolve as resolve3, join as join17 } from "path";
2769
- import { mkdir as mkdir8, cp as cp2, readFile as readFile12, readdir as readdir4, rm as rm4 } from "fs/promises";
2770
- import { select as select2, text as text2, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
2771
- import pc7 from "picocolors";
3482
+ import { defineCommand as defineCommand3 } from "citty";
3483
+ import { resolve as resolve4, join as join20 } from "path";
3484
+ import { mkdir as mkdir10, cp as cp2, readFile as readFile14, readdir as readdir5, rm as rm4 } from "fs/promises";
3485
+ import { select as select3, text as text3, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3486
+ import pc8 from "picocolors";
2772
3487
  var update_default;
2773
3488
  var init_update = __esm({
2774
3489
  "src/commands/update.ts"() {
@@ -2787,7 +3502,7 @@ var init_update = __esm({
2787
3502
  init_update_check();
2788
3503
  init_telemetry();
2789
3504
  init_integrity();
2790
- update_default = defineCommand2({
3505
+ update_default = defineCommand3({
2791
3506
  meta: {
2792
3507
  name: "update",
2793
3508
  description: "Update an existing FlyDocs installation"
@@ -2832,11 +3547,11 @@ var init_update = __esm({
2832
3547
  await capture("update_started", { template_version: version });
2833
3548
  let targetDir;
2834
3549
  if (args.path) {
2835
- targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
3550
+ targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
2836
3551
  } else if (args.here) {
2837
3552
  targetDir = process.cwd();
2838
3553
  } else {
2839
- const choice = await select2({
3554
+ const choice = await select3({
2840
3555
  message: "Which project would you like to update?",
2841
3556
  options: [
2842
3557
  {
@@ -2849,21 +3564,21 @@ var init_update = __esm({
2849
3564
  }
2850
3565
  ]
2851
3566
  });
2852
- if (isCancel4(choice)) {
2853
- cancel3("Update cancelled.");
3567
+ if (isCancel5(choice)) {
3568
+ cancel4("Update cancelled.");
2854
3569
  process.exit(0);
2855
3570
  }
2856
3571
  if (choice === "cwd") {
2857
3572
  targetDir = process.cwd();
2858
3573
  } else {
2859
- const enteredPath = await text2({
3574
+ const enteredPath = await text3({
2860
3575
  message: "Enter project path:"
2861
3576
  });
2862
- if (isCancel4(enteredPath)) {
2863
- cancel3("Update cancelled.");
3577
+ if (isCancel5(enteredPath)) {
3578
+ cancel4("Update cancelled.");
2864
3579
  process.exit(0);
2865
3580
  }
2866
- targetDir = resolve3(
3581
+ targetDir = resolve4(
2867
3582
  enteredPath.replace(/^~/, process.env.HOME ?? "~")
2868
3583
  );
2869
3584
  }
@@ -2872,11 +3587,11 @@ var init_update = __esm({
2872
3587
  printError(`Directory does not exist: ${targetDir}`);
2873
3588
  process.exit(1);
2874
3589
  }
2875
- targetDir = resolve3(targetDir);
3590
+ targetDir = resolve4(targetDir);
2876
3591
  process.chdir(targetDir);
2877
- const hasVersion = await pathExists(join17(targetDir, ".flydocs", "version"));
3592
+ const hasVersion = await pathExists(join20(targetDir, ".flydocs", "version"));
2878
3593
  const hasConfig = await pathExists(
2879
- join17(targetDir, ".flydocs", "config.json")
3594
+ join20(targetDir, ".flydocs", "config.json")
2880
3595
  );
2881
3596
  if (!hasVersion && !hasConfig) {
2882
3597
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -2887,8 +3602,8 @@ var init_update = __esm({
2887
3602
  console.log();
2888
3603
  let currentVersion = "0.1.0";
2889
3604
  if (hasVersion) {
2890
- const vContent = await readFile12(
2891
- join17(targetDir, ".flydocs", "version"),
3605
+ const vContent = await readFile14(
3606
+ join20(targetDir, ".flydocs", "version"),
2892
3607
  "utf-8"
2893
3608
  );
2894
3609
  currentVersion = vContent.trim();
@@ -2907,10 +3622,10 @@ var init_update = __esm({
2907
3622
  printWarning(
2908
3623
  `Project version (${currentVersion}) is newer than installer (${version})`
2909
3624
  );
2910
- const shouldContinue = await confirm3({
3625
+ const shouldContinue = await confirm4({
2911
3626
  message: "Continue anyway?"
2912
3627
  });
2913
- if (isCancel4(shouldContinue) || !shouldContinue) {
3628
+ if (isCancel5(shouldContinue) || !shouldContinue) {
2914
3629
  process.exit(0);
2915
3630
  }
2916
3631
  }
@@ -2922,10 +3637,10 @@ var init_update = __esm({
2922
3637
  });
2923
3638
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
2924
3639
  console.log();
2925
- const changelogPath = join17(templateDir, "CHANGELOG.md");
3640
+ const changelogPath = join20(templateDir, "CHANGELOG.md");
2926
3641
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
2927
3642
  if (whatsNew.length > 0) {
2928
- console.log(pc7.cyan("What's new:"));
3643
+ console.log(pc8.cyan("What's new:"));
2929
3644
  console.log();
2930
3645
  for (const entry of whatsNew) {
2931
3646
  console.log(` ${entry}`);
@@ -2934,23 +3649,23 @@ var init_update = __esm({
2934
3649
  }
2935
3650
  const now = /* @__PURE__ */ new Date();
2936
3651
  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")}`;
2937
- const backupDir = join17(targetDir, ".flydocs", `backup-${ts}`);
2938
- await mkdir8(backupDir, { recursive: true });
3652
+ const backupDir = join20(targetDir, ".flydocs", `backup-${ts}`);
3653
+ await mkdir10(backupDir, { recursive: true });
2939
3654
  if (hasConfig) {
2940
3655
  await cp2(
2941
- join17(targetDir, ".flydocs", "config.json"),
2942
- join17(backupDir, "config.json")
3656
+ join20(targetDir, ".flydocs", "config.json"),
3657
+ join20(backupDir, "config.json")
2943
3658
  );
2944
3659
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
2945
3660
  }
2946
3661
  try {
2947
- const flydocsDir = join17(targetDir, ".flydocs");
2948
- const entries = await readdir4(flydocsDir);
3662
+ const flydocsDir = join20(targetDir, ".flydocs");
3663
+ const entries = await readdir5(flydocsDir);
2949
3664
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
2950
3665
  if (backups.length > 3) {
2951
3666
  const toRemove = backups.slice(0, backups.length - 3);
2952
3667
  for (const old of toRemove) {
2953
- await rm4(join17(flydocsDir, old), { recursive: true, force: true });
3668
+ await rm4(join20(flydocsDir, old), { recursive: true, force: true });
2954
3669
  }
2955
3670
  }
2956
3671
  } catch {
@@ -2989,17 +3704,17 @@ var init_update = __esm({
2989
3704
  await ensureDirectories(targetDir, effectiveTier);
2990
3705
  console.log("Replacing framework directories...");
2991
3706
  await replaceDirectory(
2992
- join17(templateDir, ".flydocs", "templates"),
2993
- join17(targetDir, ".flydocs", "templates")
3707
+ join20(templateDir, ".flydocs", "templates"),
3708
+ join20(targetDir, ".flydocs", "templates")
2994
3709
  );
2995
3710
  printStatus(".flydocs/templates");
2996
3711
  await replaceDirectory(
2997
- join17(templateDir, ".claude", "hooks"),
2998
- join17(targetDir, ".claude", "hooks")
3712
+ join20(templateDir, ".claude", "hooks"),
3713
+ join20(targetDir, ".claude", "hooks")
2999
3714
  );
3000
3715
  printStatus(".claude/hooks");
3001
3716
  const hasExistingAgents = await pathExists(
3002
- join17(targetDir, ".claude", "agents")
3717
+ join20(targetDir, ".claude", "agents")
3003
3718
  );
3004
3719
  let installAgents;
3005
3720
  if (args.yes) {
@@ -3008,7 +3723,7 @@ var init_update = __esm({
3008
3723
  installAgents = true;
3009
3724
  } else {
3010
3725
  console.log();
3011
- console.log(` ${pc7.bold(pc7.yellow("Sub-Agents (Recommended)"))}`);
3726
+ console.log(` ${pc8.bold(pc8.yellow("Sub-Agents (Recommended)"))}`);
3012
3727
  console.log();
3013
3728
  console.log(
3014
3729
  " Sub-agents are specialized roles (PM, implementation, review,"
@@ -3020,31 +3735,31 @@ var init_update = __esm({
3020
3735
  " structure work but are not required \u2014 everything works without them."
3021
3736
  );
3022
3737
  console.log();
3023
- const agentConfirm = await confirm3({
3738
+ const agentConfirm = await confirm4({
3024
3739
  message: "Install sub-agents?",
3025
3740
  initialValue: true
3026
3741
  });
3027
- if (isCancel4(agentConfirm)) {
3742
+ if (isCancel5(agentConfirm)) {
3028
3743
  installAgents = false;
3029
3744
  } else {
3030
3745
  installAgents = agentConfirm;
3031
3746
  }
3032
3747
  }
3033
3748
  if (installAgents) {
3034
- const claudeAgentsSrc = join17(templateDir, ".claude", "agents");
3749
+ const claudeAgentsSrc = join20(templateDir, ".claude", "agents");
3035
3750
  if (await pathExists(claudeAgentsSrc)) {
3036
- await mkdir8(join17(targetDir, ".claude", "agents"), { recursive: true });
3751
+ await mkdir10(join20(targetDir, ".claude", "agents"), { recursive: true });
3037
3752
  await copyDirectoryContents(
3038
3753
  claudeAgentsSrc,
3039
- join17(targetDir, ".claude", "agents")
3754
+ join20(targetDir, ".claude", "agents")
3040
3755
  );
3041
3756
  }
3042
- const cursorAgentsSrc = join17(templateDir, ".cursor", "agents");
3757
+ const cursorAgentsSrc = join20(templateDir, ".cursor", "agents");
3043
3758
  if (await pathExists(cursorAgentsSrc)) {
3044
- await mkdir8(join17(targetDir, ".cursor", "agents"), { recursive: true });
3759
+ await mkdir10(join20(targetDir, ".cursor", "agents"), { recursive: true });
3045
3760
  await copyDirectoryContents(
3046
3761
  cursorAgentsSrc,
3047
- join17(targetDir, ".cursor", "agents")
3762
+ join20(targetDir, ".cursor", "agents")
3048
3763
  );
3049
3764
  }
3050
3765
  printStatus(
@@ -3058,58 +3773,58 @@ var init_update = __esm({
3058
3773
  console.log();
3059
3774
  console.log("Replacing framework files...");
3060
3775
  await copyFile(
3061
- join17(templateDir, ".claude", "CLAUDE.md"),
3062
- join17(targetDir, ".claude", "CLAUDE.md")
3776
+ join20(templateDir, ".claude", "CLAUDE.md"),
3777
+ join20(targetDir, ".claude", "CLAUDE.md")
3063
3778
  );
3064
3779
  await copyFile(
3065
- join17(templateDir, ".claude", "settings.json"),
3066
- join17(targetDir, ".claude", "settings.json")
3780
+ join20(templateDir, ".claude", "settings.json"),
3781
+ join20(targetDir, ".claude", "settings.json")
3067
3782
  );
3068
3783
  printStatus(".claude/CLAUDE.md, settings.json");
3069
3784
  await copyDirectoryContents(
3070
- join17(templateDir, ".claude", "commands"),
3071
- join17(targetDir, ".claude", "commands")
3785
+ join20(templateDir, ".claude", "commands"),
3786
+ join20(targetDir, ".claude", "commands")
3072
3787
  );
3073
3788
  await copyDirectoryContents(
3074
- join17(templateDir, ".claude", "commands"),
3075
- join17(targetDir, ".cursor", "commands")
3789
+ join20(templateDir, ".claude", "commands"),
3790
+ join20(targetDir, ".cursor", "commands")
3076
3791
  );
3077
3792
  printStatus(".claude/commands, .cursor/commands");
3078
- const skillsReadmeSrc = join17(templateDir, ".claude", "skills", "README.md");
3793
+ const skillsReadmeSrc = join20(templateDir, ".claude", "skills", "README.md");
3079
3794
  if (await pathExists(skillsReadmeSrc)) {
3080
3795
  await copyFile(
3081
3796
  skillsReadmeSrc,
3082
- join17(targetDir, ".claude", "skills", "README.md")
3797
+ join20(targetDir, ".claude", "skills", "README.md")
3083
3798
  );
3084
3799
  }
3085
3800
  printStatus(".claude/skills/README.md");
3086
3801
  await copyFile(
3087
- join17(templateDir, ".cursor", "hooks.json"),
3088
- join17(targetDir, ".cursor", "hooks.json")
3802
+ join20(templateDir, ".cursor", "hooks.json"),
3803
+ join20(targetDir, ".cursor", "hooks.json")
3089
3804
  );
3090
3805
  printStatus(".cursor/hooks.json");
3091
3806
  await copyFile(
3092
- join17(templateDir, "AGENTS.md"),
3093
- join17(targetDir, "AGENTS.md")
3807
+ join20(templateDir, "AGENTS.md"),
3808
+ join20(targetDir, "AGENTS.md")
3094
3809
  );
3095
3810
  printStatus("AGENTS.md");
3096
- const envExampleSrc = join17(templateDir, ".env.example");
3811
+ const envExampleSrc = join20(templateDir, ".env.example");
3097
3812
  if (await pathExists(envExampleSrc)) {
3098
- await copyFile(envExampleSrc, join17(targetDir, ".env.example"));
3813
+ await copyFile(envExampleSrc, join20(targetDir, ".env.example"));
3099
3814
  printStatus(".env.example");
3100
3815
  }
3101
- const knowledgeTemplatesDir = join17(
3816
+ const knowledgeTemplatesDir = join20(
3102
3817
  targetDir,
3103
3818
  "flydocs",
3104
3819
  "knowledge",
3105
3820
  "templates"
3106
3821
  );
3107
3822
  if (!await pathExists(knowledgeTemplatesDir)) {
3108
- await mkdir8(knowledgeTemplatesDir, { recursive: true });
3823
+ await mkdir10(knowledgeTemplatesDir, { recursive: true });
3109
3824
  }
3110
3825
  for (const tmpl of ["decision.md", "feature.md", "note.md"]) {
3111
- const src = join17(templateDir, "flydocs", "knowledge", "templates", tmpl);
3112
- const dest = join17(knowledgeTemplatesDir, tmpl);
3826
+ const src = join20(templateDir, "flydocs", "knowledge", "templates", tmpl);
3827
+ const dest = join20(knowledgeTemplatesDir, tmpl);
3113
3828
  if (await pathExists(src) && !await pathExists(dest)) {
3114
3829
  await copyFile(src, dest);
3115
3830
  }
@@ -3136,18 +3851,18 @@ var init_update = __esm({
3136
3851
  printWarning("Config merge failed \u2014 config.json preserved as-is");
3137
3852
  }
3138
3853
  await copyFile(
3139
- join17(templateDir, ".flydocs", "version"),
3140
- join17(targetDir, ".flydocs", "version")
3854
+ join20(templateDir, ".flydocs", "version"),
3855
+ join20(targetDir, ".flydocs", "version")
3141
3856
  );
3142
3857
  printStatus(`.flydocs/version \u2192 ${version}`);
3143
- const clSrc = join17(templateDir, "CHANGELOG.md");
3858
+ const clSrc = join20(templateDir, "CHANGELOG.md");
3144
3859
  if (await pathExists(clSrc)) {
3145
- await copyFile(clSrc, join17(targetDir, ".flydocs", "CHANGELOG.md"));
3860
+ await copyFile(clSrc, join20(targetDir, ".flydocs", "CHANGELOG.md"));
3146
3861
  printStatus(".flydocs/CHANGELOG.md");
3147
3862
  }
3148
- const mfSrc = join17(templateDir, "manifest.json");
3863
+ const mfSrc = join20(templateDir, "manifest.json");
3149
3864
  if (await pathExists(mfSrc)) {
3150
- await copyFile(mfSrc, join17(targetDir, ".flydocs", "manifest.json"));
3865
+ await copyFile(mfSrc, join20(targetDir, ".flydocs", "manifest.json"));
3151
3866
  printStatus(".flydocs/manifest.json");
3152
3867
  }
3153
3868
  await generateIntegrity(targetDir, version);
@@ -3209,22 +3924,22 @@ var uninstall_exports = {};
3209
3924
  __export(uninstall_exports, {
3210
3925
  default: () => uninstall_default
3211
3926
  });
3212
- import { defineCommand as defineCommand3 } from "citty";
3213
- import { resolve as resolve4, join as join18 } from "path";
3214
- import { readdir as readdir5, rm as rm5, rename as rename2 } from "fs/promises";
3215
- import { confirm as confirm4, select as select3, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3216
- import pc8 from "picocolors";
3927
+ import { defineCommand as defineCommand4 } from "citty";
3928
+ import { resolve as resolve5, join as join21 } from "path";
3929
+ import { readdir as readdir6, rm as rm5, rename as rename2 } from "fs/promises";
3930
+ import { confirm as confirm5, select as select4, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
3931
+ import pc9 from "picocolors";
3217
3932
  async function removeOwnedSkills(targetDir) {
3218
- const skillsDir = join18(targetDir, ".claude", "skills");
3933
+ const skillsDir = join21(targetDir, ".claude", "skills");
3219
3934
  const removed = [];
3220
3935
  if (!await pathExists(skillsDir)) {
3221
3936
  return removed;
3222
3937
  }
3223
3938
  try {
3224
- const entries = await readdir5(skillsDir);
3939
+ const entries = await readdir6(skillsDir);
3225
3940
  for (const entry of entries) {
3226
3941
  if (entry.startsWith(OWNED_SKILL_PREFIX)) {
3227
- await rm5(join18(skillsDir, entry), { recursive: true, force: true });
3942
+ await rm5(join21(skillsDir, entry), { recursive: true, force: true });
3228
3943
  removed.push(`.claude/skills/${entry}`);
3229
3944
  }
3230
3945
  }
@@ -3233,16 +3948,16 @@ async function removeOwnedSkills(targetDir) {
3233
3948
  return removed;
3234
3949
  }
3235
3950
  async function removeOwnedCursorRules(targetDir) {
3236
- const rulesDir = join18(targetDir, ".cursor", "rules");
3951
+ const rulesDir = join21(targetDir, ".cursor", "rules");
3237
3952
  const removed = [];
3238
3953
  if (!await pathExists(rulesDir)) {
3239
3954
  return removed;
3240
3955
  }
3241
3956
  try {
3242
- const entries = await readdir5(rulesDir);
3957
+ const entries = await readdir6(rulesDir);
3243
3958
  for (const entry of entries) {
3244
3959
  if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
3245
- await rm5(join18(rulesDir, entry), { force: true });
3960
+ await rm5(join21(rulesDir, entry), { force: true });
3246
3961
  removed.push(`.cursor/rules/${entry}`);
3247
3962
  }
3248
3963
  }
@@ -3252,7 +3967,7 @@ async function removeOwnedCursorRules(targetDir) {
3252
3967
  }
3253
3968
  async function isEmptyDir(dirPath) {
3254
3969
  try {
3255
- const entries = await readdir5(dirPath);
3970
+ const entries = await readdir6(dirPath);
3256
3971
  return entries.length === 0;
3257
3972
  } catch {
3258
3973
  return false;
@@ -3261,7 +3976,7 @@ async function isEmptyDir(dirPath) {
3261
3976
  async function cleanupEmptyParents(targetDir, dirs) {
3262
3977
  const cleaned = [];
3263
3978
  for (const dir of dirs) {
3264
- const fullPath = join18(targetDir, dir);
3979
+ const fullPath = join21(targetDir, dir);
3265
3980
  if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
3266
3981
  await rm5(fullPath, { recursive: true, force: true });
3267
3982
  cleaned.push(dir);
@@ -3293,7 +4008,7 @@ var init_uninstall = __esm({
3293
4008
  ];
3294
4009
  OWNED_SKILL_PREFIX = "flydocs-";
3295
4010
  OWNED_RULE_PREFIX = "flydocs-";
3296
- uninstall_default = defineCommand3({
4011
+ uninstall_default = defineCommand4({
3297
4012
  meta: {
3298
4013
  name: "uninstall",
3299
4014
  description: "Remove FlyDocs from a project directory"
@@ -3329,7 +4044,7 @@ var init_uninstall = __esm({
3329
4044
  printBanner(CLI_VERSION);
3330
4045
  let targetDir;
3331
4046
  if (args.path) {
3332
- targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
4047
+ targetDir = resolve5(args.path.replace(/^~/, process.env.HOME ?? "~"));
3333
4048
  } else if (args.here) {
3334
4049
  targetDir = process.cwd();
3335
4050
  } else {
@@ -3339,9 +4054,9 @@ var init_uninstall = __esm({
3339
4054
  printError(`Directory does not exist: ${targetDir}`);
3340
4055
  process.exit(1);
3341
4056
  }
3342
- targetDir = resolve4(targetDir);
3343
- const hasFlydocs = await pathExists(join18(targetDir, ".flydocs"));
3344
- const hasAgentsMd = await pathExists(join18(targetDir, "AGENTS.md"));
4057
+ targetDir = resolve5(targetDir);
4058
+ const hasFlydocs = await pathExists(join21(targetDir, ".flydocs"));
4059
+ const hasAgentsMd = await pathExists(join21(targetDir, "AGENTS.md"));
3345
4060
  if (!hasFlydocs && !hasAgentsMd) {
3346
4061
  printError(`Not a FlyDocs project: ${targetDir}`);
3347
4062
  printInfo("No .flydocs/ directory or AGENTS.md found.");
@@ -3353,12 +4068,12 @@ var init_uninstall = __esm({
3353
4068
  const removeAll = forceAll || args.all;
3354
4069
  const skipPrompts = forceAll || args.yes;
3355
4070
  let contentAction = "preserve";
3356
- const hasUserContent = await pathExists(join18(targetDir, "flydocs"));
4071
+ const hasUserContent = await pathExists(join21(targetDir, "flydocs"));
3357
4072
  if (hasUserContent) {
3358
4073
  if (removeAll) {
3359
4074
  contentAction = "remove";
3360
4075
  } else if (!skipPrompts) {
3361
- const choice = await select3({
4076
+ const choice = await select4({
3362
4077
  message: "What should happen to your flydocs/ content (project docs, knowledge base)?",
3363
4078
  options: [
3364
4079
  {
@@ -3378,8 +4093,8 @@ var init_uninstall = __esm({
3378
4093
  }
3379
4094
  ]
3380
4095
  });
3381
- if (isCancel5(choice)) {
3382
- cancel4("Uninstall cancelled.");
4096
+ if (isCancel6(choice)) {
4097
+ cancel5("Uninstall cancelled.");
3383
4098
  process.exit(0);
3384
4099
  }
3385
4100
  contentAction = choice;
@@ -3387,40 +4102,40 @@ var init_uninstall = __esm({
3387
4102
  }
3388
4103
  if (!skipPrompts) {
3389
4104
  console.log();
3390
- console.log(pc8.bold("The following will be removed:"));
4105
+ console.log(pc9.bold("The following will be removed:"));
3391
4106
  console.log();
3392
4107
  console.log(" Framework files:");
3393
4108
  for (const [path] of ALWAYS_REMOVED) {
3394
- console.log(` ${pc8.dim(path)}`);
4109
+ console.log(` ${pc9.dim(path)}`);
3395
4110
  }
3396
- console.log(` ${pc8.dim(".claude/skills/flydocs-*")}`);
3397
- console.log(` ${pc8.dim(".cursor/rules/flydocs-*.mdc")}`);
4111
+ console.log(` ${pc9.dim(".claude/skills/flydocs-*")}`);
4112
+ console.log(` ${pc9.dim(".cursor/rules/flydocs-*.mdc")}`);
3398
4113
  if (hasUserContent) {
3399
4114
  if (contentAction === "archive") {
3400
4115
  console.log();
3401
4116
  console.log(
3402
- ` User content: ${pc8.yellow("flydocs/ -> flydocs-archive/")}`
4117
+ ` User content: ${pc9.yellow("flydocs/ -> flydocs-archive/")}`
3403
4118
  );
3404
4119
  } else if (contentAction === "remove") {
3405
4120
  console.log();
3406
- console.log(` User content: ${pc8.red("flydocs/ (deleted)")}`);
4121
+ console.log(` User content: ${pc9.red("flydocs/ (deleted)")}`);
3407
4122
  } else {
3408
4123
  console.log();
3409
- console.log(` User content: ${pc8.green("flydocs/ (preserved)")}`);
4124
+ console.log(` User content: ${pc9.green("flydocs/ (preserved)")}`);
3410
4125
  }
3411
4126
  }
3412
4127
  console.log();
3413
- console.log(pc8.bold("Preserved:"));
4128
+ console.log(pc9.bold("Preserved:"));
3414
4129
  console.log(
3415
- ` ${pc8.dim(".claude/skills/ (non-flydocs community skills)")}`
4130
+ ` ${pc9.dim(".claude/skills/ (non-flydocs community skills)")}`
3416
4131
  );
3417
- console.log(` ${pc8.dim(".env, .env.local")}`);
4132
+ console.log(` ${pc9.dim(".env, .env.local")}`);
3418
4133
  console.log();
3419
- const shouldContinue = await confirm4({
4134
+ const shouldContinue = await confirm5({
3420
4135
  message: "Proceed with uninstall?"
3421
4136
  });
3422
- if (isCancel5(shouldContinue) || !shouldContinue) {
3423
- cancel4("Uninstall cancelled.");
4137
+ if (isCancel6(shouldContinue) || !shouldContinue) {
4138
+ cancel5("Uninstall cancelled.");
3424
4139
  process.exit(0);
3425
4140
  }
3426
4141
  }
@@ -3436,7 +4151,7 @@ var init_uninstall = __esm({
3436
4151
  const removedRules = await removeOwnedCursorRules(targetDir);
3437
4152
  result.removed.push(...removedRules);
3438
4153
  for (const [relativePath, type] of ALWAYS_REMOVED) {
3439
- const fullPath = join18(targetDir, relativePath);
4154
+ const fullPath = join21(targetDir, relativePath);
3440
4155
  if (!await pathExists(fullPath)) {
3441
4156
  result.skipped.push(relativePath);
3442
4157
  continue;
@@ -3454,9 +4169,9 @@ var init_uninstall = __esm({
3454
4169
  }
3455
4170
  }
3456
4171
  if (hasUserContent) {
3457
- const flydocsPath = join18(targetDir, "flydocs");
4172
+ const flydocsPath = join21(targetDir, "flydocs");
3458
4173
  if (contentAction === "archive") {
3459
- const archivePath = join18(targetDir, "flydocs-archive");
4174
+ const archivePath = join21(targetDir, "flydocs-archive");
3460
4175
  if (await pathExists(archivePath)) {
3461
4176
  await rm5(archivePath, { recursive: true, force: true });
3462
4177
  }
@@ -3482,17 +4197,17 @@ var init_uninstall = __esm({
3482
4197
  result.restored = originals.map((f) => f.relativePath);
3483
4198
  }
3484
4199
  console.log();
3485
- console.log(pc8.bold("Uninstall Summary"));
4200
+ console.log(pc9.bold("Uninstall Summary"));
3486
4201
  console.log();
3487
4202
  if (result.removed.length > 0) {
3488
- console.log(` ${pc8.green("Removed")} (${result.removed.length}):`);
4203
+ console.log(` ${pc9.green("Removed")} (${result.removed.length}):`);
3489
4204
  for (const item of result.removed) {
3490
4205
  printStatus(item);
3491
4206
  }
3492
4207
  }
3493
4208
  if (result.archived.length > 0) {
3494
4209
  console.log();
3495
- console.log(` ${pc8.yellow("Archived")} (${result.archived.length}):`);
4210
+ console.log(` ${pc9.yellow("Archived")} (${result.archived.length}):`);
3496
4211
  for (const item of result.archived) {
3497
4212
  printInfo(item);
3498
4213
  }
@@ -3500,7 +4215,7 @@ var init_uninstall = __esm({
3500
4215
  if (result.restored.length > 0) {
3501
4216
  console.log();
3502
4217
  console.log(
3503
- ` ${pc8.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
4218
+ ` ${pc9.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
3504
4219
  );
3505
4220
  for (const item of result.restored) {
3506
4221
  printInfo(item);
@@ -3509,16 +4224,16 @@ var init_uninstall = __esm({
3509
4224
  if (result.skipped.length > 0) {
3510
4225
  console.log();
3511
4226
  console.log(
3512
- ` ${pc8.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
4227
+ ` ${pc9.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
3513
4228
  );
3514
4229
  for (const item of result.skipped) {
3515
- console.log(` ${pc8.dim(item)}`);
4230
+ console.log(` ${pc9.dim(item)}`);
3516
4231
  }
3517
4232
  }
3518
4233
  console.log();
3519
4234
  printStatus("FlyDocs has been removed from this project.");
3520
4235
  console.log();
3521
- printInfo(`To reinstall: ${pc8.cyan("npx @flydocs/cli install --here")}`);
4236
+ printInfo(`To reinstall: ${pc9.cyan("npx @flydocs/cli install --here")}`);
3522
4237
  console.log();
3523
4238
  }
3524
4239
  });
@@ -3530,28 +4245,28 @@ var setup_exports = {};
3530
4245
  __export(setup_exports, {
3531
4246
  default: () => setup_default
3532
4247
  });
3533
- import { defineCommand as defineCommand4 } from "citty";
3534
- import pc9 from "picocolors";
4248
+ import { defineCommand as defineCommand5 } from "citty";
4249
+ import pc10 from "picocolors";
3535
4250
  var setup_default;
3536
4251
  var init_setup = __esm({
3537
4252
  "src/commands/setup.ts"() {
3538
4253
  "use strict";
3539
- setup_default = defineCommand4({
4254
+ setup_default = defineCommand5({
3540
4255
  meta: {
3541
4256
  name: "setup",
3542
4257
  description: "Configure FlyDocs settings for this project"
3543
4258
  },
3544
4259
  run() {
3545
4260
  console.log();
3546
- console.log(` ${pc9.bold("FlyDocs Setup")}`);
4261
+ console.log(` ${pc10.bold("FlyDocs Setup")}`);
3547
4262
  console.log();
3548
4263
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
3549
4264
  console.log();
3550
4265
  console.log(
3551
- ` ${pc9.cyan("Claude Code:")} Type ${pc9.bold("/flydocs-setup")} in chat`
4266
+ ` ${pc10.cyan("Claude Code:")} Type ${pc10.bold("/flydocs-setup")} in chat`
3552
4267
  );
3553
4268
  console.log(
3554
- ` ${pc9.cyan("Cursor:")} Type ${pc9.bold("/flydocs-setup")} in chat`
4269
+ ` ${pc10.cyan("Cursor:")} Type ${pc10.bold("/flydocs-setup")} in chat`
3555
4270
  );
3556
4271
  console.log();
3557
4272
  console.log(` This configures your project context, detects your stack,`);
@@ -3567,14 +4282,14 @@ var skills_exports = {};
3567
4282
  __export(skills_exports, {
3568
4283
  default: () => skills_default
3569
4284
  });
3570
- import { defineCommand as defineCommand5 } from "citty";
3571
- import pc10 from "picocolors";
4285
+ import { defineCommand as defineCommand6 } from "citty";
4286
+ import pc11 from "picocolors";
3572
4287
  var list, search, add, remove, skills_default;
3573
4288
  var init_skills2 = __esm({
3574
4289
  "src/commands/skills.ts"() {
3575
4290
  "use strict";
3576
4291
  init_skill_manager();
3577
- list = defineCommand5({
4292
+ list = defineCommand6({
3578
4293
  meta: {
3579
4294
  name: "list",
3580
4295
  description: "List installed skills"
@@ -3590,26 +4305,26 @@ var init_skills2 = __esm({
3590
4305
  console.log(`${total} skill(s) installed:`);
3591
4306
  if (result.platform.length > 0) {
3592
4307
  console.log();
3593
- console.log(pc10.bold("Platform"));
4308
+ console.log(pc11.bold("Platform"));
3594
4309
  for (const skill of result.platform) {
3595
4310
  console.log(
3596
- ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
4311
+ ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
3597
4312
  );
3598
4313
  }
3599
4314
  }
3600
4315
  if (result.community.length > 0) {
3601
4316
  console.log();
3602
- console.log(pc10.bold("Community"));
4317
+ console.log(pc11.bold("Community"));
3603
4318
  for (const skill of result.community) {
3604
4319
  console.log(
3605
- ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
4320
+ ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
3606
4321
  );
3607
4322
  }
3608
4323
  }
3609
4324
  console.log();
3610
4325
  }
3611
4326
  });
3612
- search = defineCommand5({
4327
+ search = defineCommand6({
3613
4328
  meta: {
3614
4329
  name: "search",
3615
4330
  description: "Search community skills"
@@ -3625,24 +4340,24 @@ var init_skills2 = __esm({
3625
4340
  const results = await searchCatalog(args.keyword);
3626
4341
  if (results.length === 0) {
3627
4342
  console.log(`No skills found for "${args.keyword}".`);
3628
- console.log(` Browse the catalog at: ${pc10.cyan("https://skills.sh/")}`);
4343
+ console.log(` Browse the catalog at: ${pc11.cyan("https://skills.sh/")}`);
3629
4344
  return;
3630
4345
  }
3631
4346
  console.log();
3632
4347
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
3633
4348
  console.log();
3634
4349
  for (const skill of results) {
3635
- console.log(` ${pc10.bold(skill.name)}`);
4350
+ console.log(` ${pc11.bold(skill.name)}`);
3636
4351
  console.log(` ${skill.description}`);
3637
- console.log(` ${pc10.dim(skill.repo)}`);
4352
+ console.log(` ${pc11.dim(skill.repo)}`);
3638
4353
  if (skill.tags.length > 0) {
3639
- console.log(` ${pc10.dim(skill.tags.join(", "))}`);
4354
+ console.log(` ${pc11.dim(skill.tags.join(", "))}`);
3640
4355
  }
3641
4356
  console.log();
3642
4357
  }
3643
4358
  }
3644
4359
  });
3645
- add = defineCommand5({
4360
+ add = defineCommand6({
3646
4361
  meta: {
3647
4362
  name: "add",
3648
4363
  description: "Install a community skill"
@@ -3658,7 +4373,7 @@ var init_skills2 = __esm({
3658
4373
  await addSkill(process.cwd(), args.source);
3659
4374
  }
3660
4375
  });
3661
- remove = defineCommand5({
4376
+ remove = defineCommand6({
3662
4377
  meta: {
3663
4378
  name: "remove",
3664
4379
  description: "Remove an installed community skill"
@@ -3674,7 +4389,7 @@ var init_skills2 = __esm({
3674
4389
  await removeSkill(process.cwd(), args.name);
3675
4390
  }
3676
4391
  });
3677
- skills_default = defineCommand5({
4392
+ skills_default = defineCommand6({
3678
4393
  meta: {
3679
4394
  name: "skills",
3680
4395
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -3694,10 +4409,10 @@ var connect_exports = {};
3694
4409
  __export(connect_exports, {
3695
4410
  default: () => connect_default
3696
4411
  });
3697
- import { defineCommand as defineCommand6 } from "citty";
3698
- import { text as text3, confirm as confirm5, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
3699
- import pc11 from "picocolors";
3700
- import { join as join19 } from "path";
4412
+ import { defineCommand as defineCommand7 } from "citty";
4413
+ import { text as text4, confirm as confirm6, isCancel as isCancel7, cancel as cancel6 } from "@clack/prompts";
4414
+ import pc12 from "picocolors";
4415
+ import { join as join22 } from "path";
3701
4416
  var connect_default;
3702
4417
  var init_connect = __esm({
3703
4418
  "src/commands/connect.ts"() {
@@ -3707,7 +4422,7 @@ var init_connect = __esm({
3707
4422
  init_template();
3708
4423
  init_ui();
3709
4424
  init_api_key();
3710
- connect_default = defineCommand6({
4425
+ connect_default = defineCommand7({
3711
4426
  meta: {
3712
4427
  name: "connect",
3713
4428
  description: "Connect FlyDocs to a cloud provider"
@@ -3732,11 +4447,11 @@ var init_connect = __esm({
3732
4447
  },
3733
4448
  async run({ args }) {
3734
4449
  const targetDir = args.path ?? process.cwd();
3735
- const configPath = join19(targetDir, ".flydocs", "config.json");
4450
+ const configPath = join22(targetDir, ".flydocs", "config.json");
3736
4451
  if (!await pathExists(configPath)) {
3737
4452
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
3738
4453
  console.log(
3739
- ` Run ${pc11.cyan("flydocs")} first to install FlyDocs in this project.`
4454
+ ` Run ${pc12.cyan("flydocs")} first to install FlyDocs in this project.`
3740
4455
  );
3741
4456
  process.exit(1);
3742
4457
  }
@@ -3744,24 +4459,24 @@ var init_connect = __esm({
3744
4459
  if (config.tier === "cloud") {
3745
4460
  printInfo("This project is already connected to the cloud tier.");
3746
4461
  console.log();
3747
- const reconnect = await confirm5({
4462
+ const reconnect = await confirm6({
3748
4463
  message: "Want to update your API key?"
3749
4464
  });
3750
- if (isCancel6(reconnect) || !reconnect) {
4465
+ if (isCancel7(reconnect) || !reconnect) {
3751
4466
  console.log(` No changes made.`);
3752
4467
  return;
3753
4468
  }
3754
4469
  }
3755
4470
  console.log();
3756
- console.log(` ${pc11.bold("Connect to FlyDocs Cloud")}`);
4471
+ console.log(` ${pc12.bold("Connect to FlyDocs Cloud")}`);
3757
4472
  console.log();
3758
4473
  console.log(
3759
- ` ${pc11.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4474
+ ` ${pc12.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3760
4475
  );
3761
4476
  console.log();
3762
4477
  let apiKey = args.key ?? "";
3763
4478
  if (!apiKey) {
3764
- const keyInput = await text3({
4479
+ const keyInput = await text4({
3765
4480
  message: "Enter your API key",
3766
4481
  placeholder: "fdk_...",
3767
4482
  validate(value) {
@@ -3772,8 +4487,8 @@ var init_connect = __esm({
3772
4487
  return void 0;
3773
4488
  }
3774
4489
  });
3775
- if (isCancel6(keyInput)) {
3776
- cancel5("Connection cancelled.");
4490
+ if (isCancel7(keyInput)) {
4491
+ cancel6("Connection cancelled.");
3777
4492
  process.exit(0);
3778
4493
  }
3779
4494
  apiKey = keyInput.trim();
@@ -3794,7 +4509,7 @@ var init_connect = __esm({
3794
4509
  console.log(` Check your key and try again.`);
3795
4510
  process.exit(1);
3796
4511
  }
3797
- printStatus(`Connected to ${pc11.bold(result.org)}`);
4512
+ printStatus(`Connected to ${pc12.bold(result.org)}`);
3798
4513
  } catch {
3799
4514
  printError(
3800
4515
  "Could not reach relay API. Check your network and try again."
@@ -3802,7 +4517,7 @@ var init_connect = __esm({
3802
4517
  process.exit(1);
3803
4518
  }
3804
4519
  const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
3805
- printStatus(`API key stored in ${pc11.dim(envFile)}`);
4520
+ printStatus(`API key stored in ${pc12.dim(envFile)}`);
3806
4521
  } else {
3807
4522
  try {
3808
4523
  const result = await validateLinearKey(apiKey);
@@ -3812,7 +4527,7 @@ var init_connect = __esm({
3812
4527
  process.exit(1);
3813
4528
  }
3814
4529
  printStatus(
3815
- `Authenticated as ${pc11.bold(result.name)} (${result.email})`
4530
+ `Authenticated as ${pc12.bold(result.name)} (${result.email})`
3816
4531
  );
3817
4532
  } catch {
3818
4533
  printError("Invalid API key or network error.");
@@ -3820,7 +4535,7 @@ var init_connect = __esm({
3820
4535
  process.exit(1);
3821
4536
  }
3822
4537
  const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
3823
- printStatus(`API key stored in ${pc11.dim(envFile)}`);
4538
+ printStatus(`API key stored in ${pc12.dim(envFile)}`);
3824
4539
  }
3825
4540
  const wasLocal = config.tier === "local";
3826
4541
  config.tier = "cloud";
@@ -3836,14 +4551,473 @@ var init_connect = __esm({
3836
4551
  }
3837
4552
  console.log();
3838
4553
  console.log(
3839
- ` ${pc11.bold("Connected!")} Your project is now on the cloud tier.`
4554
+ ` ${pc12.bold("Connected!")} Your project is now on the cloud tier.`
3840
4555
  );
3841
4556
  console.log();
3842
4557
  console.log(` Next steps:`);
3843
4558
  console.log(
3844
- ` 1. Run ${pc11.cyan("/flydocs-setup")} in your IDE to configure your project`
4559
+ ` 1. Run ${pc12.cyan("/flydocs-setup")} in your IDE to configure your project`
4560
+ );
4561
+ console.log(` 2. Run ${pc12.cyan("/start-session")} to begin working`);
4562
+ console.log();
4563
+ }
4564
+ });
4565
+ }
4566
+ });
4567
+
4568
+ // src/commands/auth.ts
4569
+ var auth_exports = {};
4570
+ __export(auth_exports, {
4571
+ default: () => auth_default
4572
+ });
4573
+ import { defineCommand as defineCommand8 } from "citty";
4574
+ import { text as text5, confirm as confirm7, isCancel as isCancel8, cancel as cancel7 } from "@clack/prompts";
4575
+ import pc13 from "picocolors";
4576
+ var auth_default;
4577
+ var init_auth = __esm({
4578
+ "src/commands/auth.ts"() {
4579
+ "use strict";
4580
+ init_ui();
4581
+ init_api_key();
4582
+ init_global_config();
4583
+ auth_default = defineCommand8({
4584
+ meta: {
4585
+ name: "auth",
4586
+ description: "Store API key globally (~/.flydocs/credentials)"
4587
+ },
4588
+ args: {
4589
+ key: {
4590
+ type: "positional",
4591
+ description: "FlyDocs API key (fdk_...)",
4592
+ required: false
4593
+ }
4594
+ },
4595
+ async run({ args }) {
4596
+ console.log();
4597
+ console.log(` ${pc13.bold("FlyDocs Authentication")}`);
4598
+ console.log();
4599
+ let apiKey = args.key ?? "";
4600
+ const existing = await readGlobalCredential();
4601
+ if (existing?.apiKey && !apiKey) {
4602
+ printInfo(
4603
+ `Existing key found: ${pc13.dim(existing.apiKey.slice(0, 8) + "...")}`
4604
+ );
4605
+ const replace = await confirm7({
4606
+ message: "Replace with a new key?"
4607
+ });
4608
+ if (isCancel8(replace) || !replace) {
4609
+ console.log(` No changes made.`);
4610
+ return;
4611
+ }
4612
+ }
4613
+ if (!apiKey) {
4614
+ console.log(
4615
+ ` ${pc13.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4616
+ );
4617
+ console.log();
4618
+ const keyInput = await text5({
4619
+ message: "Enter your API key",
4620
+ placeholder: "fdk_...",
4621
+ validate(value) {
4622
+ if (!value.trim()) return "API key is required";
4623
+ if (detectKeyType(value.trim()) !== "relay") {
4624
+ return "Key must start with fdk_ (FlyDocs API key)";
4625
+ }
4626
+ return void 0;
4627
+ }
4628
+ });
4629
+ if (isCancel8(keyInput)) {
4630
+ cancel7("Authentication cancelled.");
4631
+ process.exit(0);
4632
+ }
4633
+ apiKey = keyInput.trim();
4634
+ }
4635
+ if (detectKeyType(apiKey) !== "relay") {
4636
+ printError("Invalid key format. Expected fdk_ prefix (FlyDocs API key).");
4637
+ process.exit(1);
4638
+ }
4639
+ if (existing?.apiKey === apiKey) {
4640
+ printInfo("This key is already stored.");
4641
+ await checkCredentialPermissions();
4642
+ return;
4643
+ }
4644
+ printInfo("Validating API key...");
4645
+ try {
4646
+ const result = await validateRelayKey(apiKey);
4647
+ if (!result.valid) {
4648
+ printError("Invalid API key. Check your key and try again.");
4649
+ process.exit(1);
4650
+ }
4651
+ printStatus(`Authenticated with ${pc13.bold(result.org)}`);
4652
+ } catch {
4653
+ printError(
4654
+ "Could not reach FlyDocs API. Check your network and try again."
4655
+ );
4656
+ process.exit(1);
4657
+ }
4658
+ if (existing?.apiKey && existing.apiKey !== apiKey) {
4659
+ printWarning("Replacing existing key.");
4660
+ }
4661
+ await writeGlobalCredential({
4662
+ apiKey,
4663
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4664
+ lastValidated: (/* @__PURE__ */ new Date()).toISOString()
4665
+ });
4666
+ printStatus(`Key stored at ${pc13.dim(credentialsPath())}`);
4667
+ await checkCredentialPermissions();
4668
+ console.log();
4669
+ console.log(` ${pc13.bold("Authenticated!")} Key stored globally.`);
4670
+ console.log(` All FlyDocs projects on this machine will use this key.`);
4671
+ console.log();
4672
+ }
4673
+ });
4674
+ }
4675
+ });
4676
+
4677
+ // src/commands/sync.ts
4678
+ var sync_exports = {};
4679
+ __export(sync_exports, {
4680
+ default: () => sync_default
4681
+ });
4682
+ import { defineCommand as defineCommand9 } from "citty";
4683
+ import pc14 from "picocolors";
4684
+ import { join as join23 } from "path";
4685
+ import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
4686
+ var sync_default;
4687
+ var init_sync = __esm({
4688
+ "src/commands/sync.ts"() {
4689
+ "use strict";
4690
+ init_ui();
4691
+ init_global_config();
4692
+ init_config();
4693
+ init_config_integrity();
4694
+ init_fs_ops();
4695
+ init_types();
4696
+ init_relay_client();
4697
+ sync_default = defineCommand9({
4698
+ meta: {
4699
+ name: "sync",
4700
+ description: "Pull latest config and templates from server"
4701
+ },
4702
+ args: {
4703
+ path: {
4704
+ type: "string",
4705
+ description: "Path to project directory"
4706
+ }
4707
+ },
4708
+ async run({ args }) {
4709
+ const targetDir = args.path ?? process.cwd();
4710
+ const changes = [];
4711
+ console.log();
4712
+ printInfo("Syncing with server...");
4713
+ const resolved = await resolveApiKey(void 0, targetDir);
4714
+ if (!resolved) {
4715
+ printError(
4716
+ "No API key found. Run `flydocs auth` or `flydocs init` first."
4717
+ );
4718
+ process.exit(1);
4719
+ }
4720
+ const apiKey = resolved.key;
4721
+ const cred = await readGlobalCredential();
4722
+ const workspaceId = cred?.workspaceId;
4723
+ if (!workspaceId) {
4724
+ printError(
4725
+ "No workspace ID found. Run `flydocs init` to set up your workspace."
4726
+ );
4727
+ process.exit(1);
4728
+ }
4729
+ let currentTemplateVersion = 0;
4730
+ try {
4731
+ const currentConfig = await readAnyConfig(targetDir);
4732
+ if (isConfigV2(currentConfig)) {
4733
+ currentTemplateVersion = currentConfig.configVersion ?? 0;
4734
+ }
4735
+ } catch {
4736
+ }
4737
+ let serverResponse;
4738
+ try {
4739
+ serverResponse = await fetchConfigV2(apiKey, { workspaceId });
4740
+ } catch (err) {
4741
+ if (err instanceof RelayError) {
4742
+ printWarning(
4743
+ `Server unavailable (${err.status}), using cached config.`
4744
+ );
4745
+ } else {
4746
+ printWarning("Server unreachable, using cached config.");
4747
+ }
4748
+ console.log(` ${pc14.dim("Config may be stale. Retry when connected.")}`);
4749
+ return;
4750
+ }
4751
+ if (!serverResponse.valid) {
4752
+ printWarning("Server returned invalid config. Keeping current config.");
4753
+ return;
4754
+ }
4755
+ await mkdir11(join23(targetDir, ".flydocs"), { recursive: true });
4756
+ const configWithHash = applyConfigHash(serverResponse.config);
4757
+ await writeConfig(targetDir, configWithHash);
4758
+ changes.push("Updated .flydocs/config.json");
4759
+ const serverTemplateVersion = serverResponse.templates.version;
4760
+ if (serverTemplateVersion > currentTemplateVersion) {
4761
+ try {
4762
+ const templatesResponse = await fetchTemplates(
4763
+ apiKey,
4764
+ currentTemplateVersion,
4765
+ workspaceId
4766
+ );
4767
+ if (templatesResponse.templates.length > 0) {
4768
+ const templatesDir = join23(
4769
+ targetDir,
4770
+ ".claude",
4771
+ "skills",
4772
+ "flydocs-workflow",
4773
+ "templates"
4774
+ );
4775
+ await mkdir11(templatesDir, { recursive: true });
4776
+ for (const template of templatesResponse.templates) {
4777
+ const filename = `${template.type}.md`;
4778
+ const subdir = template.category === "issue" ? "issues" : template.category === "pr" ? "pr" : template.category === "comment" ? "." : ".";
4779
+ const templateDir = join23(templatesDir, subdir);
4780
+ await mkdir11(templateDir, { recursive: true });
4781
+ await writeFile13(
4782
+ join23(templateDir, filename),
4783
+ template.content,
4784
+ "utf-8"
4785
+ );
4786
+ }
4787
+ changes.push(
4788
+ `Updated ${templatesResponse.templates.length} templates (v${currentTemplateVersion} \u2192 v${serverTemplateVersion})`
4789
+ );
4790
+ }
4791
+ } catch (err) {
4792
+ if (err instanceof RelayError) {
4793
+ printWarning("Could not fetch templates. Will retry on next sync.");
4794
+ } else {
4795
+ printWarning("Template sync failed. Will retry on next sync.");
4796
+ }
4797
+ }
4798
+ }
4799
+ if (changes.length === 0) {
4800
+ printStatus("Already up to date.");
4801
+ } else {
4802
+ for (const change of changes) {
4803
+ printStatus(change);
4804
+ }
4805
+ }
4806
+ console.log();
4807
+ }
4808
+ });
4809
+ }
4810
+ });
4811
+
4812
+ // src/commands/cleanup.ts
4813
+ var cleanup_exports = {};
4814
+ __export(cleanup_exports, {
4815
+ default: () => cleanup_default
4816
+ });
4817
+ import { defineCommand as defineCommand10 } from "citty";
4818
+ import pc15 from "picocolors";
4819
+ import { join as join24 } from "path";
4820
+ import { readFile as readFile15, writeFile as writeFile14, rm as rm6 } from "fs/promises";
4821
+ async function scanArtifacts(targetDir) {
4822
+ const actions = [];
4823
+ const localMePath = join24(targetDir, ".flydocs", "me.json");
4824
+ if (await pathExists(localMePath) && await pathExists(globalMePath())) {
4825
+ actions.push({
4826
+ description: "Remove .flydocs/me.json (migrated to ~/.flydocs/me.json)",
4827
+ path: localMePath,
4828
+ type: "file"
4829
+ });
4830
+ }
4831
+ const validationCachePath = join24(
4832
+ targetDir,
4833
+ ".flydocs",
4834
+ "validation-cache.json"
4835
+ );
4836
+ if (await pathExists(validationCachePath)) {
4837
+ actions.push({
4838
+ description: "Remove .flydocs/validation-cache.json (replaced by configVersion)",
4839
+ path: validationCachePath,
4840
+ type: "file"
4841
+ });
4842
+ }
4843
+ const hasGlobalCreds = await pathExists(credentialsPath());
4844
+ for (const envFile of [".env", ".env.local"]) {
4845
+ const envPath = join24(targetDir, envFile);
4846
+ if (!await pathExists(envPath)) continue;
4847
+ const content = await readFile15(envPath, "utf-8");
4848
+ const lines = content.split("\n");
4849
+ for (const line of lines) {
4850
+ const trimmed = line.trim();
4851
+ if (hasGlobalCreds && trimmed.startsWith("FLYDOCS_API_KEY=")) {
4852
+ actions.push({
4853
+ description: `Remove FLYDOCS_API_KEY from ${envFile} (migrated to ~/.flydocs/credentials)`,
4854
+ path: envPath,
4855
+ type: "line"
4856
+ });
4857
+ }
4858
+ if (trimmed.startsWith("FLYDOCS_RELAY_URL=")) {
4859
+ actions.push({
4860
+ description: `Remove FLYDOCS_RELAY_URL from ${envFile}`,
4861
+ path: envPath,
4862
+ type: "line"
4863
+ });
4864
+ }
4865
+ }
4866
+ }
4867
+ try {
4868
+ const config = await readAnyConfig(targetDir);
4869
+ const rawContent = await readFile15(
4870
+ join24(targetDir, ".flydocs", "config.json"),
4871
+ "utf-8"
4872
+ );
4873
+ const raw = JSON.parse(rawContent);
4874
+ const ghostFields = ["provider", "statusMapping"];
4875
+ for (const field of ghostFields) {
4876
+ if (field in raw) {
4877
+ actions.push({
4878
+ description: `Remove ghost field "${field}" from config.json`,
4879
+ path: join24(targetDir, ".flydocs", "config.json"),
4880
+ type: "field"
4881
+ });
4882
+ }
4883
+ }
4884
+ if (isConfigV2(config)) {
4885
+ const v1OnlyFields = [
4886
+ "workspaceId",
4887
+ "issueLabels",
4888
+ "workspace",
4889
+ "onboardComplete",
4890
+ "sourceRepo",
4891
+ "designSystem",
4892
+ "aiLabor"
4893
+ ];
4894
+ for (const field of v1OnlyFields) {
4895
+ if (field in raw) {
4896
+ actions.push({
4897
+ description: `Remove v1 field "${field}" from config.json (server-owned in v2)`,
4898
+ path: join24(targetDir, ".flydocs", "config.json"),
4899
+ type: "field"
4900
+ });
4901
+ }
4902
+ }
4903
+ }
4904
+ } catch {
4905
+ }
4906
+ return actions;
4907
+ }
4908
+ async function executeCleanup(targetDir, actions) {
4909
+ const fileRemovals = actions.filter((a) => a.type === "file");
4910
+ const lineRemovals = actions.filter((a) => a.type === "line");
4911
+ const fieldRemovals = actions.filter((a) => a.type === "field");
4912
+ for (const action of fileRemovals) {
4913
+ await rm6(action.path, { force: true });
4914
+ }
4915
+ const envFiles = /* @__PURE__ */ new Map();
4916
+ for (const action of lineRemovals) {
4917
+ if (!envFiles.has(action.path)) {
4918
+ const content = await readFile15(action.path, "utf-8");
4919
+ envFiles.set(action.path, content.split("\n"));
4920
+ }
4921
+ }
4922
+ for (const [envPath, lines] of envFiles) {
4923
+ const filtered = lines.filter((line) => {
4924
+ const trimmed = line.trim();
4925
+ return !trimmed.startsWith("FLYDOCS_API_KEY=") && !trimmed.startsWith("FLYDOCS_RELAY_URL=");
4926
+ });
4927
+ const hasContent = filtered.some(
4928
+ (l) => l.trim().length > 0 && !l.trim().startsWith("#")
4929
+ );
4930
+ if (!hasContent) {
4931
+ await rm6(envPath, { force: true });
4932
+ } else {
4933
+ await writeFile14(envPath, filtered.join("\n"), "utf-8");
4934
+ }
4935
+ }
4936
+ if (fieldRemovals.length > 0) {
4937
+ const configPath = join24(targetDir, ".flydocs", "config.json");
4938
+ const content = await readFile15(configPath, "utf-8");
4939
+ const config = JSON.parse(content);
4940
+ for (const action of fieldRemovals) {
4941
+ const match = action.description.match(/"(\w+)"/);
4942
+ if (match) {
4943
+ delete config[match[1]];
4944
+ }
4945
+ }
4946
+ await writeFile14(
4947
+ configPath,
4948
+ JSON.stringify(config, null, 2) + "\n",
4949
+ "utf-8"
4950
+ );
4951
+ }
4952
+ }
4953
+ var cleanup_default;
4954
+ var init_cleanup = __esm({
4955
+ "src/commands/cleanup.ts"() {
4956
+ "use strict";
4957
+ init_ui();
4958
+ init_fs_ops();
4959
+ init_config();
4960
+ init_types();
4961
+ init_global_config();
4962
+ cleanup_default = defineCommand10({
4963
+ meta: {
4964
+ name: "cleanup",
4965
+ description: "Remove legacy v1 artifacts after migration (dry-run by default)"
4966
+ },
4967
+ args: {
4968
+ confirm: {
4969
+ type: "boolean",
4970
+ description: "Actually remove artifacts (default: dry-run only)",
4971
+ default: false
4972
+ },
4973
+ path: {
4974
+ type: "string",
4975
+ description: "Path to project directory"
4976
+ }
4977
+ },
4978
+ async run({ args }) {
4979
+ const targetDir = args.path ?? process.cwd();
4980
+ console.log();
4981
+ console.log(` ${pc15.bold("FlyDocs Cleanup")}`);
4982
+ console.log();
4983
+ const hasConfig = await pathExists(
4984
+ join24(targetDir, ".flydocs", "config.json")
3845
4985
  );
3846
- console.log(` 2. Run ${pc11.cyan("/start-session")} to begin working`);
4986
+ if (!hasConfig) {
4987
+ printError("No .flydocs/config.json found. Run flydocs init first.");
4988
+ process.exit(1);
4989
+ }
4990
+ const hasGlobalCreds = await pathExists(credentialsPath());
4991
+ if (!hasGlobalCreds) {
4992
+ printWarning(
4993
+ "No global credentials found. Run flydocs init to set up credentials before cleanup."
4994
+ );
4995
+ }
4996
+ const actions = await scanArtifacts(targetDir);
4997
+ if (actions.length === 0) {
4998
+ printStatus("No legacy artifacts found. Already clean.");
4999
+ console.log();
5000
+ return;
5001
+ }
5002
+ const mode = args.confirm ? "Removing" : "Would remove";
5003
+ printInfo(`${mode} ${actions.length} legacy artifact(s):`);
5004
+ console.log();
5005
+ for (const action of actions) {
5006
+ const icon = args.confirm ? pc15.red("-") : pc15.yellow("?");
5007
+ console.log(` ${icon} ${action.description}`);
5008
+ }
5009
+ console.log();
5010
+ if (!args.confirm) {
5011
+ printInfo(
5012
+ `Dry-run complete. Run ${pc15.cyan("flydocs cleanup --confirm")} to remove.`
5013
+ );
5014
+ console.log();
5015
+ return;
5016
+ }
5017
+ await executeCleanup(targetDir, actions);
5018
+ printCompletionBox("Cleanup Complete", [
5019
+ `${pc15.green("+")} Removed ${actions.length} legacy artifact(s)`
5020
+ ]);
3847
5021
  console.log();
3848
5022
  }
3849
5023
  });
@@ -3855,15 +5029,15 @@ var upgrade_exports = {};
3855
5029
  __export(upgrade_exports, {
3856
5030
  default: () => upgrade_default
3857
5031
  });
3858
- import { defineCommand as defineCommand7 } from "citty";
3859
- import pc12 from "picocolors";
5032
+ import { defineCommand as defineCommand11 } from "citty";
5033
+ import pc16 from "picocolors";
3860
5034
  var upgrade_default;
3861
5035
  var init_upgrade = __esm({
3862
5036
  "src/commands/upgrade.ts"() {
3863
5037
  "use strict";
3864
5038
  init_config();
3865
5039
  init_fs_ops();
3866
- upgrade_default = defineCommand7({
5040
+ upgrade_default = defineCommand11({
3867
5041
  meta: {
3868
5042
  name: "upgrade",
3869
5043
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -3892,34 +5066,34 @@ var init_upgrade = __esm({
3892
5066
  console.log();
3893
5067
  if (currentTier === "cloud") {
3894
5068
  console.log(
3895
- ` ${pc12.green("\u2713")} You're already on the ${pc12.bold("cloud")} tier.`
5069
+ ` ${pc16.green("\u2713")} You're already on the ${pc16.bold("cloud")} tier.`
3896
5070
  );
3897
5071
  console.log();
3898
5072
  console.log(` Your issues sync with your provider via the relay API.`);
3899
5073
  console.log(
3900
- ` Run ${pc12.cyan("flydocs connect")} to update your connection settings.`
5074
+ ` Run ${pc16.cyan("flydocs connect")} to update your connection settings.`
3901
5075
  );
3902
5076
  console.log();
3903
5077
  return;
3904
5078
  }
3905
- console.log(` ${pc12.bold("FlyDocs Cloud Tier")}`);
5079
+ console.log(` ${pc16.bold("FlyDocs Cloud Tier")}`);
3906
5080
  console.log();
3907
- console.log(` You're currently on the ${pc12.yellow("local")} tier.`);
5081
+ console.log(` You're currently on the ${pc16.yellow("local")} tier.`);
3908
5082
  console.log(` Upgrade to cloud for:`);
3909
5083
  console.log();
3910
- console.log(` ${pc12.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
3911
- console.log(` ${pc12.cyan("\u2192")} Project milestones and cycle management`);
3912
- console.log(` ${pc12.cyan("\u2192")} Team assignment and priority tracking`);
3913
- console.log(` ${pc12.cyan("\u2192")} Project health updates and dashboards`);
3914
- console.log(` ${pc12.cyan("\u2192")} Cross-project issue linking`);
5084
+ console.log(` ${pc16.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
5085
+ console.log(` ${pc16.cyan("\u2192")} Project milestones and cycle management`);
5086
+ console.log(` ${pc16.cyan("\u2192")} Team assignment and priority tracking`);
5087
+ console.log(` ${pc16.cyan("\u2192")} Project health updates and dashboards`);
5088
+ console.log(` ${pc16.cyan("\u2192")} Cross-project issue linking`);
3915
5089
  console.log();
3916
- console.log(` ${pc12.bold("How to upgrade:")}`);
5090
+ console.log(` ${pc16.bold("How to upgrade:")}`);
3917
5091
  console.log();
3918
- console.log(` Option 1: Run ${pc12.cyan("/flydocs-upgrade")} in your IDE`);
5092
+ console.log(` Option 1: Run ${pc16.cyan("/flydocs-upgrade")} in your IDE`);
3919
5093
  console.log(` Guided migration with issue transfer`);
3920
5094
  console.log();
3921
5095
  console.log(
3922
- ` Option 2: Run ${pc12.cyan("flydocs connect")} from terminal`
5096
+ ` Option 2: Run ${pc16.cyan("flydocs connect")} from terminal`
3923
5097
  );
3924
5098
  console.log(` Quick tier swap (no issue migration)`);
3925
5099
  console.log();
@@ -3933,23 +5107,23 @@ var self_update_exports = {};
3933
5107
  __export(self_update_exports, {
3934
5108
  default: () => self_update_default
3935
5109
  });
3936
- import { defineCommand as defineCommand8 } from "citty";
5110
+ import { defineCommand as defineCommand12 } from "citty";
3937
5111
  import { execSync } from "child_process";
3938
- import pc13 from "picocolors";
5112
+ import pc17 from "picocolors";
3939
5113
  var self_update_default;
3940
5114
  var init_self_update = __esm({
3941
5115
  "src/commands/self-update.ts"() {
3942
5116
  "use strict";
3943
5117
  init_constants();
3944
5118
  init_ui();
3945
- self_update_default = defineCommand8({
5119
+ self_update_default = defineCommand12({
3946
5120
  meta: {
3947
5121
  name: "self-update",
3948
5122
  description: "Update FlyDocs CLI to the latest version"
3949
5123
  },
3950
5124
  async run() {
3951
5125
  console.log();
3952
- console.log(` Updating ${pc13.cyan(PACKAGE_NAME)}...`);
5126
+ console.log(` Updating ${pc17.cyan(PACKAGE_NAME)}...`);
3953
5127
  console.log();
3954
5128
  try {
3955
5129
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -3974,15 +5148,15 @@ var telemetry_exports = {};
3974
5148
  __export(telemetry_exports, {
3975
5149
  default: () => telemetry_default
3976
5150
  });
3977
- import { defineCommand as defineCommand9 } from "citty";
3978
- import pc14 from "picocolors";
5151
+ import { defineCommand as defineCommand13 } from "citty";
5152
+ import pc18 from "picocolors";
3979
5153
  var enable, disable, status, telemetry_default;
3980
5154
  var init_telemetry2 = __esm({
3981
5155
  "src/commands/telemetry.ts"() {
3982
5156
  "use strict";
3983
5157
  init_telemetry();
3984
5158
  init_ui();
3985
- enable = defineCommand9({
5159
+ enable = defineCommand13({
3986
5160
  meta: {
3987
5161
  name: "enable",
3988
5162
  description: "Enable anonymous usage analytics"
@@ -3997,7 +5171,7 @@ var init_telemetry2 = __esm({
3997
5171
  }
3998
5172
  }
3999
5173
  });
4000
- disable = defineCommand9({
5174
+ disable = defineCommand13({
4001
5175
  meta: {
4002
5176
  name: "disable",
4003
5177
  description: "Disable anonymous usage analytics"
@@ -4015,7 +5189,7 @@ var init_telemetry2 = __esm({
4015
5189
  }
4016
5190
  }
4017
5191
  });
4018
- status = defineCommand9({
5192
+ status = defineCommand13({
4019
5193
  meta: {
4020
5194
  name: "status",
4021
5195
  description: "Show current telemetry status"
@@ -4023,37 +5197,37 @@ var init_telemetry2 = __esm({
4023
5197
  async run() {
4024
5198
  const info = await getStatus();
4025
5199
  console.log();
4026
- console.log(pc14.bold("Telemetry Status"));
5200
+ console.log(pc18.bold("Telemetry Status"));
4027
5201
  console.log();
4028
5202
  const effectivelyEnabled = info.enabled && !info.envOverride;
4029
5203
  console.log(
4030
- ` Enabled: ${effectivelyEnabled ? pc14.green("yes") : pc14.yellow("no")}`
5204
+ ` Enabled: ${effectivelyEnabled ? pc18.green("yes") : pc18.yellow("no")}`
4031
5205
  );
4032
5206
  if (info.envOverride) {
4033
5207
  console.log(
4034
- ` Env override: ${pc14.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
5208
+ ` Env override: ${pc18.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
4035
5209
  );
4036
5210
  }
4037
5211
  if (info.anonymousId) {
4038
- console.log(` Anonymous ID: ${pc14.dim(info.anonymousId)}`);
5212
+ console.log(` Anonymous ID: ${pc18.dim(info.anonymousId)}`);
4039
5213
  } else {
4040
5214
  console.log(
4041
- ` Anonymous ID: ${pc14.dim("(not yet created \u2014 generated on first run)")}`
5215
+ ` Anonymous ID: ${pc18.dim("(not yet created \u2014 generated on first run)")}`
4042
5216
  );
4043
5217
  }
4044
5218
  console.log();
4045
5219
  console.log(
4046
- pc14.dim(
5220
+ pc18.dim(
4047
5221
  " FlyDocs collects anonymous usage analytics to improve the CLI."
4048
5222
  )
4049
5223
  );
4050
5224
  console.log(
4051
- pc14.dim(" No personal data, file contents, or code is ever collected.")
5225
+ pc18.dim(" No personal data, file contents, or code is ever collected.")
4052
5226
  );
4053
5227
  console.log();
4054
5228
  }
4055
5229
  });
4056
- telemetry_default = defineCommand9({
5230
+ telemetry_default = defineCommand13({
4057
5231
  meta: {
4058
5232
  name: "telemetry",
4059
5233
  description: "Manage anonymous usage analytics (enable, disable, status)"
@@ -4069,14 +5243,18 @@ var init_telemetry2 = __esm({
4069
5243
 
4070
5244
  // src/cli.ts
4071
5245
  init_constants();
4072
- import { defineCommand as defineCommand10, runMain } from "citty";
5246
+ import { defineCommand as defineCommand14, runMain } from "citty";
4073
5247
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
4074
5248
  "install",
5249
+ "init",
4075
5250
  "update",
4076
5251
  "uninstall",
4077
5252
  "setup",
4078
5253
  "skills",
4079
5254
  "connect",
5255
+ "auth",
5256
+ "sync",
5257
+ "cleanup",
4080
5258
  "upgrade",
4081
5259
  "self-update",
4082
5260
  "telemetry"
@@ -4089,7 +5267,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
4089
5267
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
4090
5268
  process.argv.splice(2, 0, "install");
4091
5269
  }
4092
- var main = defineCommand10({
5270
+ var main = defineCommand14({
4093
5271
  meta: {
4094
5272
  name: CLI_NAME,
4095
5273
  version: CLI_VERSION,
@@ -4097,11 +5275,15 @@ var main = defineCommand10({
4097
5275
  },
4098
5276
  subCommands: {
4099
5277
  install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
5278
+ init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
4100
5279
  update: () => Promise.resolve().then(() => (init_update(), update_exports)).then((m) => m.default),
4101
5280
  uninstall: () => Promise.resolve().then(() => (init_uninstall(), uninstall_exports)).then((m) => m.default),
4102
5281
  setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
4103
5282
  skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
4104
5283
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
5284
+ auth: () => Promise.resolve().then(() => (init_auth(), auth_exports)).then((m) => m.default),
5285
+ sync: () => Promise.resolve().then(() => (init_sync(), sync_exports)).then((m) => m.default),
5286
+ cleanup: () => Promise.resolve().then(() => (init_cleanup(), cleanup_exports)).then((m) => m.default),
4105
5287
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
4106
5288
  "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default),
4107
5289
  telemetry: () => Promise.resolve().then(() => (init_telemetry2(), telemetry_exports)).then((m) => m.default)