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

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.26";
18
+ CLI_VERSION = "0.6.0-alpha.28";
19
19
  CLI_NAME = "flydocs";
20
20
  PACKAGE_NAME = "@flydocs/cli";
21
21
  POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
@@ -2928,12 +2928,14 @@ var init_global_config = __esm({
2928
2928
  function resolveRelayUrl() {
2929
2929
  return process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? DEFAULT_RELAY_URL;
2930
2930
  }
2931
- function headers(apiKey) {
2932
- return {
2931
+ function headers(apiKey, workspaceId) {
2932
+ const h = {
2933
2933
  Authorization: `Bearer ${apiKey}`,
2934
2934
  "Content-Type": "application/json",
2935
2935
  Accept: "application/json"
2936
2936
  };
2937
+ if (workspaceId) h["X-Workspace"] = workspaceId;
2938
+ return h;
2937
2939
  }
2938
2940
  async function fetchConfigV2(apiKey, options = {}) {
2939
2941
  const baseUrl = resolveRelayUrl();
@@ -2941,7 +2943,7 @@ async function fetchConfigV2(apiKey, options = {}) {
2941
2943
  if (options.includeContext) params.set("includeContext", "true");
2942
2944
  const response = await fetch(`${baseUrl}/config/generate?${params}`, {
2943
2945
  method: "GET",
2944
- headers: headers(apiKey),
2946
+ headers: headers(apiKey, options.workspaceId),
2945
2947
  signal: AbortSignal.timeout(3e4)
2946
2948
  });
2947
2949
  if (!response.ok) {
@@ -2954,11 +2956,11 @@ async function fetchConfigV2(apiKey, options = {}) {
2954
2956
  }
2955
2957
  return await response.json();
2956
2958
  }
2957
- async function fetchTemplates(apiKey, sinceVersion) {
2959
+ async function fetchTemplates(apiKey, sinceVersion, workspaceId) {
2958
2960
  const baseUrl = resolveRelayUrl();
2959
2961
  const response = await fetch(`${baseUrl}/templates?since=${sinceVersion}`, {
2960
2962
  method: "GET",
2961
- headers: headers(apiKey),
2963
+ headers: headers(apiKey, workspaceId),
2962
2964
  signal: AbortSignal.timeout(15e3)
2963
2965
  });
2964
2966
  if (!response.ok) {
@@ -2987,33 +2989,250 @@ var init_relay_client = __esm({
2987
2989
  }
2988
2990
  });
2989
2991
 
2992
+ // src/commands/cleanup.ts
2993
+ var cleanup_exports = {};
2994
+ __export(cleanup_exports, {
2995
+ default: () => cleanup_default,
2996
+ executeCleanup: () => executeCleanup,
2997
+ scanArtifacts: () => scanArtifacts
2998
+ });
2999
+ import { defineCommand as defineCommand2 } from "citty";
3000
+ import pc7 from "picocolors";
3001
+ import { join as join18 } from "path";
3002
+ import { readFile as readFile13, writeFile as writeFile11, rm as rm4 } from "fs/promises";
3003
+ async function scanArtifacts(targetDir) {
3004
+ const actions = [];
3005
+ const localMePath = join18(targetDir, ".flydocs", "me.json");
3006
+ if (await pathExists(localMePath) && await pathExists(globalMePath())) {
3007
+ actions.push({
3008
+ description: "Remove .flydocs/me.json (migrated to ~/.flydocs/me.json)",
3009
+ path: localMePath,
3010
+ type: "file"
3011
+ });
3012
+ }
3013
+ const validationCachePath = join18(
3014
+ targetDir,
3015
+ ".flydocs",
3016
+ "validation-cache.json"
3017
+ );
3018
+ if (await pathExists(validationCachePath)) {
3019
+ actions.push({
3020
+ description: "Remove .flydocs/validation-cache.json (replaced by configVersion)",
3021
+ path: validationCachePath,
3022
+ type: "file"
3023
+ });
3024
+ }
3025
+ const hasGlobalCreds = await pathExists(credentialsPath());
3026
+ for (const envFile of [".env", ".env.local"]) {
3027
+ const envPath = join18(targetDir, envFile);
3028
+ if (!await pathExists(envPath)) continue;
3029
+ const content = await readFile13(envPath, "utf-8");
3030
+ const lines = content.split("\n");
3031
+ for (const line of lines) {
3032
+ const trimmed = line.trim();
3033
+ if (hasGlobalCreds && trimmed.startsWith("FLYDOCS_API_KEY=")) {
3034
+ actions.push({
3035
+ description: `Remove FLYDOCS_API_KEY from ${envFile} (migrated to ~/.flydocs/credentials)`,
3036
+ path: envPath,
3037
+ type: "line"
3038
+ });
3039
+ }
3040
+ if (trimmed.startsWith("FLYDOCS_RELAY_URL=")) {
3041
+ actions.push({
3042
+ description: `Remove FLYDOCS_RELAY_URL from ${envFile}`,
3043
+ path: envPath,
3044
+ type: "line"
3045
+ });
3046
+ }
3047
+ }
3048
+ }
3049
+ try {
3050
+ const config = await readAnyConfig(targetDir);
3051
+ const rawContent = await readFile13(
3052
+ join18(targetDir, ".flydocs", "config.json"),
3053
+ "utf-8"
3054
+ );
3055
+ const raw = JSON.parse(rawContent);
3056
+ const ghostFields = ["provider", "statusMapping"];
3057
+ for (const field of ghostFields) {
3058
+ if (field in raw) {
3059
+ actions.push({
3060
+ description: `Remove ghost field "${field}" from config.json`,
3061
+ path: join18(targetDir, ".flydocs", "config.json"),
3062
+ type: "field"
3063
+ });
3064
+ }
3065
+ }
3066
+ if (isConfigV2(config)) {
3067
+ const v1OnlyFields = [
3068
+ "workspaceId",
3069
+ "issueLabels",
3070
+ "workspace",
3071
+ "onboardComplete",
3072
+ "sourceRepo",
3073
+ "designSystem",
3074
+ "aiLabor"
3075
+ ];
3076
+ for (const field of v1OnlyFields) {
3077
+ if (field in raw) {
3078
+ actions.push({
3079
+ description: `Remove v1 field "${field}" from config.json (server-owned in v2)`,
3080
+ path: join18(targetDir, ".flydocs", "config.json"),
3081
+ type: "field"
3082
+ });
3083
+ }
3084
+ }
3085
+ }
3086
+ } catch {
3087
+ }
3088
+ return actions;
3089
+ }
3090
+ async function executeCleanup(targetDir, actions) {
3091
+ const fileRemovals = actions.filter((a) => a.type === "file");
3092
+ const lineRemovals = actions.filter((a) => a.type === "line");
3093
+ const fieldRemovals = actions.filter((a) => a.type === "field");
3094
+ for (const action of fileRemovals) {
3095
+ await rm4(action.path, { force: true });
3096
+ }
3097
+ const envFiles = /* @__PURE__ */ new Map();
3098
+ for (const action of lineRemovals) {
3099
+ if (!envFiles.has(action.path)) {
3100
+ const content = await readFile13(action.path, "utf-8");
3101
+ envFiles.set(action.path, content.split("\n"));
3102
+ }
3103
+ }
3104
+ for (const [envPath, lines] of envFiles) {
3105
+ const filtered = lines.filter((line) => {
3106
+ const trimmed = line.trim();
3107
+ return !trimmed.startsWith("FLYDOCS_API_KEY=") && !trimmed.startsWith("FLYDOCS_RELAY_URL=");
3108
+ });
3109
+ const hasContent = filtered.some(
3110
+ (l) => l.trim().length > 0 && !l.trim().startsWith("#")
3111
+ );
3112
+ if (!hasContent) {
3113
+ await rm4(envPath, { force: true });
3114
+ } else {
3115
+ await writeFile11(envPath, filtered.join("\n"), "utf-8");
3116
+ }
3117
+ }
3118
+ if (fieldRemovals.length > 0) {
3119
+ const configPath = join18(targetDir, ".flydocs", "config.json");
3120
+ const content = await readFile13(configPath, "utf-8");
3121
+ const config = JSON.parse(content);
3122
+ for (const action of fieldRemovals) {
3123
+ const match = action.description.match(/"(\w+)"/);
3124
+ if (match) {
3125
+ delete config[match[1]];
3126
+ }
3127
+ }
3128
+ await writeFile11(
3129
+ configPath,
3130
+ JSON.stringify(config, null, 2) + "\n",
3131
+ "utf-8"
3132
+ );
3133
+ }
3134
+ }
3135
+ var cleanup_default;
3136
+ var init_cleanup = __esm({
3137
+ "src/commands/cleanup.ts"() {
3138
+ "use strict";
3139
+ init_ui();
3140
+ init_fs_ops();
3141
+ init_config();
3142
+ init_types();
3143
+ init_global_config();
3144
+ cleanup_default = defineCommand2({
3145
+ meta: {
3146
+ name: "cleanup",
3147
+ description: "Remove legacy v1 artifacts after migration (dry-run by default)"
3148
+ },
3149
+ args: {
3150
+ confirm: {
3151
+ type: "boolean",
3152
+ description: "Actually remove artifacts (default: dry-run only)",
3153
+ default: false
3154
+ },
3155
+ path: {
3156
+ type: "string",
3157
+ description: "Path to project directory"
3158
+ }
3159
+ },
3160
+ async run({ args }) {
3161
+ const targetDir = args.path ?? process.cwd();
3162
+ console.log();
3163
+ console.log(` ${pc7.bold("FlyDocs Cleanup")}`);
3164
+ console.log();
3165
+ const hasConfig = await pathExists(
3166
+ join18(targetDir, ".flydocs", "config.json")
3167
+ );
3168
+ if (!hasConfig) {
3169
+ printError("No .flydocs/config.json found. Run flydocs init first.");
3170
+ process.exit(1);
3171
+ }
3172
+ const hasGlobalCreds = await pathExists(credentialsPath());
3173
+ if (!hasGlobalCreds) {
3174
+ printWarning(
3175
+ "No global credentials found. Run flydocs init to set up credentials before cleanup."
3176
+ );
3177
+ }
3178
+ const actions = await scanArtifacts(targetDir);
3179
+ if (actions.length === 0) {
3180
+ printStatus("No legacy artifacts found. Already clean.");
3181
+ console.log();
3182
+ return;
3183
+ }
3184
+ const mode = args.confirm ? "Removing" : "Would remove";
3185
+ printInfo(`${mode} ${actions.length} legacy artifact(s):`);
3186
+ console.log();
3187
+ for (const action of actions) {
3188
+ const icon = args.confirm ? pc7.red("-") : pc7.yellow("?");
3189
+ console.log(` ${icon} ${action.description}`);
3190
+ }
3191
+ console.log();
3192
+ if (!args.confirm) {
3193
+ printInfo(
3194
+ `Dry-run complete. Run ${pc7.cyan("flydocs cleanup --confirm")} to remove.`
3195
+ );
3196
+ console.log();
3197
+ return;
3198
+ }
3199
+ await executeCleanup(targetDir, actions);
3200
+ printCompletionBox("Cleanup Complete", [
3201
+ `${pc7.green("+")} Removed ${actions.length} legacy artifact(s)`
3202
+ ]);
3203
+ console.log();
3204
+ }
3205
+ });
3206
+ }
3207
+ });
3208
+
2990
3209
  // 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";
3210
+ import { readFile as readFile14, writeFile as writeFile12, readdir as readdir4, stat as stat2 } from "fs/promises";
3211
+ import { join as join19, dirname as dirname3, relative, resolve as resolve3 } from "path";
2993
3212
  async function readWorkspaceFile(dir) {
2994
- const filePath = join18(dir, WORKSPACE_FILENAME);
3213
+ const filePath = join19(dir, WORKSPACE_FILENAME);
2995
3214
  if (!await pathExists(filePath)) return null;
2996
- const content = await readFile13(filePath, "utf-8");
3215
+ const content = await readFile14(filePath, "utf-8");
2997
3216
  const parsed = JSON.parse(content);
2998
3217
  validateWorkspaceFile(parsed);
2999
3218
  return parsed;
3000
3219
  }
3001
3220
  async function writeWorkspaceFile(dir, workspace) {
3002
- const filePath = join18(dir, WORKSPACE_FILENAME);
3221
+ const filePath = join19(dir, WORKSPACE_FILENAME);
3003
3222
  const content = JSON.stringify(workspace, null, 2) + "\n";
3004
- await writeFile11(filePath, content, "utf-8");
3223
+ await writeFile12(filePath, content, "utf-8");
3005
3224
  }
3006
3225
  async function detectSiblingRepos(parentDir) {
3007
3226
  const repos = {};
3008
3227
  const entries = await readdir4(parentDir);
3009
3228
  for (const entry of entries) {
3010
3229
  if (entry.startsWith(".")) continue;
3011
- const entryPath = join18(parentDir, entry);
3230
+ const entryPath = join19(parentDir, entry);
3012
3231
  const entryStat = await stat2(entryPath).catch(() => null);
3013
3232
  if (!entryStat?.isDirectory()) continue;
3014
- const hasGit = await pathExists(join18(entryPath, ".git"));
3233
+ const hasGit = await pathExists(join19(entryPath, ".git"));
3015
3234
  const hasFlydocs = await pathExists(
3016
- join18(entryPath, ".flydocs", "config.json")
3235
+ join19(entryPath, ".flydocs", "config.json")
3017
3236
  );
3018
3237
  if (hasGit || hasFlydocs) {
3019
3238
  repos[entry] = { path: `./${entry}` };
@@ -3028,10 +3247,10 @@ async function readSiblingDescriptors(workspaceDir, repos) {
3028
3247
  const descriptors = [];
3029
3248
  for (const [name, entry] of Object.entries(repos)) {
3030
3249
  const repoDir = resolve3(workspaceDir, entry.path);
3031
- const serviceJsonPath = join18(repoDir, "flydocs", "context", "service.json");
3250
+ const serviceJsonPath = join19(repoDir, "flydocs", "context", "service.json");
3032
3251
  if (!await pathExists(serviceJsonPath)) continue;
3033
3252
  try {
3034
- const content = await readFile13(serviceJsonPath, "utf-8");
3253
+ const content = await readFile14(serviceJsonPath, "utf-8");
3035
3254
  const descriptor = JSON.parse(content);
3036
3255
  descriptors.push({ name, path: entry.path, descriptor });
3037
3256
  } catch {
@@ -3139,16 +3358,18 @@ var init_exports = {};
3139
3358
  __export(init_exports, {
3140
3359
  default: () => init_default
3141
3360
  });
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";
3361
+ import { defineCommand as defineCommand3 } from "citty";
3362
+ import { text as text2, confirm as confirm3, select as select2, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
3363
+ import pc8 from "picocolors";
3364
+ import { join as join20 } from "path";
3365
+ import { mkdir as mkdir9, writeFile as writeFile13 } from "fs/promises";
3366
+ import { execFile } from "child_process";
3367
+ import { promisify } from "util";
3147
3368
  async function resolveAndValidateKey(keyArg, targetDir) {
3148
3369
  let resolved = await resolveApiKey(keyArg, targetDir);
3149
3370
  if (!resolved) {
3150
3371
  console.log(
3151
- ` ${pc7.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3372
+ ` ${pc8.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3152
3373
  );
3153
3374
  console.log();
3154
3375
  const keyInput = await text2({
@@ -3178,32 +3399,56 @@ async function resolveAndValidateKey(keyArg, targetDir) {
3178
3399
  printError("Invalid API key. Check your key and try again.");
3179
3400
  process.exit(1);
3180
3401
  }
3181
- printStatus(`Authenticated with ${pc7.bold(result.org)}`);
3402
+ printStatus(`Authenticated with ${pc8.bold(result.org)}`);
3182
3403
  } catch {
3183
3404
  printError(
3184
3405
  "Could not reach FlyDocs API. Check your network and try again."
3185
3406
  );
3186
3407
  console.log(
3187
- ` ${pc7.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3408
+ ` ${pc8.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3188
3409
  );
3189
3410
  process.exit(1);
3190
3411
  }
3412
+ const workspaces = await fetchWorkspaces(apiKey);
3413
+ let workspaceId;
3414
+ if (workspaces.length === 0) {
3415
+ printError("No workspaces found. Create one at app.flydocs.ai first.");
3416
+ process.exit(1);
3417
+ } else if (workspaces.length === 1) {
3418
+ workspaceId = workspaces[0].id;
3419
+ printStatus(`Workspace: ${pc8.bold(workspaces[0].name)}`);
3420
+ } else {
3421
+ const choice = await select2({
3422
+ message: "Select workspace",
3423
+ options: workspaces.map((ws) => ({
3424
+ value: ws.id,
3425
+ label: ws.name
3426
+ }))
3427
+ });
3428
+ if (isCancel4(choice)) {
3429
+ cancel3("Init cancelled.");
3430
+ process.exit(0);
3431
+ }
3432
+ workspaceId = choice;
3433
+ }
3191
3434
  await writeGlobalCredential({
3192
3435
  apiKey,
3436
+ workspaceId,
3193
3437
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3194
3438
  lastValidated: (/* @__PURE__ */ new Date()).toISOString()
3195
3439
  });
3196
3440
  await checkCredentialPermissions();
3197
- return apiKey;
3441
+ return { apiKey, workspaceId };
3198
3442
  }
3199
- async function pullServerConfig(apiKey, targetDir) {
3443
+ async function pullServerConfig(apiKey, targetDir, workspaceId) {
3200
3444
  printInfo("Pulling config from server...");
3201
3445
  const isFirstInit = !await pathExists(
3202
- join19(targetDir, ".flydocs", "config.json")
3446
+ join20(targetDir, ".flydocs", "config.json")
3203
3447
  );
3204
3448
  try {
3205
3449
  const response = await fetchConfigV2(apiKey, {
3206
- includeContext: isFirstInit
3450
+ includeContext: isFirstInit,
3451
+ workspaceId
3207
3452
  });
3208
3453
  if (!response.valid) {
3209
3454
  printError("Server returned invalid config. Contact support.");
@@ -3215,7 +3460,7 @@ async function pullServerConfig(apiKey, targetDir) {
3215
3460
  printError(`Server error: ${err.message}`);
3216
3461
  if (err.status === 401) {
3217
3462
  console.log(
3218
- ` ${pc7.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3463
+ ` ${pc8.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3219
3464
  );
3220
3465
  }
3221
3466
  } else {
@@ -3227,7 +3472,7 @@ async function pullServerConfig(apiKey, targetDir) {
3227
3472
  async function initSingleRepo(targetDir, apiKey, serverResponse) {
3228
3473
  const actions = [];
3229
3474
  const skipped = [];
3230
- await mkdir9(join19(targetDir, ".flydocs"), { recursive: true });
3475
+ await mkdir9(join20(targetDir, ".flydocs"), { recursive: true });
3231
3476
  const configWithHash = applyConfigHash(serverResponse.config);
3232
3477
  await writeConfig(targetDir, configWithHash);
3233
3478
  actions.push("Wrote .flydocs/config.json (from server)");
@@ -3240,18 +3485,18 @@ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3240
3485
  actions.push("Wrote ~/.flydocs/me.json");
3241
3486
  }
3242
3487
  if (serverResponse.context) {
3243
- const contextDir = join19(targetDir, "flydocs", "context");
3488
+ const contextDir = join20(targetDir, "flydocs", "context");
3244
3489
  await mkdir9(contextDir, { recursive: true });
3245
- const projectMdPath = join19(contextDir, "project.md");
3490
+ const projectMdPath = join20(contextDir, "project.md");
3246
3491
  if (!await pathExists(projectMdPath)) {
3247
- await writeFile12(projectMdPath, serverResponse.context.projectMd, "utf-8");
3492
+ await writeFile13(projectMdPath, serverResponse.context.projectMd, "utf-8");
3248
3493
  actions.push("Wrote flydocs/context/project.md");
3249
3494
  } else {
3250
3495
  skipped.push("flydocs/context/project.md (already exists)");
3251
3496
  }
3252
- const serviceJsonPath = join19(contextDir, "service.json");
3497
+ const serviceJsonPath = join20(contextDir, "service.json");
3253
3498
  if (serverResponse.context.serviceJson && !await pathExists(serviceJsonPath)) {
3254
- await writeFile12(
3499
+ await writeFile13(
3255
3500
  serviceJsonPath,
3256
3501
  JSON.stringify(serverResponse.context.serviceJson, null, 2) + "\n",
3257
3502
  "utf-8"
@@ -3264,10 +3509,44 @@ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3264
3509
  await ensureGitignore(targetDir);
3265
3510
  await ensurePlatformIgnores(targetDir);
3266
3511
  actions.push("Updated ignore files");
3512
+ const artifacts = await scanArtifacts(targetDir);
3513
+ if (artifacts.length > 0) {
3514
+ await executeCleanup(targetDir, artifacts);
3515
+ actions.push(`Cleaned ${artifacts.length} legacy artifact(s)`);
3516
+ }
3267
3517
  return { actions, skipped };
3268
3518
  }
3519
+ async function checkGitFreshness(repoDir) {
3520
+ const hasGit = await pathExists(join20(repoDir, ".git"));
3521
+ if (!hasGit) return;
3522
+ try {
3523
+ await execFileAsync("git", ["-C", repoDir, "fetch", "--quiet"], {
3524
+ timeout: 15e3
3525
+ });
3526
+ const { stdout } = await execFileAsync(
3527
+ "git",
3528
+ ["-C", repoDir, "rev-list", "--count", "HEAD..@{upstream}"],
3529
+ { timeout: 5e3 }
3530
+ );
3531
+ const behind = parseInt(stdout.trim(), 10);
3532
+ if (behind <= 0) return;
3533
+ printWarning(`Repo is ${behind} commit(s) behind remote.`);
3534
+ const shouldPull = await confirm3({
3535
+ message: "Pull latest before init?"
3536
+ });
3537
+ if (isCancel4(shouldPull) || !shouldPull) {
3538
+ printInfo("Continuing without pulling.");
3539
+ return;
3540
+ }
3541
+ await execFileAsync("git", ["-C", repoDir, "pull", "--ff-only"], {
3542
+ timeout: 3e4
3543
+ });
3544
+ printStatus("Pulled latest from remote.");
3545
+ } catch {
3546
+ }
3547
+ }
3269
3548
  async function isParentDirectory(dir) {
3270
- const hasGit = await pathExists(join19(dir, ".git"));
3549
+ const hasGit = await pathExists(join20(dir, ".git"));
3271
3550
  if (hasGit) return false;
3272
3551
  const repos = await detectSiblingRepos(dir);
3273
3552
  return Object.keys(repos).length >= 2;
@@ -3277,7 +3556,7 @@ async function runMultiRepoInit(parentDir, keyArg) {
3277
3556
  const repoNames = Object.keys(repos).sort();
3278
3557
  printInfo(`Detected ${repoNames.length} repos:`);
3279
3558
  for (const name of repoNames) {
3280
- console.log(` ${pc7.cyan(name)}`);
3559
+ console.log(` ${pc8.cyan(name)}`);
3281
3560
  }
3282
3561
  console.log();
3283
3562
  const shouldContinue = await confirm3({
@@ -3287,8 +3566,11 @@ async function runMultiRepoInit(parentDir, keyArg) {
3287
3566
  cancel3("Init cancelled.");
3288
3567
  process.exit(0);
3289
3568
  }
3290
- const firstRepoDir = join19(parentDir, repoNames[0]);
3291
- const apiKey = await resolveAndValidateKey(keyArg, firstRepoDir);
3569
+ const firstRepoDir = join20(parentDir, repoNames[0]);
3570
+ const { apiKey, workspaceId } = await resolveAndValidateKey(
3571
+ keyArg,
3572
+ firstRepoDir
3573
+ );
3292
3574
  const allActions = [
3293
3575
  "Stored credential globally (~/.flydocs/credentials)"
3294
3576
  ];
@@ -3296,10 +3578,11 @@ async function runMultiRepoInit(parentDir, keyArg) {
3296
3578
  let allWarnings = [];
3297
3579
  let firstResponse;
3298
3580
  for (const repoName of repoNames) {
3299
- const repoDir = join19(parentDir, repoName);
3581
+ const repoDir = join20(parentDir, repoName);
3300
3582
  console.log();
3301
- printInfo(`Initializing ${pc7.bold(repoName)}...`);
3302
- const serverResponse = await pullServerConfig(apiKey, repoDir);
3583
+ printInfo(`Initializing ${pc8.bold(repoName)}...`);
3584
+ await checkGitFreshness(repoDir);
3585
+ const serverResponse = await pullServerConfig(apiKey, repoDir, workspaceId);
3303
3586
  if (!firstResponse) firstResponse = serverResponse;
3304
3587
  const { actions, skipped } = await initSingleRepo(
3305
3588
  repoDir,
@@ -3327,47 +3610,60 @@ async function runMultiRepoInit(parentDir, keyArg) {
3327
3610
  await writeWorkspaceFile(parentDir, workspaceFile);
3328
3611
  allActions.push(`.flydocs-workspace.json (${repoNames.length} repos)`);
3329
3612
  }
3330
- const contextDir = join19(parentDir, "flydocs", "context");
3331
- const workspaceMdPath = join19(contextDir, "workspace.md");
3613
+ const contextDir = join20(parentDir, "flydocs", "context");
3614
+ const workspaceMdPath = join20(contextDir, "workspace.md");
3332
3615
  if (await pathExists(workspaceMdPath)) {
3333
3616
  allSkipped.push("flydocs/context/workspace.md (already exists)");
3334
3617
  } else {
3335
3618
  await mkdir9(contextDir, { recursive: true });
3336
3619
  const workspaceJson = await readWorkspaceFile(parentDir) ?? buildWorkspaceFile(firstResponse.workspaceId, repos);
3337
3620
  const content = firstResponse.context?.workspaceMd ?? await generateWorkspaceMd(parentDir, workspaceJson);
3338
- await writeFile12(workspaceMdPath, content, "utf-8");
3621
+ await writeFile13(workspaceMdPath, content, "utf-8");
3339
3622
  const source = firstResponse.context?.workspaceMd ? "from server" : "generated locally";
3340
3623
  allActions.push(`Wrote flydocs/context/workspace.md (${source})`);
3341
3624
  }
3342
3625
  }
3343
3626
  console.log();
3344
- printInitReport(allActions, allSkipped, allWarnings);
3627
+ printInitReport(
3628
+ allActions,
3629
+ allSkipped,
3630
+ allWarnings,
3631
+ !!firstResponse?.context
3632
+ );
3345
3633
  }
3346
- function printInitReport(actions, skipped, warnings) {
3634
+ function printInitReport(actions, skipped, warnings, hasContext) {
3347
3635
  console.log();
3348
3636
  const lines = [];
3349
3637
  for (const action of actions) {
3350
- lines.push(`${pc7.green("+")} ${action}`);
3638
+ lines.push(`${pc8.green("+")} ${action}`);
3351
3639
  }
3352
3640
  for (const skip of skipped) {
3353
- lines.push(`${pc7.dim("-")} ${skip}`);
3641
+ lines.push(`${pc8.dim("-")} ${skip}`);
3642
+ }
3643
+ if (!hasContext) {
3644
+ lines.push("");
3645
+ lines.push(
3646
+ `${pc8.yellow("!")} No generated context yet. An admin can generate it from the portal.`
3647
+ );
3354
3648
  }
3355
3649
  if (warnings.length > 0) {
3356
3650
  lines.push("");
3357
3651
  for (const warning of warnings) {
3358
- lines.push(`${pc7.yellow("!")} ${warning}`);
3652
+ lines.push(`${pc8.yellow("!")} ${warning}`);
3359
3653
  }
3360
3654
  }
3361
3655
  printCompletionBox("FlyDocs Initialized", lines);
3362
3656
  console.log();
3363
3657
  console.log(` Next steps:`);
3364
3658
  console.log(
3365
- ` ${pc7.cyan("flydocs sync")} \u2014 pull latest config and templates`
3659
+ ` ${pc8.cyan("flydocs sync")} Pull latest config and templates`
3660
+ );
3661
+ console.log(
3662
+ ` ${pc8.cyan("cursor .")} ${pc8.dim("or")} ${pc8.cyan("code .")} Open your IDE, then use ${pc8.bold("/start-session")}`
3366
3663
  );
3367
- console.log(` ${pc7.cyan("/start-session")} \u2014 begin working`);
3368
3664
  console.log();
3369
3665
  }
3370
- var init_default;
3666
+ var execFileAsync, init_default;
3371
3667
  var init_init = __esm({
3372
3668
  "src/commands/init.ts"() {
3373
3669
  "use strict";
@@ -3379,8 +3675,10 @@ var init_init = __esm({
3379
3675
  init_gitignore();
3380
3676
  init_fs_ops();
3381
3677
  init_relay_client();
3678
+ init_cleanup();
3382
3679
  init_workspace();
3383
- init_default = defineCommand2({
3680
+ execFileAsync = promisify(execFile);
3681
+ init_default = defineCommand3({
3384
3682
  meta: {
3385
3683
  name: "init",
3386
3684
  description: "Initialize FlyDocs in this project (unified setup)"
@@ -3398,14 +3696,22 @@ var init_init = __esm({
3398
3696
  async run({ args }) {
3399
3697
  const targetDir = args.path ?? process.cwd();
3400
3698
  console.log();
3401
- console.log(` ${pc7.bold("FlyDocs Init")}`);
3699
+ console.log(` ${pc8.bold("FlyDocs Init")}`);
3402
3700
  console.log();
3403
3701
  if (await isParentDirectory(targetDir)) {
3404
3702
  await runMultiRepoInit(targetDir, args.key);
3405
3703
  return;
3406
3704
  }
3407
- const apiKey = await resolveAndValidateKey(args.key, targetDir);
3408
- const serverResponse = await pullServerConfig(apiKey, targetDir);
3705
+ await checkGitFreshness(targetDir);
3706
+ const { apiKey, workspaceId } = await resolveAndValidateKey(
3707
+ args.key,
3708
+ targetDir
3709
+ );
3710
+ const serverResponse = await pullServerConfig(
3711
+ apiKey,
3712
+ targetDir,
3713
+ workspaceId
3714
+ );
3409
3715
  const { actions, skipped } = await initSingleRepo(
3410
3716
  targetDir,
3411
3717
  apiKey,
@@ -3414,7 +3720,7 @@ var init_init = __esm({
3414
3720
  actions.unshift("Stored credential globally (~/.flydocs/credentials)");
3415
3721
  const topology = serverResponse.config.topology;
3416
3722
  if (topology?.type === 4 && topology.label === "sibling-repos") {
3417
- const parentDir = join19(targetDir, "..");
3723
+ const parentDir = join20(targetDir, "..");
3418
3724
  const existing = await readWorkspaceFile(parentDir);
3419
3725
  if (existing) {
3420
3726
  skipped.push(".flydocs-workspace.json (already exists)");
@@ -3432,7 +3738,12 @@ var init_init = __esm({
3432
3738
  }
3433
3739
  }
3434
3740
  }
3435
- printInitReport(actions, skipped, serverResponse.warnings);
3741
+ printInitReport(
3742
+ actions,
3743
+ skipped,
3744
+ serverResponse.warnings,
3745
+ !!serverResponse.context
3746
+ );
3436
3747
  }
3437
3748
  });
3438
3749
  }
@@ -3443,11 +3754,11 @@ var update_exports = {};
3443
3754
  __export(update_exports, {
3444
3755
  default: () => update_default
3445
3756
  });
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";
3757
+ import { defineCommand as defineCommand4 } from "citty";
3758
+ import { resolve as resolve4, join as join21 } from "path";
3759
+ import { mkdir as mkdir10, cp as cp2, readFile as readFile15, readdir as readdir5, rm as rm5 } from "fs/promises";
3760
+ import { select as select3, text as text3, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3761
+ import pc9 from "picocolors";
3451
3762
  var update_default;
3452
3763
  var init_update = __esm({
3453
3764
  "src/commands/update.ts"() {
@@ -3466,7 +3777,7 @@ var init_update = __esm({
3466
3777
  init_update_check();
3467
3778
  init_telemetry();
3468
3779
  init_integrity();
3469
- update_default = defineCommand3({
3780
+ update_default = defineCommand4({
3470
3781
  meta: {
3471
3782
  name: "update",
3472
3783
  description: "Update an existing FlyDocs installation"
@@ -3515,7 +3826,7 @@ var init_update = __esm({
3515
3826
  } else if (args.here) {
3516
3827
  targetDir = process.cwd();
3517
3828
  } else {
3518
- const choice = await select2({
3829
+ const choice = await select3({
3519
3830
  message: "Which project would you like to update?",
3520
3831
  options: [
3521
3832
  {
@@ -3553,9 +3864,9 @@ var init_update = __esm({
3553
3864
  }
3554
3865
  targetDir = resolve4(targetDir);
3555
3866
  process.chdir(targetDir);
3556
- const hasVersion = await pathExists(join20(targetDir, ".flydocs", "version"));
3867
+ const hasVersion = await pathExists(join21(targetDir, ".flydocs", "version"));
3557
3868
  const hasConfig = await pathExists(
3558
- join20(targetDir, ".flydocs", "config.json")
3869
+ join21(targetDir, ".flydocs", "config.json")
3559
3870
  );
3560
3871
  if (!hasVersion && !hasConfig) {
3561
3872
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -3566,8 +3877,8 @@ var init_update = __esm({
3566
3877
  console.log();
3567
3878
  let currentVersion = "0.1.0";
3568
3879
  if (hasVersion) {
3569
- const vContent = await readFile14(
3570
- join20(targetDir, ".flydocs", "version"),
3880
+ const vContent = await readFile15(
3881
+ join21(targetDir, ".flydocs", "version"),
3571
3882
  "utf-8"
3572
3883
  );
3573
3884
  currentVersion = vContent.trim();
@@ -3601,10 +3912,10 @@ var init_update = __esm({
3601
3912
  });
3602
3913
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
3603
3914
  console.log();
3604
- const changelogPath = join20(templateDir, "CHANGELOG.md");
3915
+ const changelogPath = join21(templateDir, "CHANGELOG.md");
3605
3916
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
3606
3917
  if (whatsNew.length > 0) {
3607
- console.log(pc8.cyan("What's new:"));
3918
+ console.log(pc9.cyan("What's new:"));
3608
3919
  console.log();
3609
3920
  for (const entry of whatsNew) {
3610
3921
  console.log(` ${entry}`);
@@ -3613,23 +3924,23 @@ var init_update = __esm({
3613
3924
  }
3614
3925
  const now = /* @__PURE__ */ new Date();
3615
3926
  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")}`;
3616
- const backupDir = join20(targetDir, ".flydocs", `backup-${ts}`);
3927
+ const backupDir = join21(targetDir, ".flydocs", `backup-${ts}`);
3617
3928
  await mkdir10(backupDir, { recursive: true });
3618
3929
  if (hasConfig) {
3619
3930
  await cp2(
3620
- join20(targetDir, ".flydocs", "config.json"),
3621
- join20(backupDir, "config.json")
3931
+ join21(targetDir, ".flydocs", "config.json"),
3932
+ join21(backupDir, "config.json")
3622
3933
  );
3623
3934
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
3624
3935
  }
3625
3936
  try {
3626
- const flydocsDir = join20(targetDir, ".flydocs");
3937
+ const flydocsDir = join21(targetDir, ".flydocs");
3627
3938
  const entries = await readdir5(flydocsDir);
3628
3939
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
3629
3940
  if (backups.length > 3) {
3630
3941
  const toRemove = backups.slice(0, backups.length - 3);
3631
3942
  for (const old of toRemove) {
3632
- await rm4(join20(flydocsDir, old), { recursive: true, force: true });
3943
+ await rm5(join21(flydocsDir, old), { recursive: true, force: true });
3633
3944
  }
3634
3945
  }
3635
3946
  } catch {
@@ -3668,17 +3979,17 @@ var init_update = __esm({
3668
3979
  await ensureDirectories(targetDir, effectiveTier);
3669
3980
  console.log("Replacing framework directories...");
3670
3981
  await replaceDirectory(
3671
- join20(templateDir, ".flydocs", "templates"),
3672
- join20(targetDir, ".flydocs", "templates")
3982
+ join21(templateDir, ".flydocs", "templates"),
3983
+ join21(targetDir, ".flydocs", "templates")
3673
3984
  );
3674
3985
  printStatus(".flydocs/templates");
3675
3986
  await replaceDirectory(
3676
- join20(templateDir, ".claude", "hooks"),
3677
- join20(targetDir, ".claude", "hooks")
3987
+ join21(templateDir, ".claude", "hooks"),
3988
+ join21(targetDir, ".claude", "hooks")
3678
3989
  );
3679
3990
  printStatus(".claude/hooks");
3680
3991
  const hasExistingAgents = await pathExists(
3681
- join20(targetDir, ".claude", "agents")
3992
+ join21(targetDir, ".claude", "agents")
3682
3993
  );
3683
3994
  let installAgents;
3684
3995
  if (args.yes) {
@@ -3687,7 +3998,7 @@ var init_update = __esm({
3687
3998
  installAgents = true;
3688
3999
  } else {
3689
4000
  console.log();
3690
- console.log(` ${pc8.bold(pc8.yellow("Sub-Agents (Recommended)"))}`);
4001
+ console.log(` ${pc9.bold(pc9.yellow("Sub-Agents (Recommended)"))}`);
3691
4002
  console.log();
3692
4003
  console.log(
3693
4004
  " Sub-agents are specialized roles (PM, implementation, review,"
@@ -3710,20 +4021,20 @@ var init_update = __esm({
3710
4021
  }
3711
4022
  }
3712
4023
  if (installAgents) {
3713
- const claudeAgentsSrc = join20(templateDir, ".claude", "agents");
4024
+ const claudeAgentsSrc = join21(templateDir, ".claude", "agents");
3714
4025
  if (await pathExists(claudeAgentsSrc)) {
3715
- await mkdir10(join20(targetDir, ".claude", "agents"), { recursive: true });
4026
+ await mkdir10(join21(targetDir, ".claude", "agents"), { recursive: true });
3716
4027
  await copyDirectoryContents(
3717
4028
  claudeAgentsSrc,
3718
- join20(targetDir, ".claude", "agents")
4029
+ join21(targetDir, ".claude", "agents")
3719
4030
  );
3720
4031
  }
3721
- const cursorAgentsSrc = join20(templateDir, ".cursor", "agents");
4032
+ const cursorAgentsSrc = join21(templateDir, ".cursor", "agents");
3722
4033
  if (await pathExists(cursorAgentsSrc)) {
3723
- await mkdir10(join20(targetDir, ".cursor", "agents"), { recursive: true });
4034
+ await mkdir10(join21(targetDir, ".cursor", "agents"), { recursive: true });
3724
4035
  await copyDirectoryContents(
3725
4036
  cursorAgentsSrc,
3726
- join20(targetDir, ".cursor", "agents")
4037
+ join21(targetDir, ".cursor", "agents")
3727
4038
  );
3728
4039
  }
3729
4040
  printStatus(
@@ -3737,47 +4048,47 @@ var init_update = __esm({
3737
4048
  console.log();
3738
4049
  console.log("Replacing framework files...");
3739
4050
  await copyFile(
3740
- join20(templateDir, ".claude", "CLAUDE.md"),
3741
- join20(targetDir, ".claude", "CLAUDE.md")
4051
+ join21(templateDir, ".claude", "CLAUDE.md"),
4052
+ join21(targetDir, ".claude", "CLAUDE.md")
3742
4053
  );
3743
4054
  await copyFile(
3744
- join20(templateDir, ".claude", "settings.json"),
3745
- join20(targetDir, ".claude", "settings.json")
4055
+ join21(templateDir, ".claude", "settings.json"),
4056
+ join21(targetDir, ".claude", "settings.json")
3746
4057
  );
3747
4058
  printStatus(".claude/CLAUDE.md, settings.json");
3748
4059
  await copyDirectoryContents(
3749
- join20(templateDir, ".claude", "commands"),
3750
- join20(targetDir, ".claude", "commands")
4060
+ join21(templateDir, ".claude", "commands"),
4061
+ join21(targetDir, ".claude", "commands")
3751
4062
  );
3752
4063
  await copyDirectoryContents(
3753
- join20(templateDir, ".claude", "commands"),
3754
- join20(targetDir, ".cursor", "commands")
4064
+ join21(templateDir, ".claude", "commands"),
4065
+ join21(targetDir, ".cursor", "commands")
3755
4066
  );
3756
4067
  printStatus(".claude/commands, .cursor/commands");
3757
- const skillsReadmeSrc = join20(templateDir, ".claude", "skills", "README.md");
4068
+ const skillsReadmeSrc = join21(templateDir, ".claude", "skills", "README.md");
3758
4069
  if (await pathExists(skillsReadmeSrc)) {
3759
4070
  await copyFile(
3760
4071
  skillsReadmeSrc,
3761
- join20(targetDir, ".claude", "skills", "README.md")
4072
+ join21(targetDir, ".claude", "skills", "README.md")
3762
4073
  );
3763
4074
  }
3764
4075
  printStatus(".claude/skills/README.md");
3765
4076
  await copyFile(
3766
- join20(templateDir, ".cursor", "hooks.json"),
3767
- join20(targetDir, ".cursor", "hooks.json")
4077
+ join21(templateDir, ".cursor", "hooks.json"),
4078
+ join21(targetDir, ".cursor", "hooks.json")
3768
4079
  );
3769
4080
  printStatus(".cursor/hooks.json");
3770
4081
  await copyFile(
3771
- join20(templateDir, "AGENTS.md"),
3772
- join20(targetDir, "AGENTS.md")
4082
+ join21(templateDir, "AGENTS.md"),
4083
+ join21(targetDir, "AGENTS.md")
3773
4084
  );
3774
4085
  printStatus("AGENTS.md");
3775
- const envExampleSrc = join20(templateDir, ".env.example");
4086
+ const envExampleSrc = join21(templateDir, ".env.example");
3776
4087
  if (await pathExists(envExampleSrc)) {
3777
- await copyFile(envExampleSrc, join20(targetDir, ".env.example"));
4088
+ await copyFile(envExampleSrc, join21(targetDir, ".env.example"));
3778
4089
  printStatus(".env.example");
3779
4090
  }
3780
- const knowledgeTemplatesDir = join20(
4091
+ const knowledgeTemplatesDir = join21(
3781
4092
  targetDir,
3782
4093
  "flydocs",
3783
4094
  "knowledge",
@@ -3787,8 +4098,8 @@ var init_update = __esm({
3787
4098
  await mkdir10(knowledgeTemplatesDir, { recursive: true });
3788
4099
  }
3789
4100
  for (const tmpl of ["decision.md", "feature.md", "note.md"]) {
3790
- const src = join20(templateDir, "flydocs", "knowledge", "templates", tmpl);
3791
- const dest = join20(knowledgeTemplatesDir, tmpl);
4101
+ const src = join21(templateDir, "flydocs", "knowledge", "templates", tmpl);
4102
+ const dest = join21(knowledgeTemplatesDir, tmpl);
3792
4103
  if (await pathExists(src) && !await pathExists(dest)) {
3793
4104
  await copyFile(src, dest);
3794
4105
  }
@@ -3815,18 +4126,18 @@ var init_update = __esm({
3815
4126
  printWarning("Config merge failed \u2014 config.json preserved as-is");
3816
4127
  }
3817
4128
  await copyFile(
3818
- join20(templateDir, ".flydocs", "version"),
3819
- join20(targetDir, ".flydocs", "version")
4129
+ join21(templateDir, ".flydocs", "version"),
4130
+ join21(targetDir, ".flydocs", "version")
3820
4131
  );
3821
4132
  printStatus(`.flydocs/version \u2192 ${version}`);
3822
- const clSrc = join20(templateDir, "CHANGELOG.md");
4133
+ const clSrc = join21(templateDir, "CHANGELOG.md");
3823
4134
  if (await pathExists(clSrc)) {
3824
- await copyFile(clSrc, join20(targetDir, ".flydocs", "CHANGELOG.md"));
4135
+ await copyFile(clSrc, join21(targetDir, ".flydocs", "CHANGELOG.md"));
3825
4136
  printStatus(".flydocs/CHANGELOG.md");
3826
4137
  }
3827
- const mfSrc = join20(templateDir, "manifest.json");
4138
+ const mfSrc = join21(templateDir, "manifest.json");
3828
4139
  if (await pathExists(mfSrc)) {
3829
- await copyFile(mfSrc, join20(targetDir, ".flydocs", "manifest.json"));
4140
+ await copyFile(mfSrc, join21(targetDir, ".flydocs", "manifest.json"));
3830
4141
  printStatus(".flydocs/manifest.json");
3831
4142
  }
3832
4143
  await generateIntegrity(targetDir, version);
@@ -3888,13 +4199,13 @@ var uninstall_exports = {};
3888
4199
  __export(uninstall_exports, {
3889
4200
  default: () => uninstall_default
3890
4201
  });
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";
4202
+ import { defineCommand as defineCommand5 } from "citty";
4203
+ import { resolve as resolve5, join as join22 } from "path";
4204
+ import { readdir as readdir6, rm as rm6, rename as rename2 } from "fs/promises";
4205
+ import { confirm as confirm5, select as select4, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
4206
+ import pc10 from "picocolors";
3896
4207
  async function removeOwnedSkills(targetDir) {
3897
- const skillsDir = join21(targetDir, ".claude", "skills");
4208
+ const skillsDir = join22(targetDir, ".claude", "skills");
3898
4209
  const removed = [];
3899
4210
  if (!await pathExists(skillsDir)) {
3900
4211
  return removed;
@@ -3903,7 +4214,7 @@ async function removeOwnedSkills(targetDir) {
3903
4214
  const entries = await readdir6(skillsDir);
3904
4215
  for (const entry of entries) {
3905
4216
  if (entry.startsWith(OWNED_SKILL_PREFIX)) {
3906
- await rm5(join21(skillsDir, entry), { recursive: true, force: true });
4217
+ await rm6(join22(skillsDir, entry), { recursive: true, force: true });
3907
4218
  removed.push(`.claude/skills/${entry}`);
3908
4219
  }
3909
4220
  }
@@ -3912,7 +4223,7 @@ async function removeOwnedSkills(targetDir) {
3912
4223
  return removed;
3913
4224
  }
3914
4225
  async function removeOwnedCursorRules(targetDir) {
3915
- const rulesDir = join21(targetDir, ".cursor", "rules");
4226
+ const rulesDir = join22(targetDir, ".cursor", "rules");
3916
4227
  const removed = [];
3917
4228
  if (!await pathExists(rulesDir)) {
3918
4229
  return removed;
@@ -3921,7 +4232,7 @@ async function removeOwnedCursorRules(targetDir) {
3921
4232
  const entries = await readdir6(rulesDir);
3922
4233
  for (const entry of entries) {
3923
4234
  if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
3924
- await rm5(join21(rulesDir, entry), { force: true });
4235
+ await rm6(join22(rulesDir, entry), { force: true });
3925
4236
  removed.push(`.cursor/rules/${entry}`);
3926
4237
  }
3927
4238
  }
@@ -3940,9 +4251,9 @@ async function isEmptyDir(dirPath) {
3940
4251
  async function cleanupEmptyParents(targetDir, dirs) {
3941
4252
  const cleaned = [];
3942
4253
  for (const dir of dirs) {
3943
- const fullPath = join21(targetDir, dir);
4254
+ const fullPath = join22(targetDir, dir);
3944
4255
  if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
3945
- await rm5(fullPath, { recursive: true, force: true });
4256
+ await rm6(fullPath, { recursive: true, force: true });
3946
4257
  cleaned.push(dir);
3947
4258
  }
3948
4259
  }
@@ -3972,7 +4283,7 @@ var init_uninstall = __esm({
3972
4283
  ];
3973
4284
  OWNED_SKILL_PREFIX = "flydocs-";
3974
4285
  OWNED_RULE_PREFIX = "flydocs-";
3975
- uninstall_default = defineCommand4({
4286
+ uninstall_default = defineCommand5({
3976
4287
  meta: {
3977
4288
  name: "uninstall",
3978
4289
  description: "Remove FlyDocs from a project directory"
@@ -4019,8 +4330,8 @@ var init_uninstall = __esm({
4019
4330
  process.exit(1);
4020
4331
  }
4021
4332
  targetDir = resolve5(targetDir);
4022
- const hasFlydocs = await pathExists(join21(targetDir, ".flydocs"));
4023
- const hasAgentsMd = await pathExists(join21(targetDir, "AGENTS.md"));
4333
+ const hasFlydocs = await pathExists(join22(targetDir, ".flydocs"));
4334
+ const hasAgentsMd = await pathExists(join22(targetDir, "AGENTS.md"));
4024
4335
  if (!hasFlydocs && !hasAgentsMd) {
4025
4336
  printError(`Not a FlyDocs project: ${targetDir}`);
4026
4337
  printInfo("No .flydocs/ directory or AGENTS.md found.");
@@ -4032,12 +4343,12 @@ var init_uninstall = __esm({
4032
4343
  const removeAll = forceAll || args.all;
4033
4344
  const skipPrompts = forceAll || args.yes;
4034
4345
  let contentAction = "preserve";
4035
- const hasUserContent = await pathExists(join21(targetDir, "flydocs"));
4346
+ const hasUserContent = await pathExists(join22(targetDir, "flydocs"));
4036
4347
  if (hasUserContent) {
4037
4348
  if (removeAll) {
4038
4349
  contentAction = "remove";
4039
4350
  } else if (!skipPrompts) {
4040
- const choice = await select3({
4351
+ const choice = await select4({
4041
4352
  message: "What should happen to your flydocs/ content (project docs, knowledge base)?",
4042
4353
  options: [
4043
4354
  {
@@ -4066,34 +4377,34 @@ var init_uninstall = __esm({
4066
4377
  }
4067
4378
  if (!skipPrompts) {
4068
4379
  console.log();
4069
- console.log(pc9.bold("The following will be removed:"));
4380
+ console.log(pc10.bold("The following will be removed:"));
4070
4381
  console.log();
4071
4382
  console.log(" Framework files:");
4072
4383
  for (const [path] of ALWAYS_REMOVED) {
4073
- console.log(` ${pc9.dim(path)}`);
4384
+ console.log(` ${pc10.dim(path)}`);
4074
4385
  }
4075
- console.log(` ${pc9.dim(".claude/skills/flydocs-*")}`);
4076
- console.log(` ${pc9.dim(".cursor/rules/flydocs-*.mdc")}`);
4386
+ console.log(` ${pc10.dim(".claude/skills/flydocs-*")}`);
4387
+ console.log(` ${pc10.dim(".cursor/rules/flydocs-*.mdc")}`);
4077
4388
  if (hasUserContent) {
4078
4389
  if (contentAction === "archive") {
4079
4390
  console.log();
4080
4391
  console.log(
4081
- ` User content: ${pc9.yellow("flydocs/ -> flydocs-archive/")}`
4392
+ ` User content: ${pc10.yellow("flydocs/ -> flydocs-archive/")}`
4082
4393
  );
4083
4394
  } else if (contentAction === "remove") {
4084
4395
  console.log();
4085
- console.log(` User content: ${pc9.red("flydocs/ (deleted)")}`);
4396
+ console.log(` User content: ${pc10.red("flydocs/ (deleted)")}`);
4086
4397
  } else {
4087
4398
  console.log();
4088
- console.log(` User content: ${pc9.green("flydocs/ (preserved)")}`);
4399
+ console.log(` User content: ${pc10.green("flydocs/ (preserved)")}`);
4089
4400
  }
4090
4401
  }
4091
4402
  console.log();
4092
- console.log(pc9.bold("Preserved:"));
4403
+ console.log(pc10.bold("Preserved:"));
4093
4404
  console.log(
4094
- ` ${pc9.dim(".claude/skills/ (non-flydocs community skills)")}`
4405
+ ` ${pc10.dim(".claude/skills/ (non-flydocs community skills)")}`
4095
4406
  );
4096
- console.log(` ${pc9.dim(".env, .env.local")}`);
4407
+ console.log(` ${pc10.dim(".env, .env.local")}`);
4097
4408
  console.log();
4098
4409
  const shouldContinue = await confirm5({
4099
4410
  message: "Proceed with uninstall?"
@@ -4115,16 +4426,16 @@ var init_uninstall = __esm({
4115
4426
  const removedRules = await removeOwnedCursorRules(targetDir);
4116
4427
  result.removed.push(...removedRules);
4117
4428
  for (const [relativePath, type] of ALWAYS_REMOVED) {
4118
- const fullPath = join21(targetDir, relativePath);
4429
+ const fullPath = join22(targetDir, relativePath);
4119
4430
  if (!await pathExists(fullPath)) {
4120
4431
  result.skipped.push(relativePath);
4121
4432
  continue;
4122
4433
  }
4123
4434
  try {
4124
4435
  if (type === "dir") {
4125
- await rm5(fullPath, { recursive: true, force: true });
4436
+ await rm6(fullPath, { recursive: true, force: true });
4126
4437
  } else {
4127
- await rm5(fullPath, { force: true });
4438
+ await rm6(fullPath, { force: true });
4128
4439
  }
4129
4440
  result.removed.push(relativePath);
4130
4441
  } catch {
@@ -4133,16 +4444,16 @@ var init_uninstall = __esm({
4133
4444
  }
4134
4445
  }
4135
4446
  if (hasUserContent) {
4136
- const flydocsPath = join21(targetDir, "flydocs");
4447
+ const flydocsPath = join22(targetDir, "flydocs");
4137
4448
  if (contentAction === "archive") {
4138
- const archivePath = join21(targetDir, "flydocs-archive");
4449
+ const archivePath = join22(targetDir, "flydocs-archive");
4139
4450
  if (await pathExists(archivePath)) {
4140
- await rm5(archivePath, { recursive: true, force: true });
4451
+ await rm6(archivePath, { recursive: true, force: true });
4141
4452
  }
4142
4453
  await rename2(flydocsPath, archivePath);
4143
4454
  result.archived.push("flydocs/ -> flydocs-archive/");
4144
4455
  } else if (contentAction === "remove") {
4145
- await rm5(flydocsPath, { recursive: true, force: true });
4456
+ await rm6(flydocsPath, { recursive: true, force: true });
4146
4457
  result.removed.push("flydocs/");
4147
4458
  }
4148
4459
  }
@@ -4161,17 +4472,17 @@ var init_uninstall = __esm({
4161
4472
  result.restored = originals.map((f) => f.relativePath);
4162
4473
  }
4163
4474
  console.log();
4164
- console.log(pc9.bold("Uninstall Summary"));
4475
+ console.log(pc10.bold("Uninstall Summary"));
4165
4476
  console.log();
4166
4477
  if (result.removed.length > 0) {
4167
- console.log(` ${pc9.green("Removed")} (${result.removed.length}):`);
4478
+ console.log(` ${pc10.green("Removed")} (${result.removed.length}):`);
4168
4479
  for (const item of result.removed) {
4169
4480
  printStatus(item);
4170
4481
  }
4171
4482
  }
4172
4483
  if (result.archived.length > 0) {
4173
4484
  console.log();
4174
- console.log(` ${pc9.yellow("Archived")} (${result.archived.length}):`);
4485
+ console.log(` ${pc10.yellow("Archived")} (${result.archived.length}):`);
4175
4486
  for (const item of result.archived) {
4176
4487
  printInfo(item);
4177
4488
  }
@@ -4179,7 +4490,7 @@ var init_uninstall = __esm({
4179
4490
  if (result.restored.length > 0) {
4180
4491
  console.log();
4181
4492
  console.log(
4182
- ` ${pc9.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
4493
+ ` ${pc10.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
4183
4494
  );
4184
4495
  for (const item of result.restored) {
4185
4496
  printInfo(item);
@@ -4188,16 +4499,16 @@ var init_uninstall = __esm({
4188
4499
  if (result.skipped.length > 0) {
4189
4500
  console.log();
4190
4501
  console.log(
4191
- ` ${pc9.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
4502
+ ` ${pc10.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
4192
4503
  );
4193
4504
  for (const item of result.skipped) {
4194
- console.log(` ${pc9.dim(item)}`);
4505
+ console.log(` ${pc10.dim(item)}`);
4195
4506
  }
4196
4507
  }
4197
4508
  console.log();
4198
4509
  printStatus("FlyDocs has been removed from this project.");
4199
4510
  console.log();
4200
- printInfo(`To reinstall: ${pc9.cyan("npx @flydocs/cli install --here")}`);
4511
+ printInfo(`To reinstall: ${pc10.cyan("npx @flydocs/cli install --here")}`);
4201
4512
  console.log();
4202
4513
  }
4203
4514
  });
@@ -4209,28 +4520,28 @@ var setup_exports = {};
4209
4520
  __export(setup_exports, {
4210
4521
  default: () => setup_default
4211
4522
  });
4212
- import { defineCommand as defineCommand5 } from "citty";
4213
- import pc10 from "picocolors";
4523
+ import { defineCommand as defineCommand6 } from "citty";
4524
+ import pc11 from "picocolors";
4214
4525
  var setup_default;
4215
4526
  var init_setup = __esm({
4216
4527
  "src/commands/setup.ts"() {
4217
4528
  "use strict";
4218
- setup_default = defineCommand5({
4529
+ setup_default = defineCommand6({
4219
4530
  meta: {
4220
4531
  name: "setup",
4221
4532
  description: "Configure FlyDocs settings for this project"
4222
4533
  },
4223
4534
  run() {
4224
4535
  console.log();
4225
- console.log(` ${pc10.bold("FlyDocs Setup")}`);
4536
+ console.log(` ${pc11.bold("FlyDocs Setup")}`);
4226
4537
  console.log();
4227
4538
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
4228
4539
  console.log();
4229
4540
  console.log(
4230
- ` ${pc10.cyan("Claude Code:")} Type ${pc10.bold("/flydocs-setup")} in chat`
4541
+ ` ${pc11.cyan("Claude Code:")} Type ${pc11.bold("/flydocs-setup")} in chat`
4231
4542
  );
4232
4543
  console.log(
4233
- ` ${pc10.cyan("Cursor:")} Type ${pc10.bold("/flydocs-setup")} in chat`
4544
+ ` ${pc11.cyan("Cursor:")} Type ${pc11.bold("/flydocs-setup")} in chat`
4234
4545
  );
4235
4546
  console.log();
4236
4547
  console.log(` This configures your project context, detects your stack,`);
@@ -4246,14 +4557,14 @@ var skills_exports = {};
4246
4557
  __export(skills_exports, {
4247
4558
  default: () => skills_default
4248
4559
  });
4249
- import { defineCommand as defineCommand6 } from "citty";
4250
- import pc11 from "picocolors";
4560
+ import { defineCommand as defineCommand7 } from "citty";
4561
+ import pc12 from "picocolors";
4251
4562
  var list, search, add, remove, skills_default;
4252
4563
  var init_skills2 = __esm({
4253
4564
  "src/commands/skills.ts"() {
4254
4565
  "use strict";
4255
4566
  init_skill_manager();
4256
- list = defineCommand6({
4567
+ list = defineCommand7({
4257
4568
  meta: {
4258
4569
  name: "list",
4259
4570
  description: "List installed skills"
@@ -4269,26 +4580,26 @@ var init_skills2 = __esm({
4269
4580
  console.log(`${total} skill(s) installed:`);
4270
4581
  if (result.platform.length > 0) {
4271
4582
  console.log();
4272
- console.log(pc11.bold("Platform"));
4583
+ console.log(pc12.bold("Platform"));
4273
4584
  for (const skill of result.platform) {
4274
4585
  console.log(
4275
- ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
4586
+ ` ${skill.name} ${pc12.dim(`(${skill.triggers} triggers)`)}`
4276
4587
  );
4277
4588
  }
4278
4589
  }
4279
4590
  if (result.community.length > 0) {
4280
4591
  console.log();
4281
- console.log(pc11.bold("Community"));
4592
+ console.log(pc12.bold("Community"));
4282
4593
  for (const skill of result.community) {
4283
4594
  console.log(
4284
- ` ${skill.name} ${pc11.dim(`(${skill.triggers} triggers)`)}`
4595
+ ` ${skill.name} ${pc12.dim(`(${skill.triggers} triggers)`)}`
4285
4596
  );
4286
4597
  }
4287
4598
  }
4288
4599
  console.log();
4289
4600
  }
4290
4601
  });
4291
- search = defineCommand6({
4602
+ search = defineCommand7({
4292
4603
  meta: {
4293
4604
  name: "search",
4294
4605
  description: "Search community skills"
@@ -4304,24 +4615,24 @@ var init_skills2 = __esm({
4304
4615
  const results = await searchCatalog(args.keyword);
4305
4616
  if (results.length === 0) {
4306
4617
  console.log(`No skills found for "${args.keyword}".`);
4307
- console.log(` Browse the catalog at: ${pc11.cyan("https://skills.sh/")}`);
4618
+ console.log(` Browse the catalog at: ${pc12.cyan("https://skills.sh/")}`);
4308
4619
  return;
4309
4620
  }
4310
4621
  console.log();
4311
4622
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
4312
4623
  console.log();
4313
4624
  for (const skill of results) {
4314
- console.log(` ${pc11.bold(skill.name)}`);
4625
+ console.log(` ${pc12.bold(skill.name)}`);
4315
4626
  console.log(` ${skill.description}`);
4316
- console.log(` ${pc11.dim(skill.repo)}`);
4627
+ console.log(` ${pc12.dim(skill.repo)}`);
4317
4628
  if (skill.tags.length > 0) {
4318
- console.log(` ${pc11.dim(skill.tags.join(", "))}`);
4629
+ console.log(` ${pc12.dim(skill.tags.join(", "))}`);
4319
4630
  }
4320
4631
  console.log();
4321
4632
  }
4322
4633
  }
4323
4634
  });
4324
- add = defineCommand6({
4635
+ add = defineCommand7({
4325
4636
  meta: {
4326
4637
  name: "add",
4327
4638
  description: "Install a community skill"
@@ -4337,7 +4648,7 @@ var init_skills2 = __esm({
4337
4648
  await addSkill(process.cwd(), args.source);
4338
4649
  }
4339
4650
  });
4340
- remove = defineCommand6({
4651
+ remove = defineCommand7({
4341
4652
  meta: {
4342
4653
  name: "remove",
4343
4654
  description: "Remove an installed community skill"
@@ -4353,7 +4664,7 @@ var init_skills2 = __esm({
4353
4664
  await removeSkill(process.cwd(), args.name);
4354
4665
  }
4355
4666
  });
4356
- skills_default = defineCommand6({
4667
+ skills_default = defineCommand7({
4357
4668
  meta: {
4358
4669
  name: "skills",
4359
4670
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -4373,10 +4684,10 @@ var connect_exports = {};
4373
4684
  __export(connect_exports, {
4374
4685
  default: () => connect_default
4375
4686
  });
4376
- import { defineCommand as defineCommand7 } from "citty";
4687
+ import { defineCommand as defineCommand8 } from "citty";
4377
4688
  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";
4689
+ import pc13 from "picocolors";
4690
+ import { join as join23 } from "path";
4380
4691
  var connect_default;
4381
4692
  var init_connect = __esm({
4382
4693
  "src/commands/connect.ts"() {
@@ -4386,7 +4697,7 @@ var init_connect = __esm({
4386
4697
  init_template();
4387
4698
  init_ui();
4388
4699
  init_api_key();
4389
- connect_default = defineCommand7({
4700
+ connect_default = defineCommand8({
4390
4701
  meta: {
4391
4702
  name: "connect",
4392
4703
  description: "Connect FlyDocs to a cloud provider"
@@ -4411,11 +4722,11 @@ var init_connect = __esm({
4411
4722
  },
4412
4723
  async run({ args }) {
4413
4724
  const targetDir = args.path ?? process.cwd();
4414
- const configPath = join22(targetDir, ".flydocs", "config.json");
4725
+ const configPath = join23(targetDir, ".flydocs", "config.json");
4415
4726
  if (!await pathExists(configPath)) {
4416
4727
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
4417
4728
  console.log(
4418
- ` Run ${pc12.cyan("flydocs")} first to install FlyDocs in this project.`
4729
+ ` Run ${pc13.cyan("flydocs")} first to install FlyDocs in this project.`
4419
4730
  );
4420
4731
  process.exit(1);
4421
4732
  }
@@ -4432,10 +4743,10 @@ var init_connect = __esm({
4432
4743
  }
4433
4744
  }
4434
4745
  console.log();
4435
- console.log(` ${pc12.bold("Connect to FlyDocs Cloud")}`);
4746
+ console.log(` ${pc13.bold("Connect to FlyDocs Cloud")}`);
4436
4747
  console.log();
4437
4748
  console.log(
4438
- ` ${pc12.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4749
+ ` ${pc13.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4439
4750
  );
4440
4751
  console.log();
4441
4752
  let apiKey = args.key ?? "";
@@ -4473,7 +4784,7 @@ var init_connect = __esm({
4473
4784
  console.log(` Check your key and try again.`);
4474
4785
  process.exit(1);
4475
4786
  }
4476
- printStatus(`Connected to ${pc12.bold(result.org)}`);
4787
+ printStatus(`Connected to ${pc13.bold(result.org)}`);
4477
4788
  } catch {
4478
4789
  printError(
4479
4790
  "Could not reach relay API. Check your network and try again."
@@ -4481,7 +4792,7 @@ var init_connect = __esm({
4481
4792
  process.exit(1);
4482
4793
  }
4483
4794
  const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
4484
- printStatus(`API key stored in ${pc12.dim(envFile)}`);
4795
+ printStatus(`API key stored in ${pc13.dim(envFile)}`);
4485
4796
  } else {
4486
4797
  try {
4487
4798
  const result = await validateLinearKey(apiKey);
@@ -4491,7 +4802,7 @@ var init_connect = __esm({
4491
4802
  process.exit(1);
4492
4803
  }
4493
4804
  printStatus(
4494
- `Authenticated as ${pc12.bold(result.name)} (${result.email})`
4805
+ `Authenticated as ${pc13.bold(result.name)} (${result.email})`
4495
4806
  );
4496
4807
  } catch {
4497
4808
  printError("Invalid API key or network error.");
@@ -4499,7 +4810,7 @@ var init_connect = __esm({
4499
4810
  process.exit(1);
4500
4811
  }
4501
4812
  const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
4502
- printStatus(`API key stored in ${pc12.dim(envFile)}`);
4813
+ printStatus(`API key stored in ${pc13.dim(envFile)}`);
4503
4814
  }
4504
4815
  const wasLocal = config.tier === "local";
4505
4816
  config.tier = "cloud";
@@ -4515,14 +4826,14 @@ var init_connect = __esm({
4515
4826
  }
4516
4827
  console.log();
4517
4828
  console.log(
4518
- ` ${pc12.bold("Connected!")} Your project is now on the cloud tier.`
4829
+ ` ${pc13.bold("Connected!")} Your project is now on the cloud tier.`
4519
4830
  );
4520
4831
  console.log();
4521
4832
  console.log(` Next steps:`);
4522
4833
  console.log(
4523
- ` 1. Run ${pc12.cyan("/flydocs-setup")} in your IDE to configure your project`
4834
+ ` 1. Run ${pc13.cyan("/flydocs-setup")} in your IDE to configure your project`
4524
4835
  );
4525
- console.log(` 2. Run ${pc12.cyan("/start-session")} to begin working`);
4836
+ console.log(` 2. Run ${pc13.cyan("/start-session")} to begin working`);
4526
4837
  console.log();
4527
4838
  }
4528
4839
  });
@@ -4534,9 +4845,9 @@ var auth_exports = {};
4534
4845
  __export(auth_exports, {
4535
4846
  default: () => auth_default
4536
4847
  });
4537
- import { defineCommand as defineCommand8 } from "citty";
4848
+ import { defineCommand as defineCommand9 } from "citty";
4538
4849
  import { text as text5, confirm as confirm7, isCancel as isCancel8, cancel as cancel7 } from "@clack/prompts";
4539
- import pc13 from "picocolors";
4850
+ import pc14 from "picocolors";
4540
4851
  var auth_default;
4541
4852
  var init_auth = __esm({
4542
4853
  "src/commands/auth.ts"() {
@@ -4544,7 +4855,7 @@ var init_auth = __esm({
4544
4855
  init_ui();
4545
4856
  init_api_key();
4546
4857
  init_global_config();
4547
- auth_default = defineCommand8({
4858
+ auth_default = defineCommand9({
4548
4859
  meta: {
4549
4860
  name: "auth",
4550
4861
  description: "Store API key globally (~/.flydocs/credentials)"
@@ -4558,13 +4869,13 @@ var init_auth = __esm({
4558
4869
  },
4559
4870
  async run({ args }) {
4560
4871
  console.log();
4561
- console.log(` ${pc13.bold("FlyDocs Authentication")}`);
4872
+ console.log(` ${pc14.bold("FlyDocs Authentication")}`);
4562
4873
  console.log();
4563
4874
  let apiKey = args.key ?? "";
4564
4875
  const existing = await readGlobalCredential();
4565
4876
  if (existing?.apiKey && !apiKey) {
4566
4877
  printInfo(
4567
- `Existing key found: ${pc13.dim(existing.apiKey.slice(0, 8) + "...")}`
4878
+ `Existing key found: ${pc14.dim(existing.apiKey.slice(0, 8) + "...")}`
4568
4879
  );
4569
4880
  const replace = await confirm7({
4570
4881
  message: "Replace with a new key?"
@@ -4576,7 +4887,7 @@ var init_auth = __esm({
4576
4887
  }
4577
4888
  if (!apiKey) {
4578
4889
  console.log(
4579
- ` ${pc13.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4890
+ ` ${pc14.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4580
4891
  );
4581
4892
  console.log();
4582
4893
  const keyInput = await text5({
@@ -4612,7 +4923,7 @@ var init_auth = __esm({
4612
4923
  printError("Invalid API key. Check your key and try again.");
4613
4924
  process.exit(1);
4614
4925
  }
4615
- printStatus(`Authenticated with ${pc13.bold(result.org)}`);
4926
+ printStatus(`Authenticated with ${pc14.bold(result.org)}`);
4616
4927
  } catch {
4617
4928
  printError(
4618
4929
  "Could not reach FlyDocs API. Check your network and try again."
@@ -4627,10 +4938,10 @@ var init_auth = __esm({
4627
4938
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4628
4939
  lastValidated: (/* @__PURE__ */ new Date()).toISOString()
4629
4940
  });
4630
- printStatus(`Key stored at ${pc13.dim(credentialsPath())}`);
4941
+ printStatus(`Key stored at ${pc14.dim(credentialsPath())}`);
4631
4942
  await checkCredentialPermissions();
4632
4943
  console.log();
4633
- console.log(` ${pc13.bold("Authenticated!")} Key stored globally.`);
4944
+ console.log(` ${pc14.bold("Authenticated!")} Key stored globally.`);
4634
4945
  console.log(` All FlyDocs projects on this machine will use this key.`);
4635
4946
  console.log();
4636
4947
  }
@@ -4643,10 +4954,10 @@ var sync_exports = {};
4643
4954
  __export(sync_exports, {
4644
4955
  default: () => sync_default
4645
4956
  });
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";
4957
+ import { defineCommand as defineCommand10 } from "citty";
4958
+ import pc15 from "picocolors";
4959
+ import { join as join24 } from "path";
4960
+ import { mkdir as mkdir11, writeFile as writeFile14 } from "fs/promises";
4650
4961
  var sync_default;
4651
4962
  var init_sync = __esm({
4652
4963
  "src/commands/sync.ts"() {
@@ -4658,7 +4969,7 @@ var init_sync = __esm({
4658
4969
  init_fs_ops();
4659
4970
  init_types();
4660
4971
  init_relay_client();
4661
- sync_default = defineCommand9({
4972
+ sync_default = defineCommand10({
4662
4973
  meta: {
4663
4974
  name: "sync",
4664
4975
  description: "Pull latest config and templates from server"
@@ -4682,6 +4993,14 @@ var init_sync = __esm({
4682
4993
  process.exit(1);
4683
4994
  }
4684
4995
  const apiKey = resolved.key;
4996
+ const cred = await readGlobalCredential();
4997
+ const workspaceId = cred?.workspaceId;
4998
+ if (!workspaceId) {
4999
+ printError(
5000
+ "No workspace ID found. Run `flydocs init` to set up your workspace."
5001
+ );
5002
+ process.exit(1);
5003
+ }
4685
5004
  let currentTemplateVersion = 0;
4686
5005
  try {
4687
5006
  const currentConfig = await readAnyConfig(targetDir);
@@ -4692,7 +5011,7 @@ var init_sync = __esm({
4692
5011
  }
4693
5012
  let serverResponse;
4694
5013
  try {
4695
- serverResponse = await fetchConfigV2(apiKey);
5014
+ serverResponse = await fetchConfigV2(apiKey, { workspaceId });
4696
5015
  } catch (err) {
4697
5016
  if (err instanceof RelayError) {
4698
5017
  printWarning(
@@ -4701,14 +5020,14 @@ var init_sync = __esm({
4701
5020
  } else {
4702
5021
  printWarning("Server unreachable, using cached config.");
4703
5022
  }
4704
- console.log(` ${pc14.dim("Config may be stale. Retry when connected.")}`);
5023
+ console.log(` ${pc15.dim("Config may be stale. Retry when connected.")}`);
4705
5024
  return;
4706
5025
  }
4707
5026
  if (!serverResponse.valid) {
4708
5027
  printWarning("Server returned invalid config. Keeping current config.");
4709
5028
  return;
4710
5029
  }
4711
- await mkdir11(join23(targetDir, ".flydocs"), { recursive: true });
5030
+ await mkdir11(join24(targetDir, ".flydocs"), { recursive: true });
4712
5031
  const configWithHash = applyConfigHash(serverResponse.config);
4713
5032
  await writeConfig(targetDir, configWithHash);
4714
5033
  changes.push("Updated .flydocs/config.json");
@@ -4717,10 +5036,11 @@ var init_sync = __esm({
4717
5036
  try {
4718
5037
  const templatesResponse = await fetchTemplates(
4719
5038
  apiKey,
4720
- currentTemplateVersion
5039
+ currentTemplateVersion,
5040
+ workspaceId
4721
5041
  );
4722
5042
  if (templatesResponse.templates.length > 0) {
4723
- const templatesDir = join23(
5043
+ const templatesDir = join24(
4724
5044
  targetDir,
4725
5045
  ".claude",
4726
5046
  "skills",
@@ -4731,10 +5051,10 @@ var init_sync = __esm({
4731
5051
  for (const template of templatesResponse.templates) {
4732
5052
  const filename = `${template.type}.md`;
4733
5053
  const subdir = template.category === "issue" ? "issues" : template.category === "pr" ? "pr" : template.category === "comment" ? "." : ".";
4734
- const templateDir = join23(templatesDir, subdir);
5054
+ const templateDir = join24(templatesDir, subdir);
4735
5055
  await mkdir11(templateDir, { recursive: true });
4736
- await writeFile13(
4737
- join23(templateDir, filename),
5056
+ await writeFile14(
5057
+ join24(templateDir, filename),
4738
5058
  template.content,
4739
5059
  "utf-8"
4740
5060
  );
@@ -4764,221 +5084,6 @@ var init_sync = __esm({
4764
5084
  }
4765
5085
  });
4766
5086
 
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
- ]);
4976
- console.log();
4977
- }
4978
- });
4979
- }
4980
- });
4981
-
4982
5087
  // src/commands/upgrade.ts
4983
5088
  var upgrade_exports = {};
4984
5089
  __export(upgrade_exports, {