@lumi-ai-lab/harness-data 0.0.20 → 0.0.22

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 CHANGED
@@ -24,7 +24,7 @@ or:
24
24
  GITHUB_TOKEN=... npx @lumi-ai-lab/harness-data install
25
25
  ```
26
26
 
27
- Without a GitHub token, the installer interactively asks for local absolute paths to `cas-cli`, `qdm-indicators-cli`, `qdm-cmr-cli`, and `harness-data-wikis`. CAS username and password are always collected interactively.
27
+ Without a GitHub token, the installer interactively asks for local absolute paths to `cas-cli`, `qdm-indicators-cli`, `qdm-cmr-cli`, `qdm-sql-cli`, and `harness-data-wikis`. CAS username and password are always collected interactively.
28
28
 
29
29
  Update an existing runtime interactively:
30
30
 
@@ -38,6 +38,6 @@ Diagnose a runtime:
38
38
  npx @lumi-ai-lab/harness-data doctor
39
39
  ```
40
40
 
41
- The runtime is assembled from the `harness-data` runtime bundle, platform-specific CLI Release assets, `harness-data-wikis`, generated local config, CAS credentials, and selected Agent symlinks.
41
+ The runtime is assembled from the `harness-data` runtime bundle, platform-specific CLI Release assets, `harness-data-wikis`, generated local config, CAS credentials, and selected Agent symlinks. SQL CLI tokens are fetched through `cas-cli token --app rtp`.
42
42
 
43
43
  `--agent` supports `claude`, `codex`, `pi`, `openclaw`, `hermes`, `both`, and `all`; the default is `all`. `both` installs Claude + Codex, while `all` installs Claude + Codex + Pi + OpenClaw + Hermes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi-ai-lab/harness-data",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Installer and updater for Harness Data",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { run } from "../lib/exec.js";
4
4
  import { findWorkspaceDir } from "../lib/paths.js";
5
5
  import { binaryName } from "../lib/platform.js";
6
- import { concreteAgentNames } from "../lib/config.js";
6
+ import { concreteAgentNames, qdmCliBinaries } from "../lib/config.js";
7
7
 
