@fragments-sdk/cli 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/bin.js +152 -16
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-3SOAPJDX.js → chunk-55KERLWL.js} +2 -2
  4. package/dist/{chunk-4K7EAQ5L.js → chunk-7K3VROEP.js} +2 -2
  5. package/dist/{chunk-RF3C6LGA.js → chunk-FZLPVN32.js} +5 -5
  6. package/dist/{chunk-QM7SVOGF.js → chunk-I34BC3CU.js} +10 -1
  7. package/dist/chunk-I34BC3CU.js.map +1 -0
  8. package/dist/{chunk-DXX6HADE.js → chunk-PJT5IZ37.js} +2 -2
  9. package/dist/{chunk-UV5JQV3R.js → chunk-TXFCEDOC.js} +2 -2
  10. package/dist/{chunk-FO6EBJWP.js → chunk-Z5BUXIFJ.js} +5 -5
  11. package/dist/{chunk-SM674YAS.js → chunk-ZKTFKHWN.js} +2 -2
  12. package/dist/core/index.js +1 -1
  13. package/dist/{discovery-VSGC76JN.js → discovery-VDANZAJ2.js} +3 -3
  14. package/dist/{generate-QZXOXYFW.js → generate-RYWIPDN2.js} +4 -4
  15. package/dist/index.js +6 -6
  16. package/dist/{init-XK6PRUE5.js → init-U6534EMZ.js} +5 -5
  17. package/dist/mcp-bin.js +2 -2
  18. package/dist/{scan-CHQHXWVD.js → scan-LE2JEIJ4.js} +6 -6
  19. package/dist/{scan-generate-U3RFVDTX.js → scan-generate-TFZVL3BT.js} +4 -4
  20. package/dist/{service-MMEKG4MZ.js → service-S5LXPKV4.js} +3 -3
  21. package/dist/{snapshot-53TUR3HW.js → snapshot-C5DYIGIV.js} +2 -2
  22. package/dist/{static-viewer-KKCR4KXR.js → static-viewer-DUVC4UIM.js} +3 -3
  23. package/dist/{test-5UCKXYSC.js → test-JW7JIDFG.js} +4 -4
  24. package/dist/{tokens-L46MK5AW.js → tokens-OPVTVITP.js} +5 -5
  25. package/dist/{viewer-M2EQQSGE.js → viewer-OBTEPVY7.js} +13 -13
  26. package/package.json +6 -6
  27. package/src/bin.ts +15 -3
  28. package/src/commands/govern.ts +158 -1
  29. package/dist/chunk-QM7SVOGF.js.map +0 -1
  30. /package/dist/{chunk-3SOAPJDX.js.map → chunk-55KERLWL.js.map} +0 -0
  31. /package/dist/{chunk-4K7EAQ5L.js.map → chunk-7K3VROEP.js.map} +0 -0
  32. /package/dist/{chunk-RF3C6LGA.js.map → chunk-FZLPVN32.js.map} +0 -0
  33. /package/dist/{chunk-DXX6HADE.js.map → chunk-PJT5IZ37.js.map} +0 -0
  34. /package/dist/{chunk-UV5JQV3R.js.map → chunk-TXFCEDOC.js.map} +0 -0
  35. /package/dist/{chunk-FO6EBJWP.js.map → chunk-Z5BUXIFJ.js.map} +0 -0
  36. /package/dist/{chunk-SM674YAS.js.map → chunk-ZKTFKHWN.js.map} +0 -0
  37. /package/dist/{discovery-VSGC76JN.js.map → discovery-VDANZAJ2.js.map} +0 -0
  38. /package/dist/{generate-QZXOXYFW.js.map → generate-RYWIPDN2.js.map} +0 -0
  39. /package/dist/{init-XK6PRUE5.js.map → init-U6534EMZ.js.map} +0 -0
  40. /package/dist/{scan-CHQHXWVD.js.map → scan-LE2JEIJ4.js.map} +0 -0
  41. /package/dist/{scan-generate-U3RFVDTX.js.map → scan-generate-TFZVL3BT.js.map} +0 -0
  42. /package/dist/{service-MMEKG4MZ.js.map → service-S5LXPKV4.js.map} +0 -0
  43. /package/dist/{snapshot-53TUR3HW.js.map → snapshot-C5DYIGIV.js.map} +0 -0
  44. /package/dist/{static-viewer-KKCR4KXR.js.map → static-viewer-DUVC4UIM.js.map} +0 -0
  45. /package/dist/{test-5UCKXYSC.js.map → test-JW7JIDFG.js.map} +0 -0
  46. /package/dist/{tokens-L46MK5AW.js.map → tokens-OPVTVITP.js.map} +0 -0
  47. /package/dist/{viewer-M2EQQSGE.js.map → viewer-OBTEPVY7.js.map} +0 -0
