canicode 0.3.3 → 0.4.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.
- package/README.md +3 -29
- package/dist/cli/index.js +348 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.js +308 -0
- package/dist/mcp/server.js.map +1 -1
- package/docs/CUSTOMIZATION.md +18 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,33 +144,6 @@ canicode analyze <url> --preset strict
|
|
|
144
144
|
|
|
145
145
|
</details>
|
|
146
146
|
|
|
147
|
-
<details>
|
|
148
|
-
<summary><strong>Custom Rules</strong></summary>
|
|
149
|
-
|
|
150
|
-
Add project-specific checks via a JSON file:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
canicode analyze <url> --custom-rules ./my-rules.json
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
```json
|
|
157
|
-
[
|
|
158
|
-
{
|
|
159
|
-
"id": "icon-missing-component",
|
|
160
|
-
"category": "component",
|
|
161
|
-
"severity": "blocking",
|
|
162
|
-
"score": -10,
|
|
163
|
-
"prompt": "Check if this node is an icon and is not a component.",
|
|
164
|
-
"why": "Icons that are not components cannot be reused.",
|
|
165
|
-
"impact": "Developers will hardcode icons.",
|
|
166
|
-
"fix": "Convert to a component and publish to the library."
|
|
167
|
-
}
|
|
168
|
-
]
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
See [`examples/custom-rules.json`](examples/custom-rules.json) | Run `canicode docs rules`
|
|
172
|
-
|
|
173
|
-
</details>
|
|
174
147
|
|
|
175
148
|
<details>
|
|
176
149
|
<summary><strong>Config Overrides</strong></summary>
|
|
@@ -308,9 +281,10 @@ pnpm lint # type check
|
|
|
308
281
|
|
|
309
282
|
- [x] **Phase 1** — 39 rules, density-based scoring, HTML reports, presets, scoped analysis
|
|
310
283
|
- [x] **Phase 2** — 4-agent calibration pipeline, `/calibrate-loop` debate loop
|
|
311
|
-
- [x] **Phase 3** —
|
|
284
|
+
- [x] **Phase 3** — Config overrides, MCP server, Claude Skills
|
|
312
285
|
- [x] **Phase 4** — Figma comment from report (per-issue "Comment" button in HTML report, posts to Figma node via API)
|
|
313
|
-
- [ ] **Phase 5** —
|
|
286
|
+
- [ ] **Phase 5** — Custom rules with pattern matching (node name/type/attribute conditions)
|
|
287
|
+
- [ ] **Phase 6** — Screenshot comparison (Figma vs AI-generated code, visual diff)
|
|
314
288
|
|
|
315
289
|
## License
|
|
316
290
|
|
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,204 @@ import cac from 'cac';
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __esm = (fn, res) => function __init() {
|
|
13
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/monitoring/browser.ts
|
|
21
|
+
var browser_exports = {};
|
|
22
|
+
__export(browser_exports, {
|
|
23
|
+
initBrowserMonitoring: () => initBrowserMonitoring,
|
|
24
|
+
shutdownBrowserMonitoring: () => shutdownBrowserMonitoring,
|
|
25
|
+
trackBrowserError: () => trackBrowserError,
|
|
26
|
+
trackBrowserEvent: () => trackBrowserEvent
|
|
27
|
+
});
|
|
28
|
+
function getGlobal() {
|
|
29
|
+
return globalThis;
|
|
30
|
+
}
|
|
31
|
+
function injectScript(src) {
|
|
32
|
+
return new Promise((resolve7, reject) => {
|
|
33
|
+
const g = getGlobal();
|
|
34
|
+
const doc = g["document"];
|
|
35
|
+
if (!doc) {
|
|
36
|
+
resolve7();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const script = doc["createElement"]("script");
|
|
40
|
+
script["src"] = src;
|
|
41
|
+
script["async"] = true;
|
|
42
|
+
script["onload"] = () => resolve7();
|
|
43
|
+
script["onerror"] = () => reject(new Error(`Failed to load script: ${src}`));
|
|
44
|
+
doc["head"]["appendChild"](script);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function initBrowserMonitoring(config2) {
|
|
48
|
+
if (config2.enabled === false) return;
|
|
49
|
+
const g = getGlobal();
|
|
50
|
+
if (!g["document"]) return;
|
|
51
|
+
monitoringEnabled = true;
|
|
52
|
+
if (config2.posthogApiKey) {
|
|
53
|
+
try {
|
|
54
|
+
await injectScript("https://us-assets.i.posthog.com/static/array.js");
|
|
55
|
+
g["posthog"]?.["init"]?.(config2.posthogApiKey, {
|
|
56
|
+
api_host: "https://us.i.posthog.com",
|
|
57
|
+
autocapture: false,
|
|
58
|
+
capture_pageview: true
|
|
59
|
+
});
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (config2.sentryDsn) {
|
|
64
|
+
try {
|
|
65
|
+
await injectScript("https://browser.sentry-cdn.com/8.0.0/bundle.min.js");
|
|
66
|
+
g["Sentry"]?.["init"]?.({
|
|
67
|
+
dsn: config2.sentryDsn,
|
|
68
|
+
environment: config2.environment ?? "web",
|
|
69
|
+
release: config2.version,
|
|
70
|
+
tracesSampleRate: 0
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function trackBrowserEvent(event, properties) {
|
|
77
|
+
if (!monitoringEnabled) return;
|
|
78
|
+
try {
|
|
79
|
+
getGlobal()["posthog"]?.["capture"]?.(event, properties);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function trackBrowserError(error, context) {
|
|
84
|
+
if (!monitoringEnabled) return;
|
|
85
|
+
try {
|
|
86
|
+
getGlobal()["Sentry"]?.["captureException"]?.(
|
|
87
|
+
error,
|
|
88
|
+
context ? { extra: context } : void 0
|
|
89
|
+
);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
getGlobal()["posthog"]?.["capture"]?.("error", {
|
|
94
|
+
error: error.message,
|
|
95
|
+
...context
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function shutdownBrowserMonitoring() {
|
|
101
|
+
monitoringEnabled = false;
|
|
102
|
+
}
|
|
103
|
+
var monitoringEnabled;
|
|
104
|
+
var init_browser = __esm({
|
|
105
|
+
"src/monitoring/browser.ts"() {
|
|
106
|
+
monitoringEnabled = false;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// src/monitoring/node.ts
|
|
111
|
+
var node_exports = {};
|
|
112
|
+
__export(node_exports, {
|
|
113
|
+
initNodeMonitoring: () => initNodeMonitoring,
|
|
114
|
+
shutdownNodeMonitoring: () => shutdownNodeMonitoring,
|
|
115
|
+
trackNodeError: () => trackNodeError,
|
|
116
|
+
trackNodeEvent: () => trackNodeEvent
|
|
117
|
+
});
|
|
118
|
+
async function initNodeMonitoring(config2) {
|
|
119
|
+
if (config2.enabled === false) return;
|
|
120
|
+
monitoringEnabled2 = true;
|
|
121
|
+
commonProps = {
|
|
122
|
+
_sdk: "canicode",
|
|
123
|
+
_sdk_version: config2.version ?? "unknown",
|
|
124
|
+
_env: config2.environment ?? "unknown"
|
|
125
|
+
};
|
|
126
|
+
if (config2.posthogApiKey) {
|
|
127
|
+
try {
|
|
128
|
+
const mod = await import('posthog-node');
|
|
129
|
+
const PostHog = mod.PostHog;
|
|
130
|
+
posthogClient = new PostHog(config2.posthogApiKey, {
|
|
131
|
+
host: "https://us.i.posthog.com",
|
|
132
|
+
flushAt: 10,
|
|
133
|
+
flushInterval: 1e4
|
|
134
|
+
});
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (config2.sentryDsn) {
|
|
139
|
+
try {
|
|
140
|
+
const mod = await import('@sentry/node');
|
|
141
|
+
sentryModule = mod;
|
|
142
|
+
sentryModule.init({
|
|
143
|
+
dsn: config2.sentryDsn,
|
|
144
|
+
environment: config2.environment ?? "cli",
|
|
145
|
+
release: config2.version,
|
|
146
|
+
tracesSampleRate: 0
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function trackNodeEvent(event, properties) {
|
|
153
|
+
if (!monitoringEnabled2 || !posthogClient) return;
|
|
154
|
+
try {
|
|
155
|
+
const captureOpts = {
|
|
156
|
+
distinctId: "anonymous",
|
|
157
|
+
event
|
|
158
|
+
};
|
|
159
|
+
captureOpts.properties = { ...commonProps, ...properties };
|
|
160
|
+
posthogClient.capture(captureOpts);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function trackNodeError(error, context) {
|
|
165
|
+
if (!monitoringEnabled2) return;
|
|
166
|
+
try {
|
|
167
|
+
sentryModule?.captureException(error, context ? { extra: context } : void 0);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
posthogClient?.capture({
|
|
172
|
+
distinctId: "anonymous",
|
|
173
|
+
event: "cic_error",
|
|
174
|
+
properties: { ...commonProps, error: error.message, ...context }
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function shutdownNodeMonitoring() {
|
|
180
|
+
if (!monitoringEnabled2) return;
|
|
181
|
+
const tasks = [];
|
|
182
|
+
if (posthogClient) {
|
|
183
|
+
tasks.push(
|
|
184
|
+
posthogClient.shutdown().catch(() => {
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (sentryModule) {
|
|
189
|
+
tasks.push(
|
|
190
|
+
sentryModule.close(2e3).catch(() => {
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
await Promise.allSettled(tasks);
|
|
195
|
+
posthogClient = null;
|
|
196
|
+
sentryModule = null;
|
|
197
|
+
monitoringEnabled2 = false;
|
|
198
|
+
}
|
|
199
|
+
var posthogClient, sentryModule, monitoringEnabled2, commonProps;
|
|
200
|
+
var init_node = __esm({
|
|
201
|
+
"src/monitoring/node.ts"() {
|
|
202
|
+
posthogClient = null;
|
|
203
|
+
sentryModule = null;
|
|
204
|
+
monitoringEnabled2 = false;
|
|
205
|
+
commonProps = {};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
10
208
|
z.object({
|
|
11
209
|
fileKey: z.string(),
|
|
12
210
|
nodeId: z.string().optional(),
|
|
@@ -1033,6 +1231,20 @@ function getReportsDir() {
|
|
|
1033
1231
|
function ensureReportsDir() {
|
|
1034
1232
|
ensureDir(REPORTS_DIR);
|
|
1035
1233
|
}
|
|
1234
|
+
function getTelemetryEnabled() {
|
|
1235
|
+
return readConfig().telemetry !== false;
|
|
1236
|
+
}
|
|
1237
|
+
function setTelemetryEnabled(enabled) {
|
|
1238
|
+
const config2 = readConfig();
|
|
1239
|
+
config2.telemetry = enabled;
|
|
1240
|
+
writeConfig(config2);
|
|
1241
|
+
}
|
|
1242
|
+
function getPosthogApiKey() {
|
|
1243
|
+
return process.env["POSTHOG_API_KEY"] ?? readConfig().posthogApiKey;
|
|
1244
|
+
}
|
|
1245
|
+
function getSentryDsn() {
|
|
1246
|
+
return process.env["SENTRY_DSN"] ?? readConfig().sentryDsn;
|
|
1247
|
+
}
|
|
1036
1248
|
function initAiready(token) {
|
|
1037
1249
|
setFigmaToken(token);
|
|
1038
1250
|
ensureReportsDir();
|
|
@@ -2818,6 +3030,77 @@ function handleDocs(topic) {
|
|
|
2818
3030
|
}
|
|
2819
3031
|
}
|
|
2820
3032
|
|
|
3033
|
+
// src/monitoring/events.ts
|
|
3034
|
+
var EVENT_PREFIX = "cic_";
|
|
3035
|
+
var EVENTS = {
|
|
3036
|
+
// Analysis
|
|
3037
|
+
ANALYSIS_STARTED: `${EVENT_PREFIX}analysis_started`,
|
|
3038
|
+
ANALYSIS_COMPLETED: `${EVENT_PREFIX}analysis_completed`,
|
|
3039
|
+
ANALYSIS_FAILED: `${EVENT_PREFIX}analysis_failed`,
|
|
3040
|
+
// Report
|
|
3041
|
+
REPORT_GENERATED: `${EVENT_PREFIX}report_generated`,
|
|
3042
|
+
COMMENT_POSTED: `${EVENT_PREFIX}comment_posted`,
|
|
3043
|
+
COMMENT_FAILED: `${EVENT_PREFIX}comment_failed`,
|
|
3044
|
+
// MCP
|
|
3045
|
+
MCP_TOOL_CALLED: `${EVENT_PREFIX}mcp_tool_called`,
|
|
3046
|
+
// CLI
|
|
3047
|
+
CLI_COMMAND: `${EVENT_PREFIX}cli_command`,
|
|
3048
|
+
CLI_INIT: `${EVENT_PREFIX}cli_init`
|
|
3049
|
+
};
|
|
3050
|
+
|
|
3051
|
+
// src/monitoring/index.ts
|
|
3052
|
+
var _trackEvent = () => {
|
|
3053
|
+
};
|
|
3054
|
+
var _trackError = () => {
|
|
3055
|
+
};
|
|
3056
|
+
var _shutdown = () => Promise.resolve();
|
|
3057
|
+
function isBrowser() {
|
|
3058
|
+
const g = globalThis;
|
|
3059
|
+
return typeof g["window"] !== "undefined" && typeof g["document"] !== "undefined";
|
|
3060
|
+
}
|
|
3061
|
+
async function initMonitoring(config2) {
|
|
3062
|
+
if (config2.enabled === false) return;
|
|
3063
|
+
if (!config2.posthogApiKey && !config2.sentryDsn) return;
|
|
3064
|
+
try {
|
|
3065
|
+
if (isBrowser()) {
|
|
3066
|
+
const { initBrowserMonitoring: initBrowserMonitoring2, trackBrowserEvent: trackBrowserEvent2, trackBrowserError: trackBrowserError2, shutdownBrowserMonitoring: shutdownBrowserMonitoring2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
|
|
3067
|
+
await initBrowserMonitoring2(config2);
|
|
3068
|
+
_trackEvent = trackBrowserEvent2;
|
|
3069
|
+
_trackError = trackBrowserError2;
|
|
3070
|
+
_shutdown = shutdownBrowserMonitoring2;
|
|
3071
|
+
} else {
|
|
3072
|
+
const { initNodeMonitoring: initNodeMonitoring2, trackNodeEvent: trackNodeEvent2, trackNodeError: trackNodeError2, shutdownNodeMonitoring: shutdownNodeMonitoring2 } = await Promise.resolve().then(() => (init_node(), node_exports));
|
|
3073
|
+
await initNodeMonitoring2(config2);
|
|
3074
|
+
_trackEvent = trackNodeEvent2;
|
|
3075
|
+
_trackError = trackNodeError2;
|
|
3076
|
+
_shutdown = shutdownNodeMonitoring2;
|
|
3077
|
+
}
|
|
3078
|
+
} catch {
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
function trackEvent(event, properties) {
|
|
3082
|
+
try {
|
|
3083
|
+
_trackEvent(event, properties);
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
function trackError(error, context) {
|
|
3088
|
+
try {
|
|
3089
|
+
_trackError(error, context);
|
|
3090
|
+
} catch {
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
async function shutdownMonitoring() {
|
|
3094
|
+
try {
|
|
3095
|
+
await _shutdown();
|
|
3096
|
+
} catch {
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/monitoring/keys.ts
|
|
3101
|
+
var POSTHOG_API_KEY = "phc_rBFeG140KqJLpUnlpYDEFgdMM6JozZeqQsf9twXf5Dq" ;
|
|
3102
|
+
var SENTRY_DSN = "https://80a836a8300b25f17ef5bbf23afb5b3a@o4511080656207872.ingest.us.sentry.io/4511080661319680" ;
|
|
3103
|
+
|
|
2821
3104
|
// src/rules/layout/index.ts
|
|
2822
3105
|
function isContainerNode(node) {
|
|
2823
3106
|
return node.type === "FRAME" || node.type === "GROUP" || node.type === "COMPONENT";
|
|
@@ -3987,6 +4270,23 @@ defineRule({
|
|
|
3987
4270
|
// src/cli/index.ts
|
|
3988
4271
|
config();
|
|
3989
4272
|
var cli = cac("canicode");
|
|
4273
|
+
{
|
|
4274
|
+
const monitoringConfig = {
|
|
4275
|
+
environment: "cli",
|
|
4276
|
+
version: "0.3.3",
|
|
4277
|
+
enabled: getTelemetryEnabled()
|
|
4278
|
+
};
|
|
4279
|
+
const phKey = getPosthogApiKey() || POSTHOG_API_KEY;
|
|
4280
|
+
monitoringConfig.posthogApiKey = phKey;
|
|
4281
|
+
const sDsn = getSentryDsn() || SENTRY_DSN;
|
|
4282
|
+
monitoringConfig.sentryDsn = sDsn;
|
|
4283
|
+
initMonitoring(monitoringConfig).catch(() => {
|
|
4284
|
+
});
|
|
4285
|
+
}
|
|
4286
|
+
process.on("beforeExit", () => {
|
|
4287
|
+
shutdownMonitoring().catch(() => {
|
|
4288
|
+
});
|
|
4289
|
+
});
|
|
3990
4290
|
var MAX_NODES_WITHOUT_SCOPE = 500;
|
|
3991
4291
|
function pickRandomScope(root) {
|
|
3992
4292
|
const candidates = [];
|
|
@@ -4019,6 +4319,8 @@ function countNodes2(node) {
|
|
|
4019
4319
|
return count;
|
|
4020
4320
|
}
|
|
4021
4321
|
cli.command("analyze <input>", "Analyze a Figma file or JSON fixture").option("--preset <preset>", "Analysis preset (relaxed | dev-friendly | ai-ready | strict)").option("--output <path>", "HTML report output path").option("--token <token>", "Figma API token (or use FIGMA_TOKEN env var)").option("--mcp", "Load via Figma MCP (no FIGMA_TOKEN needed)").option("--api", "Load via Figma REST API (requires FIGMA_TOKEN)").option("--screenshot", "Include screenshot comparison in report (requires ANTHROPIC_API_KEY)").option("--custom-rules <path>", "Path to custom rules JSON file").option("--config <path>", "Path to config JSON file (override rule scores/settings)").option("--no-open", "Don't open report in browser after analysis").example(" canicode analyze https://www.figma.com/design/ABC123/MyDesign").example(" canicode analyze https://www.figma.com/design/ABC123/MyDesign --mcp").example(" canicode analyze https://www.figma.com/design/ABC123/MyDesign --api --token YOUR_TOKEN").example(" canicode analyze ./fixtures/design.json --output report.html").example(" canicode analyze ./fixtures/design.json --custom-rules ./my-rules.json").example(" canicode analyze ./fixtures/design.json --config ./my-config.json").action(async (input, options) => {
|
|
4322
|
+
const analysisStart = Date.now();
|
|
4323
|
+
trackEvent(EVENTS.ANALYSIS_STARTED, { source: isJsonFile(input) ? "fixture" : "figma" });
|
|
4022
4324
|
try {
|
|
4023
4325
|
if (options.mcp && options.api) {
|
|
4024
4326
|
throw new Error("Cannot use --mcp and --api together. Choose one.");
|
|
@@ -4117,6 +4419,14 @@ Analyzing: ${file.name}`);
|
|
|
4117
4419
|
await writeFile(outputPath, html, "utf-8");
|
|
4118
4420
|
console.log(`
|
|
4119
4421
|
Report saved: ${outputPath}`);
|
|
4422
|
+
trackEvent(EVENTS.ANALYSIS_COMPLETED, {
|
|
4423
|
+
nodeCount: result.nodeCount,
|
|
4424
|
+
issueCount: result.issues.length,
|
|
4425
|
+
grade: scores.overall.grade,
|
|
4426
|
+
percentage: scores.overall.percentage,
|
|
4427
|
+
duration: Date.now() - analysisStart
|
|
4428
|
+
});
|
|
4429
|
+
trackEvent(EVENTS.REPORT_GENERATED, { format: "html" });
|
|
4120
4430
|
if (!options.noOpen) {
|
|
4121
4431
|
const { exec } = await import('child_process');
|
|
4122
4432
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -4126,6 +4436,14 @@ Report saved: ${outputPath}`);
|
|
|
4126
4436
|
process.exit(1);
|
|
4127
4437
|
}
|
|
4128
4438
|
} catch (error) {
|
|
4439
|
+
trackError(
|
|
4440
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
4441
|
+
{ command: "analyze", input }
|
|
4442
|
+
);
|
|
4443
|
+
trackEvent(EVENTS.ANALYSIS_FAILED, {
|
|
4444
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4445
|
+
duration: Date.now() - analysisStart
|
|
4446
|
+
});
|
|
4129
4447
|
console.error(
|
|
4130
4448
|
"\nError:",
|
|
4131
4449
|
error instanceof Error ? error.message : String(error)
|
|
@@ -4382,6 +4700,35 @@ cli.command("init", "Set up canicode (Figma token or MCP)").option("--token <tok
|
|
|
4382
4700
|
process.exit(1);
|
|
4383
4701
|
}
|
|
4384
4702
|
});
|
|
4703
|
+
cli.command("config", "Manage canicode configuration").option("--telemetry", "Enable anonymous telemetry").option("--no-telemetry", "Disable anonymous telemetry").action((options) => {
|
|
4704
|
+
try {
|
|
4705
|
+
if (options.noTelemetry === true) {
|
|
4706
|
+
setTelemetryEnabled(false);
|
|
4707
|
+
console.log("Telemetry disabled. No analytics data will be sent.");
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
if (options.telemetry === true) {
|
|
4711
|
+
setTelemetryEnabled(true);
|
|
4712
|
+
console.log("Telemetry enabled. Only anonymous usage events are tracked \u2014 no design data.");
|
|
4713
|
+
return;
|
|
4714
|
+
}
|
|
4715
|
+
const cfg = readConfig();
|
|
4716
|
+
console.log("CANICODE CONFIG\n");
|
|
4717
|
+
console.log(` Config path: ${getConfigPath()}`);
|
|
4718
|
+
console.log(` Figma token: ${cfg.figmaToken ? "set" : "not set"}`);
|
|
4719
|
+
console.log(` Telemetry: ${cfg.telemetry !== false ? "enabled" : "disabled"}`);
|
|
4720
|
+
console.log(`
|
|
4721
|
+
Options:`);
|
|
4722
|
+
console.log(` canicode config --no-telemetry Opt out of anonymous telemetry`);
|
|
4723
|
+
console.log(` canicode config --telemetry Opt back in`);
|
|
4724
|
+
} catch (error) {
|
|
4725
|
+
console.error(
|
|
4726
|
+
"\nError:",
|
|
4727
|
+
error instanceof Error ? error.message : String(error)
|
|
4728
|
+
);
|
|
4729
|
+
process.exit(1);
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4385
4732
|
cli.command("list-rules", "List all analysis rules with scores and severity").option("--custom-rules <path>", "Include custom rules from JSON file").option("--config <path>", "Apply config overrides to show effective scores").option("--json", "Output as JSON").action(async (options) => {
|
|
4386
4733
|
try {
|
|
4387
4734
|
let configs = { ...RULE_CONFIGS };
|
|
@@ -4460,7 +4807,6 @@ cli.help((sections) => {
|
|
|
4460
4807
|
{
|
|
4461
4808
|
title: "\nCustomization",
|
|
4462
4809
|
body: [
|
|
4463
|
-
` --custom-rules <path> Add custom rules (see: canicode docs rules)`,
|
|
4464
4810
|
` --config <path> Override rule settings (see: canicode docs config)`
|
|
4465
4811
|
].join("\n")
|
|
4466
4812
|
},
|
|
@@ -4470,8 +4816,7 @@ cli.help((sections) => {
|
|
|
4470
4816
|
` $ canicode analyze "https://www.figma.com/design/..." --mcp`,
|
|
4471
4817
|
` $ canicode analyze "https://www.figma.com/design/..." --api`,
|
|
4472
4818
|
` $ canicode analyze "https://www.figma.com/design/..." --preset strict`,
|
|
4473
|
-
` $ canicode analyze "https://www.figma.com/design/..." --config ./my-config.json
|
|
4474
|
-
` $ canicode analyze "https://www.figma.com/design/..." --custom-rules ./my-rules.json`
|
|
4819
|
+
` $ canicode analyze "https://www.figma.com/design/..." --config ./my-config.json`
|
|
4475
4820
|
].join("\n")
|
|
4476
4821
|
},
|
|
4477
4822
|
{
|