hyperclaw 4.0.1 → 4.0.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/README.md +7 -4
- package/dist/a2ui-protocol-CT_jDEU9.js +75 -0
- package/dist/agents-routing-683Q2JGp.js +129 -0
- package/dist/agents-routing-BpZBswBH.js +4 -0
- package/dist/api-keys-guide-Dq5Obbp4.js +149 -0
- package/dist/audit-BYxPlnTQ.js +248 -0
- package/dist/bounty-tools-C6LyzxM-.js +211 -0
- package/dist/browser-tools-CQBSbIuO.js +5 -0
- package/dist/browser-tools-YQmwRLLM.js +179 -0
- package/dist/claw-tasks-BRLUvFRD.js +80 -0
- package/dist/connector-3HnyH8fn.js +167 -0
- package/dist/connector-6PMZo5Ky.js +189 -0
- package/dist/connector-B6eoF3DD.js +181 -0
- package/dist/connector-B9tLG8UZ.js +196 -0
- package/dist/connector-BOlqjXWP.js +182 -0
- package/dist/connector-BP8zsbP8.js +189 -0
- package/dist/connector-BPoSevxp.js +286 -0
- package/dist/connector-BRHj773i.js +163 -0
- package/dist/connector-BToxU-jV.js +267 -0
- package/dist/connector-BliDVsJQ.js +239 -0
- package/dist/connector-Bv6s9oP7.js +88 -0
- package/dist/connector-By5wWGTR.js +343 -0
- package/dist/connector-C1BaFFgN.js +213 -0
- package/dist/connector-CRRWY5Wv.js +167 -0
- package/dist/connector-CXPQVGyI.js +85 -0
- package/dist/connector-Cdk1CXKi.js +194 -0
- package/dist/connector-CwlgFgjx.js +181 -0
- package/dist/connector-DFchk6l7.js +178 -0
- package/dist/connector-DKw7tRAy.js +192 -0
- package/dist/connector-DRv1ahC_.js +2 -2
- package/dist/connector-DU63KW94.js +165 -0
- package/dist/connector-Dbvb1Cj9.js +280 -0
- package/dist/connector-DcZdQcgR.js +173 -0
- package/dist/connector-DxKL8VvZ.js +182 -0
- package/dist/connector-T_YdZtzv.js +162 -0
- package/dist/connector-i4gOS9xL.js +154 -0
- package/dist/connector-rHXE1ZD2.js +167 -0
- package/dist/connector-wdUXChwa.js +172 -0
- package/dist/cost-tracker-pVE15Yq4.js +103 -0
- package/dist/credentials-store-BvnMPJwi.js +4 -0
- package/dist/credentials-store-sb-TRLwR.js +77 -0
- package/dist/cron-tasks-BvDFNyiE.js +82 -0
- package/dist/delivery-D5Z98EVq.js +95 -0
- package/dist/delivery-DCOXhXEO.js +5 -0
- package/dist/destructive-gate-m-dWqUFg.js +101 -0
- package/dist/developer-keys-JaJK3T27.js +127 -0
- package/dist/developer-keys-kyqmtWK3.js +8 -0
- package/dist/doctor-3oi89QIc.js +175 -0
- package/dist/doctor-Cf1XSfp9.js +4 -0
- package/dist/engine-B4eMiTgl.js +7 -0
- package/dist/engine-B8M7dYul.js +7 -0
- package/dist/engine-BhT-1M9W.js +256 -0
- package/dist/engine-D49jnSd_.js +256 -0
- package/dist/env-resolve-DWOQ45jG.js +9 -0
- package/dist/env-resolve-szSWl0UF.js +94 -0
- package/dist/extraction-tools-D3qDFBJ1.js +91 -0
- package/dist/extraction-tools-DLr_AEwq.js +5 -0
- package/dist/form_data-B_hIUrxU.js +8657 -0
- package/dist/gmail-watch-setup-Czt8rXaX.js +40 -0
- package/dist/heartbeat-engine-CRqfPcFM.js +83 -0
- package/dist/hub-DTsqe5Bt.js +6 -0
- package/dist/hub-FrPTA33j.js +515 -0
- package/dist/hyperclawbot-D9KCtc4P.js +480 -0
- package/dist/hyperclawbot-Dw27pJo4.js +480 -0
- package/dist/inference-CTWJeX9Q.js +922 -0
- package/dist/inference-ix607p7k.js +6 -0
- package/dist/knowledge-graph-DqA-Fztl.js +131 -0
- package/dist/loader-CISCqBto.js +400 -0
- package/dist/loader-CYMQ8VOS.js +4 -0
- package/dist/logger-8tEtAd3y.js +83 -0
- package/dist/manager-CPjeRe-6.js +4 -0
- package/dist/manager-Cwzj7w5R.js +105 -0
- package/dist/manager-DLmZI-9R.js +6 -0
- package/dist/manager-DSGhn5i3.js +117 -0
- package/dist/manager-DgyF52mg.js +218 -0
- package/dist/manager-Dm8nrMFx.js +40 -0
- package/dist/mcp-B_9Ber63.js +139 -0
- package/dist/mcp-loader-DSM5UiFG.js +94 -0
- package/dist/mcp-loader-j5ZLLw5O.js +94 -0
- package/dist/memory-BI1kPkAN.js +4 -0
- package/dist/memory-BVFGkxxK.js +270 -0
- package/dist/memory-auto-Bc7euou4.js +306 -0
- package/dist/memory-auto-DPfbkMVt.js +5 -0
- package/dist/memory-integration-DZExqWr4.js +91 -0
- package/dist/moltbook-B6ZeGN5_.js +81 -0
- package/dist/node-pwL6O_KX.js +222 -0
- package/dist/nodes-registry-CsPm_-CJ.js +52 -0
- package/dist/oauth-flow-CpWlgvNB.js +150 -0
- package/dist/oauth-provider-BZb6qOw5.js +110 -0
- package/dist/observability-B43YvNQV.js +89 -0
- package/dist/onboard-Bd_wsYdi.js +4086 -0
- package/dist/onboard-CAN7x3me.js +3026 -0
- package/dist/onboard-DnegOHMh.js +4 -4
- package/dist/onboard-RYtDlYBw.js +9 -0
- package/dist/onboard-aTwlQs-4.js +9 -0
- package/dist/orchestrator-BSp2M5EU.js +189 -0
- package/dist/orchestrator-C7ko5tWa.js +6 -0
- package/dist/orchestrator-DfPkIx2Z.js +6 -0
- package/dist/orchestrator-NJQsmiBE.js +189 -0
- package/dist/pairing-DU0_J28n.js +87 -0
- package/dist/pairing-DWllbSbO.js +4 -0
- package/dist/pc-access-Ly-uA8mn.js +8 -0
- package/dist/pc-access-NxBvTrRj.js +819 -0
- package/dist/pending-approval-DIHvwwWS.js +22 -0
- package/dist/puppeteer-2o3QOwAy.js +2 -2
- package/dist/puppeteer-BYTMp3BI.js +2 -2
- package/dist/puppeteer-DQ45qwWk.js +2 -2
- package/dist/reminders-store-D79qdfN0.js +58 -0
- package/dist/renderer-pqlDRKbH.js +225 -0
- package/dist/rules-BooT_qFP.js +103 -0
- package/dist/run-main.js +289 -1031
- package/dist/runner-D1rjuMTJ.js +810 -0
- package/dist/sdk/index.js +2 -2
- package/dist/sdk/index.mjs +2 -2
- package/dist/security-C-5URby1.js +73 -0
- package/dist/security-_xve79aq.js +4 -0
- package/dist/server-0kgyELx4.js +1047 -0
- package/dist/server-BIuTobTC.js +4 -0
- package/dist/server-BRlCEjyT.js +1047 -0
- package/dist/server-CCI1hv45.js +2 -2
- package/dist/server-DU9POoWc.js +4 -0
- package/dist/session-store-CujxByI6.js +113 -0
- package/dist/session-store-qpJUg2M1.js +5 -0
- package/dist/sessions-tools-CB2qbwIk.js +5 -0
- package/dist/sessions-tools-DHMaTZIs.js +95 -0
- package/dist/skill-loader-BkceKkIg.js +7 -0
- package/dist/skill-loader-DhgIwK4J.js +159 -0
- package/dist/skill-runtime--LqxWrp5.js +102 -0
- package/dist/skill-runtime-C5l0Tgt-.js +5 -0
- package/dist/skill-runtime-DsXK_HYG.js +102 -0
- package/dist/skill-runtime-IVTiqrMR.js +5 -0
- package/dist/src-BEVLgaF1.js +63 -0
- package/dist/src-Bgu_OxTQ.js +458 -0
- package/dist/src-Bq-oKt7Z.js +458 -0
- package/dist/src-DWCUhnD4.js +20 -0
- package/dist/src-cfRTjFef.js +63 -0
- package/dist/sub-agent-tools-BD9DF8_g.js +39 -0
- package/dist/sub-agent-tools-V7b3T9_s.js +39 -0
- package/dist/tool-policy-DNvNRnve.js +189 -0
- package/dist/tts-elevenlabs-BUOGKL-k.js +61 -0
- package/dist/update-check-BD4qH7Am.js +81 -0
- package/dist/vision-DRq-f-Dj.js +121 -0
- package/dist/vision-tools-CFZEpQKm.js +5 -0
- package/dist/vision-tools-CQnBI9aa.js +51 -0
- package/dist/voice-transcription-CgWq54hn.js +138 -0
- package/dist/website-watch-tools-Bk_TnwuE.js +5 -0
- package/dist/website-watch-tools-DraMPxdl.js +139 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
2
|
<img src="assets/icon.png" width="120" alt="HyperClaw">
|
|
3
3
|
<br>
|
|
4
4
|
<h1 align="center">🦅 HyperClaw — Personal AI Assistant</h1>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="https://img.shields.io/badge/build-passing-brightgreen?style=flat-square" alt="build">
|
|
9
|
-
<img src="https://img.shields.io/badge/release-v4.0.
|
|
9
|
+
<img src="https://img.shields.io/badge/release-v4.0.2-blue?style=flat-square" alt="release">
|
|
10
10
|
<img src="https://img.shields.io/badge/node-%E2%89%A522-green?style=flat-square" alt="node">
|
|
11
11
|
<img src="https://img.shields.io/badge/license-MIT-gray?style=flat-square" alt="license">
|
|
12
12
|
<img src="https://img.shields.io/badge/typescript-5.4-3178c6?style=flat-square&logo=typescript&logoColor=white" alt="typescript">
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
|
|
56
56
|
## Install
|
|
57
57
|
|
|
58
|
-
Runtime: Node ≥ 22.
|
|
58
|
+
Runtime: Node ≥ 22. Runs **natively on Windows, macOS, and Linux** — no WSL2 required.
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
npm install -g hyperclaw@latest
|
|
@@ -68,8 +68,11 @@ hyperclaw onboard
|
|
|
68
68
|
hyperclaw onboard --install-daemon
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
> **Windows users**: HyperClaw runs natively via Node.js. No WSL2, no admin rights needed.
|
|
72
|
+
> The daemon uses **Task Scheduler** and runs as your user account with full desktop access.
|
|
73
|
+
|
|
71
74
|
The wizard guides you step by step — provider, model, gateway, channels, and skills.
|
|
72
|
-
Works on **macOS, Linux, and Windows** (
|
|
75
|
+
Works on **macOS, Linux, and Windows** (native — no WSL2 required). Compatible with npm, pnpm, and bun.
|
|
73
76
|
|
|
74
77
|
---
|
|
75
78
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
|
|
3
|
+
//#region src/canvas/a2ui-protocol.ts
|
|
4
|
+
/** Map HyperClaw component type to A2UI-compatible type. */
|
|
5
|
+
function toA2UIType(t) {
|
|
6
|
+
const map = {
|
|
7
|
+
chart: "chart",
|
|
8
|
+
table: "table",
|
|
9
|
+
form: "form",
|
|
10
|
+
markdown: "markdown",
|
|
11
|
+
image: "image",
|
|
12
|
+
custom: "custom",
|
|
13
|
+
script: "script"
|
|
14
|
+
};
|
|
15
|
+
return map[t] || "custom";
|
|
16
|
+
}
|
|
17
|
+
/** Convert a CanvasComponent to A2UI surface. */
|
|
18
|
+
function componentToSurface(c) {
|
|
19
|
+
return {
|
|
20
|
+
id: c.id,
|
|
21
|
+
type: toA2UIType(c.type),
|
|
22
|
+
props: {
|
|
23
|
+
title: c.title,
|
|
24
|
+
width: c.width || "half"
|
|
25
|
+
},
|
|
26
|
+
data: c.data
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Generate beginRendering from canvas state (full sync). */
|
|
30
|
+
function toBeginRendering(canvas) {
|
|
31
|
+
const surfaces = canvas.components.map(componentToSurface);
|
|
32
|
+
const dataModel = {};
|
|
33
|
+
for (const c of canvas.components) if (c.data != null) dataModel[c.id] = c.data;
|
|
34
|
+
return {
|
|
35
|
+
type: "beginRendering",
|
|
36
|
+
surfaceId: canvas.id,
|
|
37
|
+
surfaces,
|
|
38
|
+
dataModel: Object.keys(dataModel).length ? dataModel : void 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Generate surfaceUpdate for newly added component. */
|
|
42
|
+
function toSurfaceUpdate(canvasId, component) {
|
|
43
|
+
return {
|
|
44
|
+
type: "surfaceUpdate",
|
|
45
|
+
surfaceId: canvasId,
|
|
46
|
+
surfaces: [componentToSurface(component)]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Generate dataModelUpdate when component data changes. */
|
|
50
|
+
function toDataModelUpdate(canvasId, componentId, data) {
|
|
51
|
+
return {
|
|
52
|
+
type: "dataModelUpdate",
|
|
53
|
+
surfaceId: canvasId,
|
|
54
|
+
updates: { [componentId]: data }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Generate deleteSurface for removed components. */
|
|
58
|
+
function toDeleteSurface(canvasId, componentIds) {
|
|
59
|
+
return {
|
|
60
|
+
type: "deleteSurface",
|
|
61
|
+
surfaceId: canvasId,
|
|
62
|
+
surfaceIds: componentIds
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Serialize A2UI messages to JSONL (one message per line). */
|
|
66
|
+
function toJSONL(messages) {
|
|
67
|
+
return messages.map((m) => JSON.stringify(m)).join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
exports.toBeginRendering = toBeginRendering;
|
|
72
|
+
exports.toDataModelUpdate = toDataModelUpdate;
|
|
73
|
+
exports.toDeleteSurface = toDeleteSurface;
|
|
74
|
+
exports.toJSONL = toJSONL;
|
|
75
|
+
exports.toSurfaceUpdate = toSurfaceUpdate;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const chalk = require_chunk.__toESM(require("chalk"));
|
|
3
|
+
const inquirer = require_chunk.__toESM(require("inquirer"));
|
|
4
|
+
const fs_extra = require_chunk.__toESM(require("fs-extra"));
|
|
5
|
+
const path = require_chunk.__toESM(require("path"));
|
|
6
|
+
const os = require_chunk.__toESM(require("os"));
|
|
7
|
+
|
|
8
|
+
//#region src/routing/agents-routing.ts
|
|
9
|
+
var AgentRouter = class {
|
|
10
|
+
stateFile;
|
|
11
|
+
agents = [];
|
|
12
|
+
constructor() {
|
|
13
|
+
this.stateFile = path.default.join(os.default.homedir(), ".hyperclaw", "agents.json");
|
|
14
|
+
this.load();
|
|
15
|
+
}
|
|
16
|
+
load() {
|
|
17
|
+
try {
|
|
18
|
+
this.agents = fs_extra.default.readJsonSync(this.stateFile);
|
|
19
|
+
} catch {
|
|
20
|
+
this.agents = [{
|
|
21
|
+
workspace: path.default.join(os.default.homedir(), ".hyperclaw", "workspace"),
|
|
22
|
+
name: "default",
|
|
23
|
+
model: void 0,
|
|
24
|
+
bindings: []
|
|
25
|
+
}];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
save() {
|
|
29
|
+
fs_extra.default.ensureDirSync(path.default.dirname(this.stateFile));
|
|
30
|
+
fs_extra.default.writeJsonSync(this.stateFile, this.agents, { spaces: 2 });
|
|
31
|
+
}
|
|
32
|
+
listBindings() {
|
|
33
|
+
console.log(chalk.default.bold.cyan("\n 🦅 AGENT BINDINGS\n"));
|
|
34
|
+
if (this.agents.length === 0) {
|
|
35
|
+
console.log(chalk.default.gray(" No agents configured."));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const agent of this.agents) {
|
|
39
|
+
console.log(` ${chalk.default.bold(agent.name)} ${chalk.default.gray(agent.workspace)}`);
|
|
40
|
+
if (agent.bindings.length === 0) console.log(` ${chalk.default.gray("No channel bindings — receives from all channels")}`);
|
|
41
|
+
else for (const b of agent.bindings) {
|
|
42
|
+
const acct = b.accountId ? chalk.default.gray(`@${b.accountId}`) : chalk.default.gray("(all accounts)");
|
|
43
|
+
const role = b.role === "primary" ? chalk.default.green("[primary]") : chalk.default.gray("[secondary]");
|
|
44
|
+
console.log(` ${chalk.default.cyan(b.channelId)} ${acct} ${role}`);
|
|
45
|
+
}
|
|
46
|
+
console.log();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async bind() {
|
|
50
|
+
console.log(chalk.default.cyan("\n Bind a channel to an agent workspace\n"));
|
|
51
|
+
const { channel, workspace, role } = await inquirer.default.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: "input",
|
|
54
|
+
name: "channel",
|
|
55
|
+
message: "Channel ID (e.g. telegram, discord, slack):",
|
|
56
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "input",
|
|
60
|
+
name: "workspace",
|
|
61
|
+
message: "Agent workspace (directory or name):",
|
|
62
|
+
default: "default"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "list",
|
|
66
|
+
name: "role",
|
|
67
|
+
message: "Binding role:",
|
|
68
|
+
choices: [{
|
|
69
|
+
name: "primary — routes all traffic from this channel",
|
|
70
|
+
value: "primary"
|
|
71
|
+
}, {
|
|
72
|
+
name: "secondary — fallback if primary is busy",
|
|
73
|
+
value: "secondary"
|
|
74
|
+
}]
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
let agent = this.agents.find((a) => a.name === workspace || a.workspace === workspace);
|
|
78
|
+
if (!agent) {
|
|
79
|
+
agent = {
|
|
80
|
+
workspace,
|
|
81
|
+
name: workspace,
|
|
82
|
+
bindings: []
|
|
83
|
+
};
|
|
84
|
+
this.agents.push(agent);
|
|
85
|
+
}
|
|
86
|
+
agent.bindings.push({
|
|
87
|
+
channelId: channel,
|
|
88
|
+
agentWorkspace: agent.workspace,
|
|
89
|
+
role,
|
|
90
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
91
|
+
});
|
|
92
|
+
this.save();
|
|
93
|
+
console.log(chalk.default.green(`\n ✔ Bound ${channel} → ${workspace} (${role})\n`));
|
|
94
|
+
}
|
|
95
|
+
async unbind() {
|
|
96
|
+
const allBindings = [];
|
|
97
|
+
for (const agent of this.agents) for (const b of agent.bindings) allBindings.push({
|
|
98
|
+
agent: agent.name,
|
|
99
|
+
channel: b.channelId
|
|
100
|
+
});
|
|
101
|
+
if (allBindings.length === 0) {
|
|
102
|
+
console.log(chalk.default.gray("\n No bindings to remove.\n"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const { toRemove } = await inquirer.default.prompt([{
|
|
106
|
+
type: "checkbox",
|
|
107
|
+
name: "toRemove",
|
|
108
|
+
message: "Select bindings to remove:",
|
|
109
|
+
choices: allBindings.map((b) => ({
|
|
110
|
+
name: `${b.channel} → ${b.agent}`,
|
|
111
|
+
value: b
|
|
112
|
+
}))
|
|
113
|
+
}]);
|
|
114
|
+
for (const { agent: agentName, channel } of toRemove) {
|
|
115
|
+
const agent = this.agents.find((a) => a.name === agentName);
|
|
116
|
+
if (agent) agent.bindings = agent.bindings.filter((b) => b.channelId !== channel);
|
|
117
|
+
}
|
|
118
|
+
this.save();
|
|
119
|
+
console.log(chalk.default.green(`\n ✔ Removed ${toRemove.length} binding(s)\n`));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
Object.defineProperty(exports, 'AgentRouter', {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
get: function () {
|
|
127
|
+
return AgentRouter;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
|
|
3
|
+
//#region src/infra/api-keys-guide.ts
|
|
4
|
+
const API_KEYS_GUIDE = [
|
|
5
|
+
{
|
|
6
|
+
serviceId: "anthropic",
|
|
7
|
+
name: "Anthropic (Claude)",
|
|
8
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
9
|
+
url: "platform.anthropic.com",
|
|
10
|
+
setupSteps: [
|
|
11
|
+
"1. Go to platform.anthropic.com → API Keys.",
|
|
12
|
+
"2. Sign up / sign in with an Anthropic account.",
|
|
13
|
+
"3. Create Key — copy it (starts with sk-ant-). Not shown again!",
|
|
14
|
+
"",
|
|
15
|
+
" 🔗 platform.anthropic.com/settings/keys"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
serviceId: "openai",
|
|
20
|
+
name: "OpenAI (GPT)",
|
|
21
|
+
envVar: "OPENAI_API_KEY",
|
|
22
|
+
url: "platform.openai.com",
|
|
23
|
+
setupSteps: [
|
|
24
|
+
"1. Go to platform.openai.com → API keys.",
|
|
25
|
+
"2. Create new secret key — copy it (starts with sk-). Not shown again!",
|
|
26
|
+
"3. You need billing enabled for production use.",
|
|
27
|
+
"",
|
|
28
|
+
" 🔗 platform.openai.com/api-keys"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
serviceId: "openrouter",
|
|
33
|
+
name: "OpenRouter",
|
|
34
|
+
envVar: "OPENROUTER_API_KEY",
|
|
35
|
+
url: "openrouter.ai",
|
|
36
|
+
setupSteps: [
|
|
37
|
+
"1. Go to openrouter.ai → Keys.",
|
|
38
|
+
"2. Sign in (Google/GitHub).",
|
|
39
|
+
"3. Create Key — copy it. OpenRouter provides access to many models (Claude, GPT etc.).",
|
|
40
|
+
"",
|
|
41
|
+
" 🔗 openrouter.ai/keys"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
serviceId: "tavily",
|
|
46
|
+
name: "Tavily (Web Search)",
|
|
47
|
+
envVar: "TAVILY_API_KEY",
|
|
48
|
+
url: "tavily.com",
|
|
49
|
+
setupSteps: [
|
|
50
|
+
"1. Go to tavily.com → Sign up.",
|
|
51
|
+
"2. Dashboard → API Keys → Create API Key.",
|
|
52
|
+
"3. Copy the key. Used for the web-search skill.",
|
|
53
|
+
"",
|
|
54
|
+
" 🔗 app.tavily.com"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
serviceId: "elevenlabs",
|
|
59
|
+
name: "ElevenLabs (TTS)",
|
|
60
|
+
envVar: "ELEVENLABS_API_KEY",
|
|
61
|
+
url: "elevenlabs.io",
|
|
62
|
+
setupSteps: [
|
|
63
|
+
"1. Go to elevenlabs.io → Profile → API Key.",
|
|
64
|
+
"2. Copy the API key (or create a new one).",
|
|
65
|
+
"3. Used for talk mode (voice responses).",
|
|
66
|
+
"",
|
|
67
|
+
" 🔗 elevenlabs.io/app/settings/api-keys"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
serviceId: "deepl",
|
|
72
|
+
name: "DeepL (Translation)",
|
|
73
|
+
envVar: "DEEPL_API_KEY",
|
|
74
|
+
url: "deepl.com",
|
|
75
|
+
setupSteps: [
|
|
76
|
+
"1. Go to deepl.com/pro-api → Get API key.",
|
|
77
|
+
"2. Sign up (free tier available).",
|
|
78
|
+
"3. Account → API keys — copy the Authentication Key.",
|
|
79
|
+
"",
|
|
80
|
+
" 🔗 deepl.com/pro-api"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
serviceId: "github",
|
|
85
|
+
name: "GitHub (PAT)",
|
|
86
|
+
envVar: "GITHUB_TOKEN",
|
|
87
|
+
url: "github.com",
|
|
88
|
+
setupSteps: [
|
|
89
|
+
"1. GitHub → Settings → Developer settings → Personal access tokens.",
|
|
90
|
+
"2. Generate new token (classic or fine-grained).",
|
|
91
|
+
"3. Select scopes: repo, read:user etc. depending on use case.",
|
|
92
|
+
"",
|
|
93
|
+
" 🔗 github.com/settings/tokens"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
serviceId: "xai",
|
|
98
|
+
name: "xAI (Grok)",
|
|
99
|
+
envVar: "XAI_API_KEY",
|
|
100
|
+
url: "x.ai",
|
|
101
|
+
setupSteps: [
|
|
102
|
+
"1. Go to console.x.ai → API keys.",
|
|
103
|
+
"2. Sign in and create a new key.",
|
|
104
|
+
"3. Copy the key.",
|
|
105
|
+
"",
|
|
106
|
+
" 🔗 console.x.ai"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
serviceId: "google",
|
|
111
|
+
name: "Google AI (Gemini)",
|
|
112
|
+
envVar: "GOOGLE_AI_API_KEY",
|
|
113
|
+
url: "ai.google.dev",
|
|
114
|
+
setupSteps: [
|
|
115
|
+
"1. Go to aistudio.google.com/apikey.",
|
|
116
|
+
"2. Get API key or Create API key.",
|
|
117
|
+
"3. Copy the key.",
|
|
118
|
+
"",
|
|
119
|
+
" 🔗 aistudio.google.com/apikey"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
const SERVICE_ID_MAP = new Map(API_KEYS_GUIDE.map((g) => [g.serviceId.toLowerCase(), g]));
|
|
124
|
+
/** Known name aliases (e.g. anthropic, claude -> anthropic) */
|
|
125
|
+
const ALIASES = {
|
|
126
|
+
claude: "anthropic",
|
|
127
|
+
gpt: "openai",
|
|
128
|
+
xai: "xai",
|
|
129
|
+
google: "google"
|
|
130
|
+
};
|
|
131
|
+
function getApiKeyGuide(serviceId) {
|
|
132
|
+
const id = serviceId.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
133
|
+
return SERVICE_ID_MAP.get(id) ?? SERVICE_ID_MAP.get(ALIASES[id] ?? "") ?? null;
|
|
134
|
+
}
|
|
135
|
+
/** For unknown services — generic API key instructions */
|
|
136
|
+
const GENERIC_API_KEY_STEPS = [
|
|
137
|
+
"For an unknown service:",
|
|
138
|
+
"1. Go to the official service website (e.g. developers.xxx.com).",
|
|
139
|
+
"2. Sign up / sign in. An account is usually required.",
|
|
140
|
+
"3. Look for \"API Keys\", \"Credentials\", \"Developer\" or \"Integrations\" section.",
|
|
141
|
+
"4. Create a new API key or token. Copy it immediately — many services do not show it again.",
|
|
142
|
+
"5. Keep it secret — do not share it or commit it to a repo.",
|
|
143
|
+
"",
|
|
144
|
+
" 💡 Known services: anthropic, openai, openrouter, tavily, elevenlabs, deepl, github, xai, google"
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
exports.GENERIC_API_KEY_STEPS = GENERIC_API_KEY_STEPS;
|
|
149
|
+
exports.getApiKeyGuide = getApiKeyGuide;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const chalk = require_chunk.__toESM(require("chalk"));
|
|
3
|
+
const ora = require_chunk.__toESM(require("ora"));
|
|
4
|
+
const fs_extra = require_chunk.__toESM(require("fs-extra"));
|
|
5
|
+
const path = require_chunk.__toESM(require("path"));
|
|
6
|
+
const os = require_chunk.__toESM(require("os"));
|
|
7
|
+
|
|
8
|
+
//#region src/security/audit.ts
|
|
9
|
+
const HC_DIR = path.default.join(os.default.homedir(), ".hyperclaw");
|
|
10
|
+
async function checkFilePermissions() {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const sensitiveFiles = [
|
|
13
|
+
path.default.join(HC_DIR, "hyperclaw.json"),
|
|
14
|
+
path.default.join(HC_DIR, "auth.json"),
|
|
15
|
+
path.default.join(HC_DIR, ".env"),
|
|
16
|
+
path.default.join(HC_DIR, "AGENTS.md")
|
|
17
|
+
];
|
|
18
|
+
for (const f of sensitiveFiles) if (await fs_extra.default.pathExists(f)) {
|
|
19
|
+
const stat = await fs_extra.default.stat(f);
|
|
20
|
+
if ((stat.mode & 63) !== 0) findings.push({
|
|
21
|
+
severity: "high",
|
|
22
|
+
category: "File Permissions",
|
|
23
|
+
title: `Unsafe permissions on ${path.default.basename(f)}`,
|
|
24
|
+
detail: `Mode ${(stat.mode & 511).toString(8)} allows group/other read`,
|
|
25
|
+
remediation: `chmod 600 ${f}`,
|
|
26
|
+
cvss: 7.5
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const credsDir = path.default.join(HC_DIR, "credentials");
|
|
30
|
+
if (await fs_extra.default.pathExists(credsDir)) {
|
|
31
|
+
const stat = await fs_extra.default.stat(credsDir);
|
|
32
|
+
if ((stat.mode & 63) !== 0) findings.push({
|
|
33
|
+
severity: "critical",
|
|
34
|
+
category: "File Permissions",
|
|
35
|
+
title: "credentials/ directory is world-readable",
|
|
36
|
+
detail: `Mode ${(stat.mode & 511).toString(8)} — all credential files are exposed`,
|
|
37
|
+
remediation: `chmod 700 ${credsDir}`,
|
|
38
|
+
cvss: 9.1
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
43
|
+
async function checkGatewayConfig() {
|
|
44
|
+
const findings = [];
|
|
45
|
+
let cfg = null;
|
|
46
|
+
try {
|
|
47
|
+
cfg = await fs_extra.default.readJson(path.default.join(HC_DIR, "hyperclaw.json"));
|
|
48
|
+
} catch {
|
|
49
|
+
return findings;
|
|
50
|
+
}
|
|
51
|
+
const token = cfg.gateway?.authToken;
|
|
52
|
+
if (!token) findings.push({
|
|
53
|
+
severity: "critical",
|
|
54
|
+
category: "Authentication",
|
|
55
|
+
title: "Gateway auth token not set",
|
|
56
|
+
detail: "Any client can connect to the gateway without authentication",
|
|
57
|
+
remediation: "hyperclaw gateway config --set-token",
|
|
58
|
+
cvss: 9.8
|
|
59
|
+
});
|
|
60
|
+
else if (token.length < 32) findings.push({
|
|
61
|
+
severity: "high",
|
|
62
|
+
category: "Authentication",
|
|
63
|
+
title: "Gateway auth token is too short",
|
|
64
|
+
detail: `Token is ${token.length} chars — minimum recommended is 32`,
|
|
65
|
+
remediation: "hyperclaw gateway config --regenerate-token",
|
|
66
|
+
cvss: 7.3
|
|
67
|
+
});
|
|
68
|
+
if (cfg.gateway?.bind === "0.0.0.0") findings.push({
|
|
69
|
+
severity: "medium",
|
|
70
|
+
category: "Network Exposure",
|
|
71
|
+
title: "Gateway bound to all interfaces (0.0.0.0)",
|
|
72
|
+
detail: "Gateway is accessible from the local network. Ensure auth token is strong.",
|
|
73
|
+
remediation: "Use 127.0.0.1 unless you need LAN access",
|
|
74
|
+
cvss: 5.3
|
|
75
|
+
});
|
|
76
|
+
if (cfg.gateway?.tailscaleExposure === "funnel") findings.push({
|
|
77
|
+
severity: "medium",
|
|
78
|
+
category: "Network Exposure",
|
|
79
|
+
title: "Gateway exposed via Tailscale Funnel (public internet)",
|
|
80
|
+
detail: "Your gateway is reachable from the public internet via Tailscale Funnel",
|
|
81
|
+
remediation: "Ensure auth token is strong and DM policies are restrictive",
|
|
82
|
+
cvss: 5.8
|
|
83
|
+
});
|
|
84
|
+
for (const [ch, chCfg] of Object.entries(cfg.channelConfigs || {})) {
|
|
85
|
+
const policy = chCfg?.dmPolicy?.policy;
|
|
86
|
+
if (policy === "open") findings.push({
|
|
87
|
+
severity: "high",
|
|
88
|
+
category: "DM Policy",
|
|
89
|
+
title: `DM policy is "open" on ${ch}`,
|
|
90
|
+
detail: "Anyone can send DMs to your agent. This is a prompt injection risk.",
|
|
91
|
+
remediation: `hyperclaw channels add ${ch} # and choose pairing or allowlist`,
|
|
92
|
+
cvss: 7.1
|
|
93
|
+
});
|
|
94
|
+
if (policy === "allowlist") {
|
|
95
|
+
const allowFrom = chCfg?.dmPolicy?.allowFrom || [];
|
|
96
|
+
if (allowFrom.length === 0) findings.push({
|
|
97
|
+
severity: "high",
|
|
98
|
+
category: "DM Policy",
|
|
99
|
+
title: `Empty allowlist on ${ch} — DMs are silently dropped`,
|
|
100
|
+
detail: `allowFrom is [] — no one can reach your agent on ${ch}`,
|
|
101
|
+
remediation: `hyperclaw pairing approve ${ch} <code>`,
|
|
102
|
+
cvss: 4
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return findings;
|
|
107
|
+
}
|
|
108
|
+
async function checkSecretsInPrompts() {
|
|
109
|
+
const findings = [];
|
|
110
|
+
const secretPatterns = [
|
|
111
|
+
{
|
|
112
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/,
|
|
113
|
+
name: "OpenAI API key"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
pattern: /tvly-[a-zA-Z0-9]{20,}/,
|
|
117
|
+
name: "Tavily API key"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
pattern: /xai-[a-zA-Z0-9]{20,}/,
|
|
121
|
+
name: "xAI API key"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/,
|
|
125
|
+
name: "GitHub PAT"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
pattern: /or-[a-zA-Z0-9]{20,}/,
|
|
129
|
+
name: "OpenRouter API key"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
pattern: /[a-f0-9]{64}/,
|
|
133
|
+
name: "Potential hex secret"
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
const filesToScan = [
|
|
137
|
+
path.default.join(HC_DIR, "AGENTS.md"),
|
|
138
|
+
path.default.join(HC_DIR, "MEMORY.md"),
|
|
139
|
+
path.default.join(HC_DIR, "hyperclaw.json")
|
|
140
|
+
];
|
|
141
|
+
for (const f of filesToScan) {
|
|
142
|
+
if (!await fs_extra.default.pathExists(f)) continue;
|
|
143
|
+
const content = await fs_extra.default.readFile(f, "utf8");
|
|
144
|
+
for (const { pattern, name } of secretPatterns) if (pattern.test(content)) findings.push({
|
|
145
|
+
severity: "critical",
|
|
146
|
+
category: "Secret Exposure",
|
|
147
|
+
title: `${name} potentially embedded in ${path.default.basename(f)}`,
|
|
148
|
+
detail: `Found pattern matching ${name} in plaintext file. Secrets in prompts are a serious risk.`,
|
|
149
|
+
remediation: `Remove the secret and use: hyperclaw secrets set KEY=value`,
|
|
150
|
+
cvss: 9.1
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
async function deepScan() {
|
|
156
|
+
const findings = [];
|
|
157
|
+
const hubState = path.default.join(HC_DIR, "hub-state.json");
|
|
158
|
+
if (await fs_extra.default.pathExists(hubState)) {
|
|
159
|
+
const state = await fs_extra.default.readJson(hubState);
|
|
160
|
+
const dangerous = state.installed?.filter((s) => s.risk === "dangerous") || [];
|
|
161
|
+
for (const s of dangerous) findings.push({
|
|
162
|
+
severity: "critical",
|
|
163
|
+
category: "Installed Skills",
|
|
164
|
+
title: `Dangerous skill installed: ${s.name}`,
|
|
165
|
+
detail: s.riskReason || "Skill is flagged as dangerous",
|
|
166
|
+
remediation: `hyperclaw hub --uninstall ${s.id}`,
|
|
167
|
+
cvss: 8.5
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
let cfg = null;
|
|
171
|
+
try {
|
|
172
|
+
cfg = await fs_extra.default.readJson(path.default.join(HC_DIR, "hyperclaw.json"));
|
|
173
|
+
} catch {}
|
|
174
|
+
if (cfg?.gateway?.authToken) {
|
|
175
|
+
const token = cfg.gateway.authToken;
|
|
176
|
+
const entropy = estimateEntropy(token);
|
|
177
|
+
if (entropy < 3.5) findings.push({
|
|
178
|
+
severity: "high",
|
|
179
|
+
category: "Token Quality",
|
|
180
|
+
title: "Gateway auth token has low entropy",
|
|
181
|
+
detail: `Estimated entropy: ${entropy.toFixed(2)} bits/char — token may be guessable`,
|
|
182
|
+
remediation: "hyperclaw gateway config --regenerate-token",
|
|
183
|
+
cvss: 6.5
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return findings;
|
|
187
|
+
}
|
|
188
|
+
function estimateEntropy(str) {
|
|
189
|
+
const freq = /* @__PURE__ */ new Map();
|
|
190
|
+
for (const ch of str) freq.set(ch, (freq.get(ch) || 0) + 1);
|
|
191
|
+
let entropy = 0;
|
|
192
|
+
for (const count of freq.values()) {
|
|
193
|
+
const p = count / str.length;
|
|
194
|
+
entropy -= p * Math.log2(p);
|
|
195
|
+
}
|
|
196
|
+
return entropy;
|
|
197
|
+
}
|
|
198
|
+
const SEVERITY_ORDER = [
|
|
199
|
+
"critical",
|
|
200
|
+
"high",
|
|
201
|
+
"medium",
|
|
202
|
+
"low",
|
|
203
|
+
"info"
|
|
204
|
+
];
|
|
205
|
+
const SEVERITY_COLOR = {
|
|
206
|
+
critical: chalk.default.bgRed.white.bold,
|
|
207
|
+
high: chalk.default.red,
|
|
208
|
+
medium: chalk.default.yellow,
|
|
209
|
+
low: chalk.default.cyan,
|
|
210
|
+
info: chalk.default.gray
|
|
211
|
+
};
|
|
212
|
+
async function runSecurityAudit(deep = false) {
|
|
213
|
+
console.log(chalk.default.bold.cyan("\n 🔐 HYPERCLAW SECURITY AUDIT\n"));
|
|
214
|
+
const spinner = (0, ora.default)("Running security checks...").start();
|
|
215
|
+
const allFindings = [
|
|
216
|
+
...await checkFilePermissions(),
|
|
217
|
+
...await checkGatewayConfig(),
|
|
218
|
+
...await checkSecretsInPrompts(),
|
|
219
|
+
...deep ? await deepScan() : []
|
|
220
|
+
];
|
|
221
|
+
spinner.stop();
|
|
222
|
+
if (deep) console.log(chalk.default.gray(" Mode: DEEP SCAN\n"));
|
|
223
|
+
else console.log(chalk.default.gray(" Mode: standard (run with --deep for full scan)\n"));
|
|
224
|
+
if (allFindings.length === 0) {
|
|
225
|
+
console.log(chalk.default.green(" ✔ No security issues found!\n"));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
allFindings.sort((a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity));
|
|
229
|
+
for (const f of allFindings) {
|
|
230
|
+
const color = SEVERITY_COLOR[f.severity];
|
|
231
|
+
const badge = color(` ${f.severity.toUpperCase()} `);
|
|
232
|
+
const cvss = f.cvss ? chalk.default.gray(` CVSS ${f.cvss}`) : "";
|
|
233
|
+
console.log(` ${badge}${cvss} ${chalk.default.white(f.title)}`);
|
|
234
|
+
console.log(` ${chalk.default.gray(`Category: ${f.category}`)}`);
|
|
235
|
+
console.log(` ${chalk.default.gray(f.detail)}`);
|
|
236
|
+
console.log(` ${chalk.default.cyan("Fix: " + f.remediation)}`);
|
|
237
|
+
console.log();
|
|
238
|
+
}
|
|
239
|
+
const counts = SEVERITY_ORDER.reduce((acc, sev) => {
|
|
240
|
+
acc[sev] = allFindings.filter((f) => f.severity === sev).length;
|
|
241
|
+
return acc;
|
|
242
|
+
}, {});
|
|
243
|
+
console.log(` ${chalk.default.bold("Summary:")} ${chalk.default.bgRed.white.bold(` ${counts.critical} CRITICAL `)} ${chalk.default.red(`${counts.high} high`)} ${chalk.default.yellow(`${counts.medium} medium`)} ${chalk.default.cyan(`${counts.low} low`)}\n`);
|
|
244
|
+
if (!deep) console.log(chalk.default.gray(" Run: hyperclaw security audit --deep for full credential entropy scan\n"));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
exports.runSecurityAudit = runSecurityAudit;
|