@newpeak/barista-cli 0.1.131 → 0.1.132

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,194 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ search.py — Barista CLI 意图搜索
4
+
5
+ AI agent 通过此脚本根据用户意图查询相关命令。
6
+ SKILL.md 指导 AI 在需要查找命令时调用此脚本。
7
+
8
+ 用法:
9
+ python3 search.py "<query>" [--domain <domain>] [--format text|json] [--max N]
10
+
11
+ 示例:
12
+ python3 search.py "采购订单"
13
+ python3 search.py "创建入库单" --domain liberica
14
+ python3 search.py "subscription" --domain arabica
15
+ python3 search.py "invoice" --domain arabica.invoices
16
+ """
17
+
18
+ import sys
19
+ import json
20
+ import argparse
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+
25
+ def load_data() -> dict[str, Any]:
26
+ """Load commands data from JSON or YAML file."""
27
+ script_dir = Path(__file__).resolve().parent
28
+ data_dir = script_dir.parent / "data"
29
+
30
+ json_path = data_dir / "commands.json"
31
+ yaml_path = data_dir / "commands.yaml"
32
+
33
+ if json_path.exists():
34
+ with open(json_path, "r", encoding="utf-8") as f:
35
+ return json.load(f)
36
+
37
+ if yaml_path.exists():
38
+ try:
39
+ import yaml as yaml_lib
40
+ with open(yaml_path, "r", encoding="utf-8") as f:
41
+ return yaml_lib.safe_load(f)
42
+ except ImportError:
43
+ # Fallback: parse YAML lines manually (basic)
44
+ return fallback_to_json(yaml_path)
45
+
46
+ sys.stderr.write("❌ No commands data found. Run 'npm run generate-skills-data' first.\n")
47
+ sys.exit(1)
48
+
49
+
50
+ def fallback_to_json(yaml_path: Path) -> dict[str, Any]:
51
+ """Fallback to JSON when PyYAML is not installed."""
52
+ sys.stderr.write("⚠️ PyYAML not installed. Install with: pip install pyyaml\n")
53
+ sys.stderr.write(" Falling back to JSON format (if available).\n")
54
+ json_path = yaml_path.with_suffix(".json")
55
+ if json_path.exists():
56
+ with open(json_path, "r", encoding="utf-8") as f:
57
+ return json.load(f)
58
+ return {"global": {}, "liberica": {}, "arabica": {}}
59
+
60
+
61
+ def flatten_commands(data: dict[str, Any]) -> list[dict[str, Any]]:
62
+ """Flatten nested command structure into a searchable list."""
63
+ results = []
64
+ for service in ["global", "liberica", "arabica"]:
65
+ svc_data = data.get(service, {})
66
+ for module_name, module in svc_data.items():
67
+ group = module.get("group", "")
68
+ for cmd_name, cmd in module.get("commands", {}).items():
69
+ desc = cmd.get("description", "")
70
+ options = cmd.get("options", [])
71
+ results.append({
72
+ "service": service,
73
+ "module": module_name,
74
+ "group": group,
75
+ "action": cmd_name,
76
+ "description": desc,
77
+ "options": options,
78
+ # For relevance scoring
79
+ "keywords": f"{module_name} {cmd_name} {group} {desc} {service}".lower(),
80
+ })
81
+ return results
82
+
83
+
84
+ def search(flat: list[dict[str, Any]], query: str, domain: str = "", max_results: int = 10) -> list[dict[str, Any]]:
85
+ """Search commands by keyword matching with relevance scoring."""
86
+ query_lower = query.lower()
87
+ query_words = [w for w in query_lower.split() if len(w) > 1]
88
+
89
+ if not query_words:
90
+ return []
91
+
92
+ scored = []
93
+ for cmd in flat:
94
+ # Domain filter
95
+ if domain:
96
+ domain_lower = domain.lower()
97
+ if domain_lower == "liberica" and cmd["service"] != "liberica":
98
+ continue
99
+ if domain_lower == "arabica" and cmd["service"] != "arabica":
100
+ continue
101
+ if domain_lower.startswith("liberica."):
102
+ mod = domain_lower[len("liberica."):]
103
+ if cmd["module"] != mod and not cmd["module"].startswith(mod + "/"):
104
+ continue
105
+ if domain_lower.startswith("arabica."):
106
+ mod = domain_lower[len("arabica."):]
107
+ if cmd["module"] != mod:
108
+ continue
109
+
110
+ # Relevance scoring
111
+ keywords = cmd["keywords"]
112
+ score = 0
113
+ matched_words = []
114
+
115
+ for word in query_words:
116
+ if word in keywords:
117
+ score += 3
118
+ matched_words.append(word)
119
+ # Partial match (substring)
120
+ elif any(word in kw_part for kw_part in keywords.split()):
121
+ score += 1
122
+ matched_words.append(word)
123
+
124
+ # Boost exact module match
125
+ if query_lower == cmd["module"]:
126
+ score += 5
127
+ if query_lower == cmd["action"]:
128
+ score += 3
129
+ # Boost description match priority
130
+ if any(w in cmd["description"].lower() for w in query_words):
131
+ score += 2
132
+
133
+ if score > 0:
134
+ scored.append((score, cmd))
135
+
136
+ # Sort by score descending
137
+ scored.sort(key=lambda x: -x[0])
138
+ return [cmd for _, cmd in scored[:max_results]]
139
+
140
+
141
+ def format_text(results: list[dict[str, Any]]) -> str:
142
+ """Format results as human-readable text."""
143
+ if not results:
144
+ return "No matching commands found."
145
+
146
+ lines = []
147
+ last_service = ""
148
+ last_group = ""
149
+
150
+ for r in results:
151
+ if r["service"] != last_service:
152
+ lines.append(f"[{r['service'].upper()}]")
153
+ last_service = r["service"]
154
+ last_group = ""
155
+
156
+ if r["group"] != last_group:
157
+ lines.append(f" {r['group']}:")
158
+ last_group = r["group"]
159
+
160
+ opts = ""
161
+ if r["options"]:
162
+ opts = " [" + ", ".join(str(o) for o in r["options"]) + "]"
163
+ desc = f" — {r['description']}" if r["description"] else ""
164
+ lines.append(f" barista {r['service']} {r['module']} {r['action']}{opts}{desc}")
165
+
166
+ lines.append("")
167
+ lines.append(f"({len(results)} commands found)")
168
+ return "\n".join(lines)
169
+
170
+
171
+ def main():
172
+ parser = argparse.ArgumentParser(description="Barista CLI command search")
173
+ parser.add_argument("query", help="Search query (user intent)")
174
+ parser.add_argument("--domain", default="",
175
+ help="Filter by domain (liberica, arabica, liberica.purchase-orders, etc.)")
176
+ parser.add_argument("--format", choices=["text", "json"], default="text",
177
+ help="Output format")
178
+ parser.add_argument("--max", type=int, default=10,
179
+ help="Maximum results (default: 10)")
180
+
181
+ args = parser.parse_args()
182
+
183
+ data = load_data()
184
+ flat = flatten_commands(data)
185
+ results = search(flat, args.query, args.domain, args.max)
186
+
187
+ if args.format == "json":
188
+ print(json.dumps({"success": True, "data": {"items": results}}, indent=2, ensure_ascii=False))
189
+ else:
190
+ print(format_text(results))
191
+
192
+
193
+ if __name__ == "__main__":
194
+ main()
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "barista-cli",
3
+ "displayName": "Barista CLI",
4
+ "version": "0.1.0",
5
+ "description": "AI agent skill for operating Barista CLI — bridge AI assistants to Liberica production management SaaS and Arabica subscription platform. Covers 70+ Liberica modules (employees, materials, inventory, production, BOM, quality, finance, HR, teams) and 7 Arabica modules (plans, subscription, orders, invoices, enterprises).",
6
+ "author": "Newpeak Technology",
7
+ "license": "MIT",
8
+ "homepage": "https://gitlab.newpeaksh.com/coffee/coffee-barista-cli",
9
+ "repository": "https://gitlab.newpeaksh.com/coffee/coffee-barista-cli.git",
10
+ "platforms": ["claude", "cursor", "windsurf", "copilot", "kiro", "roocode", "codex", "qoder", "gemini", "trae", "opencode", "continue", "droid"],
11
+ "install": "npx @newpeak/barista-cli skills install --ai {{platform}}",
12
+ "keywords": ["barista", "cli", "liberica", "arabica", "production-management", "saas", "erp", "enterprise"],
13
+ "metadata": {
14
+ "dataSource": "auto-generated from src/commands/",
15
+ "search": "barista skills query <intent> or python3 scripts/search.py"
16
+ }
17
+ }