8
8
  function existsExecutable(file) {
9
9
  try {
@@ -36,8 +36,9 @@ function configPathsValid(workspace) {
36
36
  const file = path.join(workspace, "config", "qdm-cli-paths.env");
37
37
  if (!fs.existsSync(file)) return false;
38
38
  const content = fs.readFileSync(file, "utf8");
39
- const matches = [...content.matchAll(/="([^"]+)"/g)].map((match) => match[1]);
40
- return matches.length >= 3 && matches.every((entry) => fs.existsSync(entry));
39
+ const required = ["QDM_CMR_CLI", "QDM_INDICATORS_CLI", "QDM_SQL_CLI", "QDM_CAS_CLI"];
40
+ const values = new Map([...content.matchAll(/^export\s+([A-Z0-9_]+)="([^"]+)"/gm)].map((match) => [match[1], match[2]]));
41
+ return required.every((name) => values.has(name) && fs.existsSync(values.get(name)));
41
42
  }
42
43
 
43
44
  function casCredentialsValid(dir) {
@@ -66,7 +67,7 @@ export async function collectDoctor(workspace, options = {}) {
66
67
  add("wikis/reports", fs.existsSync(path.join(workspace, "wikis", "reports")));
67
68
  add("wikis/dims", fs.existsSync(path.join(workspace, "wikis", "dims")));
68
69
  add("wikis/rules", fs.existsSync(path.join(workspace, "wikis", "rules")));
69
- for (const binary of ["data-harness-cli", "qdm-cmr-cli", "qdm-indicators-cli", "cas-cli"]) {
70
+ for (const binary of qdmCliBinaries) {
70
71
  add(`bin/${binary}`, existsExecutable(path.join(workspace, "bin", binaryName(binary))));
71
72
  }
72
73
  add("config/harness-config.yaml", fs.existsSync(path.join(workspace, "config", "harness-config.yaml")));
@@ -75,6 +76,7 @@ export async function collectDoctor(workspace, options = {}) {
75
76
  add("CAS credentials file", casCredentialsValid(casConfigDir), casConfigDir);
76
77
  add("CMR token", await tokenCheck(workspace, "qdm-cmr-cli", env));
77
78
  add("Indicators token", await tokenCheck(workspace, "qdm-indicators-cli", env));
79
+ add("SQL token", await tokenCheck(workspace, "qdm-sql-cli", env));
78
80
  add("Agent hook", concreteAgentNames.some((name) => agentOk(workspace, name)));
79
81
  for (const name of ["openclaw", "hermes"]) {
80
82
  if (fs.existsSync(path.join(workspace, `.${name}`))) {
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { commandExists, run } from "../lib/exec.js";
4
- import { writeLocalConfig, linkAgents } from "../lib/config.js";
4
+ import { localPathToolNames, writeLocalConfig, linkAgents } from "../lib/config.js";
5
5
  import { ask, askSecret, chooseAgent } from "../lib/prompt.js";
6
6
  import { readUserState, resolveWorkspaceDir, writeState } from "../lib/paths.js";
7
7
  import { installToolsFromManifest, manifestDigest, readManifest } from "../lib/manifest.js";
@@ -156,7 +156,7 @@ async function installLocalTools(runtimeDir, options = {}) {
156
156
  const binDir = path.join(runtimeDir, "bin");
157
157
  fs.mkdirSync(binDir, { recursive: true });
158
158
  const installed = {};
159
- for (const name of ["cas-cli", "qdm-indicators-cli", "qdm-cmr-cli"]) {
159
+ for (const name of localPathToolNames) {
160
160
  const source = await promptExecutable(runtimeDir, name, options);
161
161
  const target = path.join(binDir, binaryName(name));
162
162
  if (path.resolve(source) !== path.resolve(target)) {
@@ -225,7 +225,7 @@ async function writeCasCredentials(runtimeDir, options = {}) {
225
225
  return dir;
226
226
  }
227
227
 
228
- async function configureTokens(runtimeDir, casDir) {
228
+ export async function configureTokens(runtimeDir, casDir) {
229
229
  const env = { QDM_CAS_CONFIG_DIR: casDir };
230
230
  const bin = (name) => path.join(runtimeDir, "bin", binaryName(name));
231
231
  const cmrToken = (await run(bin("cas-cli"), ["token", "--app", "cmr"], { cwd: runtimeDir, env })).stdout.trim();
@@ -234,8 +234,12 @@ async function configureTokens(runtimeDir, casDir) {
234
234
  const indicatorsToken = (await run(bin("cas-cli"), ["token", "--app", "indicators"], { cwd: runtimeDir, env })).stdout.trim();
235
235
  await run(bin("qdm-indicators-cli"), ["config", "set-token", indicatorsToken], { cwd: runtimeDir, env });
236
236
  ok("Indicators Token 已配置");
237
+ const sqlToken = (await run(bin("cas-cli"), ["token", "--app", "rtp"], { cwd: runtimeDir, env })).stdout.trim();
238
+ await run(bin("qdm-sql-cli"), ["config", "set-token", sqlToken], { cwd: runtimeDir, env });
239
+ ok("SQL Token 已配置");
237
240
  await run(bin("qdm-cmr-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
238
241
  await run(bin("qdm-indicators-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
242
+ await run(bin("qdm-sql-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
239
243
  }
240
244
 
241
245
  export async function buildAndCheck(runtimeDir, options = {}) {
@@ -264,11 +268,12 @@ export function printDoctorSummary(doctor, options = {}) {
264
268
  ok("wikis/reports");
265
269
  ok("wikis/dims");
266
270
  ok("wikis/rules");
267
- ok("4 个 CLI");
271
+ ok("5 个 CLI");
268
272
  ok("本地配置");
269
273
  ok("CAS 凭证");
270
274
  ok("CMR Token");
271
275
  ok("Indicators Token");
276
+ ok("SQL Token");
272
277
  if (!warnings.some((check) => check.name.startsWith("Agent hook"))) ok("Agent Hook");
273
278
  for (const check of warnings) warn(`${check.name}${check.detail ? ` (${check.detail})` : ""}`);
274
279
  return;
@@ -344,7 +349,8 @@ export async function installCommand(options = {}) {
344
349
  blank();
345
350
 
346
351
  step(8, 8, "配置 Agent Hook");
347
- const linkedAgents = linkAgents(runtimeDir, await chooseAgent(options));
352
+ const selectedAgent = await chooseAgent(options);
353
+ const linkedAgents = linkAgents(runtimeDir, selectedAgent);
348
354
  for (const [source, target] of linkedAgents) {
349
355
  if (fs.existsSync(path.join(runtimeDir, target))) ok(`${target} -> ${source}`);
350
356
  }
@@ -363,6 +369,7 @@ export async function installCommand(options = {}) {
363
369
  tools: manifest.installedTools || {},
364
370
  manifestSha256: manifestDigest(manifest),
365
371
  packageVersion: packageVersion(),
372
+ agent: selectedAgent,
366
373
  lastCheckAt: new Date().toISOString()
367
374
  });
368
375
  console.log(`安装完成:${runtimeDir}`);
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { confirm } from "../lib/prompt.js";
3
+ import { chooseAgent, confirm } from "../lib/prompt.js";
4
4
  import { findWorkspaceDir, readUserState, writeState } from "../lib/paths.js";
5
5
  import { run } from "../lib/exec.js";
6
6
  import { installToolsFromManifest, manifestDigest, readManifest } from "../lib/manifest.js";
@@ -11,7 +11,7 @@ import { resolveLatestTool } from "../lib/tool-release.js";
11
11
  import { protocolFromUrl, runGitWithProtocol } from "../lib/git-auth.js";
12
12
  import { buildAndCheck, installRuntimeBundle, printDoctorSummary } from "./install.js";
13
13
  import { collectDoctor } from "./doctor.js";
14
- import { writeLocalConfig } from "../lib/config.js";
14
+ import { hasAnyAgentHook, linkAgents, writeLocalConfig } from "../lib/config.js";
15
15
  import { action, blank, header, ok, shortSha, skip, step, warn } from "../lib/log.js";
16
16
 
17
17
  export function isNonBlockingUpdateDoctorCheck(check) {
@@ -19,7 +19,8 @@ export function isNonBlockingUpdateDoctorCheck(check) {
19
19
  check.name.startsWith("Agent hook .") ||
20
20
  check.name === "CAS credentials file" ||
21
21
  check.name === "CMR token" ||
22
- check.name === "Indicators token";
22
+ check.name === "Indicators token" ||
23
+ check.name === "SQL token";
23
24
  }
24
25
 
25
26
  async function npmLatest() {
@@ -52,7 +53,7 @@ async function maybeUpdateTool(runtimeDir, manifest, tool, options, state) {
52
53
  return null;
53
54
  }
54
55
  action(`发现更新:${tool.name} ${current.version || "unknown"} -> ${tag}`);
55
- if (!(await confirm(`是否更新 ${tool.name}?`, { defaultNo: true }))) {
56
+ if (!(await confirm(`是否更新 ${tool.name}?`))) {
56
57
  skip(tool.name);
57
58
  options.skippedUpdates?.push(`${tool.name} ${tag}`);
58
59
  return null;
@@ -92,7 +93,7 @@ export async function updateWikis(runtimeDir, options, state) {
92
93
  return null;
93
94
  }
94
95
  action(`发现更新:harness-data-wikis ${shortSha(local)} -> ${shortSha(remote)}`);
95
- if (!(await confirm("是否更新 harness-data-wikis?", { defaultNo: true }))) {
96
+ if (!(await confirm("是否更新 harness-data-wikis?"))) {
96
97
  skip("harness-data-wikis");
97
98
  options.skippedUpdates?.push(`harness-data-wikis ${shortSha(remote)}`);
98
99
  return null;
@@ -103,6 +104,20 @@ export async function updateWikis(runtimeDir, options, state) {
103
104
  return { commit };
104
105
  }
105
106
 
107
+ export async function restoreAgentHooksIfMissing(runtimeDir, options = {}) {
108
+ if (hasAnyAgentHook(runtimeDir)) {
109
+ ok("Agent Hook 已配置");
110
+ return null;
111
+ }
112
+ action("未检测到 Agent Hook,重新配置");
113
+ const agent = await chooseAgent(options);
114
+ const linkedAgents = linkAgents(runtimeDir, agent);
115
+ for (const [source, target] of linkedAgents) {
116
+ if (fs.existsSync(path.join(runtimeDir, target))) ok(`${target} -> ${source}`);
117
+ }
118
+ return { agent, linkedAgents };
119
+ }
120
+
106
121
  export async function checkUpdates(workspace, options = {}) {
107
122
  const state = readUserState();
108
123
  const latestInstaller = await npmLatest();
@@ -131,7 +146,7 @@ export async function updateCommand(options = {}) {
131
146
  const skipped = [];
132
147
  const trackingOptions = { ...options, skippedUpdates: skipped };
133
148
 
134
- step(1, 6, "检查 installer");
149
+ step(1, 7, "检查 installer");
135
150
  const latestInstaller = await npmLatest();
136
151
  if (latestInstaller && latestInstaller !== packageVersion()) {
137
152
  warn(`installer 有新版本 ${packageVersion()} -> ${latestInstaller}`);
@@ -141,11 +156,11 @@ export async function updateCommand(options = {}) {
141
156
  }
142
157
  blank();
143
158
 
144
- step(2, 6, "检查 runtime bundle");
159
+ step(2, 7, "检查 runtime bundle");
145
160
  const runtimeRelease = await latestRelease("lumi-ai-lab/harness-data", options);
146
161
  if (state.runtimeTag && state.runtimeTag !== runtimeRelease.tag_name) {
147
162
  action(`发现更新:runtime bundle ${state.runtimeTag} -> ${runtimeRelease.tag_name}`);
148
- if (await confirm("是否更新 runtime bundle?", { defaultNo: true })) {
163
+ if (await confirm("是否更新 runtime bundle?")) {
149
164
  const bundle = await installRuntimeBundle(runtimeDir, { ...trackingOptions, force: true });
150
165
  runtimeTag = bundle.tag || runtimeRelease.tag_name;
151
166
  changed = true;
@@ -159,7 +174,7 @@ export async function updateCommand(options = {}) {
159
174
  }
160
175
  blank();
161
176
 
162
- step(3, 6, "检查 CLI 工具");
177
+ step(3, 7, "检查 CLI 工具");
163
178
  for (const tool of manifest.tools || []) {
164
179
  if (state.installMode === "local-path" && tool.name !== "data-harness-cli") {
165
180
  skip(`${tool.name} 为本地路径模式,请手动检查`);
@@ -174,7 +189,7 @@ export async function updateCommand(options = {}) {
174
189
  }
175
190
  blank();
176
191
 
177
- step(4, 6, "检查 Wikis 知识库");
192
+ step(4, 7, "检查 Wikis 知识库");
178
193
  const wikis = await updateWikis(runtimeDir, trackingOptions, state);
179
194
  if (wikis) {
180
195
  changed = true;
@@ -182,7 +197,11 @@ export async function updateCommand(options = {}) {
182
197
  }
183
198
  blank();
184
199
 
185
- step(5, 6, "构建 Wikis 索引");
200
+ step(5, 7, "检查 Agent Hook");
201
+ const restoredAgent = await restoreAgentHooksIfMissing(runtimeDir, trackingOptions);
202
+ blank();
203
+
204
+ step(6, 7, "构建 Wikis 索引");
186
205
  if (changed) {
187
206
  await buildAndCheck(runtimeDir, trackingOptions);
188
207
  } else {
@@ -190,7 +209,7 @@ export async function updateCommand(options = {}) {
190
209
  }
191
210
  blank();
192
211
 
193
- step(6, 6, "安装校验");
212
+ step(7, 7, "安装校验");
194
213
  if (changed) {
195
214
  writeLocalConfig(runtimeDir, { overwrite: true });
196
215
  ok("本地配置已刷新");
@@ -202,6 +221,7 @@ export async function updateCommand(options = {}) {
202
221
  runtimeTag,
203
222
  tools: nextTools,
204
223
  manifestSha256: manifestDigest(manifest),
224
+ ...(restoredAgent ? { agent: restoredAgent.agent } : {}),
205
225
  lastCheckAt: new Date().toISOString()
206
226
  });
207
227
  blank();
@@ -216,6 +236,24 @@ export async function updateCommand(options = {}) {
216
236
  console.log("已跳过:");
217
237
  for (const item of skipped) console.log(`- ${item}`);
218
238
  }
239
+ if (restoredAgent) {
240
+ console.log("");
241
+ console.log("已恢复:");
242
+ console.log(`- Agent Hook ${restoredAgent.agent}`);
243
+ }
244
+ } else if (restoredAgent) {
245
+ ok("Agent Hook 已恢复");
246
+ writeState(runtimeDir, { ...state, agent: restoredAgent.agent, lastCheckAt: new Date().toISOString() });
247
+ blank();
248
+ console.log(`配置已恢复:${runtimeDir}`);
249
+ console.log("");
250
+ console.log("已恢复:");
251
+ console.log(`- Agent Hook ${restoredAgent.agent}`);
252
+ if (skipped.length) {
253
+ console.log("");
254
+ console.log("已跳过:");
255
+ for (const item of skipped) console.log(`- ${item}`);
256
+ }
219
257
  } else {
220
258
  skip("没有组件更新");
221
259
  writeState(runtimeDir, { ...state, lastCheckAt: new Date().toISOString() });
package/src/lib/config.js CHANGED
@@ -20,6 +20,12 @@ export const agentLinks = {
20
20
  ],
21
21
  };
22
22
  export const agentChoiceText = agentChoices.join(", ");
23
+ export const qdmCliBinaries = ["data-harness-cli", "qdm-cmr-cli", "qdm-indicators-cli", "qdm-sql-cli", "cas-cli"];
24
+ export const localPathToolNames = ["cas-cli", "qdm-indicators-cli", "qdm-cmr-cli", "qdm-sql-cli"];
25
+
26
+ export function hasAnyAgentHook(workspace) {
27
+ return concreteAgentNames.some((name) => fs.existsSync(path.join(workspace, `.${name}`)));
28
+ }
23
29
 
24
30
  export function writeLocalConfig(workspace, options = {}) {
25
31
  const configDir = path.join(workspace, "config");
@@ -31,8 +37,8 @@ export function writeLocalConfig(workspace, options = {}) {
31
37
  }
32
38
  const bin = (name) => path.join(workspace, "bin", binaryName(name)).replaceAll("\\", "/");
33
39
  const casConfigDir = path.join(workspace, ".qdm-auth", "cas").replaceAll("\\", "/");
34
- fs.writeFileSync(harness, `paths:\n knowledge: wikis\n\ncli:\n qdm_cmr_cli: ${bin("qdm-cmr-cli")}\n qdm_indicators_cli: ${bin("qdm-indicators-cli")}\n qdm_cas_cli: ${bin("cas-cli")}\n`);
35
- fs.writeFileSync(env, `export QDM_CMR_CLI="${bin("qdm-cmr-cli")}"\nexport QDM_INDICATORS_CLI="${bin("qdm-indicators-cli")}"\nexport QDM_CAS_CLI="${bin("cas-cli")}"\nexport QDM_CAS_CONFIG_DIR="${casConfigDir}"\n`);
40
+ fs.writeFileSync(harness, `paths:\n knowledge: wikis\n\ncli:\n qdm_cmr_cli: ${bin("qdm-cmr-cli")}\n qdm_indicators_cli: ${bin("qdm-indicators-cli")}\n qdm_sql_cli: ${bin("qdm-sql-cli")}\n qdm_cas_cli: ${bin("cas-cli")}\n`);
41
+ fs.writeFileSync(env, `export QDM_CMR_CLI="${bin("qdm-cmr-cli")}"\nexport QDM_INDICATORS_CLI="${bin("qdm-indicators-cli")}"\nexport QDM_SQL_CLI="${bin("qdm-sql-cli")}"\nexport QDM_CAS_CLI="${bin("cas-cli")}"\nexport QDM_CAS_CONFIG_DIR="${casConfigDir}"\n`);
36
42
  }
37
43
 
38
44
  export function validateCasConfigDir(dir) {