@@ -1,14 +1,14 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
2
  import {
3
3
  scan
4
- } from "./chunk-FO6EBJWP.js";
5
- import "./chunk-3SOAPJDX.js";
6
- import "./chunk-SM674YAS.js";
7
- import "./chunk-4K7EAQ5L.js";
4
+ } from "./chunk-Z5BUXIFJ.js";
5
+ import "./chunk-55KERLWL.js";
6
+ import "./chunk-ZKTFKHWN.js";
7
+ import "./chunk-7K3VROEP.js";
8
8
  import "./chunk-D2CDBRNU.js";
9
- import "./chunk-QM7SVOGF.js";
9
+ import "./chunk-I34BC3CU.js";
10
10
  import "./chunk-Z7EY4VHE.js";
11
11
  export {
12
12
  scan
13
13
  };
14
- //# sourceMappingURL=scan-CHQHXWVD.js.map
14
+ //# sourceMappingURL=scan-LE2JEIJ4.js.map
@@ -2,14 +2,14 @@ import { createRequire as __banner_createRequire } from 'module'; const require
2
2
  import {
3
3
  createComponentExtractor
4
4
  } from "./chunk-EYXVAMEX.js";
5
- import "./chunk-3SOAPJDX.js";
5
+ import "./chunk-55KERLWL.js";
6
6
  import {
7
7
  discoverAllComponents
8
- } from "./chunk-SM674YAS.js";
8
+ } from "./chunk-ZKTFKHWN.js";
9
9
  import "./chunk-D2CDBRNU.js";
10
10
  import {
11
11
  BRAND
12
- } from "./chunk-QM7SVOGF.js";
12
+ } from "./chunk-I34BC3CU.js";
13
13
  import "./chunk-Z7EY4VHE.js";
14
14
 
15
15
  // src/commands/scan-generate.ts
@@ -1112,4 +1112,4 @@ export {
1112
1112
  parseEnrichmentResponse,
1113
1113
  scanGenerate
1114
1114
  };
1115
- //# sourceMappingURL=scan-generate-U3RFVDTX.js.map
1115
+ //# sourceMappingURL=scan-generate-TFZVL3BT.js.map
@@ -97,12 +97,12 @@ import {
97
97
  shutdownSharedPool,
98
98
  sleep,
99
99
  summarizePatternsForPrompt
100
- } from "./chunk-4K7EAQ5L.js";
100
+ } from "./chunk-7K3VROEP.js";
101
101
  import "./chunk-D2CDBRNU.js";
102
102
  import {
103
103
  BRAND,
104
104
  DEFAULTS
105
- } from "./chunk-QM7SVOGF.js";
105
+ } from "./chunk-I34BC3CU.js";
106
106
  import "./chunk-Z7EY4VHE.js";
107
107
  export {
108
108
  BRAND,
@@ -206,4 +206,4 @@ export {
206
206
  sleep,
207
207
  summarizePatternsForPrompt
208
208
  };
209
- //# sourceMappingURL=service-MMEKG4MZ.js.map
209
+ //# sourceMappingURL=service-S5LXPKV4.js.map
@@ -2,7 +2,7 @@ import { createRequire as __banner_createRequire } from 'module'; const require
2
2
  import "./chunk-D2CDBRNU.js";
3
3
  import {
4
4
  BRAND
5
- } from "./chunk-QM7SVOGF.js";
5
+ } from "./chunk-I34BC3CU.js";
6
6
  import "./chunk-Z7EY4VHE.js";
