@dhf-claude/grix 0.1.8 → 0.1.10
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/.claude-plugin/plugin.json +1 -1
- package/README.md +133 -86
- package/cli/prod-runner.js +163 -0
- package/cli/prod-runner.test.js +195 -0
- package/cli/runtime-targets.js +66 -0
- package/dist/daemon.js +2059 -905
- package/dist/index.js +1364 -2033
- package/hooks/hooks.json +34 -1
- package/package.json +8 -6
- package/scripts/dev-start.js +14 -0
- package/scripts/elicitation-hook.js +34 -27
- package/scripts/lifecycle-hook.js +3 -0
- package/scripts/notification-hook.js +0 -8
- package/scripts/prod-start.js +7 -0
- package/scripts/user-prompt-submit-hook.js +2 -1
- package/skills/grix/SKILL.md +121 -0
- package/skills/access/SKILL.md +0 -129
- package/skills/status/SKILL.md +0 -11
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
buildRuntimeArgs,
|
|
6
|
+
createManagedCommandEnv,
|
|
7
|
+
resolveRuntimeTarget,
|
|
8
|
+
} from "./runtime-targets.js";
|
|
9
|
+
import { runProdSwitch, stopRuntimeTarget } from "./prod-runner.js";
|
|
10
|
+
|
|
11
|
+
test("createManagedCommandEnv removes connection overrides", () => {
|
|
12
|
+
const env = createManagedCommandEnv({
|
|
13
|
+
KEEP_ME: "1",
|
|
14
|
+
GRIX_CLAUDE_WS_URL: "ws://stale",
|
|
15
|
+
GRIX_CLAUDE_ENDPOINT: "ws://stale-endpoint",
|
|
16
|
+
GRIX_CLAUDE_AGENT_ID: "stale-agent",
|
|
17
|
+
GRIX_CLAUDE_API_KEY: "stale-key",
|
|
18
|
+
GRIX_CLAUDE_OUTBOUND_TEXT_CHUNK_LIMIT: "4096",
|
|
19
|
+
GRIX_CLAUDE_TEXT_CHUNK_LIMIT: "4096",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
assert.equal(env.KEEP_ME, "1");
|
|
23
|
+
assert.equal("GRIX_CLAUDE_WS_URL" in env, false);
|
|
24
|
+
assert.equal("GRIX_CLAUDE_ENDPOINT" in env, false);
|
|
25
|
+
assert.equal("GRIX_CLAUDE_AGENT_ID" in env, false);
|
|
26
|
+
assert.equal("GRIX_CLAUDE_API_KEY" in env, false);
|
|
27
|
+
assert.equal("GRIX_CLAUDE_OUTBOUND_TEXT_CHUNK_LIMIT" in env, false);
|
|
28
|
+
assert.equal("GRIX_CLAUDE_TEXT_CHUNK_LIMIT" in env, false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("resolveRuntimeTarget builds the expected prod args", () => {
|
|
32
|
+
const target = resolveRuntimeTarget("prod", {
|
|
33
|
+
homeDir: "/tmp/grix-home",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
assert.deepEqual(buildRuntimeArgs(target), [
|
|
37
|
+
"--data-dir",
|
|
38
|
+
path.join("/tmp/grix-home", ".claude", "grix-claude-daemon"),
|
|
39
|
+
"--ws-url",
|
|
40
|
+
"wss://clawpool.dhf.pub/v1/agent-api/ws?agent_id=2035513096339984384",
|
|
41
|
+
"--agent-id",
|
|
42
|
+
"2035513096339984384",
|
|
43
|
+
"--api-key",
|
|
44
|
+
"ak_2035513096339984384_wyIkUkt9FyEZFJGHyvWHPu7ZOXkpj0KM",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("stopRuntimeTarget stops installed service before killing a remaining daemon pid", async () => {
|
|
49
|
+
const calls = [];
|
|
50
|
+
const target = {
|
|
51
|
+
name: "dev",
|
|
52
|
+
dataDir: "/tmp/grix-dev",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const result = await stopRuntimeTarget(target, {
|
|
56
|
+
serviceManager: {
|
|
57
|
+
async status({ dataDir }) {
|
|
58
|
+
calls.push(["status", dataDir]);
|
|
59
|
+
return { installed: true };
|
|
60
|
+
},
|
|
61
|
+
async stop({ dataDir }) {
|
|
62
|
+
calls.push(["stop", dataDir]);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
async inspectDaemonProcessStateImpl({ dataDir }) {
|
|
66
|
+
calls.push(["inspect", dataDir]);
|
|
67
|
+
return { running: true, pid: 4321 };
|
|
68
|
+
},
|
|
69
|
+
async terminateProcessTreeImpl(pid, { platform }) {
|
|
70
|
+
calls.push(["terminate", pid, platform]);
|
|
71
|
+
},
|
|
72
|
+
async waitForProcessExitImpl(pid, { timeoutMs }) {
|
|
73
|
+
calls.push(["wait", pid, timeoutMs]);
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
platform: "darwin",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
assert.deepEqual(calls, [
|
|
80
|
+
["status", "/tmp/grix-dev"],
|
|
81
|
+
["stop", "/tmp/grix-dev"],
|
|
82
|
+
["inspect", "/tmp/grix-dev"],
|
|
83
|
+
["terminate", 4321, "darwin"],
|
|
84
|
+
["wait", 4321, 5000],
|
|
85
|
+
]);
|
|
86
|
+
assert.equal(result.serviceStopped, true);
|
|
87
|
+
assert.equal(result.processTerminated, true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("runProdSwitch stops dev and installs prod service when prod is not installed", async () => {
|
|
91
|
+
const serviceCalls = [];
|
|
92
|
+
const cliCalls = [];
|
|
93
|
+
const prints = [];
|
|
94
|
+
const prodDir = path.join("/tmp/grix-home", ".claude", "grix-claude-daemon");
|
|
95
|
+
const devDir = path.join("/tmp/grix-home", ".claude", "grix-claude-daemon-dev");
|
|
96
|
+
|
|
97
|
+
await runProdSwitch({
|
|
98
|
+
GRIX_CLAUDE_AGENT_ID: "stale-agent",
|
|
99
|
+
KEEP_ME: "1",
|
|
100
|
+
}, {
|
|
101
|
+
homeDir: "/tmp/grix-home",
|
|
102
|
+
print(line) {
|
|
103
|
+
prints.push(String(line));
|
|
104
|
+
},
|
|
105
|
+
serviceManager: {
|
|
106
|
+
async status({ dataDir }) {
|
|
107
|
+
serviceCalls.push(["status", dataDir]);
|
|
108
|
+
return { installed: dataDir === devDir ? false : false };
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
async inspectDaemonProcessStateImpl({ dataDir }) {
|
|
112
|
+
serviceCalls.push(["inspect", dataDir]);
|
|
113
|
+
if (dataDir === devDir) {
|
|
114
|
+
return { running: true, pid: 2222 };
|
|
115
|
+
}
|
|
116
|
+
return { running: false, pid: 0 };
|
|
117
|
+
},
|
|
118
|
+
async terminateProcessTreeImpl(pid) {
|
|
119
|
+
serviceCalls.push(["terminate", pid]);
|
|
120
|
+
},
|
|
121
|
+
async waitForProcessExitImpl(pid) {
|
|
122
|
+
serviceCalls.push(["wait", pid]);
|
|
123
|
+
return true;
|
|
124
|
+
},
|
|
125
|
+
async runCliImpl(argv, env, deps) {
|
|
126
|
+
cliCalls.push({ argv, env, deps });
|
|
127
|
+
return 0;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
assert.deepEqual(serviceCalls, [
|
|
132
|
+
["status", devDir],
|
|
133
|
+
["inspect", devDir],
|
|
134
|
+
["terminate", 2222],
|
|
135
|
+
["wait", 2222],
|
|
136
|
+
["status", prodDir],
|
|
137
|
+
["inspect", prodDir],
|
|
138
|
+
["status", prodDir],
|
|
139
|
+
]);
|
|
140
|
+
assert.equal(cliCalls.length, 1);
|
|
141
|
+
assert.equal(cliCalls[0].argv[0], "install");
|
|
142
|
+
assert.equal(cliCalls[0].argv.includes(prodDir), true);
|
|
143
|
+
assert.equal("GRIX_CLAUDE_AGENT_ID" in cliCalls[0].env, false);
|
|
144
|
+
assert.equal(cliCalls[0].env.KEEP_ME, "1");
|
|
145
|
+
assert.ok(cliCalls[0].deps.serviceManager);
|
|
146
|
+
assert.match(prints.join("\n"), /dev 已停止/u);
|
|
147
|
+
assert.match(prints.join("\n"), /安装并启动后台服务/u);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("runProdSwitch starts existing prod service after cleaning old prod runtime", async () => {
|
|
151
|
+
const cliCalls = [];
|
|
152
|
+
const prodDir = path.join("/tmp/grix-home-2", ".claude", "grix-claude-daemon");
|
|
153
|
+
const devDir = path.join("/tmp/grix-home-2", ".claude", "grix-claude-daemon-dev");
|
|
154
|
+
const installStateByDir = new Map([
|
|
155
|
+
[devDir, true],
|
|
156
|
+
[prodDir, true],
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
await runProdSwitch({}, {
|
|
160
|
+
homeDir: "/tmp/grix-home-2",
|
|
161
|
+
print() {},
|
|
162
|
+
serviceManager: {
|
|
163
|
+
async status({ dataDir }) {
|
|
164
|
+
return { installed: installStateByDir.get(dataDir) === true };
|
|
165
|
+
},
|
|
166
|
+
async stop() {},
|
|
167
|
+
},
|
|
168
|
+
async inspectDaemonProcessStateImpl({ dataDir }) {
|
|
169
|
+
if (dataDir === prodDir) {
|
|
170
|
+
return { running: true, pid: 3333 };
|
|
171
|
+
}
|
|
172
|
+
return { running: false, pid: 0 };
|
|
173
|
+
},
|
|
174
|
+
async terminateProcessTreeImpl() {},
|
|
175
|
+
async waitForProcessExitImpl() {
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
async runCliImpl(argv) {
|
|
179
|
+
cliCalls.push(argv);
|
|
180
|
+
return 0;
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
assert.deepEqual(cliCalls, [[
|
|
185
|
+
"start",
|
|
186
|
+
"--data-dir",
|
|
187
|
+
prodDir,
|
|
188
|
+
"--ws-url",
|
|
189
|
+
"wss://clawpool.dhf.pub/v1/agent-api/ws?agent_id=2035513096339984384",
|
|
190
|
+
"--agent-id",
|
|
191
|
+
"2035513096339984384",
|
|
192
|
+
"--api-key",
|
|
193
|
+
"ak_2035513096339984384_wyIkUkt9FyEZFJGHyvWHPu7ZOXkpj0KM",
|
|
194
|
+
]]);
|
|
195
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const MANAGED_ENV_KEYS = Object.freeze([
|
|
5
|
+
"GRIX_CLAUDE_WS_URL",
|
|
6
|
+
"GRIX_CLAUDE_ENDPOINT",
|
|
7
|
+
"GRIX_CLAUDE_AGENT_ID",
|
|
8
|
+
"GRIX_CLAUDE_API_KEY",
|
|
9
|
+
"GRIX_CLAUDE_OUTBOUND_TEXT_CHUNK_LIMIT",
|
|
10
|
+
"GRIX_CLAUDE_TEXT_CHUNK_LIMIT",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const RUNTIME_PROFILES = Object.freeze({
|
|
14
|
+
dev: Object.freeze({
|
|
15
|
+
dataDirName: "grix-claude-daemon-dev",
|
|
16
|
+
wsUrl: "ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=2035251418226495488",
|
|
17
|
+
agentId: "2035251418226495488",
|
|
18
|
+
apiKey: "ak_2035251418226495488__Y-1s6Sy1dBsEJhuv_GLTI-n4PitFlYg",
|
|
19
|
+
}),
|
|
20
|
+
prod: Object.freeze({
|
|
21
|
+
dataDirName: "grix-claude-daemon",
|
|
22
|
+
wsUrl: "wss://clawpool.dhf.pub/v1/agent-api/ws?agent_id=2035513096339984384",
|
|
23
|
+
agentId: "2035513096339984384",
|
|
24
|
+
apiKey: "ak_2035513096339984384_wyIkUkt9FyEZFJGHyvWHPu7ZOXkpj0KM",
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function normalizeRuntimeName(name) {
|
|
29
|
+
return String(name ?? "").trim().toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createManagedCommandEnv(env = process.env) {
|
|
33
|
+
const nextEnv = { ...env };
|
|
34
|
+
for (const key of MANAGED_ENV_KEYS) {
|
|
35
|
+
delete nextEnv[key];
|
|
36
|
+
}
|
|
37
|
+
return nextEnv;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveRuntimeTarget(name, { homeDir = os.homedir() } = {}) {
|
|
41
|
+
const normalizedName = normalizeRuntimeName(name);
|
|
42
|
+
const profile = RUNTIME_PROFILES[normalizedName];
|
|
43
|
+
if (!profile) {
|
|
44
|
+
throw new Error(`unknown runtime target: ${name}`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
name: normalizedName,
|
|
48
|
+
dataDir: path.join(homeDir, ".claude", profile.dataDirName),
|
|
49
|
+
wsUrl: profile.wsUrl,
|
|
50
|
+
agentId: profile.agentId,
|
|
51
|
+
apiKey: profile.apiKey,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function buildRuntimeArgs(target) {
|
|
56
|
+
return [
|
|
57
|
+
"--data-dir",
|
|
58
|
+
target.dataDir,
|
|
59
|
+
"--ws-url",
|
|
60
|
+
target.wsUrl,
|
|
61
|
+
"--agent-id",
|
|
62
|
+
target.agentId,
|
|
63
|
+
"--api-key",
|
|
64
|
+
target.apiKey,
|
|
65
|
+
];
|
|
66
|
+
}
|