huaweicloud-devkit 1.1.2-next.0 → 1.1.2-next.2
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/.agents/plugins/marketplace.json +18 -18
- package/README.md +182 -38
- package/README.zh-CN.md +180 -36
- package/bin/dsh-postinstall.cjs +77 -0
- package/bin/setup.cjs +7 -4
- package/cordis.patch.yml +13 -0
- package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
- package/integrations/atomcode/hooks.json +9 -0
- package/integrations/dsh/hook-plugin.mjs +148 -0
- package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
- package/integrations/hermes/manifest.yaml +43 -0
- package/integrations/opencode/hooks/skill-tracker.js +115 -0
- package/integrations/opencode/opencode.json +1 -4
- package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
- package/package.json +74 -43
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +2 -5
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
- package/plugins/huaweicloud-core/assets/icon.png +0 -0
- package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
- package/plugins/huaweicloud-core/assets/logo.png +0 -0
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/safety/policy.json +75 -99
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
- package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
- package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
- package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
- package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
- package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
- package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
- package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
- package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
- package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
- package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
|
|
7
|
+
// ── Detect DSH profile context ──
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const dshHome = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
10
|
+
const profilesRoot = path.join(dshHome, 'profiles');
|
|
11
|
+
|
|
12
|
+
if (!cwd.startsWith(profilesRoot)) {
|
|
13
|
+
// Not inside a DSH profile — traditional install, just print message
|
|
14
|
+
console.log('\nHuaweiCloud DevKit installed. Run: npx huaweicloud-devkit install\n');
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ── Inside a DSH profile: copy skills to ~/.dsh/skills/ ──
|
|
19
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
20
|
+
|
|
21
|
+
// Rewrite the bundled patch's relative MCP server path to the absolute package
|
|
22
|
+
// path. Relative paths resolve against the DSH process cwd and break when DSH
|
|
23
|
+
// is started outside the profile directory. Idempotent: absolute paths are kept.
|
|
24
|
+
const bundledPatch = path.join(packageRoot, 'cordis.patch.yml');
|
|
25
|
+
if (fs.existsSync(bundledPatch)) {
|
|
26
|
+
const relativeArg = './node_modules/huaweicloud-devkit/plugins/huaweicloud-core/src/mcp-server.mjs';
|
|
27
|
+
const absoluteArg = path
|
|
28
|
+
.join(packageRoot, 'plugins', 'huaweicloud-core', 'src', 'mcp-server.mjs')
|
|
29
|
+
.replace(/\\/g, '/')
|
|
30
|
+
.replace(/'/g, "''");
|
|
31
|
+
const patch = fs.readFileSync(bundledPatch, 'utf8');
|
|
32
|
+
const updated = patch.split(`'${relativeArg}'`).join(`'${absoluteArg}'`);
|
|
33
|
+
if (updated !== patch) {
|
|
34
|
+
fs.writeFileSync(bundledPatch, updated);
|
|
35
|
+
console.log('HuaweiCloud DevKit: rewrote DSH patch MCP path to absolute package path');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const skillsSrc = path.join(packageRoot, 'plugins', 'huaweicloud-core', 'skills');
|
|
40
|
+
const skillsDest = path.join(dshHome, 'skills');
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(skillsSrc)) {
|
|
43
|
+
console.log('HuaweiCloud DevKit: skills source not found, skipping auto-setup');
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
48
|
+
|
|
49
|
+
function copyDir(src, dest) {
|
|
50
|
+
if (!fs.existsSync(src)) return;
|
|
51
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
52
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
53
|
+
const s = path.join(src, entry.name);
|
|
54
|
+
const d = path.join(dest, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
copyDir(s, d);
|
|
57
|
+
} else {
|
|
58
|
+
fs.copyFileSync(s, d);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let count = 0;
|
|
64
|
+
for (const entry of fs.readdirSync(skillsSrc, { withFileTypes: true })) {
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
copyDir(path.join(skillsSrc, entry.name), path.join(skillsDest, entry.name));
|
|
67
|
+
count++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`\nHuaweiCloud DevKit: ${count} skills installed to ~/.dsh/skills/`);
|
|
72
|
+
console.log('MCP server will be available after DSH restart.');
|
|
73
|
+
console.log('\n\u001b[1m\u001b[36m 首次使用请配置环境:\u001b[0m');
|
|
74
|
+
console.log(' 1. 安装 KooCLI:npx huaweicloud-devkit install-hcloud');
|
|
75
|
+
console.log(' 2. 配置凭证: npx huaweicloud-devkit auth init');
|
|
76
|
+
console.log(' 3. 重启 DSH 会话后即可使用');
|
|
77
|
+
console.log(' 或者直接在 DSH 中对 Agent 说:帮我安装华为云 KooCLI 并配置凭证\n');
|
package/bin/setup.cjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fileUrl = require('url').pathToFileURL(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fileUrl = require('node:url').pathToFileURL(
|
|
5
|
+
path.join(__dirname, '..', 'plugins', 'huaweicloud-core', 'src', 'setup-cli.mjs'),
|
|
6
|
+
).href;
|
|
7
|
+
|
|
8
|
+
import(fileUrl).catch((error) => {
|
|
9
|
+
console.error(error.message);
|
|
7
10
|
process.exit(1);
|
|
8
11
|
});
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- insert:
|
|
2
|
+
- id: huaweicloud-devkit
|
|
3
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
4
|
+
config:
|
|
5
|
+
serverName: huaweicloud
|
|
6
|
+
transport: stdio
|
|
7
|
+
command: node
|
|
8
|
+
args:
|
|
9
|
+
- './node_modules/huaweicloud-devkit/plugins/huaweicloud-core/src/mcp-server.mjs'
|
|
10
|
+
env:
|
|
11
|
+
HUAWEICLOUD_AGENT_TOOLKIT_MODE: local
|
|
12
|
+
HDKITSERVICE_ENDPOINT: ''
|
|
13
|
+
failOnStartupError: false
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* huaweicloud-telemetry.js — AtomCode PreToolUse Hook
|
|
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
|
+
* Classification logic mirrors the existing four platform hooks exactly.
|
|
9
|
+
* This hook NEVER blocks execution — it always exits 0.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { appendFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
// ══════════════════════════════════════════════════════════════════
|
|
17
|
+
// Debug
|
|
18
|
+
// ══════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
const DEBUG = process.env.HUAWEICLOUD_DEVKIT_DEBUG === 'true';
|
|
21
|
+
|
|
22
|
+
function debugLog(msg) {
|
|
23
|
+
if (!DEBUG) return;
|
|
24
|
+
try {
|
|
25
|
+
const logPath = join(TELEMETRY_DIR, 'atomcode-debug.log');
|
|
26
|
+
appendFileSync(logPath, `${new Date().toISOString()} ${msg}\n`);
|
|
27
|
+
} catch (_) {
|
|
28
|
+
/* best-effort */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ══════════════════════════════════════════════════════════════════
|
|
33
|
+
// Paths — hook is installed at <plugin_dir>/hooks/
|
|
34
|
+
// ══════════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
37
|
+
const HOOK_DIR = dirname(__filename);
|
|
38
|
+
const PLUGIN_DIR = dirname(HOOK_DIR);
|
|
39
|
+
const TELEMETRY_DIR = join(PLUGIN_DIR, 'telemetry');
|
|
40
|
+
const HOOK_EVENTS_PATH = join(TELEMETRY_DIR, 'hook-events.jsonl');
|
|
41
|
+
|
|
42
|
+
// ══════════════════════════════════════════════════════════════════
|
|
43
|
+
// Helpers
|
|
44
|
+
// ══════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
function ensureDir(dirPath) {
|
|
47
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeEvent(key, value, extra = {}) {
|
|
51
|
+
try {
|
|
52
|
+
ensureDir(TELEMETRY_DIR);
|
|
53
|
+
const line = JSON.stringify({ key, value, ...extra }) + '\n';
|
|
54
|
+
appendFileSync(HOOK_EVENTS_PATH, line, 'utf8');
|
|
55
|
+
debugLog(`writeEvent key=${key} value=${value}`);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
debugLog(`writeEvent FAILED: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ══════════════════════════════════════════════════════════════════
|
|
62
|
+
// Skill identification
|
|
63
|
+
// ══════════════════════════════════════════════════════════════════
|
|
64
|
+
|
|
65
|
+
function isHuaweiCloudSkill(name) {
|
|
66
|
+
return typeof name === 'string' && /^huawei/i.test(name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ══════════════════════════════════════════════════════════════════
|
|
70
|
+
// CLI command classification (matches skill-tracker.js exactly)
|
|
71
|
+
// ══════════════════════════════════════════════════════════════════
|
|
72
|
+
|
|
73
|
+
const HCLOUD_RE = /(?:^|[;&|]\s*)hcloud(?:\.exe)?\s+(.+)/i;
|
|
74
|
+
const READ_VERBS = /\b(List|Show|Get|Describe|NovaList|NovaShow)\w*/i;
|
|
75
|
+
const WRITE_VERBS =
|
|
76
|
+
/\b(Create|Delete|Update|Modify|Remove|Revoke|Grant|Attach|Detach|Enable|Disable|Set|Add|Bind|Unbind|Reset|Change|Activate|Deactivate|Register|Unregister|Import|Export|Download|Upload|Copy|Move|Convert|Migrate|Run|Execute|Invoke|Trigger|Deploy|Push|Start|Stop|Restart|Reboot|Suspend|Resume|Terminate|Release|Allocate)\w*/i;
|
|
77
|
+
|
|
78
|
+
function classifyHcloud(text) {
|
|
79
|
+
const m = HCLOUD_RE.exec(text);
|
|
80
|
+
if (!m) return null;
|
|
81
|
+
const rest = m[1].trim();
|
|
82
|
+
const parts = rest.split(/\s+/).filter((p) => !p.startsWith('--'));
|
|
83
|
+
const cmd = parts.join(' ');
|
|
84
|
+
if (!cmd) return null;
|
|
85
|
+
if (READ_VERBS.test(cmd)) return { key: 'cli:read', value: `hcloud ${cmd}` };
|
|
86
|
+
if (WRITE_VERBS.test(cmd)) return { key: 'cli:write', value: `hcloud ${cmd}` };
|
|
87
|
+
return { key: 'cli:invoke', value: `hcloud ${cmd}` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ══════════════════════════════════════════════════════════════════
|
|
91
|
+
// Input parsing — stdin JSON, with env-var fallback
|
|
92
|
+
// ══════════════════════════════════════════════════════════════════
|
|
93
|
+
|
|
94
|
+
function parseInput() {
|
|
95
|
+
// Primary: AtomCode passes data via env vars
|
|
96
|
+
const toolName = process.env.ATOMCODE_TOOL_NAME || '';
|
|
97
|
+
const context = process.env.ATOMCODE_HOOK_CONTEXT || '';
|
|
98
|
+
if (toolName || context) {
|
|
99
|
+
const input = { tool_name: toolName };
|
|
100
|
+
if (context) {
|
|
101
|
+
try {
|
|
102
|
+
input.tool_input = JSON.parse(context);
|
|
103
|
+
} catch (_) {
|
|
104
|
+
input.tool_input = { command: context };
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
input.tool_input = {};
|
|
108
|
+
}
|
|
109
|
+
return input;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fallback: stdin JSON (piped for manual testing)
|
|
113
|
+
try {
|
|
114
|
+
const data = readFileSync(0, 'utf8').trim();
|
|
115
|
+
if (data) return JSON.parse(data);
|
|
116
|
+
} catch (_) {
|
|
117
|
+
/* stdin unavailable */
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { tool_name: '', tool_input: {} };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function extractCommand(toolInput) {
|
|
124
|
+
if (typeof toolInput === 'string') return toolInput;
|
|
125
|
+
if (toolInput && typeof toolInput === 'object') {
|
|
126
|
+
return toolInput.command || toolInput.arguments?.command || '';
|
|
127
|
+
}
|
|
128
|
+
return '';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractSkillName(toolInput) {
|
|
132
|
+
if (typeof toolInput === 'string') return toolInput;
|
|
133
|
+
if (toolInput && typeof toolInput === 'object') {
|
|
134
|
+
return toolInput.name || toolInput.command || '';
|
|
135
|
+
}
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ══════════════════════════════════════════════════════════════════
|
|
140
|
+
// Main
|
|
141
|
+
// ══════════════════════════════════════════════════════════════════
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const input = parseInput();
|
|
145
|
+
const toolName = input.tool_name || input.tool || '';
|
|
146
|
+
const toolInput = input.tool_input || input.arguments || {};
|
|
147
|
+
|
|
148
|
+
debugLog(`tool=${toolName}`);
|
|
149
|
+
|
|
150
|
+
// ── Category 1: Skill tool calls ──
|
|
151
|
+
if (toolName === 'skill' || toolName === 'use_skill') {
|
|
152
|
+
const skillName = extractSkillName(toolInput);
|
|
153
|
+
debugLog(`skill name=${skillName}`);
|
|
154
|
+
if (isHuaweiCloudSkill(skillName)) {
|
|
155
|
+
writeEvent('skill:retrieve', skillName);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Category 2: Bash/pwsh with hcloud commands ──
|
|
160
|
+
if (toolName === 'bash' || toolName === 'pwsh') {
|
|
161
|
+
const command = extractCommand(toolInput);
|
|
162
|
+
if (command && HCLOUD_RE.test(command)) {
|
|
163
|
+
debugLog(`hcloud command: ${command.slice(0, 120)}`);
|
|
164
|
+
const classified = classifyHcloud(command);
|
|
165
|
+
if (classified) {
|
|
166
|
+
writeEvent(classified.key, classified.value, { capability: 'cli' });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Category 3: MCP Huawei Cloud tools ──
|
|
172
|
+
if (toolName.startsWith('mcp__huaweicloud')) {
|
|
173
|
+
writeEvent(`tool:${toolName}`, '1', { capability: 'mcp' });
|
|
174
|
+
debugLog(`MCP tool: ${toolName}`);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
debugLog(`FATAL: ${error?.message || error}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Always allow — telemetry hook never blocks; the process exits 0 naturally
|
|
181
|
+
// since all I/O is synchronous and nothing holds the event loop open.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// hook-plugin.mjs — DSH Cordis Hook Plugin
|
|
2
|
+
// Intercepts Huawei Cloud skill calls and hcloud CLI commands,
|
|
3
|
+
// writes events to hook-events.jsonl for the telemetry module to consume.
|
|
4
|
+
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
// ══════════════════════════════════════════════════════════════════
|
|
10
|
+
// Cordis plugin identity (required by DSH loader)
|
|
11
|
+
// ══════════════════════════════════════════════════════════════════
|
|
12
|
+
export const name = 'huaweicloud-devkit-hook';
|
|
13
|
+
export const inject = ['tools'];
|
|
14
|
+
|
|
15
|
+
// ══════════════════════════════════════════════════════════════════
|
|
16
|
+
// Paths — this file lives in $DSH_HOME/huaweicloud-plugins/
|
|
17
|
+
// ══════════════════════════════════════════════════════════════════
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const PLUGIN_DIR = dirname(__filename);
|
|
20
|
+
// telemetry/ dir is the single shared location for event files
|
|
21
|
+
const TELEMETRY_DIR = join(PLUGIN_DIR, 'telemetry');
|
|
22
|
+
const HOOK_EVENTS_PATH = join(TELEMETRY_DIR, 'hook-events.jsonl');
|
|
23
|
+
|
|
24
|
+
const DEBUG = process.env.HUAWEICLOUD_DEVKIT_DEBUG === 'true';
|
|
25
|
+
|
|
26
|
+
// ══════════════════════════════════════════════════════════════════
|
|
27
|
+
// Helpers
|
|
28
|
+
// ══════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
function ensureDir(dirPath) {
|
|
31
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function debugLog(msg) {
|
|
35
|
+
if (!DEBUG) return;
|
|
36
|
+
try {
|
|
37
|
+
const logPath = join(TELEMETRY_DIR, 'telemetry-debug.log');
|
|
38
|
+
ensureDir(TELEMETRY_DIR);
|
|
39
|
+
appendFileSync(logPath, `${new Date().toISOString()} [hook] ${msg}\n`, 'utf8');
|
|
40
|
+
} catch {
|
|
41
|
+
// debug log is best-effort; never fail the plugin
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeEvent(key, value, extra = {}) {
|
|
46
|
+
try {
|
|
47
|
+
ensureDir(TELEMETRY_DIR);
|
|
48
|
+
const line = JSON.stringify({ key, value, ...extra }) + '\n';
|
|
49
|
+
appendFileSync(HOOK_EVENTS_PATH, line, 'utf8');
|
|
50
|
+
debugLog(`writeEvent key=${key} value=${value}`);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
debugLog(`writeEvent FAILED: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ══════════════════════════════════════════════════════════════════
|
|
57
|
+
// Huawei Cloud identification
|
|
58
|
+
// ══════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
function isHuaweiCloudSkill(name) {
|
|
61
|
+
return typeof name === 'string' && /^huawei/i.test(name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ══════════════════════════════════════════════════════════════════
|
|
65
|
+
// hcloud command classification (mirrors skill-tracker.js)
|
|
66
|
+
// ══════════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
const HCLOUD_RE = /(?:^|[;&|]\s*)hcloud(?:\.exe)?\s+(.+)/i;
|
|
69
|
+
const READ_VERBS = /\b(List|Show|Get|Describe|NovaList|NovaShow)\w*/i;
|
|
70
|
+
const WRITE_VERBS =
|
|
71
|
+
/\b(Create|Delete|Update|Modify|Remove|Revoke|Grant|Attach|Detach|Enable|Disable|Set|Add|Bind|Unbind|Reset|Change|Activate|Deactivate|Register|Unregister|Import|Export|Download|Upload|Copy|Move|Convert|Migrate|Run|Execute|Invoke|Trigger|Deploy|Push|Start|Stop|Restart|Reboot|Suspend|Resume|Terminate|Release|Allocate)\w*/i;
|
|
72
|
+
|
|
73
|
+
function classifyHcloud(text) {
|
|
74
|
+
const m = HCLOUD_RE.exec(text);
|
|
75
|
+
if (!m) return null;
|
|
76
|
+
const rest = m[1].trim();
|
|
77
|
+
const parts = rest.split(/\s+/).filter((p) => !p.startsWith('--'));
|
|
78
|
+
const cmd = parts.join(' ');
|
|
79
|
+
if (!cmd) return null;
|
|
80
|
+
if (READ_VERBS.test(cmd)) return { key: 'cli:read', value: `hcloud ${cmd}`, capability: 'cli' };
|
|
81
|
+
if (WRITE_VERBS.test(cmd)) return { key: 'cli:write', value: `hcloud ${cmd}`, capability: 'cli' };
|
|
82
|
+
return { key: 'cli:invoke', value: `hcloud ${cmd}`, capability: 'cli' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ══════════════════════════════════════════════════════════════════
|
|
86
|
+
// apply() — Cordis plugin entry point
|
|
87
|
+
// ══════════════════════════════════════════════════════════════════
|
|
88
|
+
|
|
89
|
+
export function apply(ctx) {
|
|
90
|
+
debugLog('=== PLUGIN LOADED ===');
|
|
91
|
+
|
|
92
|
+
// ── Layer 1: tools/pre-execute waterfall ──────────────────────
|
|
93
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
94
|
+
try {
|
|
95
|
+
debugLog(`pre-execute tool=${exec.name}`);
|
|
96
|
+
|
|
97
|
+
// ── Intercept 1: skill tool calls ──
|
|
98
|
+
if (exec.name === 'skill') {
|
|
99
|
+
const skillName = exec.arguments?.name || '';
|
|
100
|
+
debugLog(`skill name=${skillName}`);
|
|
101
|
+
if (isHuaweiCloudSkill(skillName)) {
|
|
102
|
+
writeEvent('skill:retrieve', skillName);
|
|
103
|
+
ctx.logger.info(`[hw-hook] skill called: ${skillName}`);
|
|
104
|
+
debugLog(`SKILL TRACKED: ${skillName}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Intercept 2: pwsh/bash with hcloud commands ──
|
|
109
|
+
if (exec.name === 'pwsh' || exec.name === 'bash') {
|
|
110
|
+
const command = exec.arguments?.command || '';
|
|
111
|
+
if (!command) return next();
|
|
112
|
+
|
|
113
|
+
if (HCLOUD_RE.test(command)) {
|
|
114
|
+
debugLog(`hcloud command: ${command.slice(0, 120)}`);
|
|
115
|
+
|
|
116
|
+
// Classify and write event
|
|
117
|
+
const classified = classifyHcloud(command);
|
|
118
|
+
if (classified) {
|
|
119
|
+
writeEvent(classified.key, classified.value, { capability: 'cli' });
|
|
120
|
+
debugLog(`CLI TRACKED: ${classified.key} ${classified.value}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Intercept 3: MCP Huawei Cloud tools (record only) ──
|
|
126
|
+
if (exec.name.startsWith('mcp__huaweicloud__')) {
|
|
127
|
+
writeEvent(`tool:${exec.name}`, '1', { capability: 'mcp' });
|
|
128
|
+
debugLog(`MCP tool: ${exec.name}`);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
debugLog(`pre-execute ERROR: ${error?.message || error}`);
|
|
132
|
+
ctx.logger.warn(`[hw-hook] pre-execute error: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
return next();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Layer 2: tools/result observe ─────────────────────────────
|
|
138
|
+
ctx.on('tools/result', (exec, result) => {
|
|
139
|
+
if (exec.name.startsWith('mcp__huaweicloud__') && result.isError) {
|
|
140
|
+
const msg = result.error?.message || 'unknown error';
|
|
141
|
+
debugLog(`tool error: ${exec.name}: ${msg}`);
|
|
142
|
+
ctx.logger.info(`[hw-hook] tool error: ${exec.name}: ${msg}`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
ctx.logger.info('[hw-hook] DSH Huawei Cloud hook plugin activated');
|
|
147
|
+
debugLog('=== PLUGIN INIT DONE ===');
|
|
148
|
+
}
|