@fenglimg/fabric-cli 1.1.0 → 1.3.0

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/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @fenglimg/fabric-cli
2
+
3
+ `fabric` is the primary CLI binary for Fabric. `fab` is a permanent alias, so you can use either binary.
4
+
5
+ ## Quick Start
6
+
7
+ 1. Install dependencies from the monorepo root with `pnpm install`.
8
+ 2. Build the CLI with `pnpm --filter @fenglimg/fabric-cli build`.
9
+ 3. Run `fabric init` in the target project for the one-shot setup flow.
10
+ 4. Start `fabric serve` and verify `fab_get_rules` in your client.
11
+
12
+ `fabric init` auto-runs `bootstrap install`, `config install`, and `hooks install`. Use them standalone only for targeted re-runs.
13
+
14
+ ## Common Commands
15
+
16
+ - `fabric init`
17
+ - `fabric serve`
18
+ - `fabric doctor --audit`
19
+
20
+ ## Advanced Commands
21
+
22
+ - `fabric bootstrap install`
23
+ - `fabric config install`
24
+ - `fabric hooks install`
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ bootstrapCommand,
4
+ bootstrap_default,
5
+ installBootstrap
6
+ } from "./chunk-RUQCZA2Q.js";
7
+ import "./chunk-VMYPJPKV.js";
8
+ import "./chunk-AEOYCVBG.js";
9
+ import "./chunk-6ICJICVU.js";
10
+ export {
11
+ bootstrapCommand,
12
+ bootstrap_default as default,
13
+ installBootstrap
14
+ };
@@ -70,24 +70,53 @@ var bootstrapCommand = defineCommand({
70
70
  async run({ args }) {
71
71
  const workspaceRoot = process.cwd();
72
72
  const selectedClients = parseClientFilter(args.clients);
73
- const fabricConfig = readFabricConfig(workspaceRoot);
74
- const detectedClients = detectBootstrapClients(workspaceRoot, fabricConfig);
75
- const clients = selectedClients ?? detectedClients;
76
- if (clients.size === 0) {
73
+ const result = await installBootstrap(workspaceRoot, {
74
+ clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
75
+ });
76
+ if (result.details.length === 0) {
77
77
  process.stderr.write(
78
78
  `${t("cli.bootstrap.install.no-targets")}
79
79
  `
80
80
  );
81
81
  return;
82
82
  }
83
- for (const client of clients) {
84
- installBootstrap(client, workspaceRoot);
83
+ for (const detail of result.details) {
84
+ if (detail.action === "skipped") {
85
+ process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
86
+ `);
87
+ continue;
88
+ }
89
+ if (detail.action === "prepended") {
90
+ process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
91
+ `);
92
+ continue;
93
+ }
94
+ process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
95
+ `);
85
96
  }
86
97
  }
87
98
  })
88
99
  }
89
100
  });
90
101
  var bootstrap_default = bootstrapCommand;
102
+ async function installBootstrap(target, options = {}) {
103
+ const workspaceRoot = resolve(target);
104
+ const fabricConfig = readFabricConfig(workspaceRoot);
105
+ const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
106
+ const installed = [];
107
+ const skipped = [];
108
+ const details = [];
109
+ for (const bootstrapTarget of targets) {
110
+ const detail = installBootstrapTarget(bootstrapTarget, workspaceRoot, options);
111
+ details.push(detail);
112
+ if (detail.action === "skipped") {
113
+ skipped.push(bootstrapTarget.client);
114
+ } else {
115
+ installed.push(bootstrapTarget.client);
116
+ }
117
+ }
118
+ return { installed, skipped, details };
119
+ }
91
120
  function parseClientFilter(value) {
92
121
  if (value === void 0 || value.trim().length === 0) {
93
122
  return null;
@@ -103,15 +132,19 @@ function parseClientFilter(value) {
103
132
  }
104
133
  return clients;
105
134
  }
106
- function detectBootstrapClients(workspaceRoot, fabricConfig) {
107
- const clients = /* @__PURE__ */ new Set();
108
- for (const writer of resolveClients(workspaceRoot, fabricConfig)) {
109
- const bootstrapClient = mapClientKind(writer.clientKind);
110
- if (bootstrapClient !== null) {
111
- clients.add(bootstrapClient);
135
+ function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
136
+ const targets = [];
137
+ const seenClients = /* @__PURE__ */ new Set();
138
+ const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
139
+ for (const clientKind of clientKinds) {
140
+ const bootstrapClient = mapClientKind(clientKind);
141
+ if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
142
+ continue;
112
143
  }
144
+ seenClients.add(bootstrapClient);
145
+ targets.push({ client: clientKind, bootstrapClient });
113
146
  }
114
- return clients;
147
+ return targets;
115
148
  }
116
149
  function mapClientKind(clientKind) {
117
150
  switch (clientKind) {
@@ -132,37 +165,79 @@ function mapClientKind(clientKind) {
132
165
  return null;
133
166
  }
134
167
  }
135
- function installBootstrap(client, workspaceRoot) {
136
- const targetPath = resolve(workspaceRoot, CLIENT_TARGET_MAP[client]);
137
- const templatePath = findTemplatePath(CLIENT_TEMPLATE_MAP[client]);
168
+ function mapBootstrapClientToClientKind(client) {
169
+ switch (client) {
170
+ case "claude":
171
+ return "ClaudeCodeCLI";
172
+ case "cursor":
173
+ return "Cursor";
174
+ case "windsurf":
175
+ return "Windsurf";
176
+ case "roo":
177
+ return "RooCode";
178
+ case "gemini":
179
+ return "GeminiCLI";
180
+ case "codex":
181
+ return "CodexCLI";
182
+ }
183
+ }
184
+ function installBootstrapTarget(target, workspaceRoot, options) {
185
+ const targetPath = resolve(workspaceRoot, CLIENT_TARGET_MAP[target.bootstrapClient]);
186
+ const templatePath = findTemplatePath(CLIENT_TEMPLATE_MAP[target.bootstrapClient]);
138
187
  const template = readFileSync(templatePath, "utf8");
139
188
  mkdirSync(dirname(targetPath), { recursive: true });
140
- if (client === "codex") {
141
- writeCodexBootstrap(targetPath, template);
142
- return;
189
+ if (target.bootstrapClient === "codex") {
190
+ return {
191
+ client: target.client,
192
+ path: targetPath,
193
+ action: writeCodexBootstrap(targetPath, template, options.force)
194
+ };
143
195
  }
196
+ const existed = existsSync(targetPath);
144
197
  writeFileSync(targetPath, ensureTrailingNewline(template), "utf8");
145
- process.stderr.write(`${t("cli.bootstrap.install.installed", { path: targetPath })}
146
- `);
198
+ return {
199
+ client: target.client,
200
+ path: targetPath,
201
+ action: existed ? "overwritten" : "installed"
202
+ };
147
203
  }
148
- function writeCodexBootstrap(targetPath, template) {
204
+ function writeCodexBootstrap(targetPath, template, force) {
149
205
  const nextContent = ensureTrailingNewline(template);
150
206
  if (!existsSync(targetPath)) {
151
207
  writeFileSync(targetPath, nextContent, "utf8");
152
- process.stderr.write(`${t("cli.bootstrap.install.installed", { path: targetPath })}
153
- `);
154
- return;
208
+ return "installed";
155
209
  }
156
210
  const existing = readFileSync(targetPath, "utf8");
157
211
  if (existing.includes("# Fabric Bootstrap")) {
158
- process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: targetPath })}
159
- `);
160
- return;
212
+ if (!force) {
213
+ return "skipped";
214
+ }
215
+ const remainder = stripExistingCodexBootstrap(existing, nextContent);
216
+ writeFileSync(targetPath, joinBootstrapSections(nextContent, remainder), "utf8");
217
+ return "overwritten";
161
218
  }
162
- const separator = existing.startsWith("\n") || existing.length === 0 ? "" : "\n";
163
- writeFileSync(targetPath, `${nextContent}${separator}${existing}`, "utf8");
164
- process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: targetPath })}
165
- `);
219
+ writeFileSync(targetPath, joinBootstrapSections(nextContent, existing), "utf8");
220
+ return force ? "overwritten" : "prepended";
221
+ }
222
+ function stripExistingCodexBootstrap(existing, template) {
223
+ if (existing.startsWith(template)) {
224
+ return existing.slice(template.length).replace(/^\n+/, "");
225
+ }
226
+ if (!existing.startsWith("# Fabric Bootstrap")) {
227
+ return existing;
228
+ }
229
+ const nextTopLevelHeadingIndex = existing.indexOf("\n# ", "# Fabric Bootstrap".length);
230
+ if (nextTopLevelHeadingIndex === -1) {
231
+ return "";
232
+ }
233
+ return existing.slice(nextTopLevelHeadingIndex + 1).replace(/^\n+/, "");
234
+ }
235
+ function joinBootstrapSections(header, body) {
236
+ if (body.trim().length === 0) {
237
+ return header;
238
+ }
239
+ const separator = body.startsWith("\n") ? "" : "\n";
240
+ return `${header}${separator}${body}`;
166
241
  }
167
242
  function ensureTrailingNewline(content) {
168
243
  return content.endsWith("\n") ? content : `${content}