7
7
 
8
8
  // src/commands/snapshot.ts
@@ -136,4 +136,4 @@ ${BRAND.name} Visual Snapshots
136
136
  export {
137
137
  snapshot
138
138
  };
139
- //# sourceMappingURL=snapshot-53TUR3HW.js.map
139
+ //# sourceMappingURL=snapshot-C5DYIGIV.js.map
@@ -2,12 +2,12 @@ import { createRequire as __banner_createRequire } from 'module'; const require
2
2
  import {
3
3
  generateStaticViewer,
4
4
  generateViewerFromJson
5
- } from "./chunk-UV5JQV3R.js";
5
+ } from "./chunk-TXFCEDOC.js";
6
6
  import "./chunk-D2CDBRNU.js";
7
- import "./chunk-QM7SVOGF.js";
7
+ import "./chunk-I34BC3CU.js";
8
8
  import "./chunk-Z7EY4VHE.js";
9
9
  export {
10
10
  generateStaticViewer,
11
11
  generateViewerFromJson
12
12
  };
13
- //# sourceMappingURL=static-viewer-KKCR4KXR.js.map
13
+ //# sourceMappingURL=static-viewer-DUVC4UIM.js.map
@@ -1,11 +1,11 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
2
  import {
3
3
  parseFragmentFile
4
- } from "./chunk-3SOAPJDX.js";
4
+ } from "./chunk-55KERLWL.js";
5
5
  import {
6
6
  discoverFragmentFiles
7
- } from "./chunk-SM674YAS.js";
8
- import "./chunk-QM7SVOGF.js";
7
+ } from "./chunk-ZKTFKHWN.js";
8
+ import "./chunk-I34BC3CU.js";
9
9
  import "./chunk-Z7EY4VHE.js";
10
10
 
11
11
  // src/test/index.ts
@@ -1071,4 +1071,4 @@ export {
1071
1071
  listTests,
1072
1072
  runTestCommand
1073
1073
  };
1074
- //# sourceMappingURL=test-5UCKXYSC.js.map
1074
+ //# sourceMappingURL=test-JW7JIDFG.js.map
@@ -1,15 +1,15 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
2
  import {
3
3
  loadConfig
4
- } from "./chunk-3SOAPJDX.js";
5
- import "./chunk-SM674YAS.js";
4
+ } from "./chunk-55KERLWL.js";
5
+ import "./chunk-ZKTFKHWN.js";
6
6
  import {
7
7
  parseTokenFiles
8
- } from "./chunk-4K7EAQ5L.js";
8
+ } from "./chunk-7K3VROEP.js";
9
9
  import "./chunk-D2CDBRNU.js";
10
10
  import {
11
11
  BRAND
12
- } from "./chunk-QM7SVOGF.js";
12
+ } from "./chunk-I34BC3CU.js";
13
13
  import "./chunk-Z7EY4VHE.js";
14
14
 
15
15
  // src/commands/tokens.ts
@@ -172,4 +172,4 @@ export {
172
172
  tokens_default as default,
173
173
  tokens
174
174
  };
175
- //# sourceMappingURL=tokens-L46MK5AW.js.map
175
+ //# sourceMappingURL=tokens-OPVTVITP.js.map
@@ -5,16 +5,16 @@ import {
5
5
  generatePreviewModule,
6
6
  loadConfig,
7
7
  parseFragmentFile
8
- } from "./chunk-3SOAPJDX.js";
8
+ } from "./chunk-55KERLWL.js";
9
9
  import {
10
10
  discoverFragmentFiles,
11
11
  discoverInstalledFragments
12
- } from "./chunk-SM674YAS.js";
12
+ } from "./chunk-ZKTFKHWN.js";
13
13
  import "./chunk-D2CDBRNU.js";
