@lobehub/cli 0.0.64 → 0.0.65

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.
Files changed (3) hide show
  1. package/dist/index.js +531 -39
  2. package/man/man1/lh.1 +1 -1
  3. package/package.json +28 -27
package/dist/index.js CHANGED
@@ -292909,18 +292909,35 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
292909
292909
 
292910
292910
  //#endregion
292911
292911
  //#region src/auth/apiKey.ts
292912
+ var ApiKeyValidationError = class extends Error {
292913
+ constructor(message, retryable) {
292914
+ super(message);
292915
+ this.retryable = retryable;
292916
+ this.name = "ApiKeyValidationError";
292917
+ }
292918
+ };
292912
292919
  async function getUserIdFromApiKey(apiKey, serverUrl) {
292913
292920
  const normalizedServerUrl = normalizeUrl(serverUrl) || resolveServerUrl();
292914
- const response = await fetch(`${normalizedServerUrl}/api/v1/users/me?includeCount=0`, { headers: { Authorization: `Bearer ${apiKey}` } });
292921
+ let response;
292922
+ try {
292923
+ response = await fetch(`${normalizedServerUrl}/api/v1/users/me?includeCount=0`, { headers: { Authorization: `Bearer ${apiKey}` } });
292924
+ } catch (error) {
292925
+ throw new ApiKeyValidationError(error instanceof Error ? error.message : String(error), true);
292926
+ }
292927
+ const retryableStatus = response.status >= 500 || [
292928
+ 408,
292929
+ 425,
292930
+ 429
292931
+ ].includes(response.status);
292915
292932
  let body;
292916
292933
  try {
292917
292934
  body = await response.json();
292918
292935
  } catch {
292919
- throw new Error(`Failed to parse response from ${normalizedServerUrl}/api/v1/users/me?includeCount=0.`);
292936
+ throw new ApiKeyValidationError(`Failed to parse response from ${normalizedServerUrl}/api/v1/users/me?includeCount=0.`, response.ok || retryableStatus);
292920
292937
  }
292921
- if (!response.ok || body?.success === false) throw new Error(body?.error || body?.message || `Request failed with status ${response.status}.`);
292938
+ if (!response.ok || body?.success === false) throw new ApiKeyValidationError(body?.error || body?.message || `Request failed with status ${response.status}.`, retryableStatus);
292922
292939
  const userId = body?.data?.id || body?.data?.userId;
292923
- if (!userId) throw new Error("Current user response did not include a user id.");
292940
+ if (!userId) throw new ApiKeyValidationError("Current user response did not include a user id.", true);
292924
292941
  return userId;
292925
292942
  }
292926
292943
 
@@ -292998,7 +293015,7 @@ async function resolveToken(options) {
292998
293015
  } catch (error) {
292999
293016
  const message = error instanceof Error ? error.message : String(error);
293000
293017
  log$13.error(`Failed to validate ${source.origin}: ${message}`);
293001
- process.exit(1);
293018
+ process.exit(error instanceof ApiKeyValidationError && error.retryable ? options.unavailableExitCode ?? 1 : 1);
293002
293019
  }
293003
293020
  }
293004
293021
  const result = await getValidToken();
@@ -293020,7 +293037,7 @@ async function resolveToken(options) {
293020
293037
  }
293021
293038
  const report = describeTokenLookup(result);
293022
293039
  log$13.error(report ? `${report.detail} ${report.fix}` : `No authentication found. Run '${"lh"} login' first, or set ${CLI_API_KEY_ENV}, or provide --token.`);
293023
- process.exit(1);
293040
+ process.exit(result.status === "unavailable" ? options.unavailableExitCode ?? 1 : 1);
293024
293041
  }
293025
293042
 
293026
293043
  //#endregion
