@dhf-hermes/grix 0.1.0

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.

Potentially problematic release.


This version of @dhf-hermes/grix might be problematic. Click here for more details.

Files changed (51) hide show
  1. package/.gitignore +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +98 -0
  4. package/bin/grix-hermes.mjs +93 -0
  5. package/grix-admin/SKILL.md +109 -0
  6. package/grix-admin/agents/openai.yaml +7 -0
  7. package/grix-admin/scripts/admin.mjs +12 -0
  8. package/grix-admin/scripts/bind_from_json.py +118 -0
  9. package/grix-admin/scripts/bind_local.py +226 -0
  10. package/grix-egg/SKILL.md +73 -0
  11. package/grix-egg/agents/openai.yaml +7 -0
  12. package/grix-egg/references/acceptance-checklist.md +10 -0
  13. package/grix-egg/scripts/card-link.mjs +12 -0
  14. package/grix-egg/scripts/validate_install_context.mjs +74 -0
  15. package/grix-group/SKILL.md +42 -0
  16. package/grix-group/agents/openai.yaml +7 -0
  17. package/grix-group/scripts/group.mjs +12 -0
  18. package/grix-query/SKILL.md +53 -0
  19. package/grix-query/agents/openai.yaml +7 -0
  20. package/grix-query/scripts/query.mjs +12 -0
  21. package/grix-register/SKILL.md +68 -0
  22. package/grix-register/agents/openai.yaml +7 -0
  23. package/grix-register/references/handoff-contract.md +21 -0
  24. package/grix-register/scripts/create_api_agent_and_bind.py +105 -0
  25. package/grix-register/scripts/grix_auth.py +487 -0
  26. package/grix-update/SKILL.md +50 -0
  27. package/grix-update/agents/openai.yaml +7 -0
  28. package/grix-update/references/cron-setup.md +11 -0
  29. package/grix-update/scripts/grix_update.py +99 -0
  30. package/lib/manifest.mjs +68 -0
  31. package/message-send/SKILL.md +71 -0
  32. package/message-send/agents/openai.yaml +7 -0
  33. package/message-send/scripts/card-link.mjs +40 -0
  34. package/message-send/scripts/send.mjs +12 -0
  35. package/message-unsend/SKILL.md +39 -0
  36. package/message-unsend/agents/openai.yaml +7 -0
  37. package/message-unsend/scripts/unsend.mjs +12 -0
  38. package/openclaw-memory-setup/SKILL.md +38 -0
  39. package/openclaw-memory-setup/agents/openai.yaml +7 -0
  40. package/openclaw-memory-setup/scripts/bench_ollama_embeddings.py +257 -0
  41. package/openclaw-memory-setup/scripts/set_openclaw_memory_model.py +240 -0
  42. package/openclaw-memory-setup/scripts/survey_host_readiness.py +379 -0
  43. package/package.json +51 -0
  44. package/shared/cli/actions.mjs +339 -0
  45. package/shared/cli/aibot-client.mjs +274 -0
  46. package/shared/cli/card-links.mjs +90 -0
  47. package/shared/cli/config.mjs +141 -0
  48. package/shared/cli/grix-hermes.mjs +87 -0
  49. package/shared/cli/targets.mjs +119 -0
  50. package/shared/references/grix-card-links.md +27 -0
  51. package/shared/references/hermes-grix-config.md +30 -0