14
14
  import {
15
15
  BRAND,
16
16
  generateContext
17
- } from "./chunk-QM7SVOGF.js";
17
+ } from "./chunk-I34BC3CU.js";
18
18
  import "./chunk-Z7EY4VHE.js";
19
19
 
20
20
  // src/viewer/server.ts
@@ -373,7 +373,7 @@ var sharedRenderPool = null;
373
373
  var browserPoolModule = null;
374
374
  async function getSharedRenderPool() {
375
375
  if (!browserPoolModule) {
376
- browserPoolModule = await import("./service-MMEKG4MZ.js");
376
+ browserPoolModule = await import("./service-S5LXPKV4.js");
377
377
  }
378
378
  if (!sharedRenderPool) {
379
379
  sharedRenderPool = new browserPoolModule.BrowserPool({
@@ -616,7 +616,7 @@ function fragmentsPlugin(options) {
616
616
  const address = _server.httpServer?.address();
617
617
  const port = typeof address === "object" && address ? address.port : 6006;
618
618
  const renderViewport = viewport || { width: 800, height: 600 };
619
- const { FigmaClient, bufferToBase64Url } = await import("./service-MMEKG4MZ.js");
619
+ const { FigmaClient, bufferToBase64Url } = await import("./service-S5LXPKV4.js");
620
620
  const figmaClient = new FigmaClient({
621
621
  accessToken: figmaToken
622
622
  });
@@ -707,7 +707,7 @@ function fragmentsPlugin(options) {
707
707
  );
708
708
  return;
709
709
  }
710
- const { FigmaClient } = await import("./service-MMEKG4MZ.js");
710
+ const { FigmaClient } = await import("./service-S5LXPKV4.js");
711
711
  const figmaClient = new FigmaClient({ accessToken: figmaToken });
712
712
  const { fileKey, nodeId } = figmaClient.parseUrl(figmaUrl);
713
713
  const figmaDesignProps = await figmaClient.getNodeProperties(
@@ -748,7 +748,7 @@ function fragmentsPlugin(options) {
748
748
  }));
749
749
  return;
750
750
  }
751
- const { getSharedTokenRegistry } = await import("./service-MMEKG4MZ.js");
751
+ const { getSharedTokenRegistry } = await import("./service-S5LXPKV4.js");
752
752
  const registry = getSharedTokenRegistry();
753
753
  if (!registry.isInitialized()) {
754
754
  await registry.initialize(config.tokens, projectRoot);
@@ -808,7 +808,7 @@ function fragmentsPlugin(options) {
808
808
  }));
809
809
  return;
810
810
  }
811
- const { getSharedTokenRegistry } = await import("./service-MMEKG4MZ.js");
811
+ const { getSharedTokenRegistry } = await import("./service-S5LXPKV4.js");
812
812
  const registry = getSharedTokenRegistry();
