lokuma-cli 1.3.3 → 1.3.5

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.
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Lokuma Design — Cloud Client
5
+
6
+ Just describe what you're building. Lokuma figures out the rest.
7
+
8
+ Usage:
9
+ python design.py "<describe your product or design need>"
10
+ python design.py "<describe your product or design need>" -p "Project Name"
11
+ python design.py "<describe your product or design need>" -f json
12
+ python design.py "<describe your product or design need>" -f markdown
13
+
14
+ Examples:
15
+ python design.py "A meditation app for stressed professionals. Calm, premium, organic."
16
+ python design.py "A landing page for an AI sales SaaS. Sharp, fast, conversion-focused." -p "Closer"
17
+ python design.py "What color palette fits a luxury skincare brand?" -f json
18
+ python design.py "Best font pairing for a fintech dashboard" -f markdown
19
+
20
+ Setup:
21
+ export LOKUMA_API_KEY=lokuma_your_key_here
22
+ Get your key at https://lokuma.ai
23
+ """
24
+
25
+ import argparse
26
+ import json
27
+ import os
28
+ import sys
29
+ import io
30
+ import urllib.request
31
+ import urllib.error
32
+ from typing import Optional
33
+
34
+ # Force UTF-8 output on Windows
35
+ if sys.stdout.encoding and sys.stdout.encoding.lower() != "utf-8":
36
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
37
+ if sys.stderr.encoding and sys.stderr.encoding.lower() != "utf-8":
38
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
39
+
40
+
41
+ # ─────────────────────────────────────────────
42
+ # Config
43
+ # ─────────────────────────────────────────────
44
+
45
+ API_BASE = os.environ.get("LOKUMA_API_URL", "https://api.lokuma.ai").rstrip("/")
46
+ API_VERSION = "v1"
47
+ _BASE = f"{API_BASE}/{API_VERSION}"
48
+
49
+
50
+ def _get_api_key() -> str:
51
+ key = os.environ.get("LOKUMA_API_KEY", "").strip()
52
+ if not key:
53
+ print(
54
+ "Error: LOKUMA_API_KEY is not set.\n"
55
+ "Set it with: export LOKUMA_API_KEY=lokuma_your_key_here\n"
56
+ "Get your key at https://lokuma.ai",
57
+ file=sys.stderr,
58
+ )
59
+ sys.exit(1)
60
+ return key
61
+
62
+
63
+ # ─────────────────────────────────────────────
64
+ # HTTP client (stdlib only, zero deps)
65
+ # ─────────────────────────────────────────────
66
+
67
+ def _post(endpoint: str, payload: dict) -> dict:
68
+ api_key = _get_api_key()
69
+ url = f"{_BASE}/{endpoint}"
70
+ data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
71
+
72
+ req = urllib.request.Request(
73
+ url,
74
+ data=data,
75
+ headers={
76
+ "Content-Type": "application/json",
77
+ "X-API-Key": api_key,
78
+ "User-Agent": "lokuma-skill/2.0",
79
+ },
80
+ method="POST",
81
+ )
82
+
83
+ try:
84
+ with urllib.request.urlopen(req, timeout=60) as resp:
85
+ body = resp.read().decode("utf-8")
86
+ return json.loads(body)
87
+ except urllib.error.HTTPError as e:
88
+ body = e.read().decode("utf-8", errors="replace")
89
+ try:
90
+ err = json.loads(body)
91
+ msg = err.get("error", body)
92
+ except Exception:
93
+ msg = body
94
+ if e.code in (401, 403):
95
+ print(f"Error: Invalid or expired API key (HTTP {e.code})", file=sys.stderr)
96
+ elif e.code == 402:
97
+ print(f"Error: {msg}", file=sys.stderr)
98
+ else:
99
+ print(f"Error: API returned HTTP {e.code}: {msg}", file=sys.stderr)
100
+ sys.exit(1)
101
+ except urllib.error.URLError as e:
102
+ print(f"Error: Could not reach Lokuma API: {e.reason}", file=sys.stderr)
103
+ sys.exit(1)
104
+ except json.JSONDecodeError as e:
105
+ print(f"Error: Invalid JSON response: {e}", file=sys.stderr)
106
+ sys.exit(1)
107
+
108
+
109
+ # ─────────────────────────────────────────────
110
+ # Output formatting
111
+ # ─────────────────────────────────────────────
112
+
113
+ def _format_output(result: dict, fmt: str) -> str:
114
+ if "error" in result:
115
+ return f"Error: {result['error']}"
116
+
117
+ # design-system response (has "output" key for ascii/markdown)
118
+ if "output" in result:
119
+ return result["output"]
120
+
121
+ # design-system json response
122
+ if "design_system" in result:
123
+ ds = result["design_system"]
124
+ if fmt == "json":
125
+ return json.dumps(ds, indent=2, ensure_ascii=False)
126
+ lines = [f"## Design System: {ds.get('project_name', '')}\n"]
127
+ for section, data in ds.items():
128
+ if section == "project_name":
129
+ continue
130
+ lines.append(f"### {section.replace('_', ' ').title()}")
131
+ if isinstance(data, dict):
132
+ for k, v in data.items():
133
+ lines.append(f"- **{k}**: {v}")
134
+ else:
135
+ lines.append(str(data))
136
+ lines.append("")
137
+ return "\n".join(lines)
138
+
139
+ # multi-domain response
140
+ if result.get("strategy") == "multi":
141
+ if fmt == "json":
142
+ return json.dumps(result, indent=2, ensure_ascii=False)
143
+ lines = [f"## Lokuma Design Results\n"]
144
+ for r in result.get("results", []):
145
+ domain = r.get("domain", "")
146
+ lines.append(f"### {domain.title()}")
147
+ for i, row in enumerate(r.get("results", []), 1):
148
+ lines.append(f"**{i}.** " + " | ".join(
149
+ f"{k}: {str(v)[:100]}" for k, v in list(row.items())[:4]
150
+ ))
151
+ lines.append("")
152
+ return "\n".join(lines)
153
+
154
+ # single-domain response
155
+ if fmt == "json":
156
+ return json.dumps(result, indent=2, ensure_ascii=False)
157
+
158
+ lines = [f"## Lokuma — {result.get('domain', '').title()}\n"]
159
+ lines.append(f"**Query:** {result.get('query', '')}\n")
160
+ for i, row in enumerate(result.get("results", []), 1):
161
+ lines.append(f"### Result {i}")
162
+ for key, value in row.items():
163
+ v = str(value)
164
+ if len(v) > 300:
165
+ v = v[:300] + "..."
166
+ lines.append(f"- **{key}:** {v}")
167
+ lines.append("")
168
+ return "\n".join(lines)
169
+
170
+
171
+ # ─────────────────────────────────────────────
172
+ # CLI
173
+ # ─────────────────────────────────────────────
174
+
175
+ def main():
176
+ parser = argparse.ArgumentParser(
177
+ description="Lokuma Design Intelligence",
178
+ formatter_class=argparse.RawDescriptionHelpFormatter,
179
+ epilog=__doc__,
180
+ )
181
+ parser.add_argument("query", help="Describe your product or design need in natural language")
182
+ parser.add_argument("--project-name", "-p", type=str, default=None,
183
+ help="Optional project name")
184
+ parser.add_argument("--format", "-f", choices=["ascii", "markdown", "json"],
185
+ default="ascii", help="Output format (default: ascii)")
186
+
187
+ args = parser.parse_args()
188
+
189
+ payload = {"query": args.query, "format": args.format}
190
+ if args.project_name:
191
+ payload["project_name"] = args.project_name
192
+
193
+ result = _post("design", payload)
194
+ print(_format_output(result, args.format))
195
+
196
+
197
+ if __name__ == "__main__":
198
+ main()
package/dist/index.js CHANGED
@@ -76,7 +76,7 @@ var AI_SKILL_PATHS = {
76
76
  import { createRequire } from "node:module";
77
77
  var _require = createRequire(import.meta.url);
78
78
  var _pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
79
- var SEARCH_PY = readFileSync(join(_pkgDir, "assets", "search.py"), "utf-8");
79
+ var DESIGN_PY = readFileSync(join(_pkgDir, "assets", "design.py"), "utf-8");
80
80
  function resolvePath(p) {
81
81
  if (p.startsWith("~/")) return join(homedir(), p.slice(2));
82
82
  return p;
@@ -88,8 +88,8 @@ description: >
88
88
  Lokuma design intelligence. Use this skill whenever building or modifying UI:
89
89
  landing pages, dashboards, SaaS products, mobile apps, e-commerce, portfolios,
90
90
  admin panels, onboarding flows, settings screens, pricing pages, forms, charts,
91
- and design systems. Best used by describing the product, audience, platform,
92
- tone, and business goal in natural language.
91
+ and design systems. Describe the product, audience, platform, tone, and goal
92
+ in natural language. Lokuma will decide the best design route automatically.
93
93
  ---
94
94
 
95
95
  # Lokuma \u2014 Design Intelligence Skill
@@ -123,119 +123,55 @@ Get your key at **https://lokuma.ai**
123
123
 
124
124
  ---
125
125
 
126
- ## How to Query Lokuma (important)
126
+ ## How to Use Lokuma
127
127
 
128
- **You do NOT need to invent rigid search keywords anymore.**
128
+ **Do not decide between design-system, domain search, or routing yourself.**
129
129
 
130
- Lokuma now uses an AI semantic selector in the cloud. That means the best input is usually just a **clear natural-language description of the design need**.
130
+ If the task is about UI, design, layout, colors, typography, UX, landing pages, charts, or visual direction:
131
131
 
132
- ### Good inputs
133
- - What the product is
134
- - Who it is for
135
- - What platform it lives on (web, mobile app, dashboard, landing page)
136
- - What feeling or brand tone it should convey
137
- - Any special goals (conversion, trust, clarity, premium feel, speed, playfulness)
132
+ 1. Keep the user's request in natural language
133
+ 2. Pass it directly to Lokuma
134
+ 3. Lokuma will decide the best route in the cloud automatically
138
135
 
139
- ### Examples of good natural-language input
140
- - "A meditation and sleep mobile app for young professionals. Calm, premium, organic, not too clinical."
141
- - "A landing page for an AI note-taking SaaS. Clean, modern, trustworthy, conversion-focused."
142
- - "A fintech dashboard for small businesses. Professional, data-dense, readable, high trust."
143
- - "An e-commerce brand for handmade skincare. Warm, soft, elegant, natural, slightly editorial."
144
-
145
- ### Better results if you include
146
- - **Product**: what it is
147
- - **Audience**: who uses it
148
- - **Platform**: app / web / dashboard / landing / admin
149
- - **Tone**: calm / playful / bold / premium / trustworthy / minimal / editorial
150
- - **Goal**: conversion / retention / clarity / efficiency / immersion / delight
151
-
152
- ---
153
-
154
- ## Recommended Workflow
155
-
156
- ### 1) Start with design system
157
- For any new project, new page, or fuzzy design request, start here:
136
+ ### Preferred command
158
137
 
159
138
  \`\`\`bash
160
- python3 ${scriptPath} "<natural language product/design request>" --design-system [-p "Project Name"]
139
+ python3 ${scriptPath} "<natural language design request>"
161
140
  \`\`\`
162
141
 
163
- Examples:
142
+ Optional:
164
143
 
165
144
  \`\`\`bash
166
- python3 ${scriptPath} "A meditation app for stressed professionals. Calm, premium, organic, soft and trustworthy." --design-system -p "Still"
167
-
168
- python3 ${scriptPath} "A landing page for an AI sales assistant SaaS. Modern, sharp, fast, conversion-focused, investor-friendly." --design-system -p "Closer"
145
+ python3 ${scriptPath} "<natural language design request>" -p "Project Name"
146
+ python3 ${scriptPath} "<natural language design request>" -f json
147
+ python3 ${scriptPath} "<natural language design request>" -f markdown
169
148
  \`\`\`
170
149
 
171
- This returns a full design direction:
172
- - page / conversion pattern
173
- - visual style
174
- - color direction
175
- - typography
176
- - key effects
177
- - anti-patterns to avoid
178
-
179
- ### 2) Use domain search for focused follow-up
180
- After design-system, use targeted search only when you want more detail in one area.
181
-
182
- \`\`\`bash
183
- python3 ${scriptPath} "<design need>" --domain <domain> [-n <count>]
184
- \`\`\`
185
-
186
- | Domain | Use it when you want... |
187
- |--------|--------------------------|
188
- | \`style\` | visual direction / aesthetic language |
189
- | \`color\` | palettes / mood / brand color strategy |
190
- | \`typography\` | font pairing and hierarchy |
191
- | \`product\` | industry-specific design patterns |
192
- | \`reasoning\` | design logic and decision support |
193
- | \`ux\` | usability, accessibility, interaction best practices |
194
- | \`chart\` | data visualization type recommendations |
195
- | \`landing\` | landing page structure and CTA strategy |
196
- | \`icons\` | icon style / library suggestions |
197
- | \`google-fonts\` | specific Google Font discovery |
198
-
199
- Examples:
200
-
201
- \`\`\`bash
202
- python3 ${scriptPath} "Need a calm but premium color direction for a wellness app" --domain color
203
- python3 ${scriptPath} "What typography fits a luxury skincare brand with an editorial feel?" --domain typography
204
- python3 ${scriptPath} "Best landing page structure for an AI productivity SaaS that needs signups" --domain landing
205
- python3 ${scriptPath} "UX risks in a mobile checkout flow with address, payment, and promo code" --domain ux
206
- \`\`\`
150
+ ---
207
151
 
208
- ### 3) Use stack search for implementation-specific guidance
209
- \`\`\`bash
210
- python3 ${scriptPath} "navigation performance and gestures" --stack react-native
211
- \`\`\`
152
+ ## Good Input Examples
212
153
 
213
- Use this when you need technical guidance for implementation rather than visual direction.
154
+ - "A meditation and sleep mobile app for young professionals. Calm, premium, organic, not too clinical."
155
+ - "A landing page for an AI note-taking SaaS. Clean, modern, trustworthy, conversion-focused."
156
+ - "A fintech dashboard for small businesses. Professional, data-dense, readable, high trust."
157
+ - "An e-commerce brand for handmade skincare. Warm, soft, elegant, natural, slightly editorial."
158
+ - "What color palette fits a luxury skincare brand?"
159
+ - "Best font pairing for a modern fintech dashboard"
160
+ - "How should I structure a landing page for an AI sales tool?"
214
161
 
215
162
  ---
216
163
 
217
164
  ## Practical Advice for AI Coding Assistants
218
165
 
219
166
  ### Prefer the user's original language
220
- If the user already described what they want clearly, pass that directly into Lokuma. Do **not** aggressively compress it into keywords unless needed.
221
-
222
- ### Use design-system first when the task is broad
223
- If the user says things like:
224
- - "build a landing page"
225
- - "make this app feel more premium"
226
- - "what style should this dashboard use"
227
- - "design a fintech onboarding flow"
228
-
229
- then use \`--design-system\` first.
167
+ If the user already described what they want clearly, pass that directly into Lokuma. Do **not** aggressively compress it into keywords.
230
168
 
231
- ### Use domain search when the decision is narrow
232
- If the user only needs:
233
- - a palette
234
- - a font direction
235
- - a chart type
236
- - UX review notes
169
+ ### One entry point
170
+ Do not manually choose between domain search and design-system generation.
171
+ Lokuma handles that automatically in the cloud.
237
172
 
238
- then use a focused domain query.
173
+ ### Use Lokuma early
174
+ If the user is still fuzzy about style, tone, layout, color, hierarchy, brand feel, or UX direction, use Lokuma before generating code.
239
175
 
240
176
  ---
241
177
 
@@ -243,34 +179,19 @@ then use a focused domain query.
243
179
 
244
180
  \`\`\`bash
245
181
  # Full product direction
246
- python3 ${scriptPath} "A wellness subscription app for burnout recovery. Soft, warm, calming, organic, habit-forming." --design-system -p "Exhale"
182
+ python3 ${scriptPath} "A wellness subscription app for burnout recovery. Soft, warm, calming, organic, habit-forming." -p "Exhale"
247
183
 
248
- # Landing page strategy
249
- python3 ${scriptPath} "A homepage for an AI coding assistant targeting startups. High trust, fast clarity, strong CTA." --domain landing
184
+ # Landing page / conversion direction
185
+ python3 ${scriptPath} "A homepage for an AI coding assistant targeting startups. High trust, fast clarity, strong CTA."
250
186
 
251
- # Style direction
252
- python3 ${scriptPath} "A creative portfolio site for a motion designer. Bold, editorial, experimental, but still readable." --domain style
253
-
254
- # Color direction
255
- python3 ${scriptPath} "A secure but friendly fintech app for freelancers" --domain color
256
-
257
- # UX guidance
258
- python3 ${scriptPath} "A mobile onboarding flow with permissions, account creation, and trust concerns" --domain ux
259
- \`\`\`
260
-
261
- ---
262
-
263
- ## Output Formats
264
-
265
- \`\`\`bash
266
- # JSON (best for programmatic use)
267
- python3 ${scriptPath} "A fintech mobile app for young investors" --design-system -f json
187
+ # Visual design question
188
+ python3 ${scriptPath} "A creative portfolio site for a motion designer. Bold, editorial, experimental, but still readable."
268
189
 
269
- # Markdown (best for docs / notes)
270
- python3 ${scriptPath} "A fintech mobile app for young investors" --design-system -f markdown
190
+ # Color question
191
+ python3 ${scriptPath} "A secure but friendly fintech app for freelancers"
271
192
 
272
- # ASCII (default terminal output)
273
- python3 ${scriptPath} "A fintech mobile app for young investors" --design-system
193
+ # UX question
194
+ python3 ${scriptPath} "A mobile onboarding flow with permissions, account creation, and trust concerns"
274
195
  \`\`\`
275
196
 
276
197
  ---
@@ -330,7 +251,7 @@ async function initCommand(options) {
330
251
  await mkdir(dirname(paths.skillMd), { recursive: true });
331
252
  await mkdir(dirname(paths.scriptPy), { recursive: true });
332
253
  await writeFile(paths.skillMd, buildSkillMd(paths.scriptPy), "utf-8");
333
- await writeFile(paths.scriptPy, SEARCH_PY, "utf-8");
254
+ await writeFile(paths.scriptPy, DESIGN_PY, "utf-8");
334
255
  installed.push({ name: AI_DISPLAY_NAMES[t], skillMd: paths.skillMd, scriptPy: paths.scriptPy });
335
256
  }
336
257
  spinner.succeed("Done!");
@@ -339,7 +260,7 @@ async function initCommand(options) {
339
260
  for (const item of installed) {
340
261
  console.log(chalk.green(` \u2713 ${item.name}`));
341
262
  console.log(chalk.dim(` skill : ${item.skillMd}`));
342
- console.log(chalk.dim(` search: ${item.scriptPy}`));
263
+ console.log(chalk.dim(` design: ${item.scriptPy}`));
343
264
  }
344
265
  }
345
266
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lokuma-cli",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "CLI to install Lokuma design intelligence skill for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/assets/search.py DELETED
@@ -1,229 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Lokuma Search - Cloud Client
5
- Calls the Lokuma cloud API to generate design intelligence.
6
-
7
- Usage:
8
- python search.py "<natural language design request>" [--domain <domain>] [--max-results 3]
9
- python search.py "<natural language design request>" --stack <stack>
10
- python search.py "<natural language product/design brief>" --design-system [-p "Project Name"] [-f ascii|markdown|json]
11
-
12
- Examples:
13
- python search.py "A meditation app for stressed professionals. Calm, premium, organic." --design-system -p "Still"
14
- python search.py "Need a calm but premium color direction for a wellness app" --domain color
15
- python search.py "Best landing page structure for an AI productivity SaaS that needs signups" --domain landing
16
- python search.py "Navigation performance and gestures for a React Native app" --stack react-native
17
-
18
- Configuration:
19
- Set LOKUMA_API_KEY environment variable (required):
20
- export LOKUMA_API_KEY=lokuma_your_key_here
21
-
22
- Optional overrides:
23
- LOKUMA_API_URL=https://custom.endpoint.com (default: official API)
24
-
25
- Domains: style, color, chart, landing, product, reasoning, ux, typography,
26
- google-fonts, icons
27
- Stacks: react-native
28
- """
29
-
30
- import argparse
31
- import json
32
- import os
33
- import sys
34
- import io
35
- import urllib.request
36
- import urllib.error
37
- from typing import Optional
38
-
39
- # Force UTF-8 output on Windows
40
- if sys.stdout.encoding and sys.stdout.encoding.lower() != "utf-8":
41
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
42
- if sys.stderr.encoding and sys.stderr.encoding.lower() != "utf-8":
43
- sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
44
-
45
-
46
- # ─────────────────────────────────────────────
47
- # Config
48
- # ─────────────────────────────────────────────
49
-
50
- API_BASE = os.environ.get("LOKUMA_API_URL", "https://api.lokuma.ai").rstrip("/")
51
- API_VERSION = "v1"
52
- _BASE = f"{API_BASE}/{API_VERSION}"
53
-
54
-
55
- def _get_api_key() -> str:
56
- key = os.environ.get("LOKUMA_API_KEY", "").strip()
57
- if not key:
58
- print(
59
- "Error: LOKUMA_API_KEY is not set.\n"
60
- "Set it with: export LOKUMA_API_KEY=lokuma_your_key_here",
61
- file=sys.stderr,
62
- )
63
- sys.exit(1)
64
- return key
65
-
66
-
67
- # ─────────────────────────────────────────────
68
- # HTTP client (stdlib only, zero deps)
69
- # ─────────────────────────────────────────────
70
-
71
- def _post(endpoint: str, payload: dict) -> dict:
72
- """POST JSON payload to API endpoint, return parsed response."""
73
- api_key = _get_api_key()
74
- url = f"{_BASE}/{endpoint}"
75
- data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
76
-
77
- req = urllib.request.Request(
78
- url,
79
- data=data,
80
- headers={
81
- "Content-Type": "application/json",
82
- "X-API-Key": api_key,
83
- "User-Agent": "lokuma-skill/1.0",
84
- },
85
- method="POST",
86
- )
87
-
88
- try:
89
- with urllib.request.urlopen(req, timeout=30) as resp:
90
- body = resp.read().decode("utf-8")
91
- return json.loads(body)
92
- except urllib.error.HTTPError as e:
93
- body = e.read().decode("utf-8", errors="replace")
94
- try:
95
- err = json.loads(body)
96
- msg = err.get("error", body)
97
- except Exception:
98
- msg = body
99
- if e.code == 401 or e.code == 403:
100
- print(f"Error: Invalid or expired API key (HTTP {e.code})", file=sys.stderr)
101
- else:
102
- print(f"Error: API returned HTTP {e.code}: {msg}", file=sys.stderr)
103
- sys.exit(1)
104
- except urllib.error.URLError as e:
105
- print(f"Error: Could not reach API at {url}: {e.reason}", file=sys.stderr)
106
- sys.exit(1)
107
- except json.JSONDecodeError as e:
108
- print(f"Error: Invalid JSON response from API: {e}", file=sys.stderr)
109
- sys.exit(1)
110
-
111
-
112
- # ─────────────────────────────────────────────
113
- # API wrappers
114
- # ─────────────────────────────────────────────
115
-
116
- def search(query: str, domain: Optional[str] = None, max_results: int = 3) -> dict:
117
- payload = {"query": query, "max_results": max_results}
118
- if domain:
119
- payload["domain"] = domain
120
- return _post("search", payload)
121
-
122
-
123
- def search_stack(query: str, stack: str, max_results: int = 3) -> dict:
124
- return _post("search/stack", {"query": query, "stack": stack, "max_results": max_results})
125
-
126
-
127
- def design_system(query: str, project_name: Optional[str] = None, fmt: str = "ascii") -> dict:
128
- payload = {"query": query, "format": fmt}
129
- if project_name:
130
- payload["project_name"] = project_name
131
- return _post("design-system", payload)
132
-
133
-
134
- # ─────────────────────────────────────────────
135
- # Output formatting (mirrors original search.py)
136
- # ─────────────────────────────────────────────
137
-
138
- def format_search_output(result: dict) -> str:
139
- if "error" in result:
140
- return f"Error: {result['error']}"
141
-
142
- output = []
143
- if result.get("stack"):
144
- output.append("## Lokuma Stack Guidelines")
145
- output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}")
146
- else:
147
- output.append("## Lokuma Search Results")
148
- output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
149
-
150
- source = result.get("file") or result.get("source") or "cloud API"
151
- output.append(f"**Source:** {source} | **Found:** {result.get('count', len(result.get('results', [])))} results\n")
152
-
153
- for i, row in enumerate(result.get("results", []), 1):
154
- output.append(f"### Result {i}")
155
- for key, value in row.items():
156
- value_str = str(value)
157
- if len(value_str) > 300:
158
- value_str = value_str[:300] + "..."
159
- output.append(f"- **{key}:** {value_str}")
160
- output.append("")
161
-
162
- return "\n".join(output)
163
-
164
-
165
- # ─────────────────────────────────────────────
166
- # CLI
167
- # ─────────────────────────────────────────────
168
-
169
- VALID_DOMAINS = [
170
- "style", "color", "chart", "landing", "product", "reasoning", "ux",
171
- "typography", "google-fonts", "icons",
172
- ]
173
- VALID_STACKS = ["react-native"]
174
-
175
-
176
- def main():
177
- parser = argparse.ArgumentParser(
178
- description="Lokuma Cloud Search",
179
- formatter_class=argparse.RawDescriptionHelpFormatter,
180
- epilog=__doc__,
181
- )
182
- parser.add_argument("query", help="Search query")
183
- parser.add_argument("--domain", "-d", choices=VALID_DOMAINS, help="Search domain")
184
- parser.add_argument("--stack", "-s", choices=VALID_STACKS, help="Stack-specific search")
185
- parser.add_argument("--max-results", "-n", type=int, default=3, help="Max results (default: 3)")
186
- parser.add_argument("--json", action="store_true", help="Output raw JSON")
187
- # Design system flags
188
- parser.add_argument("--design-system", "-ds", action="store_true",
189
- help="Generate complete design system")
190
- parser.add_argument("--project-name", "-p", type=str, default=None,
191
- help="Project name for design system header")
192
- parser.add_argument("--format", "-f", choices=["ascii", "markdown", "json"],
193
- default="ascii", help="Output format for design system")
194
-
195
- args = parser.parse_args()
196
-
197
- # ── Design system ──
198
- if args.design_system:
199
- fmt = args.format
200
- result = design_system(args.query, args.project_name, fmt)
201
- if args.json:
202
- print(json.dumps(result, indent=2, ensure_ascii=False))
203
- elif fmt == "json":
204
- # Pretty-print the nested design_system dict
205
- ds = result.get("design_system", result)
206
- print(json.dumps(ds, indent=2, ensure_ascii=False))
207
- else:
208
- print(result.get("output", ""))
209
- return
210
-
211
- # ── Stack search ──
212
- if args.stack:
213
- result = search_stack(args.query, args.stack, args.max_results)
214
- if args.json:
215
- print(json.dumps(result, indent=2, ensure_ascii=False))
216
- else:
217
- print(format_search_output(result))
218
- return
219
-
220
- # ── Domain search ──
221
- result = search(args.query, args.domain, args.max_results)
222
- if args.json:
223
- print(json.dumps(result, indent=2, ensure_ascii=False))
224
- else:
225
- print(format_search_output(result))
226
-
227
-
228
- if __name__ == "__main__":
229
- main()