@@ -296378,6 +296395,23 @@ var TerminalSessionManager = class {
296378
296395
  const SERVICE_NAME = CLI_CONNECT_SERVICE_NAME;
296379
296396
  const ENV_NAME_PATTERN = /^[A-Z_]\w*$/i;
296380
296397
  const SERVICE_ENV_FILE_NAME = "connect-service.env";
296398
+ /**
296399
+ * Exit code for workspace failures worth retrying (EX_TEMPFAIL). It sits outside the
296400
+ * unit's `SuccessExitStatus`, so `Restart=on-failure` restarts the service child.
296401
+ */
296402
+ const CONNECT_RETRYABLE_EXIT_CODE = 75;
296403
+ const PERMANENT_TRPC_ERROR_CODES = new Set([
296404
+ "BAD_REQUEST",
296405
+ "CONFLICT",
296406
+ "FORBIDDEN",
296407
+ "NOT_FOUND",
296408
+ "UNAUTHORIZED"
296409
+ ]);
296410
+ /** Exit code for a failed workspace operation (token mint, refresh, device registration): 1 for refusals, 75 otherwise. */
296411
+ function getWorkspaceFailureExitCode(error) {
296412
+ const code = error?.data?.code;
296413
+ return code && PERMANENT_TRPC_ERROR_CODES.has(code) ? 1 : 75;
296414
+ }
296381
296415
  function systemctl(args, stdio = "pipe") {
296382
296416
  const output = execFileSync("systemctl", ["--user", ...args], {
296383
296417
  encoding: "utf8",
@@ -296403,7 +296437,13 @@ function getExecErrorMessage(error) {
296403
296437
  if (stderr) return stderr;
296404
296438
  return error instanceof Error ? error.message : String(error);
296405
296439
  }
296406
- function renderUnit() {
296440
+ function buildServiceConnectArgs(options) {
296441
+ const args = ["connect", "--service-child"];
296442
+ if (options.workspace) args.push("--workspace", options.workspace);
296443
+ if (options.public) args.push("--public");
296444
+ return args;
296445
+ }
296446
+ function renderUnit(options) {
296407
296447
  const quoteSystemdValue = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
296408
296448
  const escapeSystemdEnvValue = (value) => value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
296409
296449
  const homeDir = os.homedir();
@@ -296414,16 +296454,14 @@ function renderUnit() {
296414
296454
  const execStart = [
296415
296455
  process.execPath,
296416
296456
  scriptPath,
296417
- "connect",
296418
- "--service-child"
296457
+ ...buildServiceConnectArgs(options)
296419
296458
  ].map(quoteSystemdValue).join(" ");
296420
296459
  return [
296421
296460
  "[Unit]",
296422
296461
  `Description=${CLI_DISPLAY_NAME} connect service`,
296423
296462
  "After=network-online.target",
296424
296463
  "Wants=network-online.target",
296425
- "StartLimitIntervalSec=60",
296426
- "StartLimitBurst=10",
296464
+ "StartLimitIntervalSec=0",
296427
296465
  "",
296428
296466
  "[Service]",
296429
296467
  "Type=simple",
@@ -296436,7 +296474,7 @@ function renderUnit() {
296436
296474
  `StandardError=append:${logPath}`,
296437
296475
  "Restart=on-failure",
296438
296476
  "SuccessExitStatus=0 1 2",
296439
- "RestartSec=1",
296477
+ "RestartSec=5",
296440
296478
  "TimeoutStopSec=20",
296441
296479
  "",
296442
296480
  "[Install]",
@@ -296493,7 +296531,8 @@ function assertConnectServiceAuthAvailable() {
296493
296531
  if (process.env.LOBEHUB_JWT || readCliApiKeyEnv() || loadCredentials()) return;
296494
296532
  throw new Error(`No authentication found. Run '${"lh"} login' first, or set ${CLI_API_KEY_ENV} before starting the connect service.`);
296495
296533
  }
296496
- function installConnectService() {
296534
+ function installConnectService(options = {}) {
296535
+ if (options.public && !options.workspace) throw new Error("--public requires --workspace.");
296497
296536
  requireLinuxSystemd();
296498
296537
  assertNoConnectDaemonRunning();
296499
296538
  assertConnectServiceAuthAvailable();
@@ -296502,13 +296541,10 @@ function installConnectService() {
296502
296541
  mode: 448,
296503
296542
  recursive: true
296504
296543
  });
296505
- fs.writeFileSync(path.join(getUserServiceDir(), SERVICE_NAME), renderUnit(), { mode: 420 });
296544
+ fs.writeFileSync(path.join(getUserServiceDir(), SERVICE_NAME), renderUnit(options), { mode: 420 });
296506
296545
  systemctl(["daemon-reload"], "inherit");
296507
- systemctl([
296508
- "enable",
296509
- "--now",
296510
- SERVICE_NAME
296511
- ], "inherit");
296546
+ systemctl(["enable", SERVICE_NAME], "inherit");
296547
+ systemctl(["restart", SERVICE_NAME], "inherit");
296512
296548
  }
296513
296549
  function uninstallConnectService() {
296514
296550
  const unitPath = path.join(getUserServiceDir(), SERVICE_NAME);
@@ -508017,7 +508053,7 @@ const sweepLocalTraces = async () => {
508017
508053
  const SHUTDOWN_METRICS_FLUSH_MS = 3e3;
508018
508054
  const CONNECT_SERVICE_NAME = CLI_CONNECT_SERVICE_NAME;
508019
508055
  function registerConnectCommand(program) {
508020
- const connectCmd = program.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Device gateway URL").option("--device-id <id>", "Device ID (auto-generated if not provided)").option("--workspace <id>", "Enroll as a device of this workspace (admin only)").option("--public", "With --workspace: enroll into the shared pool visible to every member (default: private, visible only to you)").option("-v, --verbose", "Enable verbose logging").option("-d, --daemon", "Run as a background daemon process").option("--daemon-child", "Internal: runs as the daemon child process").option("--service-child", "Internal: runs as the system service child process").action(async (options) => {
508056
+ const connectCmd = program.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Device gateway URL").option("--device-id <id>", "Device ID (auto-generated if not provided)").option("--workspace <id>", "Enroll as a device of this workspace (workspace members)").option("--public", "With --workspace: enroll into the shared pool visible to every member (default: private, visible only to you)").option("-v, --verbose", "Enable verbose logging").option("-d, --daemon", "Run as a background daemon process").option("--daemon-child", "Internal: runs as the daemon child process").option("--service-child", "Internal: runs as the system service child process").action(async (options) => {
508021
508057
  if (options.verbose) setVerbose(true);
508022
508058
  if (options.daemon) return handleDaemonStart(options);
508023
508059
  const isServiceChild = options.serviceChild || process.env.LOBEHUB_CONNECT_SERVICE === "1";
@@ -508063,9 +508099,12 @@ function registerConnectCommand(program) {
508063
508099
  });
508064
508100
  });
508065
508101
  const serviceCmd = connectCmd.command("service").description("Manage the Linux user systemd connect service");
508066
- serviceCmd.command("install").description("Install and start the Linux user systemd connect service").action(() => {
508067
- installConnectService();
508068
- log$13.info(`Installed and started ${CONNECT_SERVICE_NAME}.`);
508102
+ serviceCmd.command("install").description("Install and start the Linux user systemd connect service").option("--workspace <id>", "Enroll as a device of this workspace (workspace members)").option("--public", "With --workspace: enroll into the shared pool visible to every member (default: private, visible only to you)").action((options) => {
508103
+ installConnectService({
508104
+ public: options.public,
508105
+ workspace: options.workspace
508106
+ });
508107
+ log$13.info(options.workspace ? `Installed and started ${CONNECT_SERVICE_NAME} for workspace ${options.workspace}.` : `Installed and started ${CONNECT_SERVICE_NAME}.`);
508069
508108
  log$13.info("Run 'lh connect service status' to inspect the service.");
508070
508109
  });
508071
508110
  serviceCmd.command("uninstall").description("Remove the Linux user systemd connect service").action(() => {
@@ -508136,7 +508175,11 @@ function buildDaemonArgs(options) {
508136
508175
  return args;
508137
508176
  }
508138
508177
  async function runConnect(options, isDaemonChild) {
508139
- let auth = await resolveToken(options);
508178
+ const unavailableExitCode = options.workspace ? 75 : void 0;
508179
+ let auth = await resolveToken({
508180
+ ...options,
508181
+ unavailableExitCode
508182
+ });
508140
508183
  const settings = loadSettings();
508141
508184
  const gatewayUrl = normalizeUrl(options.gateway) || settings?.gatewayUrl;
508142
508185
  if (!gatewayUrl && settings?.serverUrl) {
@@ -508154,11 +508197,17 @@ async function runConnect(options, isDaemonChild) {
508154
508197
  let connectToken = auth.token;
508155
508198
  let connectTokenType = auth.tokenType;
508156
508199
  if (workspaceId) {
508157
- connectToken = (await mintWorkspaceConnectToken(auth, workspaceId)).token;
508200
+ try {
508201
+ connectToken = (await mintWorkspaceConnectToken(auth, workspaceId)).token;
508202
+ } catch (err) {
508203
+ log$13.error(`Workspace connect token mint failed: ${err.message}`);
508204
+ process.exit(getWorkspaceFailureExitCode(err));
508205
+ throw new Error("process.exit", { cause: err });
508206
+ }
508158
508207
  connectTokenType = "jwt";
508159
508208
  }
508160
508209
  const refreshConnectToken = async () => {
508161
- const refreshed = await resolveToken({});
508210
+ const refreshed = await resolveToken({ unavailableExitCode });
508162
508211
  if (!refreshed) return void 0;
508163
508212
  auth = refreshed;
508164
508213
  if (workspaceId) {
@@ -508397,6 +508446,7 @@ async function runConnect(options, isDaemonChild) {
508397
508446
  let cancelRefreshTimer = startProactiveRefresh();
508398
508447
  let authFailedRefreshAttempted = false;
508399
508448
  client.on("auth_failed", async (reason) => {
508449
+ let exitCode = 1;
508400
508450
  if (connectTokenType === "jwt" && !authFailedRefreshAttempted) {
508401
508451
  authFailedRefreshAttempted = true;
508402
508452
  info(`Authentication failed (${reason}). Attempting token refresh...`);
@@ -508411,16 +508461,20 @@ async function runConnect(options, isDaemonChild) {
508411
508461
  await client.reconnect();
508412
508462
  return;
508413
508463
  }
508414
- } catch {}
508464
+ } catch (err) {
508465
+ error(`Token refresh failed: ${err.message}`);
508466
+ if (workspaceId) exitCode = getWorkspaceFailureExitCode(err);
508467
+ }
508415
508468
  }
508416
508469
  error(`Authentication failed: ${reason}`);
508417
508470
  error(`Run '${"lh"} login', or set ${CLI_API_KEY_ENV} and run '${"lh"} login --server <url>' to configure API key authentication.`);
508418
508471
  cleanup();
508419
- process.exit(1);
508472
+ process.exit(exitCode);
508420
508473
  });
508421
508474
  client.on("auth_expired", async () => {
508422
508475
  if (connectTokenType === "apiKey") return;
508423
508476
  info("Authentication expired. Attempting to refresh token...");
508477
+ let exitCode = 1;
508424
508478
  try {
508425
508479
  const newToken = await refreshConnectToken();
508426
508480
  if (newToken) {
@@ -508430,10 +508484,13 @@ async function runConnect(options, isDaemonChild) {
508430
508484
  await client.reconnect();
508431
508485
  return;
508432
508486
  }
508433
- } catch {}
508487
+ } catch (err) {
508488
+ error(`Token refresh failed: ${err.message}`);
508489
+ if (workspaceId) exitCode = getWorkspaceFailureExitCode(err);
508490
+ }
508434
508491
  error("Could not refresh token. Run 'lh login' to re-authenticate.");
508435
508492
  cleanup();
508436
- process.exit(1);
508493
+ process.exit(exitCode);
508437
508494
  });
508438
508495
  client.on("replaced", () => {
508439
508496
  error(`Another '${"lh"} connect' on this machine took over this device's gateway connection. Only one can be connected at a time; stopping this one.`);
@@ -508468,7 +508525,7 @@ async function runConnect(options, isDaemonChild) {
508468
508525
  if (workspaceId) {
508469
508526
  error(`Workspace device registration failed: ${err.message}`);
508470
508527
  cleanup();
508471
- process.exit(1);
508528
+ process.exit(getWorkspaceFailureExitCode(err));
508472
508529
  }
508473
508530
  error(`Device registration failed (non-fatal): ${err.message}`);
508474
508531
  }
@@ -522856,7 +522913,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
522856
522913
  } };
522857
522914
 
522858
522915
  //#endregion
522859
- //#region node_modules/.pnpm/@hono+node-server@2.1.4_hono@4.13.13/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
522916
+ //#region node_modules/.pnpm/@hono+node-server@2.1.5_hono@4.13.13/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
522860
522917
  const X_ALREADY_SENT = "x-hono-already-sent";
522861
522918
 
522862
522919
  //#endregion
@@ -522885,7 +522942,7 @@ const defineWebSocketHelper = (handler) => {
522885
522942
  };
522886
522943
 
522887
522944
  //#endregion
522888
- //#region node_modules/.pnpm/@hono+node-server@2.1.4_hono@4.13.13/node_modules/@hono/node-server/dist/index.mjs
522945
+ //#region node_modules/.pnpm/@hono+node-server@2.1.5_hono@4.13.13/node_modules/@hono/node-server/dist/index.mjs
522889
522946
  var RequestError = class extends Error {
522890
522947
  constructor(message, options) {
522891
522948
  super(message, options);
@@ -523707,7 +523764,7 @@ const makeCloseHandler = (req, incoming, outgoing, needsBodyCleanup) => () => {
523707
523764
  if (incoming.errored) {
523708
523765
  recordBodyBufferedBeforeDisconnect(incoming);
523709
523766
  req[abortRequest](incoming.errored.toString());
523710
- } else if (!outgoing.writableFinished) {
523767
+ } else if (!outgoing.writableFinished || incoming instanceof Http2ServerRequest && incoming.aborted) {
523711
523768
  recordBodyBufferedBeforeDisconnect(incoming);
523712
523769
  req[abortRequest]("Client connection prematurely closed.");
523713
523770
  }
@@ -535153,6 +535210,345 @@ async function uploadReportEvidence(client, params) {
535153
535210
  };
535154
535211
  }
535155
535212
 
535213
+ //#endregion
535214
+ //#region src/commands/acceptanceLanguage.ts
535215
+ const HAN = {
535216
+ languageName: "Chinese",
535217
+ pattern: /\p{Script=Han}/u
535218
+ };
535219
+ const CYRILLIC = {
535220
+ languageName: "Cyrillic script",
535221
+ pattern: /\p{Script=Cyrillic}/u
535222
+ };
535223
+ const ARABIC = {
535224
+ languageName: "Arabic script",
535225
+ pattern: /\p{Script=Arabic}/u
535226
+ };
535227
+ /** Keyed by the primary language subtag of `responseLanguage` (`zh-CN` → `zh`). */
535228
+ const SCRIPT_RULES = {
535229
+ ar: {
535230
+ ...ARABIC,
535231
+ languageName: "Arabic"
535232
+ },
535233
+ be: CYRILLIC,
535234
+ bg: {
535235
+ ...CYRILLIC,
535236
+ languageName: "Bulgarian"
535237
+ },
535238
+ el: {
535239
+ languageName: "Greek",
535240
+ pattern: /\p{Script=Greek}/u
535241
+ },
535242
+ fa: {
535243
+ ...ARABIC,
535244
+ languageName: "Persian"
535245
+ },
535246
+ he: {
535247
+ languageName: "Hebrew",
535248
+ pattern: /\p{Script=Hebrew}/u
535249
+ },
535250
+ hi: {
535251
+ languageName: "Hindi",
535252
+ pattern: /\p{Script=Devanagari}/u
535253
+ },
535254
+ ja: {
535255
+ languageName: "Japanese",
535256
+ pattern: /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u
535257
+ },
535258
+ kk: CYRILLIC,
535259
+ ko: {
535260
+ languageName: "Korean",
535261
+ pattern: /\p{Script=Hangul}/u
535262
+ },
535263
+ mn: CYRILLIC,
535264
+ ru: {
535265
+ ...CYRILLIC,
535266
+ languageName: "Russian"
535267
+ },
535268
+ sr: CYRILLIC,
535269
+ th: {
535270
+ languageName: "Thai",
535271
+ pattern: /\p{Script=Thai}/u
535272
+ },
535273
+ uk: {
535274
+ ...CYRILLIC,
535275
+ languageName: "Ukrainian"
535276
+ },
535277
+ zh: HAN
535278
+ };
535279
+ const responseLanguageSettingsUrl = () => {
535280
+ const url = new URL("/settings/appearance", resolveServerUrl());
535281
+ url.username = "";
535282
+ url.password = "";
535283
+ return url.toString();
535284
+ };
535285
+ function reportLanguageFor(responseLanguage) {
535286
+ const locale = responseLanguage?.trim();
535287
+ if (!locale) return { status: "unset" };
535288
+ const rule = SCRIPT_RULES[locale.split(/[-_]/)[0].toLowerCase()];
535289
+ return rule ? {
535290
+ locale,
535291
+ rule,
535292
+ status: "enforced"
535293
+ } : {
535294
+ locale,
535295
+ status: "unchecked"
535296
+ };
535297
+ }
535298
+ async function resolveReportLanguage(client) {
535299
+ const value = (await client.user.getUserState.query())?.settings?.general?.responseLanguage;
535300
+ return reportLanguageFor(typeof value === "string" ? value : void 0);
535301
+ }
535302
+ /**
535303
+ * Code, links and paths are legitimately English inside any report; strip them
535304
+ * so "修复了 `useFoo` 的问题" passes and a bare `npm test` field is judged on
535305
+ * nothing rather than on the identifier.
535306
+ */
535307
+ const stripNonProse = (text) => text.replaceAll(/(`{3,}|~{3,})[\s\S]*?\1/g, " ").replaceAll(/`[^`\n]*`/g, " ").replaceAll(/!?\[[^\]]*\]\([^)]*\)/g, (match) => match.replace(/\([^)]*\)$/, "")).replaceAll(/https?:\/\/[^\s<>"'`,。!?、;:()【】「」《》]+/g, " ");
535308
+ /**
535309
+ * A field fails when it contains letters but none in the target script — the
535310
+ * whole field is in another language. Mixed text with identifiers passes.
535311
+ */
535312
+ function findLanguageViolations(fields, rule) {
535313
+ return fields.flatMap((field) => {
535314
+ if (typeof field.text !== "string") return [];
535315
+ const prose = stripNonProse(field.text);
535316
+ if (!/\p{L}/u.test(prose) || rule.pattern.test(prose)) return [];
535317
+ return [{
535318
+ path: field.path,
535319
+ text: field.text
535320
+ }];
535321
+ });
535322
+ }
535323
+ /** Prose in a check definition: its outcome, preconditions and step wording. */
535324
+ function checkDefinitionFields(at, definition) {
535325
+ const value = definition ?? {};
535326
+ const preconditions = Array.isArray(value.preconditions) ? value.preconditions : [];
535327
+ const steps = Array.isArray(value.steps) ? value.steps : [];
535328
+ return [
535329
+ {
535330
+ path: `${at}.expected`,
535331
+ text: value.expected
535332
+ },
535333
+ ...preconditions.map((text, index) => ({
535334
+ path: `${at}.preconditions[${index}]`,
535335
+ text
535336
+ })),
535337
+ ...steps.flatMap((step) => [{
535338
+ path: `${at}.steps[${String(step?.id)}].instruction`,
535339
+ text: step?.instruction
535340
+ }, {
535341
+ path: `${at}.steps[${String(step?.id)}].expected`,
535342
+ text: step?.expected
535343
+ }])
535344
+ ];
535345
+ }
535346
+ /**
535347
+ * Video step/claim captions and the before/after caption band. Shared by
535348
+ * `ingest` and `evidence upload --metadata`, which persist the same shape.
535349
+ */
535350
+ function evidenceCaptionFields(at, metadata) {
535351
+ const value = metadata ?? {};
535352
+ return [...(Array.isArray(value.chapters) ? value.chapters : []).flatMap((chapter, i) => [{
535353
+ path: `${at}.chapters[${i}].label`,
535354
+ text: chapter?.label
535355
+ }, {
535356
+ path: `${at}.chapters[${i}].note`,
535357
+ text: chapter?.note
535358
+ }]), {
535359
+ path: `${at}.comparison.label`,
535360
+ text: value.comparison?.label
535361
+ }];
535362
+ }
535363
+ /** Every `ingest` field that renders as prose on the acceptance page. */
535364
+ function ingestLanguageFields(input) {
535365
+ const fields = [
535366
+ {
535367
+ path: "title",
535368
+ text: input.title
535369
+ },
535370
+ {
535371
+ path: "focus",
535372
+ text: input.goal
535373
+ },
535374
+ {
535375
+ path: "requirement",
535376
+ text: input.requirement
535377
+ },
535378
+ {
535379
+ path: "summary.conclusion",
535380
+ text: input.conclusion
535381
+ },
535382
+ {
535383
+ path: "report.md",
535384
+ text: input.report
535385
+ },
535386
+ {
535387
+ path: "proposal.md",
535388
+ text: input.proposal
535389
+ }
535390
+ ];
535391
+ for (const item of input.plan ?? []) {
535392
+ const config = item.verifierConfig ?? {};
535393
+ const evidenceSpecs = Array.isArray(config.requiredEvidence) ? config.requiredEvidence : [];
535394
+ fields.push({
535395
+ path: `plan[${item.id}].title`,
535396
+ text: item.title
535397
+ }, {
535398
+ path: `plan[${item.id}].category`,
535399
+ text: item.category
535400
+ }, {
535401
+ path: `plan[${item.id}].method`,
535402
+ text: config.method
535403
+ }, {
535404
+ path: `plan[${item.id}].expected`,
535405
+ text: config.expected
535406
+ }, {
535407
+ path: `plan[${item.id}].description`,
535408
+ text: item.description
535409
+ }, ...checkDefinitionFields(`plan[${item.id}].definition`, item.definition), ...evidenceSpecs.map((spec, index) => ({
535410
+ path: `plan[${item.id}].requiredEvidence[${index}].hint`,
535411
+ text: spec?.hint
535412
+ })));
535413
+ }
535414
+ for (const { case: c, checkItemId } of input.cases) {
535415
+ fields.push({
535416
+ path: `cases[${checkItemId}].name`,
535417
+ text: c.name ?? c.case ?? c.title
535418
+ }, {
535419
+ path: `cases[${checkItemId}].observation`,
535420
+ text: c.keyObservation ?? c.observation ?? c.note
535421
+ }, {
535422
+ path: `cases[${checkItemId}].suggestion`,
535423
+ text: c.suggestion
535424
+ });
535425
+ (Array.isArray(c.evidence) ? c.evidence : [c.evidence]).forEach((entry, index) => {
535426
+ if (!entry || typeof entry !== "object") return;
535427
+ const at = `cases[${checkItemId}].evidence[${index}]`;
535428
+ fields.push({
535429
+ path: `${at}.description`,
535430
+ text: entry.description ?? entry.desc
535431
+ }, ...evidenceCaptionFields(at, entry));
535432
+ });
535433
+ fields.push(...visualizationFields(`cases[${checkItemId}]`, c));
535434
+ }
535435
+ return fields;
535436
+ }
535437
+ /**
535438
+ * Visualization prose: titles, context and axis/series labels. Categories and
535439
+ * table cells are data (model names, file names, ids) and legitimately Latin,
535440
+ * so they are not checked — a false rejection has no bypass.
535441
+ */
535442
+ function visualizationFields(at, c) {
535443
+ const datasets = Array.isArray(c.datasets) ? c.datasets : [];
535444
+ const views = Array.isArray(c.visualizations) ? c.visualizations : [];
535445
+ return [...datasets.flatMap((dataset, d) => (Array.isArray(dataset?.fields) ? dataset.fields : []).map((field, f) => ({
535446
+ path: `${at}.datasets[${d}].fields[${f}].label`,
535447
+ text: field?.label
535448
+ }))), ...views.flatMap((view, v) => {
535449
+ const series = Array.isArray(view?.encoding?.series) ? view.encoding.series : [];
535450
+ return [
535451
+ {
535452
+ path: `${at}.visualizations[${v}].title`,
535453
+ text: view?.title
535454
+ },
535455
+ {
535456
+ path: `${at}.visualizations[${v}].context`,
535457
+ text: view?.context
535458
+ },
535459
+ ...[
535460
+ "xLabel",
535461
+ "yLabel",
535462
+ "valueLabel"
535463
+ ].map((key) => ({
535464
+ path: `${at}.visualizations[${v}].encoding.${key}`,
535465
+ text: view?.encoding?.[key]
535466
+ })),
535467
+ ...series.map((entry, i) => ({
535468
+ path: `${at}.visualizations[${v}].encoding.series[${i}].label`,
535469
+ text: entry?.label
535470
+ }))
535471
+ ];
535472
+ })];
535473
+ }
535474
+ const preview = (text) => {
535475
+ const line = text.replaceAll(/\s+/g, " ").trim();
535476
+ return line.length > 80 ? `${line.slice(0, 77)}...` : line;
535477
+ };
535478
+ var ReportLanguageError = class extends Error {
535479
+ constructor(locale, violations, languageName) {
535480
+ super([
535481
+ `Report language check failed: your account's response language (settings.general.responseLanguage) is "${locale}", so every human-readable field must be written in ${languageName}.`,
535482
+ `These ${violations.length} field(s) contain no ${languageName} text:`,
535483
+ ...violations.map((v) => ` - ${v.path}: "${preview(v.text)}"`),
535484
+ `Rewrite them in ${languageName} and retry. If "${locale}" is not the language you read, change Response Language at ${responseLanguageSettingsUrl()} first.`
535485
+ ].join("\n"));
535486
+ this.locale = locale;
535487
+ this.violations = violations;
535488
+ this.name = "ReportLanguageError";
535489
+ }
535490
+ };
535491
+ const unsetLanguageWarning = () => `Report language not checked: your account has no response language set (Response Language is "Follow System" at ${responseLanguageSettingsUrl()}). Set it so acceptance text is checked against the language you read.`;
535492
+ /**
535493
+ * Throws `ReportLanguageError` before any remote write when a field is in the
535494
+ * wrong script. There is intentionally no bypass flag: an agent that hits the
535495
+ * error would just add it.
535496
+ */
535497
+ async function assertReportLanguage(client, fields, language) {
535498
+ const resolved = language ?? await resolveReportLanguage(client);
535499
+ if (resolved.status === "unset") {
535500
+ log$13.warn(unsetLanguageWarning());
535501
+ return resolved;
535502
+ }
535503
+ if (resolved.status !== "enforced") return resolved;
535504
+ const violations = findLanguageViolations(fields, resolved.rule);
535505
+ if (violations.length > 0) throw new ReportLanguageError(resolved.locale, violations, resolved.rule.languageName);
535506
+ return resolved;
535507
+ }
535508
+ /** JSON-safe view of the resolved language for `--json` output. */
535509
+ const languageSummary = (language) => ({
535510
+ enforced: language.status === "enforced",
535511
+ locale: language.status === "unset" ? null : language.locale,
535512
+ ...language.status === "enforced" ? { languageName: language.rule.languageName } : {},
535513
+ status: language.status
535514
+ });
535515
+ /**
535516
+ * Prose in a flow definition: its title, every inline check's wording, and
535517
+ * edge triggers/conditions, which render as graph labels.
535518
+ */
535519
+ function flowLanguageFields(definition) {
535520
+ const flow = definition ?? {};
535521
+ const fields = [{
535522
+ path: "definition.title",
535523
+ text: flow.title
535524
+ }];
535525
+ const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
535526
+ for (const node of nodes) {
535527
+ const check = node?.check;
535528
+ if (!check || typeof check !== "object") continue;
535529
+ const at = `nodes[${String(check.id ?? node.id)}].check`;
535530
+ fields.push({
535531
+ path: `${at}.title`,
535532
+ text: check.title
535533
+ }, {
535534
+ path: `${at}.description`,
535535
+ text: check.description
535536
+ }, ...checkDefinitionFields(`${at}.definition`, check.definition));
535537
+ }
535538
+ const edges = Array.isArray(flow.edges) ? flow.edges : [];
535539
+ for (const edge of edges) {
535540
+ const value = edge ?? {};
535541
+ fields.push({
535542
+ path: `edges[${String(value.id)}].trigger`,
535543
+ text: value.trigger
535544
+ }, {
535545
+ path: `edges[${String(value.id)}].condition`,
535546
+ text: value.condition
535547
+ });
535548
+ }
535549
+ return fields;
535550
+ }
535551
+
535156
535552
  //#endregion
535157
535553
  //#region src/commands/acceptanceRun.ts
535158
535554
  const listMaterializedFiles = (directory) => {
@@ -535237,7 +535633,15 @@ function printWiring(links) {
535237
535633
  }
535238
535634
  }
535239
535635
  async function runCreateAction(options) {
535240
- const created = await (await getTrpcClient()).verify.createRun.mutate({
535636
+ const client = await getTrpcClient();
535637
+ await assertReportLanguage(client, [{
535638
+ path: "--title",
535639
+ text: options.title
535640
+ }, {
535641
+ path: "--goal",
535642
+ text: options.goal
535643
+ }]);
535644
+ const created = await client.verify.createRun.mutate({
535241
535645
  goal: options.goal,
535242
535646
  operationId: options.operation,
535243
535647
  source: options.source,
@@ -535294,7 +535698,22 @@ async function runDeleteAction(runId, options) {
535294
535698
  console.log(`${import_picocolors.default.green("✓")} Deleted run ${import_picocolors.default.bold(result.id)}`);
535295
535699
  }
535296
535700
  async function resultIngestAction(options) {
535297
- const created = await (await getTrpcClient()).verify.ingestResult.mutate({
535701
+ const client = await getTrpcClient();
535702
+ await assertReportLanguage(client, [
535703
+ {
535704
+ path: "--title",
535705
+ text: options.title
535706
+ },
535707
+ {
535708
+ path: "--evidence",
535709
+ text: options.evidence
535710
+ },
535711
+ {
535712
+ path: "--suggestion",
535713
+ text: options.suggestion
535714
+ }
535715
+ ]);
535716
+ const created = await client.verify.ingestResult.mutate({
535298
535717
  checkItemId: options.check,
535299
535718
  checkItemIndex: options.index ? Number.parseInt(options.index, 10) : void 0,
535300
535719
  checkItemTitle: options.title,
@@ -535345,6 +535764,13 @@ async function submitAction(options) {
535345
535764
  process.exit(1);
535346
535765
  }
535347
535766
  const client = await getTrpcClient();
535767
+ await assertReportLanguage(client, [{
535768
+ path: "--title",
535769
+ text: options.title
535770
+ }, {
535771
+ path: "--desc",
535772
+ text: options.desc
535773
+ }]);
535348
535774
  let fileId;
535349
535775
  let inlineContent = options.content;
535350
535776
  if (options.file) {
@@ -535408,6 +535834,10 @@ async function evidenceUploadAction(options) {
535408
535834
  }
535409
535835
  const metadata = options.metadata ? JSON.parse(options.metadata) : void 0;
535410
535836
  const client = await getTrpcClient();
535837
+ await assertReportLanguage(client, [{
535838
+ path: "--desc",
535839
+ text: options.desc
535840
+ }, ...evidenceCaptionFields("--metadata", metadata)]);
535411
535841
  let fileId = options.fileId;
535412
535842
  let inlineContent = options.content;
535413
535843
  if (options.file) {
@@ -535464,7 +535894,15 @@ async function evidenceDeleteAction(evidenceId, options) {
535464
535894
  }
535465
535895
  async function reportUpsertAction(options) {
535466
535896
  const num = (s) => s === void 0 ? void 0 : Number.parseInt(s, 10);
535467
- const created = await (await getTrpcClient()).verify.upsertReport.mutate({
535897
+ const client = await getTrpcClient();
535898
+ await assertReportLanguage(client, [{
535899
+ path: "--summary",
535900
+ text: options.summary
535901
+ }, {
535902
+ path: "--content",
535903
+ text: options.content
535904
+ }]);
535905
+ const created = await client.verify.upsertReport.mutate({
535468
535906
  content: options.content,
535469
535907
  failedChecks: num(options.failed),
535470
535908
  passedChecks: num(options.passed),
@@ -535582,6 +536020,30 @@ async function ingestReportAction(reportDir, options) {
535582
536020
  const requirement = options.requirement ?? subject?.requirement;
535583
536021
  const conclusion = typeof summary.conclusion === "string" ? summary.conclusion : typeof summary.note === "string" ? summary.note : void 0;
535584
536022
  const client = await getTrpcClient();
536023
+ const language = await assertReportLanguage(client, ingestLanguageFields({
536024
+ cases,
536025
+ conclusion,
536026
+ goal,
536027
+ plan,
536028
+ proposal,
536029
+ report: content,
536030
+ requirement,
536031
+ title
536032
+ }));
536033
+ if (options.dryRun) {
536034
+ const checked = language.status === "enforced" ? `report language ${language.locale} ✓` : language.status === "unchecked" ? `report language ${language.locale} (not script-checked)` : "report language not set";
536035
+ if (options.json !== void 0) {
536036
+ outputJson({
536037
+ cases: cases.length,
536038
+ dryRun: true,
536039
+ language: languageSummary(language),
536040
+ planItems: plan?.length ?? 0
536041
+ }, typeof options.json === "string" ? options.json : void 0);
536042
+ return;
536043
+ }
536044
+ console.log(`${import_picocolors.default.green("✓ Dry run passed")} — ${cases.length} case(s), ${plan?.length ?? 0} plan item(s), ${checked}. Nothing was published.`);
536045
+ return;
536046
+ }
535585
536047
  let acceptance;
535586
536048
  let bundle;
535587
536049
  if (requestedAcceptanceId) {
@@ -535846,7 +536308,7 @@ function withReportUpsertOptions(cmd) {
535846
536308
  return cmd.requiredOption("--run <verifyRunId>", "Target session id").option("--verdict <verdict>", "passed|failed|uncertain").option("--summary <text>", "Short summary").option("--content <markdown>", "Full markdown body").option("--total <n>", "Total checks").option("--passed <n>", "Passed checks").option("--failed <n>", "Failed checks").option("--uncertain <n>", "Uncertain checks").option("--json [fields]", "Output JSON");
535847
536309
  }
535848
536310
  function withIngestReportOptions(cmd) {
535849
- return cmd.option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--acceptance <id>", "Append this round to an existing acceptance").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--subject <type:id>", "Attach to a task/topic/document (defaults to the current topic; otherwise standalone)").option("--requirement <text>", "Acceptance requirement recorded when the aggregate is first created").option("--open", "Print the in-app URL to open the report").option("--json [fields]", "Output JSON");
536311
+ return cmd.option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--acceptance <id>", "Append this round to an existing acceptance").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--subject <type:id>", "Attach to a task/topic/document (defaults to the current topic; otherwise standalone)").option("--requirement <text>", "Acceptance requirement recorded when the aggregate is first created").option("--open", "Print the in-app URL to open the report").option("--dry-run", "Validate the report (including its language) without publishing; works on a plan-only result.json").option("--json [fields]", "Output JSON");
535850
536312
  }
535851
536313
  /**
535852
536314
  * The run-scoped acceptance commands. A run is one immutable round of a
@@ -535960,6 +536422,7 @@ function attachAcceptanceFlowCommands(acceptance) {
535960
536422
  flow.command("publish <acceptanceId>").requiredOption("--file <path>", "JSON: definition and optional flowId").action(async (id, options) => {
535961
536423
  const client = await getTrpcClient();
535962
536424
  const input = JSON.parse(await readFile(options.file, "utf8"));
536425
+ await assertReportLanguage(client, flowLanguageFields(input.definition));
535963
536426
  outputJson(await client.acceptance.publishFlow.mutate({
535964
536427
  ...input,
535965
536428
  id
@@ -535994,6 +536457,10 @@ function attachAcceptanceFlowCommands(acceptance) {
535994
536457
  flow.command("record <acceptanceId>").requiredOption("--file <path>", "JSON: verifyRunId, checkItemId, verdict, observation").action(async (id, options) => {
535995
536458
  const client = await getTrpcClient();
535996
536459
  const input = JSON.parse(await readFile(options.file, "utf8"));
536460
+ await assertReportLanguage(client, [{
536461
+ path: "observation",
536462
+ text: input.observation
536463
+ }]);
535997
536464
  outputJson(await client.acceptance.recordFlowStep.mutate({
535998
536465
  ...input,
535999
536466
  id
@@ -536055,6 +536522,23 @@ const userGlyph = (userReview) => {
536055
536522
  function registerAcceptanceCommands(parent, options) {
536056
536523
  const acceptance = parent.command("acceptance").description(options?.deprecated ? "Deprecated alias — use `lh acceptance`" : "Delivery acceptances: the cross-round review loop (checks, feedback, decision)");
536057
536524
  attachAcceptanceFlowCommands(acceptance);
536525
+ acceptance.command("preflight").description("Check that the CLI is authenticated and print the language every acceptance text must be written in. Run it before authoring anything, and pass the language to any subagent you delegate to.").option("--json [fields]", "Output JSON").action(async (options) => {
536526
+ const language = await resolveReportLanguage(await getTrpcClient());
536527
+ const instruction = language.status === "unset" ? "No response language is set on this account, so the language is not checked. Write in the language the user converses in." : `Write every human-readable acceptance field (title, plan, case names and observations, evidence descriptions, conclusion, report.md, proposal.md) in ${language.locale}${language.status === "enforced" ? `, i.e. ${language.rule.languageName}` : ""}. Pass this language explicitly to any subagent you delegate to.`;
536528
+ if (options.json !== void 0) {
536529
+ outputJson({
536530
+ authenticated: true,
536531
+ instruction,
536532
+ language: languageSummary(language),
536533
+ settingsUrl: responseLanguageSettingsUrl()
536534
+ }, typeof options.json === "string" ? options.json : void 0);
536535
+ return;
536536
+ }
536537
+ if (language.status === "unset") log$13.warn(unsetLanguageWarning());
536538
+ console.log(`${import_picocolors.default.green("✓")} Authenticated`);
536539
+ console.log(`${import_picocolors.default.bold("report language")}: ${language.status === "unset" ? import_picocolors.default.yellow("not set") : language.locale}${language.status === "unchecked" ? import_picocolors.default.dim(" (not script-checked)") : ""}`);
536540
+ console.log(instruction);
536541
+ });
536058
536542
  acceptance.command("create").summary("Create or reuse an acceptance without creating a verification round or results").description("Create or reuse an acceptance record. This does not create checks, verification rounds, results, reports, or evidence.\n\nBefore creating, agents should load and read the acceptance skill (.agents/skills/acceptance/SKILL.md) and follow its workflow. If the skill is missing, run `lh acceptance install`, then read it.\n\nUse create for flow-first planning. If a completed local report already exists, use `lh acceptance run ingest <reportDir>` to publish it; a separate create call is unnecessary. An acceptance URL alone does not mean verification is complete: publish results and required evidence, then read back coverage before handing off a completed acceptance.").requiredOption("--requirement <text>", "Durable business goal (preserved when reusing a subject)").option("-t, --title <text>", "Display title for a standalone acceptance").option("--subject <type:id>", "Reuse or create for task/topic/document/standalone; omit for a new standalone acceptance").option("--json [fields]", "Output JSON, optionally select fields (acceptanceId, acceptanceUrl, requirement, status, subject)").action(async (options) => {
536059
536543
  const requirement = options.requirement.trim();
536060
536544
  const title = options.title?.trim();
@@ -536065,7 +536549,15 @@ function registerAcceptanceCommands(parent, options) {
536065
536549
  subjectType: "standalone"
536066
536550
  } : parseSubjectRef(options.subject);
536067
536551
  if (!subject) throw new InvalidArgumentError(`--subject must be one of ${acceptanceSubjectTypes.map((type) => `${type}:<id>`).join(" | ")}`);
536068
- const result = await (await getTrpcClient()).acceptance.ensure.mutate({
536552
+ const client = await getTrpcClient();
536553
+ await assertReportLanguage(client, [{
536554
+ path: "--requirement",
536555
+ text: requirement
536556
+ }, {
536557
+ path: "--title",
536558
+ text: title
536559
+ }]);
536560
+ const result = await client.acceptance.ensure.mutate({
536069
536561
  ...subject,
536070
536562
  requirement,
536071
536563
  title
package/man/man1/lh.1 CHANGED
@@ -1,6 +1,6 @@
1
1
  .\" Code generated by `npm run man:generate`; DO NOT EDIT.
2
2
  .\" Manual command details come from the Commander command tree.
3
- .TH LH 1 "" "@lobehub/cli 0.0.64" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.65" "User Commands"
4
4
  .SH NAME
5
5
  lh \- LobeHub CLI \- manage and connect to LobeHub services
6
6
  .SH SYNOPSIS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/cli",
3
- "version": "0.0.64",
3
+ "version": "0.0.65",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "lh": "dist/index.js",
@@ -16,11 +16,36 @@
16
16
  "dist",
17
17
  "man"
18
18
  ],
19
+ "scripts": {
20
+ "build": "bun scripts/ensureWorkspaceLinks.ts && tsdown && tsdown src/daemon/maintenanceWorker.ts --no-config --no-clean --format esm --target node22",
21
+ "cli:link": "bun link",
22
+ "cli:unlink": "bun unlink",
23
+ "dev": "LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts",
24
+ "man:generate": "bun src/man/generate.ts",
25
+ "prepublishOnly": "npm run build && npm run man:generate",
26
+ "test": "bunx vitest run --config vitest.config.mts --silent='passed-only'",
27
+ "test:coverage": "bunx vitest run --config vitest.config.mts --coverage",
28
+ "test:e2e": "bunx vitest run --config vitest.e2e.config.mts --silent='passed-only'",
29
+ "type-check": "tsc --noEmit"
30
+ },
19
31
  "dependencies": {
20
32
  "@lydell/node-pty": "1.2.0-beta.15",
21
33
  "ws": "^8.21.0"
22
34
  },
23
35
  "devDependencies": {
36
+ "@lobechat/agent-gateway-client": "workspace:*",
37
+ "@lobechat/agent-tracing": "workspace:*",
38
+ "@lobechat/const": "workspace:*",
39
+ "@lobechat/device-control": "workspace:*",
40
+ "@lobechat/device-gateway-client": "workspace:*",
41
+ "@lobechat/device-identity": "workspace:*",
42
+ "@lobechat/eval-rubric": "workspace:*",
43
+ "@lobechat/heterogeneous-agents": "workspace:*",
44
+ "@lobechat/html-artifact": "workspace:*",
45
+ "@lobechat/local-file-shell": "workspace:*",
46
+ "@lobechat/tool-runtime": "workspace:*",
47
+ "@lobechat/types": "workspace:*",
48
+ "@lobechat/utils": "workspace:*",
24
49
  "@trpc/client": "^11.18.0",
25
50
  "@types/node": "^24.13.2",
26
51
  "@types/semver": "^7.7.1",
@@ -39,20 +64,7 @@
39
64
  "tsdown": "^0.21.10",
40
65
  "typescript": "^6.0.3",
41
66
  "url-join": "^5.0.0",
42
- "vitest": "5.0.0",
43
- "@lobechat/const": "1.0.0",
44
- "@lobechat/agent-gateway-client": "1.0.0",
45
- "@lobechat/agent-tracing": "1.0.0",
46
- "@lobechat/device-gateway-client": "1.0.0",
47
- "@lobechat/device-identity": "1.0.0",
48
- "@lobechat/eval-rubric": "1.0.0",
49
- "@lobechat/device-control": "1.0.0",
50
- "@lobechat/heterogeneous-agents": "1.0.0",
51
- "@lobechat/html-artifact": "1.0.0",
52
- "@lobechat/local-file-shell": "1.0.0",
53
- "@lobechat/tool-runtime": "1.0.0",
54
- "@lobechat/utils": "1.0.0",
55
- "@lobechat/types": "1.0.0"
67
+ "vitest": "5.0.0"
56
68
  },
57
69
  "engines": {
58
70
  "node": ">=22.15"
@@ -60,16 +72,5 @@
60
72
  "publishConfig": {
61
73
  "access": "public",
62
74
  "registry": "https://registry.npmjs.org"
63
- },
64
- "scripts": {
65
- "build": "bun scripts/ensureWorkspaceLinks.ts && tsdown && tsdown src/daemon/maintenanceWorker.ts --no-config --no-clean --format esm --target node22",
66
- "cli:link": "bun link",
67
- "cli:unlink": "bun unlink",
68
- "dev": "LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts",
69
- "man:generate": "bun src/man/generate.ts",
70
- "test": "bunx vitest run --config vitest.config.mts --silent='passed-only'",
71
- "test:coverage": "bunx vitest run --config vitest.config.mts --coverage",
72
- "test:e2e": "bunx vitest run --config vitest.e2e.config.mts --silent='passed-only'",
73
- "type-check": "tsc --noEmit"
74
75
  }
75
- }
76
+ }