813
813
  if (!registry.isInitialized()) {
814
814
  await registry.initialize(config.tokens, projectRoot);
@@ -870,7 +870,7 @@ function fragmentsPlugin(options) {
870
870
  res.end(JSON.stringify({ error: "Could not resolve fragment file path" }));
871
871
  return;
872
872
  }
873
- const { getSharedTokenRegistry } = await import("./service-MMEKG4MZ.js");
873
+ const { getSharedTokenRegistry } = await import("./service-S5LXPKV4.js");
874
874
  const registry = getSharedTokenRegistry();
875
875
  if (!registry.isInitialized()) {
876
876
  await registry.initialize(config.tokens, projectRoot);
@@ -1048,7 +1048,7 @@ function fragmentsPlugin(options) {
1048
1048
  }
1049
1049
  if (!config.tokens || !config.tokens.include || config.tokens.include.length === 0) {
1050
1050
  try {
1051
- const { discoverTokenFiles } = await import("./discovery-VSGC76JN.js");
1051
+ const { discoverTokenFiles } = await import("./discovery-VDANZAJ2.js");
1052
1052
  const discovered = await discoverTokenFiles(projectRoot);
1053
1053
  if (discovered.length > 0) {
1054
1054
  config.tokens = {
@@ -1085,7 +1085,7 @@ function fragmentsPlugin(options) {
1085
1085
  const {
1086
1086
  getSharedTokenRegistry,
1087
1087
  generateTokenPatches
1088
- } = await import("./service-MMEKG4MZ.js");
1088
+ } = await import("./service-S5LXPKV4.js");
1089
1089
  const registry = getSharedTokenRegistry();
1090
1090
  if (!registry.isInitialized()) {
1091
1091
  await registry.initialize(config.tokens, projectRoot);
@@ -2194,7 +2194,7 @@ async function loadFullFragmentForCompare(_server, _fragmentFiles, componentName
2194
2194
  }
2195
2195
  }
2196
2196
  async function compareImages(image1Base64, image2Base64, threshold) {
2197
- const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-MMEKG4MZ.js");
2197
+ const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-S5LXPKV4.js");
2198
2198
  const { PNG } = await import("pngjs");
2199
2199
  const buffer1 = base64UrlToBuffer(image1Base64);
2200
2200
  const buffer2 = base64UrlToBuffer(image2Base64);
@@ -2727,4 +2727,4 @@ export {
2727
2727
  createDevServer,
2728
2728
  fragmentsPlugin
2729
2729
  };
2730
- //# sourceMappingURL=viewer-M2EQQSGE.js.map
2730
+ //# sourceMappingURL=viewer-OBTEPVY7.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/cli",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "CLI, MCP server, and dev tools for Fragments design system",
6
6
  "author": "Conan McNicholl",
@@ -80,11 +80,11 @@
80
80
  "vite": "^6.0.0",
81
81
  "vite-plugin-svgr": "^4.5.0",
82
82
  "zod": "^3.24.1",
83
- "@fragments-sdk/context": "0.5.0",
84
- "@fragments-sdk/core": "1.0.0",
85
- "@fragments-sdk/viewer": "0.2.4",
86
- "@fragments-sdk/webmcp": "2.0.0",
87
- "@fragments-sdk/govern": "^0.2.0"
83
+ "@fragments-sdk/context": "0.5.1",
84
+ "@fragments-sdk/core": "1.0.1",
85
+ "@fragments-sdk/govern": "^0.2.1",
86
+ "@fragments-sdk/viewer": "0.2.5",
87
+ "@fragments-sdk/webmcp": "2.0.1"
88
88
  },
89
89
  "devDependencies": {
90
90
  "@types/babel__generator": "^7.6.8",
package/src/bin.ts CHANGED
@@ -40,7 +40,7 @@ import { perf } from './commands/perf.js';
40
40
  import { doctor } from './commands/doctor.js';
41
41
  import { setup } from './commands/setup.js';
42
42
  import { sync } from './commands/sync.js';
43
- import { governCheck, governInit, governReport } from './commands/govern.js';
43
+ import { governCheck, governInit, governReport, governConnect } from './commands/govern.js';
44
44
 
45
45
  // Import existing commands that were already extracted
46
46
  import { runScreenshotCommand } from './screenshot.js';
@@ -1147,8 +1147,8 @@ governCmd
1147
1147
 
1148
1148
  governCmd
1149
1149
  .command('init')
1150
- .description('Generate a govern.config.ts template')
1151
- .option('-o, --output <path>', 'Output path', 'govern.config.ts')
1150
+ .description('Generate a fragments.config.ts with govern section')
1151
+ .option('-o, --output <path>', 'Output path', 'fragments.config.ts')
1152
1152
  .action(async (options) => {
1153
1153
  try {
1154
1154
  await governInit({ output: options.output });
@@ -1170,5 +1170,17 @@ governCmd
1170
1170
  }
1171
1171
  });
1172
1172
 
1173
+ governCmd
1174
+ .command('connect')
1175
+ .description('Connect your project to the Fragments Govern cloud dashboard')
1176
+ .action(async () => {
1177
+ try {
1178
+ await governConnect();
1179
+ } catch (error) {
1180
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1181
+ process.exit(1);
1182
+ }
1183
+ });
1184
+
1173
1185
  // Parse command line arguments
1174
1186
  program.parse();
@@ -65,13 +65,170 @@ export async function governInit(options: GovernInitOptions = {}): Promise<void>
65
65
  const { resolve } = await import('node:path');
66
66
  const { generateConfigTemplate } = await import('@fragments-sdk/govern');
67
67
 
68
- const outputPath = resolve(options.output ?? 'govern.config.ts');
68
+ const outputPath = resolve(options.output ?? 'fragments.config.ts');
69
69
  const template = generateConfigTemplate();
70
70
 
71
71
  await writeFile(outputPath, template, 'utf-8');
72
72
  console.log(pc.green(`✓ Created ${outputPath}\n`));
73
73
  }
74
74
 
75
+ // ---------------------------------------------------------------------------
76
+ // connect
77
+ // ---------------------------------------------------------------------------
78
+
79
+ export async function governConnect(): Promise<void> {
80
+ const { readFile, writeFile, appendFile } = await import('node:fs/promises');
81
+ const { existsSync } = await import('node:fs');
82
+ const { resolve } = await import('node:path');
83
+ const { platform } = await import('node:os');
84
+ const { exec } = await import('node:child_process');
85
+ const { password, confirm } = await import('@inquirer/prompts');
86
+
87
+ const cloudUrl = process.env.FRAGMENTS_URL ?? 'https://app.usefragments.com';
88
+
89
+ console.log(pc.cyan(`\n ${BRAND.name} — Connect to Cloud\n`));
90
+ console.log(
91
+ pc.dim(' This will connect your project to the Fragments dashboard\n') +
92
+ pc.dim(' for centralized audit tracking and team visibility.\n'),
93
+ );
94
+
95
+ // ── Step 1: Get API key ──────────────────────────────────────────────────
96
+ console.log(pc.bold(' Step 1 of 3: Get your API key\n'));
97
+
98
+ const dashboardUrl = `${cloudUrl}/dashboard/settings`;
99
+ console.log(pc.dim(` → Opening the dashboard in your browser...`));
100
+ console.log(pc.dim(` Copy your API key from Settings → API Keys\n`));
101
+
102
+ // Open browser (best-effort)
103
+ const os = platform();
104
+ const openCmd = os === 'darwin'
105
+ ? `open "${dashboardUrl}"`
106
+ : os === 'win32'
107
+ ? `start "" "${dashboardUrl}"`
108
+ : `xdg-open "${dashboardUrl}"`;
109
+ exec(openCmd);
110
+
111
+ let apiKey: string;
112
+ let orgName: string;
113
+
114
+ // eslint-disable-next-line no-constant-condition
115
+ while (true) {
116
+ apiKey = await password({
117
+ message: 'Paste your API key:',
118
+ mask: '*',
119
+ });
120
+
121
+ if (!apiKey.trim()) {
122
+ console.log(pc.yellow('\n API key cannot be empty. Please try again.\n'));
123
+ continue;
124
+ }
125
+
126
+ // Verify key against cloud
127
+ console.log(pc.dim('\n Verifying...'));
128
+ try {
129
+ const response = await fetch(`${cloudUrl}/api/verify`, {
130
+ headers: { Authorization: `Bearer ${apiKey.trim()}` },
131
+ });
132
+
133
+ if (!response.ok) {
134
+ console.log(pc.red(`\n ✗ Invalid API key (HTTP ${response.status}). Please try again.\n`));
135
+ continue;
136
+ }
137
+
138
+ const data = (await response.json()) as { valid: boolean; orgName?: string };
139
+ if (!data.valid) {
140
+ console.log(pc.red('\n ✗ API key not recognized. Please try again.\n'));
141
+ continue;
142
+ }
143
+
144
+ orgName = data.orgName ?? 'your organization';
145
+ console.log(pc.green(`\n ✓ Connected to "${orgName}" (verified)\n`));
146
+ break;
147
+ } catch (error) {
148
+ console.log(
149
+ pc.red('\n ✗ Could not reach the dashboard.'),
150
+ );
151
+ console.log(
152
+ pc.dim(` ${error instanceof Error ? error.message : 'Network error'}\n`),
153
+ );
154
+ continue;
155
+ }
156
+ }
157
+
158
+ // ── Step 2: Save configuration ──────────────────────────────────────────
159
+ console.log(pc.bold(' Step 2 of 3: Save configuration\n'));
160
+
161
+ const saveToEnv = await confirm({
162
+ message: 'Save API key to .env file?',
163
+ default: true,
164
+ });
165
+
166
+ if (saveToEnv) {
167
+ const envPath = resolve('.env');
168
+ const envEntry = `FRAGMENTS_API_KEY=${apiKey.trim()}`;
169
+
170
+ if (existsSync(envPath)) {
171
+ const envContent = await readFile(envPath, 'utf-8');
172
+ if (envContent.includes('FRAGMENTS_API_KEY=')) {
173
+ // Replace existing entry
174
+ const updated = envContent.replace(
175
+ /^FRAGMENTS_API_KEY=.*$/m,
176
+ envEntry,
177
+ );
178
+ await writeFile(envPath, updated, 'utf-8');
179
+ console.log(pc.green(' ✓ Updated FRAGMENTS_API_KEY in .env'));
180
+ } else {
181
+ await appendFile(envPath, `\n${envEntry}\n`, 'utf-8');
182
+ console.log(pc.green(' ✓ Added FRAGMENTS_API_KEY to .env'));
183
+ }
184
+ } else {
185
+ await writeFile(envPath, `${envEntry}\n`, 'utf-8');
186
+ console.log(pc.green(' ✓ Created .env with FRAGMENTS_API_KEY'));
187
+ }
188
+
189
+ // Write FRAGMENTS_URL only if non-default
190
+ if (cloudUrl !== 'https://app.usefragments.com') {
191
+ const envContent = await readFile(envPath, 'utf-8');
192
+ if (!envContent.includes('FRAGMENTS_URL=')) {
193
+ await appendFile(envPath, `FRAGMENTS_URL=${cloudUrl}\n`, 'utf-8');
194
+ console.log(pc.green(` ✓ Added FRAGMENTS_URL to .env`));
195
+ }
196
+ }
197
+
198
+ // Ensure .env is in .gitignore
199
+ const gitignorePath = resolve('.gitignore');
200
+ if (existsSync(gitignorePath)) {
201
+ const gitignore = await readFile(gitignorePath, 'utf-8');
202
+ if (!gitignore.split('\n').some((line) => line.trim() === '.env')) {
203
+ await appendFile(gitignorePath, '\n.env\n', 'utf-8');
204
+ console.log(pc.green(' ✓ Added .env to .gitignore'));
205
+ }
206
+ } else {
207
+ await writeFile(gitignorePath, '.env\n', 'utf-8');
208
+ console.log(pc.green(' ✓ Created .gitignore with .env entry'));
209
+ }
210
+ }
211
+
212
+ // ── Step 3: Config check ────────────────────────────────────────────────
213
+ console.log(pc.bold('\n Step 3 of 3: Config check\n'));
214
+
215
+ const { findGovernConfig } = await import('@fragments-sdk/govern');
216
+ const configPath = findGovernConfig();
217
+
218
+ if (configPath) {
219
+ console.log(pc.green(` ✓ Found govern config: ${configPath}`));
220
+ } else {
221
+ console.log(
222
+ pc.yellow(' No govern config found — run `fragments govern init` to create one'),
223
+ );
224
+ }
225
+
226
+ // ── Done ────────────────────────────────────────────────────────────────
227
+ console.log(pc.dim('\n ─────────────────────────────────────\n'));
228
+ console.log(pc.green(' ✓ All set!') + ' Run `fragments govern check` to send your first audit.\n');
229
+ console.log(pc.dim(` Dashboard: ${cloudUrl}/dashboard\n`));
230
+ }
231
+
75
232
  // ---------------------------------------------------------------------------
76
233
  // report
77
234
  // ---------------------------------------------------------------------------