@constela/cli 0.5.43 → 0.5.49

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 (3) hide show
  1. package/README.md +49 -0
  2. package/dist/index.js +117 -36
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -230,6 +230,55 @@ constela start --port 8080
230
230
 
231
231
  The server binds to `0.0.0.0` by default for deployment compatibility.
232
232
 
233
+ ### constela suggest
234
+
235
+ AI を使って DSL ファイルを分析し、改善提案を取得します。
236
+
237
+ ```bash
238
+ constela suggest <input> [options]
239
+ ```
240
+
241
+ **Arguments:**
242
+ - `<input>` - 分析対象の JSON ファイル
243
+
244
+ **Options:**
245
+ - `--aspect <type>` - 分析観点: accessibility, performance, security, ux (default: accessibility)
246
+ - `--provider <name>` - AI プロバイダー: anthropic, openai (default: anthropic)
247
+ - `--json` - JSON 形式で出力
248
+
249
+ **環境変数:**
250
+ - `ANTHROPIC_API_KEY` - Anthropic プロバイダー使用時
251
+ - `OPENAI_API_KEY` - OpenAI プロバイダー使用時
252
+
253
+ **Examples:**
254
+
255
+ ```bash
256
+ # アクセシビリティの分析
257
+ constela suggest app.json --aspect accessibility
258
+
259
+ # セキュリティの分析(OpenAI 使用)
260
+ constela suggest app.json --aspect security --provider openai
261
+
262
+ # JSON 出力
263
+ constela suggest app.json --aspect ux --json
264
+ ```
265
+
266
+ **Output:**
267
+
268
+ ```
269
+ === Suggestions for app.json (accessibility) ===
270
+
271
+ [HIGH] Missing aria-label on button
272
+ Recommendation: Add aria-label="Submit form" to the button element
273
+ Location: view.children[0].props
274
+
275
+ [MED] Low color contrast in text
276
+ Recommendation: Increase contrast ratio to meet WCAG AA standards
277
+ Location: view.children[2].props.style
278
+
279
+ Total: 2 suggestion(s)
280
+ ```
281
+
233
282
  ## Project Structure
234
283
 
235
284
  The CLI expects the following project structure:
package/dist/index.js CHANGED
@@ -23,16 +23,17 @@ var colors = {
23
23
  yellow: (s) => `\x1B[33m${s}\x1B[0m`,
24
24
  reset: "\x1B[0m"
25
25
  };
26
+ var noColors = {
27
+ red: (s) => s,
28
+ green: (s) => s,
29
+ yellow: (s) => s,
30
+ reset: ""
31
+ };
26
32
  function getColors() {
27
33
  if (shouldUseColors()) {
28
34
  return colors;
29
35
  }
30
- return {
31
- red: (s) => s,
32
- green: (s) => s,
33
- yellow: (s) => s,
34
- reset: ""
35
- };
36
+ return noColors;
36
37
  }
