@lumi-ai-lab/harness-data 0.0.21 → 0.0.23

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
 
@@ -32,12 +32,20 @@ Update an existing runtime interactively:
32
32
  npx @lumi-ai-lab/harness-data update
33
33
  ```
34
34
 
35
+ Reconfigure CAS credentials after an account/password change or after `.qdm-auth` was deleted:
36
+
37
+ ```bash
38
+ npx @lumi-ai-lab/harness-data auth --dir /path/to/runtime
39
+ ```
40
+
41
+ The command recreates `.qdm-auth/cas`, stores the new encrypted CAS credentials, refreshes the CMR, Indicators, and SQL tokens, and validates all three tokens.
42
+
35
43
  Diagnose a runtime:
36
44
 
37
45
  ```bash
38
46
  npx @lumi-ai-lab/harness-data doctor
39
47
  ```
40
48
 
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.
49
+ 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
50
 
43
51
  `--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.21",
3
+ "version": "0.0.23",
4
4
  "description": "Installer and updater for Harness Data",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2,8 +2,9 @@ import { installCommand } from "./commands/install.js";
2
2
  import { updateCommand } from "./commands/update.js";
3
3
  import { doctorCommand } from "./commands/doctor.js";
4
4
  import { versionCommand } from "./commands/version.js";
5
+ import { authCommand } from "./commands/auth.js";
5
6
 
6
- const commands = new Set(["install", "update", "doctor", "version"]);
7
+ const commands = new Set(["install", "update", "auth", "doctor", "version"]);
7
8
 
