@fenglimg/fabric-cli 1.3.1 → 1.5.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 CHANGED
@@ -13,14 +13,18 @@
13
13
 
14
14
  `fabric bootstrap install` refreshes the internal bootstrap guide at `.fabric/bootstrap/README.md`. It does not generate root `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md`.
15
15
 
16
- ## Common Commands
17
-
18
- - `fabric init`
19
- - `fabric serve`
20
- - `fabric doctor --audit`
21
-
22
- ## Advanced Commands
23
-
24
- - `fabric bootstrap install`
25
- - `fabric config install`
26
- - `fabric hooks install`
16
+ ## Common Commands
17
+
18
+ - `fabric init`
19
+ - `fabric serve`
20
+ - `fabric doctor --audit`
21
+ - `fabric approve --interactive`
22
+ - `fabric approve --all`
23
+
24
+ ## Advanced Commands
25
+
26
+ - `fabric bootstrap install`
27
+ - `fabric config install`
28
+ - `fabric hooks install`
29
+
30
+ `fabric approve` updates drifted entries in `.fabric/human-lock.json` after review. Use `--interactive` for per-entry confirmation and `--all` only when drift has already been reviewed elsewhere.
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ padEnd
4
+ } from "./chunk-WWNXR34K.js";
5
+ import {
6
+ t
7
+ } from "./chunk-6ICJICVU.js";
8
+
9
+ // src/commands/approve.ts
10
+ import { createInterface } from "readline/promises";
11
+ import { stdin as input, stdout as output } from "process";
12
+ import { isAbsolute, resolve } from "path";
13
+ import { approveHumanLock, readHumanLock } from "@fenglimg/fabric-server";
14
+ import { defineCommand, renderUsage } from "citty";
15
+ var approveCommand = defineCommand({
16
+ meta: {
17
+ name: "approve",
18
+ description: t("cli.approve.description")
19
+ },
20
+ args: {
21
+ all: {
22
+ type: "boolean",
23
+ description: t("cli.approve.args.all.description"),
24
+ default: false
25
+ },
26
+ interactive: {
27
+ type: "boolean",
28
+ description: t("cli.approve.args.interactive.description"),
29
+ default: false
30
+ },
31
+ target: {
32
+ type: "string",
33
+ description: t("cli.approve.args.target.description"),
34
+ default: process.cwd()
35
+ }
36
+ },
37
+ async run({ args }) {
38
+ const target = normalizeTarget(args.target);
39
+ if (args.all === args.interactive) {
40
+ writeStdout(await renderUsage(approveCommand));
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ if (args.all) {
45
+ await runApproveAll(target);
46
+ return;
47
+ }
48
+ await runApproveInteractive(target);
49
+ }
50
+ });
51
+ var approve_default = approveCommand;
52
+ async function runApproveAll(projectRoot) {
53
+ const driftEntries = await readDriftEntries(projectRoot);
54
+ if (driftEntries.length === 0) {
55
+ writeStdout(t("cli.approve.no-drift"));
56
+ return;
57
+ }
58
+ let approvedCount = 0;
59
+ for (const entry of driftEntries) {
60
+ await approveEntry(projectRoot, entry);
61
+ approvedCount += 1;
62
+ }
63
+ writeStdout(t("cli.approve.summary", { approved: String(approvedCount), skipped: "0", total: String(driftEntries.length) }));
64
+ }
65
+ async function runApproveInteractive(projectRoot) {
66
+ const driftEntries = await readDriftEntries(projectRoot);
67
+ if (driftEntries.length === 0) {
68
+ writeStdout(t("cli.approve.no-drift"));
69
+ return;
70
+ }
71
+ const rl = createInterface({ input, output });
72
+ let approvedCount = 0;
73
+ let skippedCount = 0;
74
+ try {
75
+ for (const entry of driftEntries) {
76
+ writeStdout(formatEntry(entry));
77
+ const answer = (await rl.question(t("cli.approve.prompt"))).trim().toLowerCase();
78
+ if (answer === "y" || answer === "yes") {
79
+ await approveEntry(projectRoot, entry);
80
+ approvedCount += 1;
81
+ writeStdout(t("cli.approve.approved-one", { location: formatLocation(entry) }));
82
+ continue;
83
+ }
84
+ skippedCount += 1;
85
+ writeStdout(t("cli.approve.skipped-one", { location: formatLocation(entry) }));
86
+ }
87
+ } finally {
88
+ rl.close();
89
+ }
90
+ writeStdout(
91
+ t("cli.approve.summary", {
92
+ approved: String(approvedCount),
93
+ skipped: String(skippedCount),
94
+ total: String(driftEntries.length)
95
+ })
96
+ );
97
+ }
98
+ async function readDriftEntries(projectRoot) {
99
+ const entries = await readHumanLock(projectRoot);
100
+ return entries.filter((entry) => entry.drift);
101
+ }
102
+ async function approveEntry(projectRoot, entry) {
103
+ await approveHumanLock(projectRoot, {
104
+ file: entry.file,
105
+ start_line: entry.start_line,
106
+ end_line: entry.end_line,
107
+ new_hash: entry.current_hash
108
+ });
109
+ }
110
+ function normalizeTarget(targetInput) {
111
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
112
+ }
113
+ function formatEntry(entry) {
114
+ return [
115
+ formatLocation(entry),
116
+ `${padEnd(t("cli.approve.table.expected"), 10)} ${shortenHash(entry.hash)}`,
117
+ `${padEnd(t("cli.approve.table.current"), 10)} ${shortenHash(entry.current_hash)}`
118
+ ].join("\n");
119
+ }
120
+ function formatLocation(entry) {
121
+ return `${entry.file}:${entry.start_line}-${entry.end_line}`;
122
+ }
123
+ function shortenHash(value) {
124
+ if (value === "missing") {
125
+ return t("cli.shared.missing");
126
+ }
127
+ return value.slice(0, 15);
128
+ }
129
+ function writeStdout(message) {
130
+ process.stdout.write(`${message}
131
+ `);
132
+ }
133
+ export {
134
+ approveCommand,
135
+ approve_default as default,
136
+ runApproveAll,
137
+ runApproveInteractive
138
+ };
@@ -3,11 +3,11 @@ import {
3
3
  bootstrapCommand,
4
4
  bootstrap_default,
5
5
  installBootstrap
6
- } from "./chunk-TKTWHAKV.js";
7
- import "./chunk-AZRKMFRY.js";
8
- import "./chunk-VOQKQ6W2.js";
6
+ } from "./chunk-T2WJF5I3.js";
7
+ import "./chunk-QSAEGVKE.js";
9
8
  import "./chunk-AEOYCVBG.js";