@@ -0,0 +1,141 @@
1
+ import fs from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import YAML from "yaml";
5
+
6
+ const DEFAULT_BASE_URL = "https://grix.dhf.pub";
7
+ const DEFAULT_CAPABILITIES = ["session_route", "thread_v1", "inbound_media_v1", "local_action_v1"];
8
+ const DEFAULT_LOCAL_ACTIONS = ["exec_approve", "exec_reject"];
9
+
10
+ function parseEnvFile(filePath) {
11
+ if (!fs.existsSync(filePath)) {
12
+ return {};
13
+ }
14
+ const result = {};
15
+ for (const rawLine of fs.readFileSync(filePath, "utf8").split(/\r?\n/)) {
16
+ const line = rawLine.trim();
17
+ if (!line || line.startsWith("#")) {
18
+ continue;
19
+ }
20
+ const eq = line.indexOf("=");
21
+ if (eq <= 0) {
22
+ continue;
23
+ }
24
+ const key = line.slice(0, eq).trim();
25
+ let value = line.slice(eq + 1).trim();
26
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
27
+ value = value.slice(1, -1);
28
+ }
29
+ result[key] = value;
30
+ }
31
+ return result;
32
+ }
33
+
34
+ function readYaml(filePath) {
35
+ if (!fs.existsSync(filePath)) {
36
+ return {};
37
+ }
38
+ const text = fs.readFileSync(filePath, "utf8");
39
+ if (!text.trim()) {
40
+ return {};
41
+ }
42
+ const parsed = YAML.parse(text);
43
+ return parsed && typeof parsed === "object" ? parsed : {};
44
+ }
45
+
46
+ function cleanText(value) {
47
+ return String(value ?? "").trim();
48
+ }
49
+
50
+ function cleanList(value, fallback = []) {
51
+ if (Array.isArray(value)) {
52
+ return value.map((item) => cleanText(item)).filter(Boolean);
53
+ }
54
+ if (typeof value === "string") {
55
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
56
+ }
57
+ return [...fallback];
58
+ }
59
+
60
+ function cleanInt(value, fallback) {
61
+ const numeric = Number.parseInt(String(value ?? ""), 10);
62
+ return Number.isFinite(numeric) ? numeric : fallback;
63
+ }
64
+
65
+ function resolveHermesHome(overrides = {}) {
66
+ return path.resolve(
67
+ cleanText(overrides.hermesHome)
68
+ || cleanText(process.env.HERMES_HOME)
69
+ || path.join(homedir(), ".hermes")
70
+ );
71
+ }
72
+
73
+ function resolvePlatformConfig(configYaml) {
74
+ const topLevel = configYaml?.platforms?.grix;
75
+ if (topLevel && typeof topLevel === "object") {
76
+ return topLevel;
77
+ }
78
+ const nested = configYaml?.gateway?.platforms?.grix;
79
+ if (nested && typeof nested === "object") {
80
+ return nested;
81
+ }
82
+ return {};
83
+ }
84
+
85
+ function mergedEnv(hermesHome) {
86
+ const envFile = path.join(hermesHome, ".env");
87
+ return {
88
+ ...parseEnvFile(envFile),
89
+ ...process.env
90
+ };
91
+ }
92
+
93
+ export function resolveRuntimeConfig(overrides = {}) {
94
+ const hermesHome = resolveHermesHome(overrides);
95
+ const env = mergedEnv(hermesHome);
96
+ const configYaml = readYaml(path.join(hermesHome, "config.yaml"));
97
+ const grix = resolvePlatformConfig(configYaml);
98
+ const extra = grix?.extra && typeof grix.extra === "object" ? grix.extra : {};
99
+
100
+ const endpoint = cleanText(overrides.endpoint)
101
+ || cleanText(env.GRIX_ENDPOINT)
102
+ || cleanText(extra.endpoint);
103
+ const agentId = cleanText(overrides.agentId)
104
+ || cleanText(env.GRIX_AGENT_ID)
105
+ || cleanText(extra.agent_id);
106
+ const apiKey = cleanText(overrides.apiKey)
107
+ || cleanText(env.GRIX_API_KEY)
108
+ || cleanText(grix?.api_key)
109
+ || cleanText(grix?.token);
110
+ const accountId = cleanText(overrides.accountId)
111
+ || cleanText(env.GRIX_ACCOUNT_ID)
112
+ || cleanText(extra.account_id)
113
+ || "main";
114
+
115
+ if (!endpoint || !agentId || !apiKey) {
116
+ throw new Error(
117
+ "Missing Grix runtime config. Need endpoint, agent_id, and api_key from Hermes env/config."
118
+ );
119
+ }
120
+
121
+ return {
122
+ hermesHome,
123
+ baseUrl: cleanText(overrides.baseUrl) || cleanText(env.GRIX_WEB_BASE_URL) || DEFAULT_BASE_URL,
124
+ connection: {
125
+ endpoint,
126
+ agentId,
127
+ apiKey,
128
+ accountId,
129
+ client: cleanText(overrides.client) || cleanText(env.GRIX_CLIENT) || cleanText(extra.client) || "grix-hermes",
130
+ clientType: cleanText(overrides.clientType) || cleanText(env.GRIX_CLIENT_TYPE) || cleanText(extra.client_type) || "hermes",
131
+ clientVersion: cleanText(overrides.clientVersion) || cleanText(extra.client_version) || "0.1.0",
132
+ hostType: cleanText(overrides.hostType) || cleanText(extra.host_type) || "hermes",
133
+ hostVersion: cleanText(overrides.hostVersion) || cleanText(extra.host_version) || undefined,
134
+ contractVersion: cleanInt(overrides.contractVersion || extra.contract_version, 1),
135
+ capabilities: cleanList(overrides.capabilities || env.GRIX_CAPABILITIES || extra.capabilities, DEFAULT_CAPABILITIES),
136
+ localActions: cleanList(overrides.localActions || extra.local_actions, DEFAULT_LOCAL_ACTIONS),
137
+ connectTimeoutMs: cleanInt(overrides.connectTimeoutMs || env.GRIX_CONNECT_TIMEOUT_MS || extra.connect_timeout_ms, 10000),
138
+ requestTimeoutMs: cleanInt(overrides.requestTimeoutMs || env.GRIX_REQUEST_TIMEOUT_MS || extra.request_timeout_ms, 20000)
139
+ }
140
+ };
141
+ }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { AibotWsClient } from "./aibot-client.mjs";
4
+ import { runAdmin, runGroup, runQuery, runSend, runUnsend } from "./actions.mjs";
5
+ import { resolveRuntimeConfig } from "./config.mjs";
6
+
7
+ function printHelp() {
8
+ console.log(`grix-hermes ws cli
9
+
10
+ Usage:
11
+ node shared/cli/grix-hermes.mjs send --to <session-or-route> --message "..."
12
+ node shared/cli/grix-hermes.mjs query --action session_search --keyword xxx
13
+ node shared/cli/grix-hermes.mjs group --action create --name dev --member-ids 1001,1002 --member-types 1,2
14
+ node shared/cli/grix-hermes.mjs admin --action create_agent --agent-name my-agent
15
+ node shared/cli/grix-hermes.mjs unsend --message-id 2033371385615093760 --session-id <session>
16
+ `);
17
+ }
18
+
19
+ function toCamelFlag(key) {
20
+ return key.replace(/-([a-z])/g, (_match, ch) => ch.toUpperCase());
21
+ }
22
+
23
+ function parseArgs(argv) {
24
+ const positional = [];
25
+ const flags = {};
26
+ for (let i = 0; i < argv.length; i += 1) {
27
+ const token = argv[i];
28
+ if (!token.startsWith("--")) {
29
+ positional.push(token);
30
+ continue;
31
+ }
32
+ const key = toCamelFlag(token.slice(2));
33
+ const next = argv[i + 1];
34
+ if (!next || next.startsWith("--")) {
35
+ flags[key] = true;
36
+ continue;
37
+ }
38
+ flags[key] = next;
39
+ i += 1;
40
+ }
41
+ return { positional, flags };
42
+ }
43
+
44
+ async function main() {
45
+ const { positional, flags } = parseArgs(process.argv.slice(2));
46
+ const command = positional[0];
47
+ if (!command || command === "help" || command === "--help" || command === "-h" || flags.help) {
48
+ printHelp();
49
+ return;
50
+ }
51
+
52
+ const runtime = resolveRuntimeConfig(flags);
53
+ const options = {
54
+ ...flags,
55
+ accountId: flags.accountId || runtime.connection.accountId
56
+ };
57
+
58
+ const client = new AibotWsClient(runtime.connection);
59
+ await client.connect();
60
+ try {
61
+ let result;
62
+ if (command === "query") {
63
+ result = await runQuery(client, options);
64
+ } else if (command === "send") {
65
+ result = await runSend(client, options);
66
+ } else if (command === "group") {
67
+ result = await runGroup(client, options);
68
+ } else if (command === "admin") {
69
+ result = await runAdmin(client, options);
70
+ } else if (command === "unsend") {
71
+ result = await runUnsend(client, options);
72
+ } else {
73
+ throw new Error(`Unsupported command: ${command}`);
74
+ }
75
+ console.log(JSON.stringify(result, null, 2));
76
+ } finally {
77
+ await client.disconnect();
78
+ }
79
+ }
80
+
81
+ main().catch((error) => {
82
+ console.error(JSON.stringify({
83
+ ok: false,
84
+ error: error instanceof Error ? error.message : String(error)
85
+ }, null, 2));
86
+ process.exit(1);
87
+ });
@@ -0,0 +1,119 @@
1
+ function cleanText(value) {
2
+ return String(value ?? "").trim();
3
+ }
4
+
5
+ function isRouteSessionKey(value) {
6
+ return /^agent:main:grix:/i.test(cleanText(value));
7
+ }
8
+
9
+ function normalizeAibotSessionTarget(raw) {
10
+ return cleanText(raw).replace(/^grix:/i, "").replace(/^session:/i, "").trim();
11
+ }
12
+
13
+ export async function resolveAibotOutboundTarget({ client, accountId, to }) {
14
+ const rawTarget = cleanText(to);
15
+ if (!rawTarget) {
16
+ throw new Error("grix outbound target must be non-empty");
17
+ }
18
+ const normalizedTarget = normalizeAibotSessionTarget(rawTarget);
19
+ if (!normalizedTarget) {
20
+ throw new Error("grix outbound target must contain session_id or route_session_key");
21
+ }
22
+ if (!isRouteSessionKey(rawTarget) && !isRouteSessionKey(normalizedTarget)) {
23
+ if (normalizedTarget.includes(":")) {
24
+ const [sessionId, threadId] = normalizedTarget.split(/:(.+)/, 2);
25
+ return {
26
+ sessionId: cleanText(sessionId),
27
+ threadId: cleanText(threadId) || undefined,
28
+ rawTarget,
29
+ normalizedTarget,
30
+ resolveSource: "direct"
31
+ };
32
+ }
33
+ return {
34
+ sessionId: normalizedTarget,
35
+ threadId: undefined,
36
+ rawTarget,
37
+ normalizedTarget,
38
+ resolveSource: "direct"
39
+ };
40
+ }
41
+ const ack = await client.resolveSessionRoute("grix", accountId, isRouteSessionKey(rawTarget) ? rawTarget : normalizedTarget);
42
+ return {
43
+ sessionId: cleanText(ack.session_id),
44
+ threadId: undefined,
45
+ rawTarget,
46
+ normalizedTarget,
47
+ resolveSource: "sessionRouteMap"
48
+ };
49
+ }
50
+
51
+ export async function resolveAibotDeleteTarget({ client, accountId, sessionId, to, topic, currentChannelId }) {
52
+ const rawTarget = cleanText(sessionId) || cleanText(to) || cleanText(topic) || cleanText(currentChannelId);
53
+ if (!rawTarget) {
54
+ return "";
55
+ }
56
+ const resolved = await resolveAibotOutboundTarget({
57
+ client,
58
+ accountId,
59
+ to: rawTarget
60
+ });
61
+ return resolved.sessionId;
62
+ }
63
+
64
+ function normalizeMessageId(value) {
65
+ const normalized = cleanText(value);
66
+ return /^\d+$/.test(normalized) ? normalized : "";
67
+ }
68
+
69
+ export async function resolveSilentUnsendPlan(params) {
70
+ const targetMessageId = normalizeMessageId(params.messageId);
71
+ if (!targetMessageId) {
72
+ throw new Error("Grix unsend requires numeric messageId.");
73
+ }
74
+ const targetSessionId = await resolveAibotDeleteTarget({
75
+ client: params.client,
76
+ accountId: params.accountId,
77
+ sessionId: params.targetSessionId,
78
+ to: params.targetTo,
79
+ topic: params.targetTopic,
80
+ currentChannelId: params.currentChannelId
81
+ });
82
+ if (!targetSessionId) {
83
+ throw new Error("Grix unsend requires sessionId or to, or must run inside an active Grix conversation.");
84
+ }
85
+ const targetDelete = {
86
+ sessionId: targetSessionId,
87
+ messageId: targetMessageId
88
+ };
89
+ const currentMessageId = normalizeMessageId(params.currentMessageId);
90
+ if (!currentMessageId) {
91
+ return { targetDelete };
92
+ }
93
+ if (currentMessageId === targetMessageId) {
94
+ return {
95
+ targetDelete,
96
+ completionMessageId: currentMessageId
97
+ };
98
+ }
99
+ const currentChannelId = cleanText(params.currentChannelId);
100
+ if (!currentChannelId) {
101
+ return { targetDelete };
102
+ }
103
+ const currentSessionId = await resolveAibotDeleteTarget({
104
+ client: params.client,
105
+ accountId: params.accountId,
106
+ currentChannelId
107
+ });
108
+ if (!currentSessionId) {
109
+ throw new Error("Grix unsend could not resolve the current command message session.");
110
+ }
111
+ return {
112
+ targetDelete,
113
+ commandDelete: {
114
+ sessionId: currentSessionId,
115
+ messageId: currentMessageId
116
+ },
117
+ completionMessageId: currentMessageId
118
+ };
119
+ }
@@ -0,0 +1,27 @@
1
+ # Grix Card Links
2
+
3
+ 当你已经拿到准确的 `session_id` 时:
4
+
5
+ - 会话卡片:
6
+
7
+ ```md
8
+ [进入测试群](grix://card/conversation?session_id=<SESSION_ID>&session_type=group&title=<URI_ENCODED_TITLE>)
9
+ ```
10
+
11
+ - Agent 资料卡:
12
+
13
+ ```md
14
+ [查看 Agent 资料](grix://card/user_profile?user_id=<AGENT_ID>&peer_type=2&nickname=<URI_ENCODED_AGENT_NAME>&avatar_url=<URI_ENCODED_AVATAR_URL>)
15
+ ```
16
+
17
+ - 安装状态卡:
18
+
19
+ ```md
20
+ [安装进行中](grix://card/egg_install_status?install_id=<INSTALL_ID>&status=running&step=<STEP>&summary=<URI_ENCODED_SUMMARY>)
21
+ ```
22
+
23
+ 注意:
24
+
25
+ 1. 这类链接要单行
26
+ 2. 要单独作为一条消息发送
27
+ 3. 在 Hermes 里应优先用 `send_message` 发送,不要依赖普通 Grix 回复
@@ -0,0 +1,30 @@
1
+ # Hermes Grix Runtime
2
+
3
+ 本项目不改 Hermes 内核,只复用 Hermes 已经配置好的 Grix 运行参数。
4
+
5
+ 共享 CLI 的参数来源顺序:
6
+
7
+ 1. 命令行显式参数
8
+ 2. 进程环境变量
9
+ 3. `HERMES_HOME/.env`
10
+ 4. `HERMES_HOME/config.yaml`
11
+
12
+ 至少需要这 3 个值:
13
+
14
+ - `GRIX_ENDPOINT`
15
+ - `GRIX_AGENT_ID`
16
+ - `GRIX_API_KEY`
17
+
18
+ 默认 `HERMES_HOME`:
19
+
20
+ ```text
21
+ ~/.hermes
22
+ ```
23
+
24
+ 如果你在多 profile 或自定义目录下运行,先设置:
25
+
26
+ ```bash
27
+ export HERMES_HOME=/path/to/hermes-home
28
+ ```
29
+
30
+ 共享 CLI 会自动复用同一套 Grix 凭证发起短连接请求,不要求额外手动登录。