8
9
  function parse(argv) {
9
10
  const args = argv.slice(2);
@@ -31,17 +32,19 @@ export async function main(argv) {
31
32
  const { command, options, unknown } = parse(argv);
32
33
  if (command === "install") return installCommand(options);
33
34
  if (command === "update") return updateCommand(options);
35
+ if (command === "auth") return authCommand(options);
34
36
  if (command === "doctor") return doctorCommand(options);
35
37
  if (command === "version") return versionCommand(options);
36
- console.log(`Usage: harness-data <install|update|doctor|version> [options]
38
+ console.log(`Usage: harness-data <install|update|auth|doctor|version> [options]
37
39
 
38
40
  Commands:
39
41
  install Install a Harness Data runtime in the current directory
40
42
  update Interactively check and apply runtime, CLI, and wikis updates
43
+ auth Configure CAS credentials and refresh access tokens
41
44
  doctor Diagnose workspace CLI, config, auth, index, and Agent hooks
42
45
  version Print installer, repository, wikis, and manifest versions
43
46
 
44
- Install options:
47
+ Install and auth options:
45
48
  --dir PATH Runtime directory (default: current directory)
46
49
  --agent NAME claude, codex, pi, openclaw, hermes, both, or all
47
50
  --github-token TOKEN GitHub token for private Release assets
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { findWorkspaceDir } from "../lib/paths.js";
4
+ import { packageVersion } from "../lib/package.js";
5
+ import { binaryName } from "../lib/platform.js";
6
+ import { blank, header, ok, step } from "../lib/log.js";
7
+ import { configureTokens, writeCasCredentials } from "./install.js";
8
+
9
+ const requiredBinaries = ["cas-cli", "qdm-cmr-cli", "qdm-indicators-cli", "qdm-sql-cli"];
10
+
11
+ function validateAuthRuntime(runtimeDir) {
12
+ if (!fs.existsSync(runtimeDir)) throw new Error(`runtime directory does not exist: ${runtimeDir}`);
13
+ for (const name of requiredBinaries) {
14
+ const file = path.join(runtimeDir, "bin", binaryName(name));
15
+ if (!fs.existsSync(file)) throw new Error(`runtime CLI is missing: ${file}`);
16
+ }
17
+ }
18
+
19
+ export async function authCommand(options = {}) {
20
+ const runtimeDir = findWorkspaceDir(options.dir);
21
+ validateAuthRuntime(runtimeDir);
22
+ header("Harness Data 认证配置", packageVersion(), [`运行目录:${runtimeDir}`]);
23
+
24
+ step(1, 2, "配置 CAS 凭证");
25
+ const casDir = await writeCasCredentials(runtimeDir, options);
26
+ blank();
27
+
28
+ step(2, 2, "刷新并校验访问 Token");
29
+ await configureTokens(runtimeDir, casDir);
30
+ blank();
31
+
32
+ ok("CAS 认证配置完成");
33
+ return { runtimeDir, casDir };
34
+ }
@@ -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)) {
@@ -213,29 +213,37 @@ function casConfigDir(runtimeDir) {
213
213
  return path.join(runtimeDir, ".qdm-auth", "cas");
214
214
  }
215
215
 
216
- async function writeCasCredentials(runtimeDir, options = {}) {
216
+ export async function writeCasCredentials(runtimeDir, options = {}) {
217
217
  const username = options.casUsername || await ask("CAS 用户名:", options);
218
218
  const password = options.casPassword || await askSecret("CAS 密码:", options);
219
219
  if (!username || !password) throw new Error("CAS username and password are required");
220
220
  const dir = casConfigDir(runtimeDir);
221
221
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
222
222
  const env = { QDM_CAS_CONFIG_DIR: dir };
223
- await run(path.join(runtimeDir, "bin", binaryName("cas-cli")), ["config", "set-credentials", "--username", username, "--password", password], { cwd: runtimeDir, env });
223
+ await run(path.join(runtimeDir, "bin", binaryName("cas-cli")), ["config", "set-credentials", "--username", username, "--password", password], {
224
+ cwd: runtimeDir,
225
+ env,
226
+ sensitiveArgs: [5]
227
+ });
224
228
  ok("CAS 凭证已加密保存到 .qdm-auth/cas/credentials.enc");
225
229
  return dir;
226
230
  }
227
231
 
228
- async function configureTokens(runtimeDir, casDir) {
232
+ export async function configureTokens(runtimeDir, casDir) {
229
233
  const env = { QDM_CAS_CONFIG_DIR: casDir };
230
234
  const bin = (name) => path.join(runtimeDir, "bin", binaryName(name));
231
235
  const cmrToken = (await run(bin("cas-cli"), ["token", "--app", "cmr"], { cwd: runtimeDir, env })).stdout.trim();
232
- await run(bin("qdm-cmr-cli"), ["config", "set-token", cmrToken], { cwd: runtimeDir, env });
236
+ await run(bin("qdm-cmr-cli"), ["config", "set-token", cmrToken], { cwd: runtimeDir, env, sensitiveArgs: [2] });
233
237
  ok("CMR Token 已配置");
234
238
  const indicatorsToken = (await run(bin("cas-cli"), ["token", "--app", "indicators"], { cwd: runtimeDir, env })).stdout.trim();
235
- await run(bin("qdm-indicators-cli"), ["config", "set-token", indicatorsToken], { cwd: runtimeDir, env });
239
+ await run(bin("qdm-indicators-cli"), ["config", "set-token", indicatorsToken], { cwd: runtimeDir, env, sensitiveArgs: [2] });
236
240
  ok("Indicators Token 已配置");
241
+ const sqlToken = (await run(bin("cas-cli"), ["token", "--app", "rtp"], { cwd: runtimeDir, env })).stdout.trim();
242
+ await run(bin("qdm-sql-cli"), ["config", "set-token", sqlToken], { cwd: runtimeDir, env, sensitiveArgs: [2] });
243
+ ok("SQL Token 已配置");
237
244
  await run(bin("qdm-cmr-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
238
245
  await run(bin("qdm-indicators-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
246
+ await run(bin("qdm-sql-cli"), ["config", "check-token"], { cwd: runtimeDir, env });
239
247
  }
240
248
 
241
249
  export async function buildAndCheck(runtimeDir, options = {}) {
@@ -264,11 +272,12 @@ export function printDoctorSummary(doctor, options = {}) {
264
272
  ok("wikis/reports");
265
273
  ok("wikis/dims");
266
274
  ok("wikis/rules");
267
- ok("4 个 CLI");
275
+ ok("5 个 CLI");
268
276
  ok("本地配置");
269
277
  ok("CAS 凭证");
270
278
  ok("CMR Token");
271
279
  ok("Indicators Token");
280
+ ok("SQL Token");
272
281
  if (!warnings.some((check) => check.name.startsWith("Agent hook"))) ok("Agent Hook");
273
282
  for (const check of warnings) warn(`${check.name}${check.detail ? ` (${check.detail})` : ""}`);
274
283
  return;
@@ -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() {
package/src/lib/config.js CHANGED
@@ -20,6 +20,8 @@ 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"];
23
25
 
24
26
  export function hasAnyAgentHook(workspace) {
25
27
  return concreteAgentNames.some((name) => fs.existsSync(path.join(workspace, `.${name}`)));
@@ -35,8 +37,8 @@ export function writeLocalConfig(workspace, options = {}) {
35
37
  }
36
38
  const bin = (name) => path.join(workspace, "bin", binaryName(name)).replaceAll("\\", "/");
37
39
  const casConfigDir = path.join(workspace, ".qdm-auth", "cas").replaceAll("\\", "/");
38
- 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`);
39
- 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`);
40
42
  }
41
43
 
42
44
  export function validateCasConfigDir(dir) {
package/src/lib/exec.js CHANGED
@@ -20,7 +20,9 @@ export function run(command, args = [], options = {}) {
20
20
  return;
21
21
  }
22
22
  const detail = stderr.trim() || stdout.trim();
23
- reject(new Error(`${command} ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`));
23
+ const sensitiveArgs = new Set(options.sensitiveArgs || []);
24
+ const displayArgs = args.map((arg, index) => sensitiveArgs.has(index) ? "******" : arg);
25
+ reject(new Error(`${command} ${displayArgs.join(" ")} failed${detail ? `: ${detail}` : ""}`));
24
26
  });
25
27
  });
26
28
  }