10
9
  import "./chunk-WWNXR34K.js";
10
+ import "./chunk-BEKSXO5N.js";
11
11
  import "./chunk-6ICJICVU.js";
12
12
  export {
13
13
  bootstrapCommand,
@@ -345,6 +345,10 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
345
345
  mcp: true,
346
346
  hook: true,
347
347
  skill: true
348
+ },
349
+ installedCapabilities: {
350
+ hook: true,
351
+ skill: true
348
352
  }
349
353
  },
350
354
  {
@@ -421,8 +425,12 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
421
425
  capabilities: {
422
426
  bootstrap: true,
423
427
  mcp: true,
424
- hook: false,
425
- skill: false
428
+ hook: true,
429
+ skill: true
430
+ },
431
+ installedCapabilities: {
432
+ hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
433
+ skill: existsSync4(join4(workspaceRoot, ".agents", "skills", "fabric-init", "SKILL.md"))
426
434
  }
427
435
  }
428
436
  ];
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveClients
4
- } from "./chunk-VOQKQ6W2.js";
4
+ } from "./chunk-BEKSXO5N.js";
5
5
  import {
6
6
  hooksCommand
7
7
  } from "./chunk-YDZJRLHL.js";
@@ -40,7 +40,7 @@ function resolveIgnores(fabricConfig) {
40
40
  }
41
41
 
42
42
  // src/commands/scan.ts
