huaweicloud-devkit 1.1.1-next.7 → 1.1.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.
Files changed (48) hide show
  1. package/README.md +11 -0
  2. package/README.zh-CN.md +11 -0
  3. package/bin/dsh-postinstall.cjs +77 -0
  4. package/cordis.patch.yml +13 -0
  5. package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
  6. package/integrations/atomcode/hooks.json +9 -0
  7. package/integrations/dsh/hook-plugin.mjs +148 -0
  8. package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
  9. package/integrations/hermes/manifest.yaml +43 -0
  10. package/integrations/opencode/hooks/skill-tracker.js +115 -0
  11. package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
  12. package/package.json +10 -5
  13. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  14. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  15. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  16. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  17. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  18. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +29 -1
  19. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  20. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +2 -2
  21. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +4 -0
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +4 -0
  23. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +1 -1
  24. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +42 -2
  25. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +4 -0
  26. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
  27. package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
  28. package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
  29. package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
  30. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +1 -1
  31. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +4 -0
  32. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +11 -11
  33. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +25 -24
  34. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +11 -9
  35. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +12 -3
  36. package/plugins/huaweicloud-core/src/auth/credentials.mjs +99 -2
  37. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
  38. package/plugins/huaweicloud-core/src/auth/service.mjs +53 -32
  39. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +22 -1
  40. package/plugins/huaweicloud-core/src/mcp-server.mjs +37 -3
  41. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -4
  42. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +5 -0
  43. package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
  44. package/plugins/huaweicloud-core/src/setup-cli.mjs +582 -36
  45. package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
  46. package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
  47. package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
  48. package/plugins/huaweicloud-core/src/tools.mjs +193 -2
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env python3
2
+ """Hermes pre_tool_call hook — Huawei Cloud telemetry data collection.
3
+
4
+ Captures Skill loading and hcloud CLI commands, writing structured events
5
+ to hook-events.jsonl for the devkit telemetry module to ingest and report.
6
+
7
+ This hook NEVER blocks execution — it always returns exit code 0.
8
+ Safety enforcement is handled by huaweicloud-safety.py.
9
+
10
+ Data flow:
11
+ stdin JSON (pre_tool_call event) → classify → hook-events.jsonl
12
+ → MCP Server telemetry.mjs :: ingestHookEvents() → eventQueue → HTTP POST
13
+
14
+ Classification logic mirrors the OpenCode skill-tracker.js exactly.
15
+ """
16
+
17
+ import json
18
+ import os
19
+ import re
20
+ import sys
21
+ from pathlib import Path
22
+
23
+
24
+ # ── Path resolution ─────────────────────────────────────────────────
25
+
26
+ def _get_plugin_dir():
27
+ """Resolve the huaweicloud-core plugin directory.
28
+
29
+ Search order:
30
+ 1. HUAWEICLOUD_PLUGIN_DIR env var (explicit override)
31
+ 2. Dev repo: hooks/ → ../plugins/... (local dev)
32
+ 3. ~/.hermes/huaweicloud-plugins/ (Hermes installed)
33
+ """
34
+ env_dir = os.environ.get("HUAWEICLOUD_PLUGIN_DIR")
35
+ if env_dir:
36
+ return Path(env_dir)
37
+
38
+ here = Path(__file__).resolve()
39
+
40
+ # Local dev: plugins/huaweicloud-core/hooks/huaweicloud-telemetry.py
41
+ # parents[1] = plugins/huaweicloud-core/
42
+ if (here.parents[1] / "skills").is_dir():
43
+ return here.parents[1]
44
+
45
+ # Local dev (from repo root): hooks/ → ../plugins/huaweicloud-core/
46
+ relative = here.parents[1] / "plugins" / "huaweicloud-core"
47
+ if relative.is_dir():
48
+ return relative
49
+
50
+ # Hermes installed: ~/AppData/Local/hermes/huaweicloud-plugins/
51
+ # hooks/ is directly under the plugin dir, so parents[1] IS the plugin dir.
52
+ if (here.parents[1] / "telemetry").is_dir() or (here.parents[1] / "src").is_dir():
53
+ return here.parents[1]
54
+
55
+ home = Path.home()
56
+
57
+ # Hermes installed plugin dir (legacy ~/.hermes/ path)
58
+ hermes_dir = home / ".hermes" / "huaweicloud-plugins"
59
+ if hermes_dir.is_dir():
60
+ return hermes_dir
61
+
62
+ raise FileNotFoundError("Cannot resolve huaweicloud-core plugin directory")
63
+
64
+
65
+ def _get_telemetry_dir():
66
+ """Return the telemetry directory shared with all devkit platforms.
67
+
68
+ Always at <plugin_dir>/telemetry/, matching AGENT_TELEMETRY_DIR
69
+ in src/telemetry/telemetry.mjs.
70
+ """
71
+ plugin_dir = _get_plugin_dir()
72
+ telemetry_dir = plugin_dir / "telemetry"
73
+ telemetry_dir.mkdir(parents=True, exist_ok=True)
74
+ return telemetry_dir
75
+
76
+
77
+ def _get_hook_events_path():
78
+ """Lazily resolve hook-events.jsonl path. Returns None if unresolvable."""
79
+ try:
80
+ return str(_get_telemetry_dir() / "hook-events.jsonl")
81
+ except Exception:
82
+ return None
83
+
84
+
85
+ # ── Classifiers (match skill-tracker.js exactly) ────────────────────
86
+
87
+ # hcloud command detection
88
+ HCLOUD_RE = re.compile(r'(?:^|[;&|]\s*)hcloud(?:\.exe)?\s+(.+)', re.IGNORECASE)
89
+
90
+ # Read operation verbs
91
+ READ_VERBS = re.compile(
92
+ r'\b(List|Show|Get|Describe|NovaList|NovaShow)\w*', re.IGNORECASE
93
+ )
94
+
95
+ # Write operation verbs
96
+ WRITE_VERBS = re.compile(
97
+ r'\b(Create|Delete|Update|Modify|Remove|Revoke|Grant|Attach|Detach|'
98
+ r'Enable|Disable|Set|Add|Bind|Unbind|Reset|Change|Activate|Deactivate|'
99
+ r'Register|Unregister|Import|Export|Download|Upload|Copy|Move|Convert|'
100
+ r'Migrate|Run|Execute|Invoke|Trigger|Deploy|Push|Start|Stop|Restart|'
101
+ r'Reboot|Suspend|Resume|Terminate|Release|Allocate)\w*', re.IGNORECASE
102
+ )
103
+
104
+ # Huawei Cloud skill name pattern
105
+ HUAWEI_SKILL_RE = re.compile(r'^huawei', re.IGNORECASE)
106
+
107
+
108
+ def classify_hcloud(text):
109
+ """Classify an hcloud CLI command as read / write / invoke.
110
+
111
+ Returns:
112
+ dict with 'key' and 'value', or None if not an hcloud command.
113
+ Matches the classifyHcloud() logic in skill-tracker.js.
114
+ """
115
+ m = HCLOUD_RE.search(text)
116
+ if not m:
117
+ return None
118
+
119
+ rest = m[1].strip()
120
+ # Extract command part, excluding flags/options
121
+ parts = rest.split()
122
+ cmd_parts = [p for p in parts if not p.startswith('--')]
123
+ cmd = ' '.join(cmd_parts).strip()
124
+
125
+ if not cmd:
126
+ return None
127
+
128
+ if READ_VERBS.search(cmd):
129
+ return {'key': 'cli:read', 'value': f'hcloud {cmd}'}
130
+ if WRITE_VERBS.search(cmd):
131
+ return {'key': 'cli:write', 'value': f'hcloud {cmd}'}
132
+ return {'key': 'cli:invoke', 'value': f'hcloud {cmd}'}
133
+
134
+
135
+ def is_huawei_skill(name):
136
+ """Check if a skill name belongs to Huawei Cloud."""
137
+ return name and HUAWEI_SKILL_RE.search(name)
138
+
139
+
140
+ # ── Event writer ────────────────────────────────────────────────────
141
+
142
+ def write_event(key, value, extra=None):
143
+ """Append a telemetry event to hook-events.jsonl.
144
+
145
+ Format matches OpenCode/DSH/WorkBuddy output exactly:
146
+ {"key": "...", "value": "...", "capability": "..."}
147
+ """
148
+ path = _get_hook_events_path()
149
+ if not path:
150
+ return # Best-effort: silently skip if path is unresolvable
151
+
152
+ event = {"key": key, "value": value}
153
+ if extra:
154
+ event.update(extra)
155
+ try:
156
+ with open(path, "a", encoding="utf-8") as f:
157
+ f.write(json.dumps(event, ensure_ascii=False) + "\n")
158
+ except Exception:
159
+ # Best-effort; never block the main tool execution
160
+ pass
161
+
162
+
163
+ # ── Command text extraction ─────────────────────────────────────────
164
+
165
+ def command_text(tool_input):
166
+ """Extract command text from various tool_input shapes."""
167
+ if isinstance(tool_input, str):
168
+ return tool_input
169
+ if isinstance(tool_input, dict):
170
+ for key in ("command", "cmd", "script", "args", "arguments"):
171
+ value = tool_input.get(key)
172
+ if isinstance(value, list):
173
+ return " ".join(str(item) for item in value)
174
+ if value is not None:
175
+ return str(value)
176
+ return json.dumps(tool_input)
177
+ return json.dumps(tool_input)
178
+
179
+
180
+ # ── Skill name extraction ───────────────────────────────────────────
181
+
182
+ def extract_skill_name(tool_name, tool_input):
183
+ """Extract skill name from tool_input for skill-related tools.
184
+
185
+ Hermes skill tools: skill_view, skills_list, skill_manage.
186
+ The skill name may be in 'name', 'skill', or 'args' field.
187
+ """
188
+ if not isinstance(tool_input, dict):
189
+ if isinstance(tool_input, str):
190
+ return tool_input
191
+ return ""
192
+
193
+ # Common field names across agent platforms
194
+ return (
195
+ tool_input.get("name", "")
196
+ or tool_input.get("skill", "")
197
+ or tool_input.get("command", "")
198
+ or ""
199
+ )
200
+
201
+
202
+ # ── Main hook logic ─────────────────────────────────────────────────
203
+
204
+ def main():
205
+ try:
206
+ data = json.load(sys.stdin)
207
+ except Exception:
208
+ # Cannot parse input → not our concern, allow execution
209
+ sys.exit(0)
210
+
211
+ tool_name = data.get("tool_name", "")
212
+ tool_input = data.get("tool_input", {})
213
+
214
+ # ── Category 1: Skill tool loading ──
215
+ # Hermes skill tools: skill_view, skills_list, skill_manage
216
+ if tool_name in ("skill_view", "skills_list", "skill_manage", "skill"):
217
+ skill_name = extract_skill_name(tool_name, tool_input)
218
+ if is_huawei_skill(skill_name):
219
+ write_event('skill:retrieve', skill_name)
220
+
221
+ # ── Category 2: Bash/terminal tool with hcloud commands ──
222
+ elif tool_name in ("Bash", "terminal", "bash", "pwsh"):
223
+ command = command_text(tool_input)
224
+ if command and HCLOUD_RE.search(command):
225
+ result = classify_hcloud(command)
226
+ if result:
227
+ write_event(result['key'], result['value'], {'capability': 'cli'})
228
+
229
+ # ── Category 3: MCP Huawei Cloud tool invocations ──
230
+ elif tool_name.startswith("mcp__huaweicloud") or tool_name.startswith("huaweicloud_"):
231
+ write_event(f"tool:{tool_name}", "1", {'capability': 'mcp'})
232
+
233
+ # Always allow — telemetry hook never blocks execution
234
+ sys.exit(0)
235
+
236
+
237
+ if __name__ == "__main__":
238
+ main()
@@ -0,0 +1,43 @@
1
+ manifest_version: 1
2
+ name: huaweicloud-devkit
3
+ description: >
4
+ Huawei Cloud DevKit provides Skills, MCP tools, KooCLI guidance,
5
+ and safety guardrails for Huawei Cloud services within Hermes Agent.
6
+ Includes 27 skills covering ECS, OBS, VPC, RDS, CCE, ModelArts, and more.
7
+
8
+ source: https://github.com/huaweicloud/huaweicloud-devkit
9
+
10
+ transport:
11
+ type: stdio
12
+ command: 'node'
13
+ args: ['plugins/huaweicloud-core/src/mcp-server.mjs']
14
+ env:
15
+ HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local'
16
+
17
+ install:
18
+ type: git
19
+ url: https://github.com/huaweicloud/huaweicloud-devkit
20
+ bootstrap:
21
+ - 'npm ci --omit=dev'
22
+ - 'node plugins/huaweicloud-core/src/setup-cli.mjs install --target hermes --skip-mcp-server'
23
+
24
+ auth:
25
+ type: none
26
+
27
+ tools:
28
+ default_enabled: []
29
+
30
+ post_install: |
31
+ Huawei Cloud DevKit installed successfully!
32
+
33
+ What was set up:
34
+ • 27 skills (ECS, OBS, VPC, RDS, CCE, ModelArts, ...) → ~/.hermes/skills/
35
+ • MCP tools (33 tools for CLI, auth, sandbox, safety) → config.yaml
36
+ • Safety hooks (pre_tool_call terminal guard) → config.yaml
37
+ • Safety policy → ~/.hermes/huaweicloud-plugins/safety/
38
+
39
+ Next steps:
40
+ 1. Configure credentials: npx huaweicloud-devkit auth init
41
+ 2. Restart Hermes Agent session (MCP tools load on restart)
42
+ 3. Verify: hermes doctor
43
+ 4. Try: "Help me check my ECS instances" or "Deploy this website to OBS"
@@ -0,0 +1,115 @@
1
+ import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const DEBUG = process.env.HUAWEICLOUD_DEVKIT_DEBUG === 'true';
6
+
7
+ const selfDir = dirname(fileURLToPath(import.meta.url));
8
+ const agentDir = join(selfDir, '..', 'huaweicloud-plugins', 'telemetry');
9
+ if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
10
+
11
+ function debugLog(msg) {
12
+ if (!DEBUG) return;
13
+ try {
14
+ appendFileSync(join(agentDir, 'plugin-debug.log'), `${new Date().toISOString()} ${msg}\n`);
15
+ } catch (_) {}
16
+ }
17
+
18
+ function isHuaweiCloudSkill(name) {
19
+ return typeof name === 'string' && name && /^huawei/i.test(name);
20
+ }
21
+
22
+ function writeEvent(key, value, extra = {}) {
23
+ const data = JSON.stringify({ key, value, ...extra }) + '\n';
24
+ try {
25
+ appendFileSync(join(agentDir, 'hook-events.jsonl'), data);
26
+ } catch (_) {}
27
+ }
28
+
29
+ // ── CLI command classification ────────────────────────────────
30
+
31
+ const HCLOUD_RE = /(?:^|[;&|]\s*)hcloud(?:\.exe)?\s+(.+)/i;
32
+ const READ_VERBS = /\b(List|Show|Get|Describe|NovaList|NovaShow)\w*/i;
33
+ const WRITE_VERBS = new RegExp(
34
+ '\\b(Create|Delete|Update|Modify|Remove|Revoke|Grant|Attach|Detach|' +
35
+ 'Enable|Disable|Set|Add|Bind|Unbind|Reset|Change|Activate|Deactivate|' +
36
+ 'Register|Unregister|Import|Export|Download|Upload|Copy|Move|Convert|' +
37
+ 'Migrate|Run|Execute|Invoke|Trigger|Deploy|Push|Start|Stop|Restart|' +
38
+ 'Reboot|Suspend|Resume|Terminate|Release|Allocate)\\w*',
39
+ 'i',
40
+ );
41
+
42
+ function classifyHcloud(text) {
43
+ const m = HCLOUD_RE.exec(text);
44
+ if (!m) return null;
45
+ const rest = m[1].trim();
46
+ const cmdEnd = rest.search(/\s[|&<>;]/);
47
+ const cmdPart = cmdEnd > -1 ? rest.slice(0, cmdEnd) : rest;
48
+ const parts = cmdPart
49
+ .split(/\s+/)
50
+ .filter((p) => !p.startsWith('--') && !/^\d*>(&?\d*|%devnull)/.test(p) && !/^(&\d+)$/.test(p));
51
+ const cmd = parts.join(' ');
52
+ if (!cmd) return null;
53
+ if (READ_VERBS.test(cmd)) return { key: 'cli:read', value: `hcloud ${cmd}` };
54
+ if (WRITE_VERBS.test(cmd)) return { key: 'cli:write', value: `hcloud ${cmd}` };
55
+ return { key: 'cli:invoke', value: `hcloud ${cmd}` };
56
+ }
57
+
58
+ // ── Shared hooks ──────────────────────────────────────────────
59
+
60
+ function getHooks() {
61
+ return {
62
+ 'tool.execute.before': function (input, output) {
63
+ try {
64
+ debugLog(`HOOK tool.execute.before tool=${input?.tool}`);
65
+ if (input.tool === 'skill') {
66
+ const name = output?.args?.name;
67
+ debugLog(`SKILL name=${name}`);
68
+ if (isHuaweiCloudSkill(name)) {
69
+ writeEvent('skill:retrieve', name);
70
+ debugLog(`SKILL TRACKED: ${name}`);
71
+ }
72
+ return;
73
+ }
74
+ if (input.tool === 'bash') {
75
+ const cmd = output?.args?.command || '';
76
+ if (!cmd) return;
77
+ const result = classifyHcloud(cmd);
78
+ if (result) writeEvent(result.key, result.value, { capability: 'cli' });
79
+ }
80
+ } catch (error) {
81
+ debugLog(`HOOK ERROR: ${error?.message || error}`);
82
+ }
83
+ },
84
+ event: function ({ event }) {
85
+ try {
86
+ if (event?.type === 'message.part.updated') {
87
+ const text = event?.properties?.part?.text;
88
+ if (typeof text === 'string') {
89
+ const m = text.match(/Base directory for this skill:\s*.*?skills[/\\]([a-z0-9-]+)/i);
90
+ if (m && isHuaweiCloudSkill(m[1])) writeEvent('skill:retrieve', m[1]);
91
+ }
92
+ }
93
+ } catch (error) {
94
+ debugLog(`EVENT ERROR: ${error?.message || error}`);
95
+ }
96
+ },
97
+ };
98
+ }
99
+
100
+ // ── Export: function-as-object serves both IDE and CLI ────────
101
+ //
102
+ // IDE : plugin.default() -> returns hooks
103
+ // CLI : plugin.default.server -> returns hooks (via { id, server })
104
+
105
+ function plugin() {
106
+ debugLog('=== PLUGIN EXPORT CALLED ===');
107
+ return getHooks();
108
+ }
109
+
110
+ plugin.id = 'huaweicloud-skill-tracker';
111
+ plugin.server = async () => getHooks();
112
+
113
+ export default plugin;
114
+
115
+ debugLog('=== PLUGIN INIT DONE ===');
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """WorkBuddy PostToolUse Hook — Huawei Cloud telemetry data collection.
3
+
4
+ Captures Skill loading, hcloud CLI commands, and MCP tool invocations,
5
+ writing structured events to hook-events.jsonl for the devkit telemetry
6
+ module to ingest and report.
7
+
8
+ This hook NEVER blocks execution — it always returns exit code 0.
9
+ Safety enforcement is handled by the MCP server's built-in layers.
10
+
11
+ Data flow:
12
+ stdin JSON (PostToolUse event) → classify → hook-events.jsonl
13
+ → MCP Server telemetry.mjs :: ingestHookEvents() → eventQueue → HTTP POST
14
+
15
+ Classification logic mirrors the OpenCode skill-tracker.js exactly.
16
+ """
17
+
18
+ import json
19
+ import os
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+
25
+ # ── Path resolution ─────────────────────────────────────────────────
26
+
27
+ def _get_plugin_dir():
28
+ """Resolve the huaweicloud-core plugin directory.
29
+
30
+ Search order matches all platforms where devkit runs:
31
+ 1. HUAWEICLOUD_PLUGIN_DIR env var (explicit override)
32
+ 2. Dev repo: scripts/ → ../plugins/... (local dev)
33
+ 3. ~/.workbuddy/huaweicloud-plugins/ (WorkBuddy installed)
34
+ 4. ~/.workbuddy/skills/huawei-*/plugins/... (WorkBuddy skills)
35
+ 5. ~/.hermes/huaweicloud-plugins/ (Hermes)
36
+ 6. ~/.dsh/huaweicloud-plugins/ (DSH)
37
+ 7. ~/.config/opencode/huaweicloud-plugins/ (OpenCode)
38
+ 8. ~/.codex/huaweicloud-plugins/ (Codex)
39
+ """
40
+ env_dir = os.environ.get("HUAWEICLOUD_PLUGIN_DIR")
41
+ if env_dir:
42
+ return Path(env_dir)
43
+
44
+ # Local dev: scripts/telemetry-tracker.py → ../plugins/huaweicloud-core/
45
+ relative = Path(__file__).resolve().parents[1] / "plugins" / "huaweicloud-core"
46
+ if relative.is_dir():
47
+ return relative
48
+
49
+ home = Path.home()
50
+
51
+ # Platform-installed plugin dirs: {home}/<platform>/huaweicloud-plugins/
52
+ for platform_dir in [
53
+ home / ".workbuddy" / "huaweicloud-plugins",
54
+ home / ".hermes" / "huaweicloud-plugins",
55
+ home / ".dsh" / "huaweicloud-plugins",
56
+ home / ".config" / "opencode" / "huaweicloud-plugins",
57
+ home / ".codex" / "huaweicloud-plugins",
58
+ ]:
59
+ if platform_dir.is_dir():
60
+ return platform_dir
61
+
62
+ # Skills fallback: {home}/.workbuddy/skills/huawei-*/.../plugins/...
63
+ skills_root = home / ".workbuddy" / "skills"
64
+ if skills_root.is_dir():
65
+ for skill_dir in sorted(skills_root.iterdir()):
66
+ if skill_dir.is_dir() and skill_dir.name.lower().startswith("huawei"):
67
+ plugin_dir = skill_dir / "plugins" / "huaweicloud-core"
68
+ if plugin_dir.is_dir():
69
+ return plugin_dir
70
+
71
+ raise FileNotFoundError("Cannot resolve huaweicloud-core plugin directory")
72
+
73
+
74
+ def _get_telemetry_dir():
75
+ """Return the telemetry directory shared with all devkit platforms.
76
+
77
+ Always at <plugin_dir>/telemetry/, matching AGENT_TELEMETRY_DIR
78
+ in src/telemetry/telemetry.mjs.
79
+ """
80
+ plugin_dir = _get_plugin_dir()
81
+ telemetry_dir = plugin_dir / "telemetry"
82
+ telemetry_dir.mkdir(parents=True, exist_ok=True)
83
+ return telemetry_dir
84
+
85
+
86
+ def _get_hook_events_path():
87
+ """Lazily resolve hook-events.jsonl path. Returns None if unresolvable."""
88
+ try:
89
+ return str(_get_telemetry_dir() / "hook-events.jsonl")
90
+ except Exception:
91
+ return None
92
+
93
+
94
+ # ── Classifiers (match skill-tracker.js exactly) ────────────────────
95
+
96
+ # hcloud command detection
97
+ HCLOUD_RE = re.compile(r'(?:^|[;&|]\s*)hcloud(?:\.exe)?\s+(.+)', re.IGNORECASE)
98
+
99
+ # Read operation verbs
100
+ READ_VERBS = re.compile(
101
+ r'\b(List|Show|Get|Describe|NovaList|NovaShow)\w*', re.IGNORECASE
102
+ )
103
+
104
+ # Write operation verbs
105
+ WRITE_VERBS = re.compile(
106
+ r'\b(Create|Delete|Update|Modify|Remove|Revoke|Grant|Attach|Detach|'
107
+ r'Enable|Disable|Set|Add|Bind|Unbind|Reset|Change|Activate|Deactivate|'
108
+ r'Register|Unregister|Import|Export|Download|Upload|Copy|Move|Convert|'
109
+ r'Migrate|Run|Execute|Invoke|Trigger|Deploy|Push|Start|Stop|Restart|'
110
+ r'Reboot|Suspend|Resume|Terminate|Release|Allocate)\w*', re.IGNORECASE
111
+ )
112
+
113
+ # Huawei Cloud skill name pattern
114
+ HUAWEI_SKILL_RE = re.compile(r'^huawei', re.IGNORECASE)
115
+
116
+
117
+ def classify_hcloud(text):
118
+ """Classify an hcloud CLI command as read / write / invoke.
119
+
120
+ Args:
121
+ text: The full shell command string.
122
+
123
+ Returns:
124
+ dict with 'key' and 'value', or None if not an hcloud command.
125
+ Matches the classifyHcloud() logic in skill-tracker.js.
126
+ """
127
+ m = HCLOUD_RE.search(text)
128
+ if not m:
129
+ return None
130
+
131
+ rest = m[1].strip()
132
+ # Extract command part, excluding flags/options
133
+ parts = rest.split()
134
+ cmd_parts = [p for p in parts if not p.startswith('--')]
135
+ cmd = ' '.join(cmd_parts).strip()
136
+
137
+ if not cmd:
138
+ return None
139
+
140
+ if READ_VERBS.search(cmd):
141
+ return {'key': 'cli:read', 'value': f'hcloud {cmd}'}
142
+ if WRITE_VERBS.search(cmd):
143
+ return {'key': 'cli:write', 'value': f'hcloud {cmd}'}
144
+ return {'key': 'cli:invoke', 'value': f'hcloud {cmd}'}
145
+
146
+
147
+ def is_huawei_skill(name):
148
+ """Check if a skill name belongs to Huawei Cloud."""
149
+ return name and HUAWEI_SKILL_RE.search(name)
150
+
151
+
152
+ # ── Event writer ────────────────────────────────────────────────────
153
+
154
+ def write_event(key, value, extra=None):
155
+ """Append a telemetry event to hook-events.jsonl.
156
+
157
+ Format matches OpenCode/DSH output exactly:
158
+ {"key": "...", "value": "...", "capability": "..."}
159
+ """
160
+ path = _get_hook_events_path()
161
+ if not path:
162
+ return # Best-effort: silently skip if path is unresolvable
163
+
164
+ event = {"key": key, "value": value}
165
+ if extra:
166
+ event.update(extra)
167
+ try:
168
+ with open(path, "a", encoding="utf-8") as f:
169
+ f.write(json.dumps(event, ensure_ascii=False) + "\n")
170
+ except Exception:
171
+ # Best-effort; never block the main tool execution
172
+ pass
173
+
174
+
175
+ # ── Main hook logic ─────────────────────────────────────────────────
176
+
177
+ def main():
178
+ try:
179
+ data = json.load(sys.stdin)
180
+ except Exception:
181
+ # Cannot parse input → not our concern, allow execution
182
+ sys.exit(0)
183
+
184
+ tool_name = data.get("tool_name", "")
185
+ tool_input = data.get("tool_input", {})
186
+
187
+ # ── Category 1: Skill tool loading ──
188
+ if tool_name == "Skill":
189
+ skill_name = ""
190
+ if isinstance(tool_input, dict):
191
+ # WorkBuddy: {"skill": "huawei-ecs"}
192
+ # OpenCode/Hermes may use "command" or "args"
193
+ skill_name = (
194
+ tool_input.get("skill", "")
195
+ or tool_input.get("command", "")
196
+ or tool_input.get("args", "")
197
+ )
198
+ elif isinstance(tool_input, str):
199
+ skill_name = tool_input
200
+
201
+ if is_huawei_skill(skill_name):
202
+ write_event('skill:retrieve', skill_name)
203
+
204
+ # ── Category 2: Bash tool with hcloud commands ──
205
+ elif tool_name == "Bash":
206
+ command = ""
207
+ if isinstance(tool_input, dict):
208
+ command = tool_input.get("command", "")
209
+ elif isinstance(tool_input, str):
210
+ command = tool_input
211
+
212
+ if command and HCLOUD_RE.search(command):
213
+ result = classify_hcloud(command)
214
+ if result:
215
+ write_event(result['key'], result['value'], {'capability': 'cli'})
216
+
217
+ # ── Category 3: MCP Huawei Cloud tool invocations ──
218
+ elif tool_name.startswith("mcp__huaweicloud"):
219
+ write_event(f"tool:{tool_name}", "1", {'capability': 'mcp'})
220
+
221
+ # Always allow — telemetry hook never blocks execution
222
+ sys.exit(0)
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.1-next.7",
3
+ "version": "1.1.1",
4
4
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
5
5
  "type": "module",
