@flydocs/cli 0.6.0-alpha.23 → 0.6.0-alpha.26

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.23";
18
+ CLI_VERSION = "0.6.0-alpha.26";
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,630 @@ 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) {
2932
+ return {
2933
+ Authorization: `Bearer ${apiKey}`,
2934
+ "Content-Type": "application/json",
2935
+ Accept: "application/json"
2936
+ };
2937
+ }
2938
+ async function fetchConfigV2(apiKey, options = {}) {
2939
+ const baseUrl = resolveRelayUrl();
2940
+ const params = new URLSearchParams({ format: "2" });
2941
+ if (options.includeContext) params.set("includeContext", "true");
2942
+ const response = await fetch(`${baseUrl}/config/generate?${params}`, {
2943
+ method: "GET",
2944
+ headers: headers(apiKey),
2945
+ signal: AbortSignal.timeout(3e4)
2946
+ });
2947
+ if (!response.ok) {
2948
+ const body = await response.text().catch(() => "");
2949
+ throw new RelayError(
2950
+ `config/generate failed (${response.status})`,
2951
+ response.status,
2952
+ body
2953
+ );
2954
+ }
2955
+ return await response.json();
2956
+ }
2957
+ async function fetchTemplates(apiKey, sinceVersion) {
2958
+ const baseUrl = resolveRelayUrl();
2959
+ const response = await fetch(`${baseUrl}/templates?since=${sinceVersion}`, {
2960
+ method: "GET",
2961
+ headers: headers(apiKey),
2962
+ signal: AbortSignal.timeout(15e3)
2963
+ });
2964
+ if (!response.ok) {
2965
+ const body = await response.text().catch(() => "");
2966
+ throw new RelayError(
2967
+ `templates fetch failed (${response.status})`,
2968
+ response.status,
2969
+ body
2970
+ );
2971
+ }
2972
+ return await response.json();
2973
+ }
2974
+ var DEFAULT_RELAY_URL, RelayError;
2975
+ var init_relay_client = __esm({
2976
+ "src/lib/relay-client.ts"() {
2977
+ "use strict";
2978
+ DEFAULT_RELAY_URL = "https://app.flydocs.ai/api/relay";
2979
+ RelayError = class extends Error {
2980
+ constructor(message, status2, body) {
2981
+ super(message);
2982
+ this.status = status2;
2983
+ this.body = body;
2984
+ this.name = "RelayError";
2985
+ }
2986
+ };
2987
+ }
2988
+ });
2989
+
2990
+ // src/lib/workspace.ts
2991
+ import { readFile as readFile13, writeFile as writeFile11, readdir as readdir4, stat as stat2 } from "fs/promises";
2992
+ import { join as join18, dirname as dirname3, relative, resolve as resolve3 } from "path";
2993
+ async function readWorkspaceFile(dir) {
2994
+ const filePath = join18(dir, WORKSPACE_FILENAME);
2995
+ if (!await pathExists(filePath)) return null;
2996
+ const content = await readFile13(filePath, "utf-8");
2997
+ const parsed = JSON.parse(content);
2998
+ validateWorkspaceFile(parsed);
2999
+ return parsed;
3000
+ }
3001
+ async function writeWorkspaceFile(dir, workspace) {
3002
+ const filePath = join18(dir, WORKSPACE_FILENAME);
3003
+ const content = JSON.stringify(workspace, null, 2) + "\n";
3004
+ await writeFile11(filePath, content, "utf-8");
3005
+ }
3006
+ async function detectSiblingRepos(parentDir) {
3007
+ const repos = {};
3008
+ const entries = await readdir4(parentDir);
3009
+ for (const entry of entries) {
3010
+ if (entry.startsWith(".")) continue;
3011
+ const entryPath = join18(parentDir, entry);
3012
+ const entryStat = await stat2(entryPath).catch(() => null);
3013
+ if (!entryStat?.isDirectory()) continue;
3014
+ const hasGit = await pathExists(join18(entryPath, ".git"));
3015
+ const hasFlydocs = await pathExists(
3016
+ join18(entryPath, ".flydocs", "config.json")
3017
+ );
3018
+ if (hasGit || hasFlydocs) {
3019
+ repos[entry] = { path: `./${entry}` };
3020
+ }
3021
+ }
3022
+ return repos;
3023
+ }
3024
+ function buildWorkspaceFile(workspaceId, repos) {
3025
+ return { workspaceId, repos };
3026
+ }
3027
+ async function readSiblingDescriptors(workspaceDir, repos) {
3028
+ const descriptors = [];
3029
+ for (const [name, entry] of Object.entries(repos)) {
3030
+ const repoDir = resolve3(workspaceDir, entry.path);
3031
+ const serviceJsonPath = join18(repoDir, "flydocs", "context", "service.json");
3032
+ if (!await pathExists(serviceJsonPath)) continue;
3033
+ try {
3034
+ const content = await readFile13(serviceJsonPath, "utf-8");
3035
+ const descriptor = JSON.parse(content);
3036
+ descriptors.push({ name, path: entry.path, descriptor });
3037
+ } catch {
3038
+ }
3039
+ }
3040
+ return descriptors;
3041
+ }
3042
+ async function generateWorkspaceMd(workspaceDir, workspace, productName) {
3043
+ const descriptors = await readSiblingDescriptors(
3044
+ workspaceDir,
3045
+ workspace.repos
3046
+ );
3047
+ const repoCount = Object.keys(workspace.repos).length;
3048
+ const lines = [];
3049
+ const title = productName ?? "Workspace";
3050
+ lines.push(`# Product: ${title}`);
3051
+ lines.push("");
3052
+ lines.push("## Overview");
3053
+ lines.push("");
3054
+ if (descriptors.length > 0) {
3055
+ const purposes = descriptors.map((d) => d.descriptor.purpose).filter(Boolean);
3056
+ if (purposes.length > 0) {
3057
+ lines.push(purposes.join(". ") + ".");
3058
+ } else {
3059
+ lines.push(`Multi-repo workspace with ${repoCount} repositories.`);
3060
+ }
3061
+ } else {
3062
+ lines.push(`Multi-repo workspace with ${repoCount} repositories.`);
3063
+ }
3064
+ lines.push("");
3065
+ lines.push("## Architecture");
3066
+ lines.push("");
3067
+ lines.push(`${repoCount}-repo sibling topology.`);
3068
+ lines.push("");
3069
+ lines.push("## Repos");
3070
+ lines.push("");
3071
+ for (const [name, entry] of Object.entries(workspace.repos)) {
3072
+ const desc = descriptors.find((d) => d.name === name);
3073
+ const purpose = desc?.descriptor.purpose ?? "";
3074
+ const stack = desc?.descriptor.stack?.join(", ") ?? "";
3075
+ const detail = [purpose, stack].filter(Boolean).join(". ");
3076
+ const projectMdLink = `${entry.path}/flydocs/context/project.md`;
3077
+ lines.push(
3078
+ `- **${name}** \u2014 ${detail || "No description"}. [Details](${projectMdLink})`
3079
+ );
3080
+ }
3081
+ lines.push("");
3082
+ const edges = [];
3083
+ for (const desc of descriptors) {
3084
+ for (const dep of desc.descriptor.dependencies ?? []) {
3085
+ const target = dep.service;
3086
+ const iface = dep.interface;
3087
+ edges.push(`- ${desc.name} \u2192 ${target} (${iface})`);
3088
+ }
3089
+ }
3090
+ lines.push("## Cross-Repo Dependencies");
3091
+ lines.push("");
3092
+ if (edges.length > 0) {
3093
+ lines.push(...edges);
3094
+ } else {
3095
+ lines.push("No cross-repo dependencies detected.");
3096
+ }
3097
+ lines.push("");
3098
+ return lines.join("\n");
3099
+ }
3100
+ function validateWorkspaceFile(value) {
3101
+ if (typeof value !== "object" || value === null) {
3102
+ throw new Error("Invalid workspace file: expected an object");
3103
+ }
3104
+ const obj = value;
3105
+ if (typeof obj.workspaceId !== "string" || obj.workspaceId.length === 0) {
3106
+ throw new Error(
3107
+ "Invalid workspace file: workspaceId must be a non-empty string"
3108
+ );
3109
+ }
3110
+ if (typeof obj.repos !== "object" || obj.repos === null) {
3111
+ throw new Error("Invalid workspace file: repos must be an object");
3112
+ }
3113
+ const repos = obj.repos;
3114
+ for (const [name, entry] of Object.entries(repos)) {
3115
+ if (typeof entry !== "object" || entry === null) {
3116
+ throw new Error(
3117
+ `Invalid workspace file: repos.${name} must be an object`
3118
+ );
3119
+ }
3120
+ const repoEntry = entry;
3121
+ if (typeof repoEntry.path !== "string" || repoEntry.path.length === 0) {
3122
+ throw new Error(
3123
+ `Invalid workspace file: repos.${name}.path must be a non-empty string`
3124
+ );
3125
+ }
3126
+ }
3127
+ }
3128
+ var WORKSPACE_FILENAME;
3129
+ var init_workspace = __esm({
3130
+ "src/lib/workspace.ts"() {
3131
+ "use strict";
3132
+ init_fs_ops();
3133
+ WORKSPACE_FILENAME = ".flydocs-workspace.json";
3134
+ }
3135
+ });
3136
+
3137
+ // src/commands/init.ts
3138
+ var init_exports = {};
3139
+ __export(init_exports, {
3140
+ default: () => init_default
3141
+ });
3142
+ import { defineCommand as defineCommand2 } from "citty";
3143
+ import { text as text2, confirm as confirm3, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
3144
+ import pc7 from "picocolors";
3145
+ import { join as join19 } from "path";
3146
+ import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
3147
+ async function resolveAndValidateKey(keyArg, targetDir) {
3148
+ let resolved = await resolveApiKey(keyArg, targetDir);
3149
+ if (!resolved) {
3150
+ console.log(
3151
+ ` ${pc7.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3152
+ );
3153
+ console.log();
3154
+ const keyInput = await text2({
3155
+ message: "Enter your API key",
3156
+ placeholder: "fdk_...",
3157
+ validate(value) {
3158
+ if (!value.trim()) return "API key is required";
3159
+ if (detectKeyType(value.trim()) !== "relay") {
3160
+ return "Key must start with fdk_ (FlyDocs API key)";
3161
+ }
3162
+ return void 0;
3163
+ }
3164
+ });
3165
+ if (isCancel4(keyInput)) {
3166
+ cancel3("Init cancelled.");
3167
+ process.exit(0);
3168
+ }
3169
+ resolved = { key: keyInput.trim(), source: "prompt" };
3170
+ } else {
3171
+ printInfo(`Using key from ${resolved.source}`);
3172
+ }
3173
+ const apiKey = resolved.key;
3174
+ printInfo("Validating API key...");
3175
+ try {
3176
+ const result = await validateRelayKey(apiKey);
3177
+ if (!result.valid) {
3178
+ printError("Invalid API key. Check your key and try again.");
3179
+ process.exit(1);
3180
+ }
3181
+ printStatus(`Authenticated with ${pc7.bold(result.org)}`);
3182
+ } catch {
3183
+ printError(
3184
+ "Could not reach FlyDocs API. Check your network and try again."
3185
+ );
3186
+ console.log(
3187
+ ` ${pc7.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3188
+ );
3189
+ process.exit(1);
3190
+ }
3191
+ await writeGlobalCredential({
3192
+ apiKey,
3193
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3194
+ lastValidated: (/* @__PURE__ */ new Date()).toISOString()
3195
+ });
3196
+ await checkCredentialPermissions();
3197
+ return apiKey;
3198
+ }
3199
+ async function pullServerConfig(apiKey, targetDir) {
3200
+ printInfo("Pulling config from server...");
3201
+ const isFirstInit = !await pathExists(
3202
+ join19(targetDir, ".flydocs", "config.json")
3203
+ );
3204
+ try {
3205
+ const response = await fetchConfigV2(apiKey, {
3206
+ includeContext: isFirstInit
3207
+ });
3208
+ if (!response.valid) {
3209
+ printError("Server returned invalid config. Contact support.");
3210
+ process.exit(1);
3211
+ }
3212
+ return response;
3213
+ } catch (err) {
3214
+ if (err instanceof RelayError) {
3215
+ printError(`Server error: ${err.message}`);
3216
+ if (err.status === 401) {
3217
+ console.log(
3218
+ ` ${pc7.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3219
+ );
3220
+ }
3221
+ } else {
3222
+ printError("Could not reach server. Check your network.");
3223
+ }
3224
+ process.exit(1);
3225
+ }
3226
+ }
3227
+ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3228
+ const actions = [];
3229
+ const skipped = [];
3230
+ await mkdir9(join19(targetDir, ".flydocs"), { recursive: true });
3231
+ const configWithHash = applyConfigHash(serverResponse.config);
3232
+ await writeConfig(targetDir, configWithHash);
3233
+ actions.push("Wrote .flydocs/config.json (from server)");
3234
+ if (serverResponse.identity) {
3235
+ await writeGlobalIdentity({
3236
+ name: serverResponse.identity.name,
3237
+ email: serverResponse.identity.email,
3238
+ providerIdentities: serverResponse.identity.providerIdentities
3239
+ });
3240
+ actions.push("Wrote ~/.flydocs/me.json");
3241
+ }
3242
+ if (serverResponse.context) {
3243
+ const contextDir = join19(targetDir, "flydocs", "context");
3244
+ await mkdir9(contextDir, { recursive: true });
3245
+ const projectMdPath = join19(contextDir, "project.md");
3246
+ if (!await pathExists(projectMdPath)) {
3247
+ await writeFile12(projectMdPath, serverResponse.context.projectMd, "utf-8");
3248
+ actions.push("Wrote flydocs/context/project.md");
3249
+ } else {
3250
+ skipped.push("flydocs/context/project.md (already exists)");
3251
+ }
3252
+ const serviceJsonPath = join19(contextDir, "service.json");
3253
+ if (serverResponse.context.serviceJson && !await pathExists(serviceJsonPath)) {
3254
+ await writeFile12(
3255
+ serviceJsonPath,
3256
+ JSON.stringify(serverResponse.context.serviceJson, null, 2) + "\n",
3257
+ "utf-8"
3258
+ );
3259
+ actions.push("Wrote flydocs/context/service.json");
3260
+ } else if (await pathExists(serviceJsonPath)) {
3261
+ skipped.push("flydocs/context/service.json (already exists)");
3262
+ }
3263
+ }
3264
+ await ensureGitignore(targetDir);
3265
+ await ensurePlatformIgnores(targetDir);
3266
+ actions.push("Updated ignore files");
3267
+ return { actions, skipped };
3268
+ }
3269
+ async function isParentDirectory(dir) {
3270
+ const hasGit = await pathExists(join19(dir, ".git"));
3271
+ if (hasGit) return false;
3272
+ const repos = await detectSiblingRepos(dir);
3273
+ return Object.keys(repos).length >= 2;
3274
+ }
3275
+ async function runMultiRepoInit(parentDir, keyArg) {
3276
+ const repos = await detectSiblingRepos(parentDir);
3277
+ const repoNames = Object.keys(repos).sort();
3278
+ printInfo(`Detected ${repoNames.length} repos:`);
3279
+ for (const name of repoNames) {
3280
+ console.log(` ${pc7.cyan(name)}`);
3281
+ }
3282
+ console.log();
3283
+ const shouldContinue = await confirm3({
3284
+ message: `Initialize all ${repoNames.length} repos?`
3285
+ });
3286
+ if (isCancel4(shouldContinue) || !shouldContinue) {
3287
+ cancel3("Init cancelled.");
3288
+ process.exit(0);
3289
+ }
3290
+ const firstRepoDir = join19(parentDir, repoNames[0]);
3291
+ const apiKey = await resolveAndValidateKey(keyArg, firstRepoDir);
3292
+ const allActions = [
3293
+ "Stored credential globally (~/.flydocs/credentials)"
3294
+ ];
3295
+ const allSkipped = [];
3296
+ let allWarnings = [];
3297
+ let firstResponse;
3298
+ for (const repoName of repoNames) {
3299
+ const repoDir = join19(parentDir, repoName);
3300
+ console.log();
3301
+ printInfo(`Initializing ${pc7.bold(repoName)}...`);
3302
+ const serverResponse = await pullServerConfig(apiKey, repoDir);
3303
+ if (!firstResponse) firstResponse = serverResponse;
3304
+ const { actions, skipped } = await initSingleRepo(
3305
+ repoDir,
3306
+ apiKey,
3307
+ serverResponse
3308
+ );
3309
+ for (const action of actions) {
3310
+ allActions.push(`[${repoName}] ${action}`);
3311
+ }
3312
+ for (const skip of skipped) {
3313
+ allSkipped.push(`[${repoName}] ${skip}`);
3314
+ }
3315
+ allWarnings = [...allWarnings, ...serverResponse.warnings];
3316
+ printStatus(`${repoName} initialized`);
3317
+ }
3318
+ if (firstResponse) {
3319
+ const existing = await readWorkspaceFile(parentDir);
3320
+ if (existing) {
3321
+ allSkipped.push(".flydocs-workspace.json (already exists)");
3322
+ } else {
3323
+ const workspaceFile = buildWorkspaceFile(
3324
+ firstResponse.workspaceId,
3325
+ repos
3326
+ );
3327
+ await writeWorkspaceFile(parentDir, workspaceFile);
3328
+ allActions.push(`.flydocs-workspace.json (${repoNames.length} repos)`);
3329
+ }
3330
+ const contextDir = join19(parentDir, "flydocs", "context");
3331
+ const workspaceMdPath = join19(contextDir, "workspace.md");
3332
+ if (await pathExists(workspaceMdPath)) {
3333
+ allSkipped.push("flydocs/context/workspace.md (already exists)");
3334
+ } else {
3335
+ await mkdir9(contextDir, { recursive: true });
3336
+ const workspaceJson = await readWorkspaceFile(parentDir) ?? buildWorkspaceFile(firstResponse.workspaceId, repos);
3337
+ const content = firstResponse.context?.workspaceMd ?? await generateWorkspaceMd(parentDir, workspaceJson);
3338
+ await writeFile12(workspaceMdPath, content, "utf-8");
3339
+ const source = firstResponse.context?.workspaceMd ? "from server" : "generated locally";
3340
+ allActions.push(`Wrote flydocs/context/workspace.md (${source})`);
3341
+ }
3342
+ }
3343
+ console.log();
3344
+ printInitReport(allActions, allSkipped, allWarnings);
3345
+ }
3346
+ function printInitReport(actions, skipped, warnings) {
3347
+ console.log();
3348
+ const lines = [];
3349
+ for (const action of actions) {
3350
+ lines.push(`${pc7.green("+")} ${action}`);
3351
+ }
3352
+ for (const skip of skipped) {
3353
+ lines.push(`${pc7.dim("-")} ${skip}`);
3354
+ }
3355
+ if (warnings.length > 0) {
3356
+ lines.push("");
3357
+ for (const warning of warnings) {
3358
+ lines.push(`${pc7.yellow("!")} ${warning}`);
3359
+ }
3360
+ }
3361
+ printCompletionBox("FlyDocs Initialized", lines);
3362
+ console.log();
3363
+ console.log(` Next steps:`);
3364
+ console.log(
3365
+ ` ${pc7.cyan("flydocs sync")} \u2014 pull latest config and templates`
3366
+ );
3367
+ console.log(` ${pc7.cyan("/start-session")} \u2014 begin working`);
3368
+ console.log();
3369
+ }
3370
+ var init_default;
3371
+ var init_init = __esm({
3372
+ "src/commands/init.ts"() {
3373
+ "use strict";
3374
+ init_ui();
3375
+ init_api_key();
3376
+ init_global_config();
3377
+ init_config();
3378
+ init_config_integrity();
3379
+ init_gitignore();
3380
+ init_fs_ops();
3381
+ init_relay_client();
3382
+ init_workspace();
3383
+ init_default = defineCommand2({
3384
+ meta: {
3385
+ name: "init",
3386
+ description: "Initialize FlyDocs in this project (unified setup)"
3387
+ },
3388
+ args: {
3389
+ key: {
3390
+ type: "string",
3391
+ description: "FlyDocs API key (fdk_...)"
3392
+ },
3393
+ path: {
3394
+ type: "string",
3395
+ description: "Path to project directory"
3396
+ }
3397
+ },
3398
+ async run({ args }) {
3399
+ const targetDir = args.path ?? process.cwd();
3400
+ console.log();
3401
+ console.log(` ${pc7.bold("FlyDocs Init")}`);
3402
+ console.log();
3403
+ if (await isParentDirectory(targetDir)) {
3404
+ await runMultiRepoInit(targetDir, args.key);
3405
+ return;
3406
+ }
3407
+ const apiKey = await resolveAndValidateKey(args.key, targetDir);
3408
+ const serverResponse = await pullServerConfig(apiKey, targetDir);
3409
+ const { actions, skipped } = await initSingleRepo(
3410
+ targetDir,
3411
+ apiKey,
3412
+ serverResponse
3413
+ );
3414
+ actions.unshift("Stored credential globally (~/.flydocs/credentials)");
3415
+ const topology = serverResponse.config.topology;
3416
+ if (topology?.type === 4 && topology.label === "sibling-repos") {
3417
+ const parentDir = join19(targetDir, "..");
3418
+ const existing = await readWorkspaceFile(parentDir);
3419
+ if (existing) {
3420
+ skipped.push(".flydocs-workspace.json (already exists)");
3421
+ } else {
3422
+ const repos = await detectSiblingRepos(parentDir);
3423
+ if (Object.keys(repos).length > 0) {
3424
+ const workspaceFile = buildWorkspaceFile(
3425
+ serverResponse.workspaceId,
3426
+ repos
3427
+ );
3428
+ await writeWorkspaceFile(parentDir, workspaceFile);
3429
+ actions.push(
3430
+ `.flydocs-workspace.json (${Object.keys(repos).length} repos detected)`
3431
+ );
3432
+ }
3433
+ }
3434
+ }
3435
+ printInitReport(actions, skipped, serverResponse.warnings);
3436
+ }
3437
+ });
3438
+ }
3439
+ });
3440
+
2762
3441
  // src/commands/update.ts
2763
3442
  var update_exports = {};
2764
3443
  __export(update_exports, {
2765
3444
  default: () => update_default
2766
3445
  });
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";
3446
+ import { defineCommand as defineCommand3 } from "citty";
3447
+ import { resolve as resolve4, join as join20 } from "path";
3448
+ import { mkdir as mkdir10, cp as cp2, readFile as readFile14, readdir as readdir5, rm as rm4 } from "fs/promises";
3449
+ import { select as select2, text as text3, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3450
+ import pc8 from "picocolors";
2772
3451
  var update_default;
2773
3452
  var init_update = __esm({
2774
3453
  "src/commands/update.ts"() {
@@ -2787,7 +3466,7 @@ var init_update = __esm({
2787
3466
  init_update_check();
2788
3467
  init_telemetry();
2789
3468
  init_integrity();
2790
- update_default = defineCommand2({
3469
+ update_default = defineCommand3({
2791
3470
  meta: {
2792
3471
  name: "update",
2793
3472
  description: "Update an existing FlyDocs installation"
@@ -2832,7 +3511,7 @@ var init_update = __esm({
2832
3511
  await capture("update_started", { template_version: version });
2833
3512
  let targetDir;
2834
3513
  if (args.path) {
2835
- targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
3514
+ targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
2836
3515
  } else if (args.here) {
2837
3516
  targetDir = process.cwd();
2838
3517
  } else {
@@ -2849,21 +3528,21 @@ var init_update = __esm({
2849
3528
  }
2850
3529
  ]
2851
3530
  });
2852
- if (isCancel4(choice)) {
2853
- cancel3("Update cancelled.");
3531
+ if (isCancel5(choice)) {
3532
+ cancel4("Update cancelled.");
2854
3533
  process.exit(0);
2855
3534
  }
2856
3535
  if (choice === "cwd") {
2857
3536
  targetDir = process.cwd();
2858
3537
  } else {
2859
- const enteredPath = await text2({
3538
+ const enteredPath = await text3({
2860
3539
  message: "Enter project path:"
2861
3540
  });
2862
- if (isCancel4(enteredPath)) {
2863
- cancel3("Update cancelled.");
3541
+ if (isCancel5(enteredPath)) {
3542
+ cancel4("Update cancelled.");
2864
3543
  process.exit(0);
2865
3544
  }
2866
- targetDir = resolve3(
3545
+ targetDir = resolve4(
2867
3546
  enteredPath.replace(/^~/, process.env.HOME ?? "~")
2868
3547
  );
2869
3548
  }
@@ -2872,11 +3551,11 @@ var init_update = __esm({
2872
3551
  printError(`Directory does not exist: ${targetDir}`);
2873
3552
  process.exit(1);
2874
3553
  }
2875
- targetDir = resolve3(targetDir);
3554
+ targetDir = resolve4(targetDir);
2876
3555
  process.chdir(targetDir);
2877
- const hasVersion = await pathExists(join17(targetDir, ".flydocs", "version"));
3556
+ const hasVersion = await pathExists(join20(targetDir, ".flydocs", "version"));
2878
3557
  const hasConfig = await pathExists(
2879
- join17(targetDir, ".flydocs", "config.json")
3558
+ join20(targetDir, ".flydocs", "config.json")
2880
3559
  );
2881
3560
  if (!hasVersion && !hasConfig) {
2882
3561
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -2887,8 +3566,8 @@ var init_update = __esm({
2887
3566
  console.log();
2888
3567
  let currentVersion = "0.1.0";
2889
3568
  if (hasVersion) {
2890
- const vContent = await readFile12(
2891
- join17(targetDir, ".flydocs", "version"),
3569
+ const vContent = await readFile14(
3570
+ join20(targetDir, ".flydocs", "version"),
2892
3571
  "utf-8"
2893
3572
  );
2894
3573
  currentVersion = vContent.trim();
@@ -2907,10 +3586,10 @@ var init_update = __esm({
2907
3586
  printWarning(
2908
3587
  `Project version (${currentVersion}) is newer than installer (${version})`
2909
3588
  );
2910
- const shouldContinue = await confirm3({
3589
+ const shouldContinue = await confirm4({
2911
3590
  message: "Continue anyway?"
2912
3591
  });
2913
- if (isCancel4(shouldContinue) || !shouldContinue) {
3592
+ if (isCancel5(shouldContinue) || !shouldContinue) {
2914
3593
  process.exit(0);
2915
3594
  }
2916
3595
  }
@@ -2922,10 +3601,10 @@ var init_update = __esm({
2922
3601
  });
2923
3602
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
2924
3603
  console.log();
2925
- const changelogPath = join17(templateDir, "CHANGELOG.md");
3604
+ const changelogPath = join20(templateDir, "CHANGELOG.md");
2926
3605
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
2927
3606
  if (whatsNew.length > 0) {
2928
- console.log(pc7.cyan("What's new:"));
3607
+ console.log(pc8.cyan("What's new:"));
2929
3608
  console.log();
2930
3609
  for (const entry of whatsNew) {
2931
3610
  console.log(` ${entry}`);
@@ -2934,23 +3613,23 @@ var init_update = __esm({
2934
3613
  }
2935
3614
  const now = /* @__PURE__ */ new Date();
2936
3615
  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 });
3616
+ const backupDir = join20(targetDir, ".flydocs", `backup-${ts}`);
3617
+ await mkdir10(backupDir, { recursive: true });
2939
3618
  if (hasConfig) {
2940
3619
  await cp2(
2941
- join17(targetDir, ".flydocs", "config.json"),
2942
- join17(backupDir, "config.json")
3620
+ join20(targetDir, ".flydocs", "config.json"),
3621
+ join20(backupDir, "config.json")
2943
3622
  );
2944
3623
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
2945
3624
  }
2946
3625
  try {
2947
- const flydocsDir = join17(targetDir, ".flydocs");
2948
- const entries = await readdir4(flydocsDir);
3626
+ const flydocsDir = join20(targetDir, ".flydocs");
3627
+ const entries = await readdir5(flydocsDir);
2949
3628
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
2950
3629
  if (backups.length > 3) {
2951
3630
  const toRemove = backups.slice(0, backups.length - 3);
2952
3631
  for (const old of toRemove) {
2953
- await rm4(join17(flydocsDir, old), { recursive: true, force: true });
3632
+ await rm4(join20(flydocsDir, old), { recursive: true, force: true });
2954
3633
  }
2955
3634
  }
2956
3635
  } catch {
@@ -2989,17 +3668,17 @@ var init_update = __esm({
2989
3668
  await ensureDirectories(targetDir, effectiveTier);
2990
3669
  console.log("Replacing framework directories...");
2991
3670
  await replaceDirectory(
2992
- join17(templateDir, ".flydocs", "templates"),
2993
- join17(targetDir, ".flydocs", "templates")
3671
+ join20(templateDir, ".flydocs", "templates"),
3672
+ join20(targetDir, ".flydocs", "templates")
2994
3673
  );
2995
3674
  printStatus(".flydocs/templates");
2996
3675
  await replaceDirectory(
2997
- join17(templateDir, ".claude", "hooks"),
2998
- join17(targetDir, ".claude", "hooks")
3676
+ join20(templateDir, ".claude", "hooks"),
3677
+ join20(targetDir, ".claude", "hooks")
2999
3678
  );
3000
3679
  printStatus(".claude/hooks");
3001
3680
  const hasExistingAgents = await pathExists(
3002
- join17(targetDir, ".claude", "agents")
3681
+ join20(targetDir, ".claude", "agents")
3003
3682
  );
3004
3683
  let installAgents;
3005
3684
  if (args.yes) {
@@ -3008,7 +3687,7 @@ var init_update = __esm({
3008
3687
  installAgents = true;
3009
3688
  } else {
3010
3689
  console.log();
3011
- console.log(` ${pc7.bold(pc7.yellow("Sub-Agents (Recommended)"))}`);
3690
+ console.log(` ${pc8.bold(pc8.yellow("Sub-Agents (Recommended)"))}`);
3012
3691
  console.log();
3013
3692
  console.log(
3014
3693
  " Sub-agents are specialized roles (PM, implementation, review,"
@@ -3020,31 +3699,31 @@ var init_update = __esm({
3020
3699
  " structure work but are not required \u2014 everything works without them."
3021
3700
  );
3022
3701
  console.log();
3023
- const agentConfirm = await confirm3({
3702
+ const agentConfirm = await confirm4({
3024
3703
  message: "Install sub-agents?",
3025
3704
  initialValue: true
3026
3705
  });
3027
- if (isCancel4(agentConfirm)) {
3706
+ if (isCancel5(agentConfirm)) {
3028
3707
  installAgents = false;
3029
3708
  } else {
3030
3709
  installAgents = agentConfirm;
3031
3710
  }
3032
3711
  }
3033
3712
  if (installAgents) {
3034
- const claudeAgentsSrc = join17(templateDir, ".claude", "agents");
3713
+ const claudeAgentsSrc = join20(templateDir, ".claude", "agents");
3035
3714
  if (await pathExists(claudeAgentsSrc)) {
3036
- await mkdir8(join17(targetDir, ".claude", "agents"), { recursive: true });
3715
+ await mkdir10(join20(targetDir, ".claude", "agents"), { recursive: true });
3037
3716
  await copyDirectoryContents(
3038
3717
  claudeAgentsSrc,
3039
- join17(targetDir, ".claude", "agents")
3718
+ join20(targetDir, ".claude", "agents")
3040
3719
  );
3041
3720
  }
3042
- const cursorAgentsSrc = join17(templateDir, ".cursor", "agents");
3721
+ const cursorAgentsSrc = join20(templateDir, ".cursor", "agents");
3043
3722
  if (await pathExists(cursorAgentsSrc)) {
3044
- await mkdir8(join17(targetDir, ".cursor", "agents"), { recursive: true });
3723
+ await mkdir10(join20(targetDir, ".cursor", "agents"), { recursive: true });
3045
3724
  await copyDirectoryContents(
3046
3725
  cursorAgentsSrc,
3047
- join17(targetDir, ".cursor", "agents")
3726
+ join20(targetDir, ".cursor", "agents")
3048
3727
  );
3049
3728
  }
3050
3729
  printStatus(
@@ -3058,58 +3737,58 @@ var init_update = __esm({
3058
3737
  console.log();
3059
3738
  console.log("Replacing framework files...");
3060
3739
  await copyFile(
3061
- join17(templateDir, ".claude", "CLAUDE.md"),
3062
- join17(targetDir, ".claude", "CLAUDE.md")
3740
+ join20(templateDir, ".claude", "CLAUDE.md"),
3741
+ join20(targetDir, ".claude", "CLAUDE.md")
3063
3742
  );
3064
3743
  await copyFile(
3065
- join17(templateDir, ".claude", "settings.json"),
3066
- join17(targetDir, ".claude", "settings.json")
3744
+ join20(templateDir, ".claude", "settings.json"),
3745
+ join20(targetDir, ".claude", "settings.json")
3067
3746
  );
3068
3747
  printStatus(".claude/CLAUDE.md, settings.json");
3069
3748
  await copyDirectoryContents(
3070
- join17(templateDir, ".claude", "commands"),
3071
- join17(targetDir, ".claude", "commands")
3749
+ join20(templateDir, ".claude", "commands"),
3750
+ join20(targetDir, ".claude", "commands")
3072
3751
  );
3073
3752
  await copyDirectoryContents(
3074
- join17(templateDir, ".claude", "commands"),
3075
- join17(targetDir, ".cursor", "commands")
3753
+ join20(templateDir, ".claude", "commands"),
3754
+ join20(targetDir, ".cursor", "commands")
3076
3755
  );
3077
3756
  printStatus(".claude/commands, .cursor/commands");
3078
- const skillsReadmeSrc = join17(templateDir, ".claude", "skills", "README.md");
3757
+ const skillsReadmeSrc = join20(templateDir, ".claude", "skills", "README.md");
3079
3758
  if (await pathExists(skillsReadmeSrc)) {
3080
3759
  await copyFile(
3081
3760
  skillsReadmeSrc,
3082
- join17(targetDir, ".claude", "skills", "README.md")
3761
+ join20(targetDir, ".claude", "skills", "README.md")
3083
3762
  );
3084
3763
  }
3085
3764
  printStatus(".claude/skills/README.md");
3086
3765
  await copyFile(
3087
- join17(templateDir, ".cursor", "hooks.json"),
3088
- join17(targetDir, ".cursor", "hooks.json")
3766
+ join20(templateDir, ".cursor", "hooks.json"),
3767
+ join20(targetDir, ".cursor", "hooks.json")
3089
3768
  );
3090
3769
  printStatus(".cursor/hooks.json");
3091
3770
  await copyFile(
3092
- join17(templateDir, "AGENTS.md"),
3093
- join17(targetDir, "AGENTS.md")
3771
+ join20(templateDir, "AGENTS.md"),
3772
+ join20(targetDir, "AGENTS.md")
3094
3773
  );
3095
3774
  printStatus("AGENTS.md");
3096
- const envExampleSrc = join17(templateDir, ".env.example");
3775
+ const envExampleSrc = join20(templateDir, ".env.example");
3097
3776
  if (await pathExists(envExampleSrc)) {
3098
- await copyFile(envExampleSrc, join17(targetDir, ".env.example"));
3777
+ await copyFile(envExampleSrc, join20(targetDir, ".env.example"));
3099
3778
  printStatus(".env.example");
3100
3779
  }
3101
- const knowledgeTemplatesDir = join17(
3780
+ const knowledgeTemplatesDir = join20(
3102
3781
  targetDir,
3103
3782
  "flydocs",
3104
3783
  "knowledge",
3105
3784
  "templates"
3106
3785
  );
3107
3786
  if (!await pathExists(knowledgeTemplatesDir)) {
3108
- await mkdir8(knowledgeTemplatesDir, { recursive: true });
3787
+ await mkdir10(knowledgeTemplatesDir, { recursive: true });
3109
3788
  }
3110
3789
  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);
3790
+ const src = join20(templateDir, "flydocs", "knowledge", "templates", tmpl);
3791
+ const dest = join20(knowledgeTemplatesDir, tmpl);
3113
3792
  if (await pathExists(src) && !await pathExists(dest)) {
3114
3793
  await copyFile(src, dest);
3115
3794
  }
@@ -3136,18 +3815,18 @@ var init_update = __esm({
3136
3815
  printWarning("Config merge failed \u2014 config.json preserved as-is");
3137
3816
  }
3138
3817
  await copyFile(
3139
- join17(templateDir, ".flydocs", "version"),
3140
- join17(targetDir, ".flydocs", "version")
3818
+ join20(templateDir, ".flydocs", "version"),
3819
+ join20(targetDir, ".flydocs", "version")
3141
3820
  );
3142
3821
  printStatus(`.flydocs/version \u2192 ${version}`);
3143
- const clSrc = join17(templateDir, "CHANGELOG.md");
3822
+ const clSrc = join20(templateDir, "CHANGELOG.md");
3144
3823
  if (await pathExists(clSrc)) {
3145
- await copyFile(clSrc, join17(targetDir, ".flydocs", "CHANGELOG.md"));
3824
+ await copyFile(clSrc, join20(targetDir, ".flydocs", "CHANGELOG.md"));
3146
3825
  printStatus(".flydocs/CHANGELOG.md");
3147
3826
  }
3148
- const mfSrc = join17(templateDir, "manifest.json");
3827
+ const mfSrc = join20(templateDir, "manifest.json");
3149
3828
  if (await pathExists(mfSrc)) {
3150
- await copyFile(mfSrc, join17(targetDir, ".flydocs", "manifest.json"));
3829
+ await copyFile(mfSrc, join20(targetDir, ".flydocs", "manifest.json"));
3151
3830
  printStatus(".flydocs/manifest.json");
3152
3831
  }
3153
3832
  await generateIntegrity(targetDir, version);
@@ -3209,22 +3888,22 @@ var uninstall_exports = {};
3209
3888
  __export(uninstall_exports, {
3210
3889
  default: () => uninstall_default
3211
3890
  });
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";
3891
+ import { defineCommand as defineCommand4 } from "citty";
3892
+ import { resolve as resolve5, join as join21 } from "path";
3893
+ import { readdir as readdir6, rm as rm5, rename as rename2 } from "fs/promises";
3894
+ import { confirm as confirm5, select as select3, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
3895
+ import pc9 from "picocolors";
3217
3896
  async function removeOwnedSkills(targetDir) {
3218
- const skillsDir = join18(targetDir, ".claude", "skills");
3897
+ const skillsDir = join21(targetDir, ".claude", "skills");
3219
3898
  const removed = [];
3220
3899
  if (!await pathExists(skillsDir)) {
3221
3900
  return removed;
3222
3901
  }
3223
3902
  try {
3224
- const entries = await readdir5(skillsDir);
3903
+ const entries = await readdir6(skillsDir);
3225
3904
  for (const entry of entries) {
3226
3905
  if (entry.startsWith(OWNED_SKILL_PREFIX)) {
3227
- await rm5(join18(skillsDir, entry), { recursive: true, force: true });
3906
+ await rm5(join21(skillsDir, entry), { recursive: true, force: true });
3228
3907
  removed.push(`.claude/skills/${entry}`);
3229
3908
  }
3230
3909
  }
@@ -3233,16 +3912,16 @@ async function removeOwnedSkills(targetDir) {
3233
3912
  return removed;
3234
3913
  }
3235
3914
  async function removeOwnedCursorRules(targetDir) {
3236
- const rulesDir = join18(targetDir, ".cursor", "rules");
3915
+ const rulesDir = join21(targetDir, ".cursor", "rules");
3237
3916
  const removed = [];
3238
3917
  if (!await pathExists(rulesDir)) {
3239
3918
  return removed;
3240
3919
  }
3241
3920
  try {
3242
- const entries = await readdir5(rulesDir);
3921
+ const entries = await readdir6(rulesDir);
3243
3922
  for (const entry of entries) {
3244
3923
  if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
3245
- await rm5(join18(rulesDir, entry), { force: true });
3924
+ await rm5(join21(rulesDir, entry), { force: true });
3246
3925
  removed.push(`.cursor/rules/${entry}`);
3247
3926
  }
3248
3927
  }
@@ -3252,7 +3931,7 @@ async function removeOwnedCursorRules(targetDir) {
3252
3931
  }
3253
3932
  async function isEmptyDir(dirPath) {
3254
3933
  try {
3255
- const entries = await readdir5(dirPath);
3934
+ const entries = await readdir6(dirPath);
3256
3935
  return entries.length === 0;
3257
3936
  } catch {
3258
3937
  return false;
@@ -3261,7 +3940,7 @@ async function isEmptyDir(dirPath) {
3261
3940
  async function cleanupEmptyParents(targetDir, dirs) {
3262
3941
  const cleaned = [];
3263
3942
  for (const dir of dirs) {
3264
- const fullPath = join18(targetDir, dir);
3943
+ const fullPath = join21(targetDir, dir);
3265
3944
  if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
3266
3945
  await rm5(fullPath, { recursive: true, force: true });
3267
3946
  cleaned.push(dir);
@@ -3293,7 +3972,7 @@ var init_uninstall = __esm({
3293
3972
  ];
3294
3973
  OWNED_SKILL_PREFIX = "flydocs-";
3295
3974
  OWNED_RULE_PREFIX = "flydocs-";
3296
- uninstall_default = defineCommand3({
3975
+ uninstall_default = defineCommand4({
3297
3976
  meta: {
3298
3977
  name: "uninstall",
3299
3978
  description: "Remove FlyDocs from a project directory"
@@ -3329,7 +4008,7 @@ var init_uninstall = __esm({
3329
4008
  printBanner(CLI_VERSION);
3330
4009
  let targetDir;
3331
4010
  if (args.path) {
3332
- targetDir = resolve4(args.path.replace(/^~/, process.env.HOME ?? "~"));
4011
+ targetDir = resolve5(args.path.replace(/^~/, process.env.HOME ?? "~"));
3333
4012
  } else if (args.here) {
3334
4013
  targetDir = process.cwd();
3335
4014
  } else {
@@ -3339,9 +4018,9 @@ var init_uninstall = __esm({
3339
4018
  printError(`Directory does not exist: ${targetDir}`);
3340
4019
  process.exit(1);
3341
4020
  }
3342
- targetDir = resolve4(targetDir);
3343
- const hasFlydocs = await pathExists(join18(targetDir, ".flydocs"));
3344
- const hasAgentsMd = await pathExists(join18(targetDir, "AGENTS.md"));
4021
+ targetDir = resolve5(targetDir);
4022
+ const hasFlydocs = await pathExists(join21(targetDir, ".flydocs"));
4023
+ const hasAgentsMd = await pathExists(join21(targetDir, "AGENTS.md"));
3345
4024
  if (!hasFlydocs && !hasAgentsMd) {
3346
4025
  printError(`Not a FlyDocs project: ${targetDir}`);
3347
4026
  printInfo("No .flydocs/ directory or AGENTS.md found.");
@@ -3353,7 +4032,7 @@ var init_uninstall = __esm({
3353
4032
  const removeAll = forceAll || args.all;
3354
4033
  const skipPrompts = forceAll || args.yes;
3355
4034
  let contentAction = "preserve";
3356
- const hasUserContent = await pathExists(join18(targetDir, "flydocs"));
4035
+ const hasUserContent = await pathExists(join21(targetDir, "flydocs"));
3357
4036
  if (hasUserContent) {
3358
4037
  if (removeAll) {
3359
4038
  contentAction = "remove";
@@ -3378,8 +4057,8 @@ var init_uninstall = __esm({
3378
4057
  }
3379
4058
  ]
3380
4059
  });
3381
- if (isCancel5(choice)) {
3382
- cancel4("Uninstall cancelled.");
4060
+ if (isCancel6(choice)) {
4061
+ cancel5("Uninstall cancelled.");
3383
4062
  process.exit(0);
3384
4063
  }
3385
4064
  contentAction = choice;
@@ -3387,40 +4066,40 @@ var init_uninstall = __esm({
3387
4066
  }
3388
4067
  if (!skipPrompts) {
3389
4068
  console.log();
3390
- console.log(pc8.bold("The following will be removed:"));
4069
+ console.log(pc9.bold("The following will be removed:"));
3391
4070
  console.log();
3392
4071
  console.log(" Framework files:");
3393
4072
  for (const [path] of ALWAYS_REMOVED) {
3394
- console.log(` ${pc8.dim(path)}`);
4073
+ console.log(` ${pc9.dim(path)}`);
3395
4074
  }
3396
- console.log(` ${pc8.dim(".claude/skills/flydocs-*")}`);
3397
- console.log(` ${pc8.dim(".cursor/rules/flydocs-*.mdc")}`);
4075
+ console.log(` ${pc9.dim(".claude/skills/flydocs-*")}`);
4076
+ console.log(` ${pc9.dim(".cursor/rules/flydocs-*.mdc")}`);
3398
4077
  if (hasUserContent) {
3399
4078
  if (contentAction === "archive") {
3400
4079
  console.log();
3401
4080
  console.log(
3402
- ` User content: ${pc8.yellow("flydocs/ -> flydocs-archive/")}`
4081
+ ` User content: ${pc9.yellow("flydocs/ -> flydocs-archive/")}`
3403
4082
  );
3404
4083
  } else if (contentAction === "remove") {
3405
4084
  console.log();
3406
- console.log(` User content: ${pc8.red("flydocs/ (deleted)")}`);
4085
+ console.log(` User content: ${pc9.red("flydocs/ (deleted)")}`);
3407
4086
  } else {
3408
4087
  console.log();
3409
- console.log(` User content: ${pc8.green("flydocs/ (preserved)")}`);
4088
+ console.log(` User content: ${pc9.green("flydocs/ (preserved)")}`);
3410
4089
  }
3411
4090
  }
3412
4091
  console.log();
3413
- console.log(pc8.bold("Preserved:"));
4092
+ console.log(pc9.bold("Preserved:"));
3414
4093
  console.log(
3415
- ` ${pc8.dim(".claude/skills/ (non-flydocs community skills)")}`
4094
+ ` ${pc9.dim(".claude/skills/ (non-flydocs community skills)")}`
3416
4095
  );
3417
- console.log(` ${pc8.dim(".env, .env.local")}`);
4096
+ console.log(` ${pc9.dim(".env, .env.local")}`);
3418
4097
  console.log();
3419
- const shouldContinue = await confirm4({
4098
+ const shouldContinue = await confirm5({
3420
4099
  message: "Proceed with uninstall?"
3421
4100
  });
3422
- if (isCancel5(shouldContinue) || !shouldContinue) {
3423
- cancel4("Uninstall cancelled.");
4101
+ if (isCancel6(shouldContinue) || !shouldContinue) {
4102
+ cancel5("Uninstall cancelled.");
3424
4103
  process.exit(0);
3425
4104
  }
3426
4105
  }
@@ -3436,7 +4115,7 @@ var init_uninstall = __esm({
3436
4115
  const removedRules = await removeOwnedCursorRules(targetDir);
3437
4116
  result.removed.push(...removedRules);
3438
4117
  for (const [relativePath, type] of ALWAYS_REMOVED) {
3439
- const fullPath = join18(targetDir, relativePath);
4118
+ const fullPath = join21(targetDir, relativePath);
3440
4119
  if (!await pathExists(fullPath)) {
3441
4120
  result.skipped.push(relativePath);
3442
4121
  continue;
@@ -3454,9 +4133,9 @@ var init_uninstall = __esm({
3454
4133
  }
3455
4134
  }
3456
4135
  if (hasUserContent) {
3457
- const flydocsPath = join18(targetDir, "flydocs");
4136
+ const flydocsPath = join21(targetDir, "flydocs");
3458
4137
  if (contentAction === "archive") {
3459
- const archivePath = join18(targetDir, "flydocs-archive");
4138
+ const archivePath = join21(targetDir, "flydocs-archive");
3460
4139
  if (await pathExists(archivePath)) {
3461
4140
  await rm5(archivePath, { recursive: true, force: true });
3462
4141
  }
@@ -3482,17 +4161,17 @@ var init_uninstall = __esm({
3482
4161
  result.restored = originals.map((f) => f.relativePath);
3483
4162
  }
3484
4163
  console.log();
3485
- console.log(pc8.bold("Uninstall Summary"));
4164
+ console.log(pc9.bold("Uninstall Summary"));
3486
4165
  console.log();
3487
4166
  if (result.removed.length > 0) {
3488
- console.log(` ${pc8.green("Removed")} (${result.removed.length}):`);
4167
+ console.log(` ${pc9.green("Removed")} (${result.removed.length}):`);
3489
4168
  for (const item of result.removed) {
3490
4169
  printStatus(item);
3491
4170
  }
3492
4171
  }
3493
4172
  if (result.archived.length > 0) {
3494
4173
  console.log();
3495
- console.log(` ${pc8.yellow("Archived")} (${result.archived.length}):`);
4174
+ console.log(` ${pc9.yellow("Archived")} (${result.archived.length}):`);
3496
4175
  for (const item of result.archived) {
3497
4176
  printInfo(item);
3498
4177
  }
@@ -3500,7 +4179,7 @@ var init_uninstall = __esm({
3500
4179
  if (result.restored.length > 0) {
3501
4180
  console.log();
3502
4181
  console.log(
3503
- ` ${pc8.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
4182
+ ` ${pc9.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
3504
4183
  );
3505
4184
  for (const item of result.restored) {
3506
4185
  printInfo(item);
@@ -3509,16 +4188,16 @@ var init_uninstall = __esm({
3509
4188
  if (result.skipped.length > 0) {
3510
4189
  console.log();
3511
4190
  console.log(
3512
- ` ${pc8.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
4191
+ ` ${pc9.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
3513
4192
  );
3514
4193
  for (const item of result.skipped) {
3515
- console.log(` ${pc8.dim(item)}`);
4194
+ console.log(` ${pc9.dim(item)}`);
3516
4195
  }
3517
4196
  }
3518
4197
  console.log();
3519
4198
  printStatus("FlyDocs has been removed from this project.");
3520
4199
  console.log();
3521
- printInfo(`To reinstall: ${pc8.cyan("npx @flydocs/cli install --here")}`);
4200
+ printInfo(`To reinstall: ${pc9.cyan("npx @flydocs/cli install --here")}`);
3522
4201
  console.log();
3523
4202
  }
3524
4203
  });
@@ -3530,28 +4209,28 @@ var setup_exports = {};
3530
4209
  __export(setup_exports, {
3531
4210
  default: () => setup_default
3532
4211
  });
3533
- import { defineCommand as defineCommand4 } from "citty";
3534
- import pc9 from "picocolors";
4212
+ import { defineCommand as defineCommand5 } from "citty";
4213
+ import pc10 from "picocolors";
3535
4214
  var setup_default;
3536
4215
  var init_setup = __esm({
3537
4216
  "src/commands/setup.ts"() {
3538
4217
  "use strict";
3539
- setup_default = defineCommand4({
4218
+ setup_default = defineCommand5({
3540
4219
  meta: {
3541
4220
  name: "setup",
3542
4221
  description: "Configure FlyDocs settings for this project"
3543
4222
  },
3544
4223
  run() {
3545
4224
  console.log();
3546
- console.log(` ${pc9.bold("FlyDocs Setup")}`);
4225
+ console.log(` ${pc10.bold("FlyDocs Setup")}`);
3547
4226
  console.log();
3548
4227
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
3549
4228
  console.log();
3550
4229
  console.log(
3551
- ` ${pc9.cyan("Claude Code:")} Type ${pc9.bold("/flydocs-setup")} in chat`
4230
+ ` ${pc10.cyan("Claude Code:")} Type ${pc10.bold("/flydocs-setup")} in chat`
3552
4231
  );
3553
4232
  console.log(
3554
- ` ${pc9.cyan("Cursor:")} Type ${pc9.bold("/flydocs-setup")} in chat`
4233
+ ` ${pc10.cyan("Cursor:")} Type ${pc10.bold("/flydocs-setup")} in chat`
3555
4234
  );
3556
4235
  console.log();
3557
4236
  console.log(` This configures your project context, detects your stack,`);
@@ -3567,14 +4246,14 @@ var skills_exports = {};
3567
4246
  __export(skills_exports, {
3568
4247
  default: () => skills_default
3569
4248
  });
3570
- import { defineCommand as defineCommand5 } from "citty";
3571
- import pc10 from "picocolors";
4249
+ import { defineCommand as defineCommand6 } from "citty";
4250
+ import pc11 from "picocolors";
3572
4251
  var list, search, add, remove, skills_default;
3573
4252
  var init_skills2 = __esm({
3574
4253
  "src/commands/skills.ts"() {
3575
4254
  "use strict";
3576
4255
  init_skill_manager();
3577
- list = defineCommand5({
4256
+ list = defineCommand6({
3578
4257
  meta: {
3579
4258
  name: "list",
3580
4259
  description: "List installed skills"
@@ -3590,26 +4269,26 @@ var init_skills2 = __esm({
3590
4269
  console.log(`${total} skill(s) installed:`);
3591
4270
  if (result.platform.length > 0) {
3592
4271
  console.log();
3593
- console.log(pc10.bold("Platform"));
4272
+ console.log(pc11.bold("Platform"));
3594
4273
  for (const skill of result.platform) {
3595
4274
  console.log(
3596
- ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
4275
+ ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
3597
4276
  );
3598
4277
  }
3599
4278
  }
3600
4279
  if (result.community.length > 0) {
3601
4280
  console.log();
3602
- console.log(pc10.bold("Community"));
4281
+ console.log(pc11.bold("Community"));
3603
4282
  for (const skill of result.community) {
3604
4283
  console.log(
3605
- ` ${skill.name} ${pc10.dim(`(${skill.triggers} triggers)`)}`
4284
+ ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
3606
4285
  );
3607
4286
  }
3608
4287
  }
3609
4288
  console.log();
3610
4289
  }
3611
4290
  });
3612
- search = defineCommand5({
4291
+ search = defineCommand6({
3613
4292
  meta: {
3614
4293
  name: "search",
3615
4294
  description: "Search community skills"
@@ -3625,24 +4304,24 @@ var init_skills2 = __esm({
3625
4304
  const results = await searchCatalog(args.keyword);
3626
4305
  if (results.length === 0) {
3627
4306
  console.log(`No skills found for "${args.keyword}".`);
3628
- console.log(` Browse the catalog at: ${pc10.cyan("https://skills.sh/")}`);
4307
+ console.log(` Browse the catalog at: ${pc11.cyan("https://skills.sh/")}`);
3629
4308
  return;
3630
4309
  }
3631
4310
  console.log();
3632
4311
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
3633
4312
  console.log();
3634
4313
  for (const skill of results) {
3635
- console.log(` ${pc10.bold(skill.name)}`);
4314
+ console.log(` ${pc11.bold(skill.name)}`);
3636
4315
  console.log(` ${skill.description}`);
3637
- console.log(` ${pc10.dim(skill.repo)}`);
4316
+ console.log(` ${pc11.dim(skill.repo)}`);
3638
4317
  if (skill.tags.length > 0) {
3639
- console.log(` ${pc10.dim(skill.tags.join(", "))}`);
4318
+ console.log(` ${pc11.dim(skill.tags.join(", "))}`);
3640
4319
  }
3641
4320
  console.log();
3642
4321
  }
3643
4322
  }
3644
4323
  });
3645
- add = defineCommand5({
4324
+ add = defineCommand6({
3646
4325
  meta: {
3647
4326
  name: "add",
3648
4327
  description: "Install a community skill"
@@ -3658,7 +4337,7 @@ var init_skills2 = __esm({
3658
4337
  await addSkill(process.cwd(), args.source);
3659
4338
  }
3660
4339
  });
3661
- remove = defineCommand5({
4340
+ remove = defineCommand6({
3662
4341
  meta: {
3663
4342
  name: "remove",
3664
4343
  description: "Remove an installed community skill"
@@ -3674,7 +4353,7 @@ var init_skills2 = __esm({
3674
4353
  await removeSkill(process.cwd(), args.name);
3675
4354
  }
3676
4355
  });
3677
- skills_default = defineCommand5({
4356
+ skills_default = defineCommand6({
3678
4357
  meta: {
3679
4358
  name: "skills",
3680
4359
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -3694,10 +4373,10 @@ var connect_exports = {};
3694
4373
  __export(connect_exports, {
3695
4374
  default: () => connect_default
3696
4375
  });
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";
4376
+ import { defineCommand as defineCommand7 } from "citty";
4377
+ import { text as text4, confirm as confirm6, isCancel as isCancel7, cancel as cancel6 } from "@clack/prompts";
4378
+ import pc12 from "picocolors";
4379
+ import { join as join22 } from "path";
3701
4380
  var connect_default;
3702
4381
  var init_connect = __esm({
3703
4382
  "src/commands/connect.ts"() {
@@ -3707,7 +4386,7 @@ var init_connect = __esm({
3707
4386
  init_template();
3708
4387
  init_ui();
3709
4388
  init_api_key();
3710
- connect_default = defineCommand6({
4389
+ connect_default = defineCommand7({
3711
4390
  meta: {
3712
4391
  name: "connect",
3713
4392
  description: "Connect FlyDocs to a cloud provider"
@@ -3732,11 +4411,11 @@ var init_connect = __esm({
3732
4411
  },
3733
4412
  async run({ args }) {
3734
4413
  const targetDir = args.path ?? process.cwd();
3735
- const configPath = join19(targetDir, ".flydocs", "config.json");
4414
+ const configPath = join22(targetDir, ".flydocs", "config.json");
3736
4415
  if (!await pathExists(configPath)) {
3737
4416
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
3738
4417
  console.log(
3739
- ` Run ${pc11.cyan("flydocs")} first to install FlyDocs in this project.`
4418
+ ` Run ${pc12.cyan("flydocs")} first to install FlyDocs in this project.`
3740
4419
  );
3741
4420
  process.exit(1);
3742
4421
  }
@@ -3744,24 +4423,24 @@ var init_connect = __esm({
3744
4423
  if (config.tier === "cloud") {
3745
4424
  printInfo("This project is already connected to the cloud tier.");
3746
4425
  console.log();
3747
- const reconnect = await confirm5({
4426
+ const reconnect = await confirm6({
3748
4427
  message: "Want to update your API key?"
3749
4428
  });
3750
- if (isCancel6(reconnect) || !reconnect) {
4429
+ if (isCancel7(reconnect) || !reconnect) {
3751
4430
  console.log(` No changes made.`);
3752
4431
  return;
3753
4432
  }
3754
4433
  }
3755
4434
  console.log();
3756
- console.log(` ${pc11.bold("Connect to FlyDocs Cloud")}`);
4435
+ console.log(` ${pc12.bold("Connect to FlyDocs Cloud")}`);
3757
4436
  console.log();
3758
4437
  console.log(
3759
- ` ${pc11.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4438
+ ` ${pc12.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3760
4439
  );
3761
4440
  console.log();
3762
4441
  let apiKey = args.key ?? "";
3763
4442
  if (!apiKey) {
3764
- const keyInput = await text3({
4443
+ const keyInput = await text4({
3765
4444
  message: "Enter your API key",
3766
4445
  placeholder: "fdk_...",
3767
4446
  validate(value) {
@@ -3772,8 +4451,8 @@ var init_connect = __esm({
3772
4451
  return void 0;
3773
4452
  }
3774
4453
  });
3775
- if (isCancel6(keyInput)) {
3776
- cancel5("Connection cancelled.");
4454
+ if (isCancel7(keyInput)) {
4455
+ cancel6("Connection cancelled.");
3777
4456
  process.exit(0);
3778
4457
  }
3779
4458
  apiKey = keyInput.trim();
@@ -3794,7 +4473,7 @@ var init_connect = __esm({
3794
4473
  console.log(` Check your key and try again.`);
3795
4474
  process.exit(1);
3796
4475
  }
3797
- printStatus(`Connected to ${pc11.bold(result.org)}`);
4476
+ printStatus(`Connected to ${pc12.bold(result.org)}`);
3798
4477
  } catch {
3799
4478
  printError(
3800
4479
  "Could not reach relay API. Check your network and try again."
@@ -3802,7 +4481,7 @@ var init_connect = __esm({
3802
4481
  process.exit(1);
3803
4482
  }
3804
4483
  const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
3805
- printStatus(`API key stored in ${pc11.dim(envFile)}`);
4484
+ printStatus(`API key stored in ${pc12.dim(envFile)}`);
3806
4485
  } else {
3807
4486
  try {
3808
4487
  const result = await validateLinearKey(apiKey);
@@ -3812,7 +4491,7 @@ var init_connect = __esm({
3812
4491
  process.exit(1);
3813
4492
  }
3814
4493
  printStatus(
3815
- `Authenticated as ${pc11.bold(result.name)} (${result.email})`
4494
+ `Authenticated as ${pc12.bold(result.name)} (${result.email})`
3816
4495
  );
3817
4496
  } catch {
3818
4497
  printError("Invalid API key or network error.");
@@ -3820,7 +4499,7 @@ var init_connect = __esm({
3820
4499
  process.exit(1);
3821
4500
  }
3822
4501
  const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
3823
- printStatus(`API key stored in ${pc11.dim(envFile)}`);
4502
+ printStatus(`API key stored in ${pc12.dim(envFile)}`);
3824
4503
  }
3825
4504
  const wasLocal = config.tier === "local";
3826
4505
  config.tier = "cloud";
@@ -3836,14 +4515,464 @@ var init_connect = __esm({
3836
4515
  }
3837
4516
  console.log();
3838
4517
  console.log(
3839
- ` ${pc11.bold("Connected!")} Your project is now on the cloud tier.`
4518
+ ` ${pc12.bold("Connected!")} Your project is now on the cloud tier.`
3840
4519
  );
3841
4520
  console.log();
3842
4521
  console.log(` Next steps:`);
3843
4522
  console.log(
3844
- ` 1. Run ${pc11.cyan("/flydocs-setup")} in your IDE to configure your project`
4523
+ ` 1. Run ${pc12.cyan("/flydocs-setup")} in your IDE to configure your project`
3845
4524
  );
3846
- console.log(` 2. Run ${pc11.cyan("/start-session")} to begin working`);
4525
+ console.log(` 2. Run ${pc12.cyan("/start-session")} to begin working`);
4526
+ console.log();
4527
+ }
4528
+ });
4529
+ }
4530
+ });
4531
+
4532
+ // src/commands/auth.ts
4533
+ var auth_exports = {};
4534
+ __export(auth_exports, {
4535
+ default: () => auth_default
4536
+ });
4537
+ import { defineCommand as defineCommand8 } from "citty";
4538
+ import { text as text5, confirm as confirm7, isCancel as isCancel8, cancel as cancel7 } from "@clack/prompts";
4539
+ import pc13 from "picocolors";
4540
+ var auth_default;
4541
+ var init_auth = __esm({
4542
+ "src/commands/auth.ts"() {
4543
+ "use strict";
4544
+ init_ui();
4545
+ init_api_key();
4546
+ init_global_config();
4547
+ auth_default = defineCommand8({
4548
+ meta: {
4549
+ name: "auth",
4550
+ description: "Store API key globally (~/.flydocs/credentials)"
4551
+ },
4552
+ args: {
4553
+ key: {
4554
+ type: "positional",
4555
+ description: "FlyDocs API key (fdk_...)",
4556
+ required: false
4557
+ }
4558
+ },
4559
+ async run({ args }) {
4560
+ console.log();
4561
+ console.log(` ${pc13.bold("FlyDocs Authentication")}`);
4562
+ console.log();
4563
+ let apiKey = args.key ?? "";
4564
+ const existing = await readGlobalCredential();
4565
+ if (existing?.apiKey && !apiKey) {
4566
+ printInfo(
4567
+ `Existing key found: ${pc13.dim(existing.apiKey.slice(0, 8) + "...")}`
4568
+ );
4569
+ const replace = await confirm7({
4570
+ message: "Replace with a new key?"
4571
+ });
4572
+ if (isCancel8(replace) || !replace) {
4573
+ console.log(` No changes made.`);
4574
+ return;
4575
+ }
4576
+ }
4577
+ if (!apiKey) {
4578
+ console.log(
4579
+ ` ${pc13.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4580
+ );
4581
+ console.log();
4582
+ const keyInput = await text5({
4583
+ message: "Enter your API key",
4584
+ placeholder: "fdk_...",
4585
+ validate(value) {
4586
+ if (!value.trim()) return "API key is required";
4587
+ if (detectKeyType(value.trim()) !== "relay") {
4588
+ return "Key must start with fdk_ (FlyDocs API key)";
4589
+ }
4590
+ return void 0;
4591
+ }
4592
+ });
4593
+ if (isCancel8(keyInput)) {
4594
+ cancel7("Authentication cancelled.");
4595
+ process.exit(0);
4596
+ }
4597
+ apiKey = keyInput.trim();
4598
+ }
4599
+ if (detectKeyType(apiKey) !== "relay") {
4600
+ printError("Invalid key format. Expected fdk_ prefix (FlyDocs API key).");
4601
+ process.exit(1);
4602
+ }
4603
+ if (existing?.apiKey === apiKey) {
4604
+ printInfo("This key is already stored.");
4605
+ await checkCredentialPermissions();
4606
+ return;
4607
+ }
4608
+ printInfo("Validating API key...");
4609
+ try {
4610
+ const result = await validateRelayKey(apiKey);
4611
+ if (!result.valid) {
4612
+ printError("Invalid API key. Check your key and try again.");
4613
+ process.exit(1);
4614
+ }
4615
+ printStatus(`Authenticated with ${pc13.bold(result.org)}`);
4616
+ } catch {
4617
+ printError(
4618
+ "Could not reach FlyDocs API. Check your network and try again."
4619
+ );
4620
+ process.exit(1);
4621
+ }
4622
+ if (existing?.apiKey && existing.apiKey !== apiKey) {
4623
+ printWarning("Replacing existing key.");
4624
+ }
4625
+ await writeGlobalCredential({
4626
+ apiKey,
4627
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4628
+ lastValidated: (/* @__PURE__ */ new Date()).toISOString()
4629
+ });
4630
+ printStatus(`Key stored at ${pc13.dim(credentialsPath())}`);
4631
+ await checkCredentialPermissions();
4632
+ console.log();
4633
+ console.log(` ${pc13.bold("Authenticated!")} Key stored globally.`);
4634
+ console.log(` All FlyDocs projects on this machine will use this key.`);
4635
+ console.log();
4636
+ }
4637
+ });
4638
+ }
4639
+ });
4640
+
4641
+ // src/commands/sync.ts
4642
+ var sync_exports = {};
4643
+ __export(sync_exports, {
4644
+ default: () => sync_default
4645
+ });
4646
+ import { defineCommand as defineCommand9 } from "citty";
4647
+ import pc14 from "picocolors";
4648
+ import { join as join23 } from "path";
4649
+ import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
4650
+ var sync_default;
4651
+ var init_sync = __esm({
4652
+ "src/commands/sync.ts"() {
4653
+ "use strict";
4654
+ init_ui();
4655
+ init_global_config();
4656
+ init_config();
4657
+ init_config_integrity();
4658
+ init_fs_ops();
4659
+ init_types();
4660
+ init_relay_client();
4661
+ sync_default = defineCommand9({
4662
+ meta: {
4663
+ name: "sync",
4664
+ description: "Pull latest config and templates from server"
4665
+ },
4666
+ args: {
4667
+ path: {
4668
+ type: "string",
4669
+ description: "Path to project directory"
4670
+ }
4671
+ },
4672
+ async run({ args }) {
4673
+ const targetDir = args.path ?? process.cwd();
4674
+ const changes = [];
4675
+ console.log();
4676
+ printInfo("Syncing with server...");
4677
+ const resolved = await resolveApiKey(void 0, targetDir);
4678
+ if (!resolved) {
4679
+ printError(
4680
+ "No API key found. Run `flydocs auth` or `flydocs init` first."
4681
+ );
4682
+ process.exit(1);
4683
+ }
4684
+ const apiKey = resolved.key;
4685
+ let currentTemplateVersion = 0;
4686
+ try {
4687
+ const currentConfig = await readAnyConfig(targetDir);
4688
+ if (isConfigV2(currentConfig)) {
4689
+ currentTemplateVersion = currentConfig.configVersion ?? 0;
4690
+ }
4691
+ } catch {
4692
+ }
4693
+ let serverResponse;
4694
+ try {
4695
+ serverResponse = await fetchConfigV2(apiKey);
4696
+ } catch (err) {
4697
+ if (err instanceof RelayError) {
4698
+ printWarning(
4699
+ `Server unavailable (${err.status}), using cached config.`
4700
+ );
4701
+ } else {
4702
+ printWarning("Server unreachable, using cached config.");
4703
+ }
4704
+ console.log(` ${pc14.dim("Config may be stale. Retry when connected.")}`);
4705
+ return;
4706
+ }
4707
+ if (!serverResponse.valid) {
4708
+ printWarning("Server returned invalid config. Keeping current config.");
4709
+ return;
4710
+ }
4711
+ await mkdir11(join23(targetDir, ".flydocs"), { recursive: true });
4712
+ const configWithHash = applyConfigHash(serverResponse.config);
4713
+ await writeConfig(targetDir, configWithHash);
4714
+ changes.push("Updated .flydocs/config.json");
4715
+ const serverTemplateVersion = serverResponse.templates.version;
4716
+ if (serverTemplateVersion > currentTemplateVersion) {
4717
+ try {
4718
+ const templatesResponse = await fetchTemplates(
4719
+ apiKey,
4720
+ currentTemplateVersion
4721
+ );
4722
+ if (templatesResponse.templates.length > 0) {
4723
+ const templatesDir = join23(
4724
+ targetDir,
4725
+ ".claude",
4726
+ "skills",
4727
+ "flydocs-workflow",
4728
+ "templates"
4729
+ );
4730
+ await mkdir11(templatesDir, { recursive: true });
4731
+ for (const template of templatesResponse.templates) {
4732
+ const filename = `${template.type}.md`;
4733
+ const subdir = template.category === "issue" ? "issues" : template.category === "pr" ? "pr" : template.category === "comment" ? "." : ".";
4734
+ const templateDir = join23(templatesDir, subdir);
4735
+ await mkdir11(templateDir, { recursive: true });
4736
+ await writeFile13(
4737
+ join23(templateDir, filename),
4738
+ template.content,
4739
+ "utf-8"
4740
+ );
4741
+ }
4742
+ changes.push(
4743
+ `Updated ${templatesResponse.templates.length} templates (v${currentTemplateVersion} \u2192 v${serverTemplateVersion})`
4744
+ );
4745
+ }
4746
+ } catch (err) {
4747
+ if (err instanceof RelayError) {
4748
+ printWarning("Could not fetch templates. Will retry on next sync.");
4749
+ } else {
4750
+ printWarning("Template sync failed. Will retry on next sync.");
4751
+ }
4752
+ }
4753
+ }
4754
+ if (changes.length === 0) {
4755
+ printStatus("Already up to date.");
4756
+ } else {
4757
+ for (const change of changes) {
4758
+ printStatus(change);
4759
+ }
4760
+ }
4761
+ console.log();
4762
+ }
4763
+ });
4764
+ }
4765
+ });
4766
+
4767
+ // src/commands/cleanup.ts
4768
+ var cleanup_exports = {};
4769
+ __export(cleanup_exports, {
4770
+ default: () => cleanup_default
4771
+ });
4772
+ import { defineCommand as defineCommand10 } from "citty";
4773
+ import pc15 from "picocolors";
4774
+ import { join as join24 } from "path";
4775
+ import { readFile as readFile15, writeFile as writeFile14, rm as rm6 } from "fs/promises";
4776
+ async function scanArtifacts(targetDir) {
4777
+ const actions = [];
4778
+ const localMePath = join24(targetDir, ".flydocs", "me.json");
4779
+ if (await pathExists(localMePath) && await pathExists(globalMePath())) {
4780
+ actions.push({
4781
+ description: "Remove .flydocs/me.json (migrated to ~/.flydocs/me.json)",
4782
+ path: localMePath,
4783
+ type: "file"
4784
+ });
4785
+ }
4786
+ const validationCachePath = join24(
4787
+ targetDir,
4788
+ ".flydocs",
4789
+ "validation-cache.json"
4790
+ );
4791
+ if (await pathExists(validationCachePath)) {
4792
+ actions.push({
4793
+ description: "Remove .flydocs/validation-cache.json (replaced by configVersion)",
4794
+ path: validationCachePath,
4795
+ type: "file"
4796
+ });
4797
+ }
4798
+ const hasGlobalCreds = await pathExists(credentialsPath());
4799
+ for (const envFile of [".env", ".env.local"]) {
4800
+ const envPath = join24(targetDir, envFile);
4801
+ if (!await pathExists(envPath)) continue;
4802
+ const content = await readFile15(envPath, "utf-8");
4803
+ const lines = content.split("\n");
4804
+ for (const line of lines) {
4805
+ const trimmed = line.trim();
4806
+ if (hasGlobalCreds && trimmed.startsWith("FLYDOCS_API_KEY=")) {
4807
+ actions.push({
4808
+ description: `Remove FLYDOCS_API_KEY from ${envFile} (migrated to ~/.flydocs/credentials)`,
4809
+ path: envPath,
4810
+ type: "line"
4811
+ });
4812
+ }
4813
+ if (trimmed.startsWith("FLYDOCS_RELAY_URL=")) {
4814
+ actions.push({
4815
+ description: `Remove FLYDOCS_RELAY_URL from ${envFile}`,
4816
+ path: envPath,
4817
+ type: "line"
4818
+ });
4819
+ }
4820
+ }
4821
+ }
4822
+ try {
4823
+ const config = await readAnyConfig(targetDir);
4824
+ const rawContent = await readFile15(
4825
+ join24(targetDir, ".flydocs", "config.json"),
4826
+ "utf-8"
4827
+ );
4828
+ const raw = JSON.parse(rawContent);
4829
+ const ghostFields = ["provider", "statusMapping"];
4830
+ for (const field of ghostFields) {
4831
+ if (field in raw) {
4832
+ actions.push({
4833
+ description: `Remove ghost field "${field}" from config.json`,
4834
+ path: join24(targetDir, ".flydocs", "config.json"),
4835
+ type: "field"
4836
+ });
4837
+ }
4838
+ }
4839
+ if (isConfigV2(config)) {
4840
+ const v1OnlyFields = [
4841
+ "workspaceId",
4842
+ "issueLabels",
4843
+ "workspace",
4844
+ "onboardComplete",
4845
+ "sourceRepo",
4846
+ "designSystem",
4847
+ "aiLabor"
4848
+ ];
4849
+ for (const field of v1OnlyFields) {
4850
+ if (field in raw) {
4851
+ actions.push({
4852
+ description: `Remove v1 field "${field}" from config.json (server-owned in v2)`,
4853
+ path: join24(targetDir, ".flydocs", "config.json"),
4854
+ type: "field"
4855
+ });
4856
+ }
4857
+ }
4858
+ }
4859
+ } catch {
4860
+ }
4861
+ return actions;
4862
+ }
4863
+ async function executeCleanup(targetDir, actions) {
4864
+ const fileRemovals = actions.filter((a) => a.type === "file");
4865
+ const lineRemovals = actions.filter((a) => a.type === "line");
4866
+ const fieldRemovals = actions.filter((a) => a.type === "field");
4867
+ for (const action of fileRemovals) {
4868
+ await rm6(action.path, { force: true });
4869
+ }
4870
+ const envFiles = /* @__PURE__ */ new Map();
4871
+ for (const action of lineRemovals) {
4872
+ if (!envFiles.has(action.path)) {
4873
+ const content = await readFile15(action.path, "utf-8");
4874
+ envFiles.set(action.path, content.split("\n"));
4875
+ }
4876
+ }
4877
+ for (const [envPath, lines] of envFiles) {
4878
+ const filtered = lines.filter((line) => {
4879
+ const trimmed = line.trim();
4880
+ return !trimmed.startsWith("FLYDOCS_API_KEY=") && !trimmed.startsWith("FLYDOCS_RELAY_URL=");
4881
+ });
4882
+ const hasContent = filtered.some(
4883
+ (l) => l.trim().length > 0 && !l.trim().startsWith("#")
4884
+ );
4885
+ if (!hasContent) {
4886
+ await rm6(envPath, { force: true });
4887
+ } else {
4888
+ await writeFile14(envPath, filtered.join("\n"), "utf-8");
4889
+ }
4890
+ }
4891
+ if (fieldRemovals.length > 0) {
4892
+ const configPath = join24(targetDir, ".flydocs", "config.json");
4893
+ const content = await readFile15(configPath, "utf-8");
4894
+ const config = JSON.parse(content);
4895
+ for (const action of fieldRemovals) {
4896
+ const match = action.description.match(/"(\w+)"/);
4897
+ if (match) {
4898
+ delete config[match[1]];
4899
+ }
4900
+ }
4901
+ await writeFile14(
4902
+ configPath,
4903
+ JSON.stringify(config, null, 2) + "\n",
4904
+ "utf-8"
4905
+ );
4906
+ }
4907
+ }
4908
+ var cleanup_default;
4909
+ var init_cleanup = __esm({
4910
+ "src/commands/cleanup.ts"() {
4911
+ "use strict";
4912
+ init_ui();
4913
+ init_fs_ops();
4914
+ init_config();
4915
+ init_types();
4916
+ init_global_config();
4917
+ cleanup_default = defineCommand10({
4918
+ meta: {
4919
+ name: "cleanup",
4920
+ description: "Remove legacy v1 artifacts after migration (dry-run by default)"
4921
+ },
4922
+ args: {
4923
+ confirm: {
4924
+ type: "boolean",
4925
+ description: "Actually remove artifacts (default: dry-run only)",
4926
+ default: false
4927
+ },
4928
+ path: {
4929
+ type: "string",
4930
+ description: "Path to project directory"
4931
+ }
4932
+ },
4933
+ async run({ args }) {
4934
+ const targetDir = args.path ?? process.cwd();
4935
+ console.log();
4936
+ console.log(` ${pc15.bold("FlyDocs Cleanup")}`);
4937
+ console.log();
4938
+ const hasConfig = await pathExists(
4939
+ join24(targetDir, ".flydocs", "config.json")
4940
+ );
4941
+ if (!hasConfig) {
4942
+ printError("No .flydocs/config.json found. Run flydocs init first.");
4943
+ process.exit(1);
4944
+ }
4945
+ const hasGlobalCreds = await pathExists(credentialsPath());
4946
+ if (!hasGlobalCreds) {
4947
+ printWarning(
4948
+ "No global credentials found. Run flydocs init to set up credentials before cleanup."
4949
+ );
4950
+ }
4951
+ const actions = await scanArtifacts(targetDir);
4952
+ if (actions.length === 0) {
4953
+ printStatus("No legacy artifacts found. Already clean.");
4954
+ console.log();
4955
+ return;
4956
+ }
4957
+ const mode = args.confirm ? "Removing" : "Would remove";
4958
+ printInfo(`${mode} ${actions.length} legacy artifact(s):`);
4959
+ console.log();
4960
+ for (const action of actions) {
4961
+ const icon = args.confirm ? pc15.red("-") : pc15.yellow("?");
4962
+ console.log(` ${icon} ${action.description}`);
4963
+ }
4964
+ console.log();
4965
+ if (!args.confirm) {
4966
+ printInfo(
4967
+ `Dry-run complete. Run ${pc15.cyan("flydocs cleanup --confirm")} to remove.`
4968
+ );
4969
+ console.log();
4970
+ return;
4971
+ }
4972
+ await executeCleanup(targetDir, actions);
4973
+ printCompletionBox("Cleanup Complete", [
4974
+ `${pc15.green("+")} Removed ${actions.length} legacy artifact(s)`
4975
+ ]);
3847
4976
  console.log();
3848
4977
  }
3849
4978
  });
@@ -3855,15 +4984,15 @@ var upgrade_exports = {};
3855
4984
  __export(upgrade_exports, {
3856
4985
  default: () => upgrade_default
3857
4986
  });
3858
- import { defineCommand as defineCommand7 } from "citty";
3859
- import pc12 from "picocolors";
4987
+ import { defineCommand as defineCommand11 } from "citty";
4988
+ import pc16 from "picocolors";
3860
4989
  var upgrade_default;
3861
4990
  var init_upgrade = __esm({
3862
4991
  "src/commands/upgrade.ts"() {
3863
4992
  "use strict";
3864
4993
  init_config();
3865
4994
  init_fs_ops();
3866
- upgrade_default = defineCommand7({
4995
+ upgrade_default = defineCommand11({
3867
4996
  meta: {
3868
4997
  name: "upgrade",
3869
4998
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -3892,34 +5021,34 @@ var init_upgrade = __esm({
3892
5021
  console.log();
3893
5022
  if (currentTier === "cloud") {
3894
5023
  console.log(
3895
- ` ${pc12.green("\u2713")} You're already on the ${pc12.bold("cloud")} tier.`
5024
+ ` ${pc16.green("\u2713")} You're already on the ${pc16.bold("cloud")} tier.`
3896
5025
  );
3897
5026
  console.log();
3898
5027
  console.log(` Your issues sync with your provider via the relay API.`);
3899
5028
  console.log(
3900
- ` Run ${pc12.cyan("flydocs connect")} to update your connection settings.`
5029
+ ` Run ${pc16.cyan("flydocs connect")} to update your connection settings.`
3901
5030
  );
3902
5031
  console.log();
3903
5032
  return;
3904
5033
  }
3905
- console.log(` ${pc12.bold("FlyDocs Cloud Tier")}`);
5034
+ console.log(` ${pc16.bold("FlyDocs Cloud Tier")}`);
3906
5035
  console.log();
3907
- console.log(` You're currently on the ${pc12.yellow("local")} tier.`);
5036
+ console.log(` You're currently on the ${pc16.yellow("local")} tier.`);
3908
5037
  console.log(` Upgrade to cloud for:`);
3909
5038
  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`);
5039
+ console.log(` ${pc16.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
5040
+ console.log(` ${pc16.cyan("\u2192")} Project milestones and cycle management`);
5041
+ console.log(` ${pc16.cyan("\u2192")} Team assignment and priority tracking`);
5042
+ console.log(` ${pc16.cyan("\u2192")} Project health updates and dashboards`);
5043
+ console.log(` ${pc16.cyan("\u2192")} Cross-project issue linking`);
3915
5044
  console.log();
3916
- console.log(` ${pc12.bold("How to upgrade:")}`);
5045
+ console.log(` ${pc16.bold("How to upgrade:")}`);
3917
5046
  console.log();
3918
- console.log(` Option 1: Run ${pc12.cyan("/flydocs-upgrade")} in your IDE`);
5047
+ console.log(` Option 1: Run ${pc16.cyan("/flydocs-upgrade")} in your IDE`);
3919
5048
  console.log(` Guided migration with issue transfer`);
3920
5049
  console.log();
3921
5050
  console.log(
3922
- ` Option 2: Run ${pc12.cyan("flydocs connect")} from terminal`
5051
+ ` Option 2: Run ${pc16.cyan("flydocs connect")} from terminal`
3923
5052
  );
3924
5053
  console.log(` Quick tier swap (no issue migration)`);
3925
5054
  console.log();
@@ -3933,23 +5062,23 @@ var self_update_exports = {};
3933
5062
  __export(self_update_exports, {
3934
5063
  default: () => self_update_default
3935
5064
  });
3936
- import { defineCommand as defineCommand8 } from "citty";
5065
+ import { defineCommand as defineCommand12 } from "citty";
3937
5066
  import { execSync } from "child_process";
3938
- import pc13 from "picocolors";
5067
+ import pc17 from "picocolors";
3939
5068
  var self_update_default;
3940
5069
  var init_self_update = __esm({
3941
5070
  "src/commands/self-update.ts"() {
3942
5071
  "use strict";
3943
5072
  init_constants();
3944
5073
  init_ui();
3945
- self_update_default = defineCommand8({
5074
+ self_update_default = defineCommand12({
3946
5075
  meta: {
3947
5076
  name: "self-update",
3948
5077
  description: "Update FlyDocs CLI to the latest version"
3949
5078
  },
3950
5079
  async run() {
3951
5080
  console.log();
3952
- console.log(` Updating ${pc13.cyan(PACKAGE_NAME)}...`);
5081
+ console.log(` Updating ${pc17.cyan(PACKAGE_NAME)}...`);
3953
5082
  console.log();
3954
5083
  try {
3955
5084
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -3974,15 +5103,15 @@ var telemetry_exports = {};
3974
5103
  __export(telemetry_exports, {
3975
5104
  default: () => telemetry_default
3976
5105
  });
3977
- import { defineCommand as defineCommand9 } from "citty";
3978
- import pc14 from "picocolors";
5106
+ import { defineCommand as defineCommand13 } from "citty";
5107
+ import pc18 from "picocolors";
3979
5108
  var enable, disable, status, telemetry_default;
3980
5109
  var init_telemetry2 = __esm({
3981
5110
  "src/commands/telemetry.ts"() {
3982
5111
  "use strict";
3983
5112
  init_telemetry();
3984
5113
  init_ui();
3985
- enable = defineCommand9({
5114
+ enable = defineCommand13({
3986
5115
  meta: {
3987
5116
  name: "enable",
3988
5117
  description: "Enable anonymous usage analytics"
@@ -3997,7 +5126,7 @@ var init_telemetry2 = __esm({
3997
5126
  }
3998
5127
  }
3999
5128
  });
4000
- disable = defineCommand9({
5129
+ disable = defineCommand13({
4001
5130
  meta: {
4002
5131
  name: "disable",
4003
5132
  description: "Disable anonymous usage analytics"
@@ -4015,7 +5144,7 @@ var init_telemetry2 = __esm({
4015
5144
  }
4016
5145
  }
4017
5146
  });
4018
- status = defineCommand9({
5147
+ status = defineCommand13({
4019
5148
  meta: {
4020
5149
  name: "status",
4021
5150
  description: "Show current telemetry status"
@@ -4023,37 +5152,37 @@ var init_telemetry2 = __esm({
4023
5152
  async run() {
4024
5153
  const info = await getStatus();
4025
5154
  console.log();
4026
- console.log(pc14.bold("Telemetry Status"));
5155
+ console.log(pc18.bold("Telemetry Status"));
4027
5156
  console.log();
4028
5157
  const effectivelyEnabled = info.enabled && !info.envOverride;
4029
5158
  console.log(
4030
- ` Enabled: ${effectivelyEnabled ? pc14.green("yes") : pc14.yellow("no")}`
5159
+ ` Enabled: ${effectivelyEnabled ? pc18.green("yes") : pc18.yellow("no")}`
4031
5160
  );
4032
5161
  if (info.envOverride) {
4033
5162
  console.log(
4034
- ` Env override: ${pc14.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
5163
+ ` Env override: ${pc18.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
4035
5164
  );
4036
5165
  }
4037
5166
  if (info.anonymousId) {
4038
- console.log(` Anonymous ID: ${pc14.dim(info.anonymousId)}`);
5167
+ console.log(` Anonymous ID: ${pc18.dim(info.anonymousId)}`);
4039
5168
  } else {
4040
5169
  console.log(
4041
- ` Anonymous ID: ${pc14.dim("(not yet created \u2014 generated on first run)")}`
5170
+ ` Anonymous ID: ${pc18.dim("(not yet created \u2014 generated on first run)")}`
4042
5171
  );
4043
5172
  }
4044
5173
  console.log();
4045
5174
  console.log(
4046
- pc14.dim(
5175
+ pc18.dim(
4047
5176
  " FlyDocs collects anonymous usage analytics to improve the CLI."
4048
5177
  )
4049
5178
  );
4050
5179
  console.log(
4051
- pc14.dim(" No personal data, file contents, or code is ever collected.")
5180
+ pc18.dim(" No personal data, file contents, or code is ever collected.")
4052
5181
  );
4053
5182
  console.log();
4054
5183
  }
4055
5184
  });
4056
- telemetry_default = defineCommand9({
5185
+ telemetry_default = defineCommand13({
4057
5186
  meta: {
4058
5187
  name: "telemetry",
4059
5188
  description: "Manage anonymous usage analytics (enable, disable, status)"
@@ -4069,14 +5198,18 @@ var init_telemetry2 = __esm({
4069
5198
 
4070
5199
  // src/cli.ts
4071
5200
  init_constants();
4072
- import { defineCommand as defineCommand10, runMain } from "citty";
5201
+ import { defineCommand as defineCommand14, runMain } from "citty";
4073
5202
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
4074
5203
  "install",
5204
+ "init",
4075
5205
  "update",
4076
5206
  "uninstall",
4077
5207
  "setup",
4078
5208
  "skills",
4079
5209
  "connect",
5210
+ "auth",
5211
+ "sync",
5212
+ "cleanup",
4080
5213
  "upgrade",
4081
5214
  "self-update",
4082
5215
  "telemetry"
@@ -4089,7 +5222,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
4089
5222
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
4090
5223
  process.argv.splice(2, 0, "install");
4091
5224
  }
4092
- var main = defineCommand10({
5225
+ var main = defineCommand14({
4093
5226
  meta: {
4094
5227
  name: CLI_NAME,
4095
5228
  version: CLI_VERSION,
@@ -4097,11 +5230,15 @@ var main = defineCommand10({
4097
5230
  },
4098
5231
  subCommands: {
4099
5232
  install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
5233
+ init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
4100
5234
  update: () => Promise.resolve().then(() => (init_update(), update_exports)).then((m) => m.default),
4101
5235
  uninstall: () => Promise.resolve().then(() => (init_uninstall(), uninstall_exports)).then((m) => m.default),
4102
5236
  setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
4103
5237
  skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
4104
5238
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
5239
+ auth: () => Promise.resolve().then(() => (init_auth(), auth_exports)).then((m) => m.default),
5240
+ sync: () => Promise.resolve().then(() => (init_sync(), sync_exports)).then((m) => m.default),
5241
+ cleanup: () => Promise.resolve().then(() => (init_cleanup(), cleanup_exports)).then((m) => m.default),
4105
5242
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
4106
5243
  "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default),
4107
5244
  telemetry: () => Promise.resolve().then(() => (init_telemetry2(), telemetry_exports)).then((m) => m.default)