@@ -194,7 +269,9 @@ function templateCandidatesFrom(start, relativePath) {
194
269
  }
195
270
  return candidates.reverse();
196
271
  }
272
+
197
273
  export {
198
274
  bootstrapCommand,
199
- bootstrap_default as default
275
+ bootstrap_default,
276
+ installBootstrap
200
277
  };
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  resolveClients
4
4
  } from "./chunk-VMYPJPKV.js";
5
+ import {
6
+ hooksCommand
7
+ } from "./chunk-YDZJRLHL.js";
5
8
  import {
6
9
  t
7
10
  } from "./chunk-6ICJICVU.js";
@@ -13,6 +16,7 @@ import { resolve } from "path";
13
16
  import { fileURLToPath } from "url";
14
17
  import { defineCommand } from "citty";
15
18
  var CLIENT_ALIASES = {
19
+ claude: "ClaudeCodeCLI",
16
20
  claudecodecli: "ClaudeCodeCLI",
17
21
  "claude-code-cli": "ClaudeCodeCLI",
18
22
  claudecli: "ClaudeCodeCLI",
@@ -57,7 +61,8 @@ async function loadFabricConfig(workspaceRoot) {
57
61
  }
58
62
  return parsed;
59
63
  }
60
- function resolveServerPath() {
64
+ function resolveServerPath(override) {
65
+ if (override) return override;
61
66
  if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
62
67
  return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
63
68
  }
@@ -71,6 +76,7 @@ var configCmd = defineCommand({
71
76
  description: t("cli.config.description")
72
77
  },
73
78
  subCommands: {
79
+ hooks: hooksCommand,
74
80
  install: defineCommand({
75
81
  meta: {
76
82
  name: "install",
@@ -88,36 +94,66 @@ var configCmd = defineCommand({
88
94
  }
89
95
  },
90
96
  async run({ args }) {
91
- const workspaceRoot = process.cwd();
92
- const fabricConfig = await loadFabricConfig(workspaceRoot);
93
97
  const selectedClients = parseClientFilter(args.clients);
94
- const serverPath = resolveServerPath();
95
- const writers = resolveClients(workspaceRoot, fabricConfig).filter(
96
- (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
97
- );
98
- if (writers.length === 0) {
98
+ const result = await installMcpClients(process.cwd(), {
99
+ clients: selectedClients === null ? void 0 : Array.from(selectedClients),
100
+ dryRun: args["dry-run"]
101
+ });
102
+ if (result.details.length === 0) {
99
103
  writeStderr(t("cli.config.install.no-configs"));
100
104
  return;
101
105
  }
102
- for (const writer of writers) {
103
- const configPath = await writer.detect(workspaceRoot);
104
- if (configPath === null) {
105
- writeStderr(t("cli.config.install.no-config-path", { client: writer.clientKind }));
106
+ for (const detail of result.details) {
107
+ if (detail.action === "skipped") {
108
+ writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
106
109
  continue;
107
110
  }
108
- if (args["dry-run"]) {
109
- writeStderr(t("cli.config.install.dry-run", { client: writer.clientKind, path: configPath }));
111
+ if (detail.action === "dry-run" && detail.path !== null) {
112
+ writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
110
113
  continue;
111
114
  }
112
- await writer.write(serverPath, workspaceRoot);
113
- writeStderr(t("cli.config.install.wrote", { client: writer.clientKind, path: configPath }));
115
+ if (detail.path !== null) {
116
+ writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
117
+ }
114
118
  }
115
119
  }
116
120
  })
117
121
  }
118
122
  });
119
123
  var config_default = configCmd;
124
+ async function installMcpClients(target, options = {}) {
125
+ const workspaceRoot = resolve(target);
126
+ const fabricConfig = await loadFabricConfig(workspaceRoot);
127
+ const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
128
+ const serverPath = resolveServerPath(options.localServerPath);
129
+ const writers = resolveClients(workspaceRoot, fabricConfig).filter(
130
+ (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
131
+ );
132
+ const installed = [];
133
+ const skipped = [];
134
+ const details = [];
135
+ for (const writer of writers) {
136
+ const configPath = await writer.detect(workspaceRoot);
137
+ if (configPath === null) {
138
+ skipped.push(writer.clientKind);
139
+ details.push({ client: writer.clientKind, path: null, action: "skipped" });
140
+ continue;
141
+ }
142
+ if (options.dryRun) {
143
+ skipped.push(writer.clientKind);
144
+ details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
145
+ continue;
146
+ }
147
+ await writer.write(serverPath, workspaceRoot);
148
+ installed.push(writer.clientKind);
149
+ details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
150
+ }
151
+ return { installed, skipped, details };
152
+ }
153
+
120
154
  export {
155
+ parseClientFilter,
121
156
  configCmd,
122
- config_default as default
157
+ config_default,
158
+ installMcpClients
123
159
  };
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-6ICJICVU.js";
5
+
6
+ // src/commands/hooks.ts
7
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
8
+ import { dirname, isAbsolute, join, parse, resolve } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import { defineCommand } from "citty";
11
+ var hooksCommand = defineCommand({
12
+ meta: {
13
+ name: "hooks",
14
+ description: t("cli.hooks.description")
15
+ },
16
+ subCommands: {
17
+ install: defineCommand({
18
+ meta: {
19
+ name: "install",
20
+ description: t("cli.hooks.install.description")
21
+ },
22
+ args: {
23
+ target: {
24
+ type: "string",
25
+ description: t("cli.hooks.install.args.target.description"),
26
+ default: process.cwd()
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ const result = await installHooks(args.target);
31
+ if (result.hookAction === "skipped") {
32
+ writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
33
+ } else if (result.hookAction === "appended") {
34
+ writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
35
+ } else {
36
+ writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
37
+ }
38
+ if (result.prepareAction === "left") {
39
+ writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
40
+ } else {
41
+ writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
42
+ }
43
+ }
44
+ })
45
+ }
46
+ });
47
+ var hooks_default = hooksCommand;
48
+ async function installHooks(target, options = {}) {
49
+ const normalizedTarget = normalizeTarget(target);
50
+ assertExistingDirectory(normalizedTarget);
51
+ const huskyDir = join(normalizedTarget, ".husky");
52
+ const hookPath = join(huskyDir, "pre-commit");
53
+ const packageJsonPath = join(normalizedTarget, "package.json");
54
+ if (!existsSync(packageJsonPath)) {
55
+ throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
56
+ }
57
+ mkdirSync(huskyDir, { recursive: true });
58
+ const templateContent = readFileSync(findTemplatePath("templates/husky/pre-commit"), "utf8");
59
+ const hookAction = installHookFile(hookPath, templateContent, options.force);
60
+ chmodSync(hookPath, 493);
61
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
62
+ const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
63
+ const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
64
+ let prepareAction = "left";
65
+ if (!hadPrepare) {
66
+ scripts.prepare = "husky install";
67
+ packageJson.scripts = scripts;
68
+ writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
69
+ `, "utf8");
70
+ prepareAction = "added";
71
+ }
72
+ const installed = [];
73
+ const skipped = [];
74
+ if (hookAction === "skipped") {
75
+ skipped.push(hookPath);
76
+ } else {
77
+ installed.push(hookPath);
78
+ }
79
+ if (prepareAction === "left") {
80
+ skipped.push(packageJsonPath);
81
+ } else {
82
+ installed.push(packageJsonPath);
83
+ }
84
+ return {
85
+ installed,
86
+ skipped,
87
+ hookPath,
88
+ packageJsonPath,
89
+ hookAction,
90
+ prepareAction
91
+ };
92
+ }
93
+ function normalizeTarget(targetInput) {
94
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
95
+ }
96
+ function assertExistingDirectory(target) {
97
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
98
+ throw new Error(t("cli.shared.target-invalid", { target }));
99
+ }
100
+ }
101
+ function installHookFile(hookPath, templateContent, force) {
102
+ if (existsSync(hookPath)) {
103
+ if (force) {
104
+ writeFileSync(hookPath, templateContent, "utf8");
105
+ return "overwritten";
106
+ }
107
+ const existing = readFileSync(hookPath, "utf8");
108
+ if (existing.includes("FAB_BIN=")) {
109
+ return "skipped";
110
+ }
111
+ const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
112
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
113
+ writeFileSync(hookPath, `${existing}${separator}# --- Fabric ---
114
+ ${fabricBlock}`, "utf8");
115
+ return "appended";
116
+ }
117
+ writeFileSync(hookPath, templateContent, "utf8");
118
+ return "created";
119
+ }
120
+ function findTemplatePath(relativePath) {
121
+ const currentModuleDir = dirname(fileURLToPath(import.meta.url));
122
+ const candidates = [
123
+ ...templateCandidatesFrom(process.cwd(), relativePath),
124
+ ...templateCandidatesFrom(currentModuleDir, relativePath)
125
+ ];
126
+ for (const candidate of candidates) {
127
+ if (existsSync(candidate)) {
128
+ return candidate;
129
+ }
130
+ }
131
+ throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
132
+ }
133
+ function templateCandidatesFrom(start, relativePath) {
134
+ const candidates = [];
135
+ let current = resolve(start);
136
+ while (true) {
137
+ candidates.push(join(current, ...relativePath.split("/")));
138
+ const parent = dirname(current);
139
+ if (parent === current || parse(current).root === current) {
140
+ break;
141
+ }
142
+ current = parent;
143
+ }
144
+ return candidates.reverse();
145
+ }
146
+ function writeStderr(message) {
147
+ process.stderr.write(`${message}
148
+ `);
149
+ }
150
+
151
+ export {
152
+ hooksCommand,
153
+ hooks_default,
154
+ installHooks
155
+ };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configCmd,
4
+ config_default,
5
+ installMcpClients,
6
+ parseClientFilter
7
+ } from "./chunk-TO5RUB4R.js";
8
+ import "./chunk-VMYPJPKV.js";
9
+ import "./chunk-YDZJRLHL.js";
10
+ import "./chunk-6ICJICVU.js";
11
+ export {
12
+ configCmd,
13
+ config_default as default,
14
+ installMcpClients,
15
+ parseClientFilter
16
+ };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ hooksCommand,
4
+ hooks_default,
5
+ installHooks
6
+ } from "./chunk-YDZJRLHL.js";
7
+ import "./chunk-6ICJICVU.js";
8
+ export {
9
+ hooks_default as default,
10
+ hooksCommand,
11
+ installHooks
12
+ };
package/dist/index.js CHANGED
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- t
4
- } from "./chunk-6ICJICVU.js";
5
2
 
6
3
  // src/index.ts
7
4
  import { realpathSync } from "fs";
@@ -11,25 +8,32 @@ import { defineCommand, runMain } from "citty";
11
8
 
12
9
  // src/commands/index.ts
13
10
  var allCommands = {
14
- bootstrap: () => import("./bootstrap-PMIA4W6G.js").then((module) => module.default),
15
- doctor: () => import("./doctor-QTSG2RWF.js").then((module) => module.default),
16
- init: () => import("./init-R73E5YTG.js").then((module) => module.default),
17
- scan: () => import("./scan-JBGFRB7P.js").then((module) => module.default),
18
- serve: () => import("./serve-4J2CQY25.js").then((module) => module.default),
11
+ init: () => import("./init-BZ73IUHH.js").then((module) => module.default),
12
+ update: () => import("./update-JZPUJ36D.js").then((module) => module.default),
13
+ scan: () => import("./scan-WKDSKEBB.js").then((module) => module.default),
14
+ serve: () => import("./serve-MMN4GYLM.js").then((module) => module.default),
15
+ doctor: () => import("./doctor-5KJGOV2P.js").then((module) => module.default),
19
16
  "sync-meta": () => import("./sync-meta-THZSEM7Y.js").then((module) => module.default),
20
17
  "human-lint": () => import("./human-lint-YSFOZHZ7.js").then((module) => module.default),
21
18
  "ledger-append": () => import("./ledger-append-XZ5SX4O5.js").then((module) => module.default),
22
- hooks: () => import("./hooks-5S5IRVQE.js").then((module) => module.default),
23
- config: () => import("./config-PXEEXWLM.js").then((module) => module.configCmd),
24
- "pre-commit": () => import("./pre-commit-BLSUMT3P.js").then((module) => module.default)
19
+ "pre-commit": () => import("./pre-commit-AK55G73F.js").then((module) => module.default),
20
+ bootstrap: () => import("./bootstrap-IUL4SAAK.js").then((module) => module.default),
21
+ config: () => import("./config-MKWKDE32.js").then((module) => module.configCmd),
22
+ hooks: () => import("./hooks-ZSWVH2JD.js").then((module) => ({
23
+ ...module.default,
24
+ meta: {
25
+ ...module.default.meta,
26
+ hidden: true
27
+ }
28
+ }))
25
29
  };
26
30
 
27
31
  // src/index.ts
28
32
  var main = defineCommand({
29
33
  meta: {
30
- name: "fab",
31
- version: "1.1.0",
32
- description: t("cli.main.description")
34
+ name: "fabric",
35
+ version: "1.3.0",
36
+ description: 'Initialize and manage Fabric projects. Use "fabric init" for one-shot setup.'
33
37
  },
34
38
  subCommands: allCommands
35
39
  });