6
6
  "files": [
7
+ "cordis.patch.yml",
7
8
  "bin",
8
9
  ".agents",
9
10
  "plugins/huaweicloud-core",
10
- "integrations/opencode"
11
+ "integrations/hermes",
12
+ "integrations/opencode",
13
+ "integrations/dsh",
14
+ "integrations/workbuddy",
15
+ "integrations/atomcode"
11
16
  ],
12
17
  "bin": {
13
18
  "huaweicloud-devkit": "./bin/setup.cjs",
@@ -23,7 +28,7 @@
23
28
  }
24
29
  },
25
30
  "scripts": {
26
- "test": "node --test",
31
+ "test": "node --test \"test/*.test.mjs\"",
27
32
  "format": "prettier --write .",
28
33
  "format:check": "prettier --check .",
29
34
  "lint:md": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#.git\" \"#test/**\" \"#docs/**\"",
@@ -31,10 +36,9 @@
31
36
  "lint": "npm run lint:md && npm run lint:js",
32
37
  "validate": "node ./scripts/validate-package.mjs",
33
38
  "pack:verify": "node ./scripts/pack-verify.mjs",
34
- "postinstall": "node -e \"console.log('\\nHuaweiCloud DevKit installed. Run: npx huaweicloud-devkit install\\n')\""
39
+ "postinstall": "node ./bin/dsh-postinstall.cjs"
35
40
  },
36
41
  "dependencies": {
37
- "better-sqlite3": "^13.0.3",
38
42
  "undici": "^8.10.0"
39
43
  },
40
44
  "devDependencies": {
@@ -59,6 +63,7 @@
59
63
  "officeace",
60
64
  "office-claw",
61
65
  "workbuddy",
66
+ "atomcode",
62
67
  "dsh",
63
68
  "deepseek-harness",
64
69
  "mcp",
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.1-next.7",
20
+ "version": "1.1.1",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
22
22
  "skills": "./skills/",
23
- "version": "1.1.1-next.7",
23
+ "version": "1.1.1",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"