43
- function createScanReport(targetInput = process.cwd(), fabricConfig) {
43
+ async function createScanReport(targetInput = process.cwd(), fabricConfig) {
44
44
  const target = normalizeTarget(targetInput);
45
45
  const framework = detectFramework(target);
46
46
  const readmeQuality = getReadmeQuality(target);
@@ -93,7 +93,7 @@ var scanCommand = defineCommand({
93
93
  for (const step of resolution.chain) {
94
94
  logger(step);
95
95
  }
96
- const report = createScanReport(resolution.target, fabricConfig);
96
+ const report = await createScanReport(resolution.target, fabricConfig);
97
97
  if (args.json) {
98
98
  console.log(JSON.stringify(report, null, 2));
99
99
  return;
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createScanReport
4
- } from "./chunk-AZRKMFRY.js";
5
- import {
6
- resolveClients
7
- } from "./chunk-VOQKQ6W2.js";
4
+ } from "./chunk-QSAEGVKE.js";
8
5
  import {
9
6
  readFabricConfig
10
7
  } from "./chunk-AEOYCVBG.js";
8
+ import {
9
+ resolveClients
10
+ } from "./chunk-BEKSXO5N.js";
11
11
  import {
12
12
  t
13
13
  } from "./chunk-6ICJICVU.js";
@@ -26,22 +26,22 @@ var AGENTS_TEMPLATE_BY_FRAMEWORK = {
26
26
  next: "templates/agents-md/variants/next.md"
27
27
  };
28
28
  var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
29
- function buildFabricBootstrapGuide(target) {
29
+ async function buildFabricBootstrapGuide(target) {
30
30
  const workspaceRoot = normalizeTarget(target);
31
- const scanReport = createScanReport(workspaceRoot);
31
+ const scanReport = await createScanReport(workspaceRoot);
32
32
  const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
33
33
  const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
34
34
  return ensureTrailingNewline(
35
35
  template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
36
36
  );
37
37
  }
38
- function ensureFabricBootstrapGuide(workspaceRoot, force) {
38
+ async function ensureFabricBootstrapGuide(workspaceRoot, force) {
39
39
  const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
40
40
  if (existsSync(guidePath) && !force) {
41
41
  return;
42
42
  }
43
43
  mkdirSync(dirname(guidePath), { recursive: true });
44
- writeFileSync(guidePath, buildFabricBootstrapGuide(workspaceRoot), "utf8");
44
+ writeFileSync(guidePath, await buildFabricBootstrapGuide(workspaceRoot), "utf8");
45
45
  }
46
46
  function findBootstrapTemplatePath(frameworkKind) {
47
47
  const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
@@ -170,7 +170,7 @@ async function installBootstrap(target, options = {}) {
170
170
  const installed = [];
171
171
  const skipped = [];
172
172
  const details = [];
173
- ensureFabricBootstrapGuide(workspaceRoot, options.force);
173
+ await ensureFabricBootstrapGuide(workspaceRoot, options.force);
174
174
  for (const bootstrapTarget of targets) {
175
175
  details.push({
176
176
  client: bootstrapTarget.client,
@@ -4,8 +4,8 @@ import {
4
4
  config_default,
5
5
  installMcpClients,
6
6
  parseClientFilter
7
- } from "./chunk-XFSQM3LJ.js";
8
- import "./chunk-VOQKQ6W2.js";
7
+ } from "./chunk-BVTMVW5M.js";
8
+ import "./chunk-BEKSXO5N.js";
9
9
  import "./chunk-YDZJRLHL.js";
10
10
  import "./chunk-6ICJICVU.js";
11
11
  export {
package/dist/index.js CHANGED
@@ -8,17 +8,18 @@ import { defineCommand, runMain } from "citty";
8
8
 
9
9
  // src/commands/index.ts
10
10
  var allCommands = {
11
- init: () => import("./init-T3LGMGAO.js").then((module) => module.default),
12
- update: () => import("./update-AN3FYF2O.js").then((module) => module.default),
13
- scan: () => import("./scan-43R3IBLR.js").then((module) => module.default),
11
+ approve: () => import("./approve-YT4DEABS.js").then((module) => module.default),
12
+ init: () => import("./init-YR7EVBYQ.js").then((module) => module.default),
13
+ update: () => import("./update-M5M5PYKE.js").then((module) => module.default),
14
+ scan: () => import("./scan-QH76LC7Z.js").then((module) => module.default),
14
15
  serve: () => import("./serve-4J2CQY25.js").then((module) => module.default),
15
16
  doctor: () => import("./doctor-QTSG2RWF.js").then((module) => module.default),
16
17
  "sync-meta": () => import("./sync-meta-LKVSO6TS.js").then((module) => module.default),
17
18
  "human-lint": () => import("./human-lint-YSFOZHZ7.js").then((module) => module.default),
18
19
  "ledger-append": () => import("./ledger-append-DULKJ6Q2.js").then((module) => module.default),
19
20
  "pre-commit": () => import("./pre-commit-IK6SJOPT.js").then((module) => module.default),
20
- bootstrap: () => import("./bootstrap-3PUKUYTY.js").then((module) => module.default),
21
- config: () => import("./config-GINBGANU.js").then((module) => module.configCmd),
21
+ bootstrap: () => import("./bootstrap-VGL3AR26.js").then((module) => module.default),
22
+ config: () => import("./config-EC5L2QNI.js").then((module) => module.configCmd),
22
23
  hooks: () => import("./hooks-ZSWVH2JD.js").then((module) => ({
23
24
  ...module.default,
24
25
  meta: {
@@ -32,7 +33,7 @@ var allCommands = {
32
33
  var main = defineCommand({
33
34
  meta: {
34
35
  name: "fabric",
35
- version: "1.3.1",
36
+ version: "1.5.0",
36
37
  description: 'Initialize and manage Fabric projects. Use "fabric init" for one-shot setup.'
37
38
  },
38
39
  subCommands: allCommands