37
38
  function countViewNodes(node) {
38
39
  let count = 1;
@@ -51,7 +52,7 @@ function countViewNodes(node) {
51
52
  }
52
53
  break;
53
54
  case "each":
54
- count += countViewNodes(node.template);
55
+ count += countViewNodes(node.body);
55
56
  break;
56
57
  // text, markdown, code, slot nodes have no children
57
58
  default:
@@ -423,7 +424,7 @@ function outputResult(inputPath, result, options, c) {
423
424
  }
424
425
  async function compileCommand(inputPath, options) {
425
426
  const isJsonMode = options.json === true;
426
- const c = isJsonMode ? { red: (s) => s, green: (s) => s, yellow: (s) => s, reset: "" } : getColors();
427
+ const c = isJsonMode ? noColors : getColors();
427
428
  const result = performCompilation(inputPath, options, c);
428
429
  outputResult(inputPath, result, options, c);
429
430
  if (!options.watch) {
@@ -470,16 +471,17 @@ var colors2 = {
470
471
  yellow: (s) => `\x1B[33m${s}\x1B[0m`,
471
472
  reset: "\x1B[0m"
472
473
  };
474
+ var noColors2 = {
475
+ red: (s) => s,
476
+ green: (s) => s,
477
+ yellow: (s) => s,
478
+ reset: ""
479
+ };
473
480
  function getColors2() {
474
481
  if (shouldUseColors2()) {
475
482
  return colors2;
476
483
  }
477
- return {
478
- red: (s) => s,
479
- green: (s) => s,
480
- yellow: (s) => s,
481
- reset: ""
482
- };
484
+ return noColors2;
483
485
  }
484
486
  function formatColoredError2(error, c) {
485
487
  const lines = [];
@@ -573,7 +575,7 @@ async function validateCommand(input, options) {
573
575
  const startTime = performance.now();
574
576
  const isJsonMode = options.json === true;
575
577
  const isAllMode = options.all === true;
576
- const c = isJsonMode ? { red: (s) => s, green: (s) => s, yellow: (s) => s, reset: "" } : getColors2();
578
+ const c = isJsonMode ? noColors2 : getColors2();
577
579
  function exitWithResult(success, output) {
578
580
  if (isJsonMode && output) {
579
581
  outputJson2(output);
@@ -730,18 +732,19 @@ var colors3 = {
730
732
  dim: (s) => `\x1B[2m${s}\x1B[0m`,
731
733
  reset: "\x1B[0m"
732
734
  };
735
+ var noColors3 = {
736
+ red: (s) => s,
737
+ green: (s) => s,
738
+ yellow: (s) => s,
739
+ cyan: (s) => s,
740
+ dim: (s) => s,
741
+ reset: ""
742
+ };
733
743
  function getColors3() {
734
744
  if (shouldUseColors3()) {
735
745
  return colors3;
736
746
  }
737
- return {
738
- red: (s) => s,
739
- green: (s) => s,
740
- yellow: (s) => s,
741
- cyan: (s) => s,
742
- dim: (s) => s,
743
- reset: ""
744
- };
747
+ return noColors3;
745
748
  }
746
749
  function summarizeStep(step) {
747
750
  switch (step.do) {
@@ -772,13 +775,14 @@ function summarizeStep(step) {
772
775
  }
773
776
  }
774
777
  function summarizeAction(action) {
775
- if (action.steps.length === 0) {
778
+ const firstStep = action.steps[0];
779
+ if (!firstStep) {
776
780
  return "(empty)";
777
781
  }
778
782
  if (action.steps.length === 1) {
779
- return summarizeStep(action.steps[0]);
783
+ return summarizeStep(firstStep);
780
784
  }
781
- return `${summarizeStep(action.steps[0])} + ${action.steps.length - 1} more`;
785
+ return `${summarizeStep(firstStep)} + ${action.steps.length - 1} more`;
782
786
  }
783
787
  function viewNodeToInfo(node) {
784
788
  const info = { kind: node.kind };
@@ -979,14 +983,7 @@ function formatViewSection(view, c) {
979
983
  }
980
984
  async function inspectCommand(input, options) {
981
985
  const isJsonMode = options.json === true;
982
- const c = isJsonMode ? {
983
- red: (s) => s,
984
- green: (s) => s,
985
- yellow: (s) => s,
986
- cyan: (s) => s,
987
- dim: (s) => s,
988
- reset: ""
989
- } : getColors3();
986
+ const c = isJsonMode ? noColors3 : getColors3();
990
987
  const showAll = !options.state && !options.actions && !options.components && !options.view;
991
988
  const showState = showAll || options.state === true;
992
989
  const showActions = showAll || options.actions === true;
@@ -1058,8 +1055,11 @@ async function inspectCommand(input, options) {
1058
1055
  sections.push(formatViewSection(program2.view, c));
1059
1056
  }
1060
1057
  for (let i = 0; i < sections.length; i++) {
1061
- for (const line of sections[i]) {
1062
- console.log(line);
1058
+ const section = sections[i];
1059
+ if (section) {
1060
+ for (const line of section) {
1061
+ console.log(line);
1062
+ }
1063
1063
  }
1064
1064
  if (i < sections.length - 1) {
1065
1065
  console.log("");
@@ -1185,6 +1185,86 @@ Ready in ${elapsed}ms (Ctrl+Click to open)`);
1185
1185
  }
1186
1186
  }
1187
1187
 
1188
+ // src/commands/suggest.ts
1189
+ import { readFileSync as readFileSync4, existsSync } from "fs";
1190
+ import { resolve } from "path";
1191
+ import {
1192
+ getProvider,
1193
+ buildSuggestPrompt,
1194
+ parseSuggestions,
1195
+ SUGGEST_SYSTEM_PROMPT
1196
+ } from "@constela/ai";
1197
+ var VALID_ASPECTS = ["accessibility", "performance", "security", "ux"];
1198
+ var VALID_PROVIDERS = ["anthropic", "openai"];
1199
+ async function suggestCommand(input, options) {
1200
+ try {
1201
+ const inputPath = resolve(process.cwd(), input);
1202
+ if (!existsSync(inputPath)) {
1203
+ console.error("Error: File not found - " + inputPath);
1204
+ process.exit(1);
1205
+ }
1206
+ const content = readFileSync4(inputPath, "utf-8");
1207
+ let dsl;
1208
+ try {
1209
+ dsl = JSON.parse(content);
1210
+ } catch {
1211
+ console.error("Error: Invalid JSON in input file");
1212
+ process.exit(1);
1213
+ }
1214
+ const aspect = options.aspect ?? "accessibility";
1215
+ if (!VALID_ASPECTS.includes(aspect)) {
1216
+ console.error("Error: Invalid aspect. Valid options: " + VALID_ASPECTS.join(", "));
1217
+ process.exit(1);
1218
+ }
1219
+ const providerType = options.provider ?? "anthropic";
1220
+ if (!VALID_PROVIDERS.includes(providerType)) {
1221
+ console.error("Error: Invalid provider. Valid options: " + VALID_PROVIDERS.join(", "));
1222
+ process.exit(1);
1223
+ }
1224
+ const apiKeyEnvVar = providerType === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
1225
+ if (!process.env[apiKeyEnvVar]) {
1226
+ console.error("Error: " + apiKeyEnvVar + " environment variable is not set");
1227
+ process.exit(1);
1228
+ }
1229
+ console.log("Analyzing " + input + " for " + aspect + " suggestions...");
1230
+ const provider = getProvider(providerType);
1231
+ const prompt = buildSuggestPrompt({ dsl, aspect });
1232
+ const response = await provider.generate(prompt, {
1233
+ systemPrompt: SUGGEST_SYSTEM_PROMPT
1234
+ });
1235
+ const suggestions = parseSuggestions(response.content);
1236
+ if (options.json) {
1237
+ console.log(JSON.stringify({ suggestions, aspect, file: input }, null, 2));
1238
+ } else {
1239
+ outputSuggestions(suggestions, aspect, input);
1240
+ }
1241
+ } catch (err) {
1242
+ const error = err;
1243
+ console.error("Error: " + error.message);
1244
+ process.exit(1);
1245
+ }
1246
+ }
1247
+ function outputSuggestions(suggestions, aspect, file) {
1248
+ console.log("");
1249
+ console.log("=== Suggestions for " + file + " (" + aspect + ") ===");
1250
+ console.log("");
1251
+ if (suggestions.length === 0) {
1252
+ console.log("No suggestions found. Great job!");
1253
+ return;
1254
+ }
1255
+ for (let i = 0; i < suggestions.length; i++) {
1256
+ const s = suggestions[i];
1257
+ const severityIcon = s.severity === "high" ? "[HIGH]" : s.severity === "medium" ? "[MED]" : "[LOW]";
1258
+ console.log(severityIcon + " " + s.issue);
1259
+ console.log(" Recommendation: " + s.recommendation);
1260
+ if (s.location) {
1261
+ console.log(" Location: " + s.location);
1262
+ }
1263
+ console.log("");
1264
+ }
1265
+ console.log("Total: " + suggestions.length + " suggestion(s)");
1266
+ }
1267
+
1188
1268
  // src/index.ts
1189
1269
  var program = new Command();
1190
1270
  program.name("constela").description("Constela UI framework CLI").version("0.1.0");
@@ -1194,6 +1274,7 @@ program.command("inspect <input>").description("Inspect Constela program structu
1194
1274
  program.command("dev").description("Start development server").option("-p, --port <number>", "Port number (default: 3000)").option("--host <string>", "Host address").option("--routesDir <path>", "Routes directory").option("--publicDir <path>", "Public directory").option("--layoutsDir <path>", "Layouts directory").action(devCommand);
1195
1275
  program.command("build").description("Build for production").option("-o, --outDir <path>", "Output directory (default: dist)").option("--routesDir <path>", "Routes directory").option("--publicDir <path>", "Public directory").option("--layoutsDir <path>", "Layouts directory").action(buildCommand);
1196
1276
  program.command("start").description("Start production server").option("-p, --port <number>", "Port number (default: 3000)").action(startCommand);
1277
+ program.command("suggest <input>").description("Get AI-powered suggestions for Constela DSL").option("--aspect <type>", "Aspect to analyze: accessibility, performance, security, ux").option("--provider <name>", "AI provider: anthropic, openai (default: anthropic)").option("--json", "Output results as JSON").action(suggestCommand);
1197
1278
  if (process.argv.length <= 2) {
1198
1279
  program.outputHelp();
1199
1280
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/cli",
3
- "version": "0.5.43",
3
+ "version": "0.5.49",
4
4
  "description": "CLI tools for Constela UI framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,9 +19,10 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "commander": "^12.0.0",
22
- "@constela/core": "0.15.2",
23
- "@constela/start": "1.8.21",
24
- "@constela/compiler": "0.14.4"
22
+ "@constela/ai": "1.0.2",
23
+ "@constela/start": "1.8.27",
24
+ "@constela/compiler": "0.14.7",
25
+ "@constela/core": "0.16.2"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/node": "^20.10.0",