@heybox/hb-sdk 0.2.0-alpha.0 → 0.2.0-alpha.1

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 (40) hide show
  1. package/README.md +142 -468
  2. package/bin/hb-sdk.cjs +1 -1
  3. package/dist/cli.cjs +660 -182
  4. package/dist/devtools/mock-host/index.html +13 -387
  5. package/dist/devtools/mock-host/main.js +975 -0
  6. package/dist/index.cjs.js +50 -63
  7. package/dist/index.esm.js +51 -50
  8. package/dist/protocol.cjs.js +72 -13
  9. package/dist/protocol.esm.js +72 -14
  10. package/dist/templates/vue3-vite-ts/README.md.ejs +1 -5
  11. package/dist/templates/vue3-vite-ts/package.json.ejs +0 -1
  12. package/package.json +3 -1
  13. package/skill/SKILL.md +95 -0
  14. package/skill/references/api-protocol.md +135 -0
  15. package/skill/references/api-root.md +346 -0
  16. package/skill/references/cli.md +360 -0
  17. package/skill/references/examples.md +107 -0
  18. package/skill/references/llms-index.md +44 -0
  19. package/skill/references/recipes.md +374 -0
  20. package/skill/references/safety-boundaries.md +28 -0
  21. package/skill/references/smoke-evaluation.md +24 -0
  22. package/skill/scripts/check-references.mjs +14 -0
  23. package/skill/scripts/package-skill.mjs +60 -0
  24. package/skill/scripts/package-skill.sh +6 -0
  25. package/skill/scripts/skill-metadata.mjs +74 -0
  26. package/skill/scripts/sync-references.mjs +541 -0
  27. package/skill/scripts/validate-skill.mjs +233 -0
  28. package/skill/skill.json +11 -0
  29. package/types/index.d.ts +8 -6
  30. package/types/modules/auth/index.d.ts +2 -9
  31. package/types/modules/network/index.d.ts +6 -11
  32. package/types/modules/share/index.d.ts +3 -9
  33. package/types/modules/share/screenshot.d.ts +1 -7
  34. package/types/modules/share/show-share-menu.d.ts +1 -7
  35. package/types/modules/storage/index.d.ts +2 -16
  36. package/types/modules/user/get-info.d.ts +1 -7
  37. package/types/modules/user/index.d.ts +2 -8
  38. package/types/modules/viewport/index.d.ts +2 -9
  39. package/types/protocol/capabilities.d.ts +180 -0
  40. package/types/protocol.d.ts +8 -13
package/dist/cli.cjs CHANGED
@@ -5030,26 +5030,27 @@ const TEMPLATE_ROOT_CANDIDATES = [
5030
5030
  path.resolve(__dirname, '..', 'templates', TEMPLATE_NAME),
5031
5031
  path.resolve(__dirname, '..', 'src', 'cli', 'templates', TEMPLATE_NAME),
5032
5032
  ];
5033
- const PACKAGE_JSON_CANDIDATES = [
5034
- path.resolve(__dirname, '..', 'package.json'),
5035
- path.resolve(__dirname, '..', '..', '..', 'package.json'),
5036
- ];
5033
+ const PACKAGE_JSON_CANDIDATES$3 = [path.resolve(__dirname, '..', 'package.json'), path.resolve(__dirname, '..', '..', '..', 'package.json')];
5037
5034
  const UNSCOPED_PACKAGE_NAME_PATTERN = /^(?![._])[a-z0-9._-]+$/;
5038
- async function runCreateCommand(projectName) {
5039
- const targetDir = path.resolve(process.cwd(), projectName);
5035
+ async function runCreateCommand(projectName, options = {}) {
5036
+ const cwd = options.cwd ?? process.cwd();
5037
+ const targetDir = path.resolve(cwd, projectName);
5040
5038
  const packageName = readPackageName(projectName, targetDir);
5041
5039
  await assertTargetDirectoryWritable(targetDir);
5042
- const templateRoot = await resolveTemplateRoot();
5040
+ const templateRoot = await resolveTemplateRoot(options.templateRoots);
5043
5041
  const data = {
5044
5042
  packageName,
5045
5043
  projectName: packageName,
5046
- sdkVersion: await readCurrentSdkVersion(),
5044
+ sdkVersion: await readCurrentSdkVersion$3(options.packageJsonFiles),
5047
5045
  };
5048
5046
  await renderTemplateDirectory(templateRoot, targetDir, data);
5049
- printCreateResult(projectName, targetDir);
5047
+ printCreateResult(projectName, targetDir, {
5048
+ console: options.console,
5049
+ cwd,
5050
+ });
5050
5051
  }
5051
5052
  function readPackageName(projectName, targetDir) {
5052
- if (projectName.trim().startsWith('@') || projectName.split(/[\\/]/).some(segment => segment.startsWith('@'))) {
5053
+ if (projectName.trim().startsWith('@') || projectName.split(/[\\/]/).some((segment) => segment.startsWith('@'))) {
5053
5054
  throw new Error('暂不支持 scoped package name,请使用 my-miniapp 这类普通目录名');
5054
5055
  }
5055
5056
  const packageName = path.basename(targetDir);
@@ -5062,7 +5063,7 @@ function readPackageName(projectName, targetDir) {
5062
5063
  return packageName;
5063
5064
  }
5064
5065
  async function assertTargetDirectoryWritable(targetDir) {
5065
- if (!(await pathExists(targetDir))) {
5066
+ if (!(await pathExists$3(targetDir))) {
5066
5067
  await fs$2.mkdir(targetDir, { recursive: true });
5067
5068
  return;
5068
5069
  }
@@ -5075,25 +5076,25 @@ async function assertTargetDirectoryWritable(targetDir) {
5075
5076
  throw new Error(`目标目录非空,已拒绝覆盖: ${targetDir}`);
5076
5077
  }
5077
5078
  }
5078
- async function resolveTemplateRoot() {
5079
- for (const candidate of TEMPLATE_ROOT_CANDIDATES) {
5080
- if (await pathExists(candidate)) {
5079
+ async function resolveTemplateRoot(templateRootCandidates = TEMPLATE_ROOT_CANDIDATES) {
5080
+ for (const candidate of templateRootCandidates) {
5081
+ if (await pathExists$3(candidate)) {
5081
5082
  return candidate;
5082
5083
  }
5083
5084
  }
5084
5085
  throw new Error(`未找到 hb-sdk create 模板: ${TEMPLATE_NAME}。请先执行 @heybox/hb-sdk 的 build:templates。`);
5085
5086
  }
5086
- async function readCurrentSdkVersion() {
5087
- const packageJsonPath = await resolvePackageJsonPath();
5087
+ async function readCurrentSdkVersion$3(packageJsonCandidates = PACKAGE_JSON_CANDIDATES$3) {
5088
+ const packageJsonPath = await resolvePackageJsonPath(packageJsonCandidates);
5088
5089
  const packageJson = JSON.parse(await fs$2.readFile(packageJsonPath, 'utf8'));
5089
5090
  if (typeof packageJson.version !== 'string' || !packageJson.version) {
5090
5091
  throw new Error('未能读取 @heybox/hb-sdk 当前版本号');
5091
5092
  }
5092
5093
  return packageJson.version;
5093
5094
  }
5094
- async function resolvePackageJsonPath() {
5095
- for (const candidate of PACKAGE_JSON_CANDIDATES) {
5096
- if (await pathExists(candidate)) {
5095
+ async function resolvePackageJsonPath(packageJsonCandidates = PACKAGE_JSON_CANDIDATES$3) {
5096
+ for (const candidate of packageJsonCandidates) {
5097
+ if (await pathExists$3(candidate)) {
5097
5098
  return candidate;
5098
5099
  }
5099
5100
  }
@@ -5124,7 +5125,7 @@ async function renderTemplateDirectory(sourceDir, targetDir, data) {
5124
5125
  function toOutputFileName(fileName) {
5125
5126
  return fileName.endsWith('.ejs') ? fileName.slice(0, -'.ejs'.length) : fileName;
5126
5127
  }
5127
- async function pathExists(filePath) {
5128
+ async function pathExists$3(filePath) {
5128
5129
  try {
5129
5130
  await fs$2.access(filePath, fs$3.constants.F_OK);
5130
5131
  return true;
@@ -5133,16 +5134,16 @@ async function pathExists(filePath) {
5133
5134
  return false;
5134
5135
  }
5135
5136
  }
5136
- function printCreateResult(inputPath, targetDir) {
5137
- const displayPath = path.isAbsolute(inputPath)
5138
- ? targetDir
5139
- : path.relative(process.cwd(), targetDir) || '.';
5140
- console.log(`[hb-sdk] Created mini program template at ${targetDir}`);
5141
- console.log('');
5142
- console.log('Next steps:');
5143
- console.log(` cd ${quoteShellPath(displayPath)}`);
5144
- console.log(' npm install');
5145
- console.log(' npm run dev:mock');
5137
+ function printCreateResult(inputPath, targetDir, options) {
5138
+ const output = options.console ?? console;
5139
+ const cwd = options.cwd;
5140
+ const displayPath = path.isAbsolute(inputPath) ? targetDir : path.relative(cwd, targetDir) || '.';
5141
+ output.log(`[hb-sdk] Created mini program template at ${targetDir}`);
5142
+ output.log('');
5143
+ output.log('Next steps:');
5144
+ output.log(` cd ${quoteShellPath(displayPath)}`);
5145
+ output.log(' npm install');
5146
+ output.log(' npm run dev');
5146
5147
  }
5147
5148
  function quoteShellPath(value) {
5148
5149
  if (!/[\s'"`$\\]/.test(value)) {
@@ -5946,45 +5947,44 @@ function readErrorMessage(error) {
5946
5947
 
5947
5948
  const DEFAULT_APP_PORT = 5173;
5948
5949
  const DEFAULT_MOCK_PORT = 5174;
5949
- const MOCK_HOST_QUERY_PARAM = 'mini_url';
5950
+ const MINI_PROGRAM_URL_QUERY_PARAM = 'mini_url';
5950
5951
  const MOCK_NETWORK_PROXY_PATH = '/__hb_sdk_mock_network__';
5951
5952
  const MOCK_NETWORK_PROXY_BODY_LIMIT = 1024 * 1024;
5952
- async function runDevCommand(options) {
5953
- const projectRoot = findProjectRoot(process.cwd());
5953
+ // Shared with Heybox H5 Vite plugins so hb-sdk can own the launch output.
5954
+ const MANAGED_DEV_OUTPUT_ENV = 'HEYBOX_DEV_SERVER_MANAGED_OUTPUT';
5955
+ const VITE_LOG_LEVEL = 'warn';
5956
+ async function runDevCommand(options, runtime = {}) {
5957
+ const output = runtime.console ?? console;
5958
+ const fetchImpl = runtime.fetchImpl ?? fetch;
5959
+ const openUrl = runtime.openExternalUrl ?? openExternalUrl;
5960
+ const projectRoot = findProjectRoot(runtime.cwd ?? process.cwd());
5954
5961
  const vite = await loadProjectVite(projectRoot);
5955
- const appServer = await vite.createServer(createViteServerOptions(projectRoot, options));
5962
+ const appServer = await withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, options)), runtime.env);
5956
5963
  await appServer.listen(options.port ?? DEFAULT_APP_PORT);
5957
- appServer.printUrls();
5958
- const appUrl = await resolveViteAppUrl(appServer, options);
5964
+ const appUrl = await resolveViteAppUrl(appServer, options, fetchImpl);
5959
5965
  const closers = [() => appServer.close()];
5960
- if (!options.mock) {
5961
- console.log(`\n[hb-sdk] Real environment URL: ${appUrl}`);
5962
- if (options.open) {
5963
- void openExternalUrl(appUrl);
5964
- }
5965
- installShutdownHandlers(closers);
5966
- return;
5967
- }
5968
- const mockHostRoot = resolveMockHostRoot();
5969
- const mockServer = createMockHostServer(mockHostRoot);
5966
+ const mockHostRoot = runtime.mockHostRoot ?? resolveMockHostRoot(runtime.mockHostRootCandidates);
5967
+ const mockServer = createMockHostServer(mockHostRoot, { fetchImpl });
5970
5968
  const mockPort = await listenHttpServer(mockServer, {
5971
5969
  host: options.host,
5972
5970
  port: options.mockPort ?? DEFAULT_MOCK_PORT,
5973
- });
5971
+ }, runtime.getPort ?? getPorts);
5974
5972
  const mockUrl = createMockHostUrl({
5975
5973
  appUrl,
5976
5974
  host: options.host,
5977
5975
  port: mockPort,
5978
5976
  });
5979
- closers.push(() => new Promise((resolve, reject) => {
5980
- mockServer.close(error => (error ? reject(error) : resolve()));
5977
+ closers.push(() => new Promise((resolve) => {
5978
+ mockServer.close(() => resolve());
5981
5979
  }));
5982
- console.log(`[hb-sdk] Mock runtime host: ${mockUrl}`);
5983
- console.log(`[hb-sdk] Mini program URL: ${appUrl}`);
5984
- if (options.open) {
5985
- void openExternalUrl(mockUrl);
5980
+ output.log(`\n[hb-sdk] Mock runtime host is ready`);
5981
+ output.log(`[hb-sdk] Mock runtime host: ${mockUrl}`);
5982
+ output.log(`[hb-sdk] Mini program URL: ${appUrl}`);
5983
+ output.log(`[hb-sdk] Mac APP: use the button in Mock runtime host`);
5984
+ if (options.open !== false) {
5985
+ void openUrl(mockUrl);
5986
5986
  }
5987
- installShutdownHandlers(closers);
5987
+ installShutdownHandlers(closers, runtime.process ?? process);
5988
5988
  }
5989
5989
  function findProjectRoot(startDir) {
5990
5990
  let current = path.resolve(startDir);
@@ -6019,22 +6019,24 @@ function createViteServerOptions(projectRoot, options) {
6019
6019
  if (configFile) {
6020
6020
  return {
6021
6021
  configFile,
6022
+ logLevel: VITE_LOG_LEVEL,
6022
6023
  server,
6023
6024
  };
6024
6025
  }
6025
6026
  return {
6027
+ logLevel: VITE_LOG_LEVEL,
6026
6028
  root: projectRoot,
6027
6029
  server,
6028
6030
  };
6029
6031
  }
6030
6032
  function resolveProjectViteConfig(projectRoot) {
6031
6033
  const candidates = ['vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs'];
6032
- return candidates.map(file => path.join(projectRoot, file)).find(file => fs$3.existsSync(file));
6034
+ return candidates.map((file) => path.join(projectRoot, file)).find((file) => fs$3.existsSync(file));
6033
6035
  }
6034
- async function resolveViteAppUrl(server, options) {
6036
+ async function resolveViteAppUrl(server, options, fetchImpl) {
6035
6037
  const candidates = createViteAppUrlCandidates(server, options);
6036
6038
  for (const candidate of candidates) {
6037
- if (await canReachDocumentUrl(candidate)) {
6039
+ if (await canReachDocumentUrl(candidate, fetchImpl)) {
6038
6040
  return candidate;
6039
6041
  }
6040
6042
  }
@@ -6043,10 +6045,7 @@ async function resolveViteAppUrl(server, options) {
6043
6045
  function createViteAppUrlCandidates(server, options) {
6044
6046
  const localUrl = server.resolvedUrls?.local[0];
6045
6047
  const origin = readViteOrigin(localUrl, options);
6046
- const candidates = [
6047
- localUrl,
6048
- createH5EntryUrl(origin, readViteBase(server, localUrl)),
6049
- ].filter((url) => Boolean(url));
6048
+ const candidates = [localUrl, createH5EntryUrl(origin, readViteBase(server, localUrl))].filter((url) => Boolean(url));
6050
6049
  return Array.from(new Set(candidates));
6051
6050
  }
6052
6051
  function readViteOrigin(localUrl, options) {
@@ -6081,9 +6080,9 @@ function createH5EntryUrl(origin, base) {
6081
6080
  const pathname = `${ensureTrailingSlash(base)}src/index.html`;
6082
6081
  return new URL(pathname, `${origin}/`).toString();
6083
6082
  }
6084
- async function canReachDocumentUrl(url) {
6083
+ async function canReachDocumentUrl(url, fetchImpl) {
6085
6084
  try {
6086
- const headResponse = await fetch(url, {
6085
+ const headResponse = await fetchImpl(url, {
6087
6086
  method: 'HEAD',
6088
6087
  redirect: 'follow',
6089
6088
  });
@@ -6093,7 +6092,7 @@ async function canReachDocumentUrl(url) {
6093
6092
  if (headResponse.status !== 404 && headResponse.status !== 405) {
6094
6093
  return false;
6095
6094
  }
6096
- const getResponse = await fetch(url, {
6095
+ const getResponse = await fetchImpl(url, {
6097
6096
  method: 'GET',
6098
6097
  redirect: 'follow',
6099
6098
  });
@@ -6106,24 +6105,25 @@ async function canReachDocumentUrl(url) {
6106
6105
  function ensureTrailingSlash(value) {
6107
6106
  return value.endsWith('/') ? value : `${value}/`;
6108
6107
  }
6109
- function resolveMockHostRoot() {
6110
- const candidates = [
6111
- path.resolve(__dirname, 'devtools/mock-host'),
6112
- path.resolve(__dirname, '../devtools/mock-host'),
6113
- path.resolve(__dirname, '../../devtools/mock-host'),
6114
- ];
6115
- const found = candidates.find(candidate => fs$3.existsSync(path.join(candidate, 'index.html')));
6108
+ const MOCK_HOST_ROOT_CANDIDATES = [
6109
+ path.resolve(__dirname, 'devtools/mock-host'),
6110
+ path.resolve(__dirname, '../devtools/mock-host'),
6111
+ path.resolve(__dirname, '../../devtools/mock-host'),
6112
+ path.resolve(__dirname, '../../../devtools/mock-host'),
6113
+ ];
6114
+ function resolveMockHostRoot(candidates = MOCK_HOST_ROOT_CANDIDATES) {
6115
+ const found = candidates.find((candidate) => fs$3.existsSync(path.join(candidate, 'index.html')));
6116
6116
  if (!found) {
6117
6117
  throw new Error(`未找到 hb-sdk mock host 静态产物。请先执行 @heybox/hb-sdk 的 build:mock-host。`);
6118
6118
  }
6119
6119
  return found;
6120
6120
  }
6121
- function createMockHostServer(root) {
6121
+ function createMockHostServer(root, runtime) {
6122
6122
  return node_http.createServer(async (request, response) => {
6123
- const requestUrl = new URL(request.url || '/', 'http://localhost');
6123
+ const requestUrl = new URL(request.url, 'http://localhost');
6124
6124
  const pathname = decodeURIComponent(requestUrl.pathname);
6125
6125
  if (pathname === MOCK_NETWORK_PROXY_PATH) {
6126
- await handleMockNetworkProxy(request, response);
6126
+ await handleMockNetworkProxy(request, response, runtime.fetchImpl);
6127
6127
  return;
6128
6128
  }
6129
6129
  const targetPath = resolveStaticPath(root, pathname);
@@ -6151,7 +6151,7 @@ function createMockHostServer(root) {
6151
6151
  }
6152
6152
  });
6153
6153
  }
6154
- async function handleMockNetworkProxy(request, response) {
6154
+ async function handleMockNetworkProxy(request, response, fetchImpl) {
6155
6155
  if (request.method === 'OPTIONS') {
6156
6156
  writeJsonResponse(response, 204, undefined);
6157
6157
  return;
@@ -6165,27 +6165,25 @@ async function handleMockNetworkProxy(request, response) {
6165
6165
  }
6166
6166
  try {
6167
6167
  const payload = await readJsonRequestBody(request);
6168
- const result = await requestMockNetwork(payload);
6168
+ const result = await requestMockNetwork(payload, fetchImpl);
6169
6169
  writeJsonResponse(response, 200, result);
6170
6170
  }
6171
6171
  catch (error) {
6172
6172
  writeJsonResponse(response, readMockNetworkErrorStatus(error), toMockNetworkErrorPayload(error));
6173
6173
  }
6174
6174
  }
6175
- async function requestMockNetwork(payload) {
6175
+ async function requestMockNetwork(payload, fetchImpl) {
6176
6176
  if (!isRecord$1(payload) || typeof payload.url !== 'string' || !payload.url.trim()) {
6177
6177
  throw createMockNetworkError(400, 'INVALID_NETWORK_REQUEST', 'network.request url 必须是非空字符串');
6178
6178
  }
6179
6179
  const url = createMockNetworkUrl(payload.url, payload.params);
6180
- const method = typeof payload.method === 'string' && payload.method.trim()
6181
- ? payload.method.trim().toUpperCase()
6182
- : 'GET';
6180
+ const method = typeof payload.method === 'string' && payload.method.trim() ? payload.method.trim().toUpperCase() : 'GET';
6183
6181
  const headers = normalizeMockNetworkHeaders(payload.headers);
6184
6182
  const controller = new AbortController();
6185
6183
  const timeout = Number(payload.timeout) > 0 ? Number(payload.timeout) : 10000;
6186
6184
  const timer = setTimeout(() => controller.abort(), timeout);
6187
6185
  try {
6188
- const response = await fetch(url.toString(), {
6186
+ const response = await fetchImpl(url.toString(), {
6189
6187
  method,
6190
6188
  headers,
6191
6189
  body: createMockNetworkBody(method, payload.data, headers),
@@ -6194,6 +6192,7 @@ async function requestMockNetwork(payload) {
6194
6192
  });
6195
6193
  const contentType = response.headers.get('content-type') || '';
6196
6194
  const text = await response.text();
6195
+ clearTimeout(timer);
6197
6196
  return {
6198
6197
  data: contentType.includes('application/json') ? safeJsonParse(text) : text,
6199
6198
  status: response.status,
@@ -6202,12 +6201,8 @@ async function requestMockNetwork(payload) {
6202
6201
  };
6203
6202
  }
6204
6203
  catch (error) {
6205
- throw createMockNetworkError(502, 'MOCK_NETWORK_REQUEST_FAILED', error instanceof Error && error.name === 'AbortError'
6206
- ? `network.request timeout after ${timeout}ms`
6207
- : readErrorMessage(error));
6208
- }
6209
- finally {
6210
6204
  clearTimeout(timer);
6205
+ throw createMockNetworkError(502, 'MOCK_NETWORK_REQUEST_FAILED', error instanceof Error && error.name === 'AbortError' ? `network.request timeout after ${timeout}ms` : readErrorMessage(error));
6211
6206
  }
6212
6207
  }
6213
6208
  function createMockNetworkUrl(input, params) {
@@ -6272,8 +6267,8 @@ function readJsonRequestBody(request) {
6272
6267
  return new Promise((resolve, reject) => {
6273
6268
  const chunks = [];
6274
6269
  let size = 0;
6275
- request.on('data', chunk => {
6276
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
6270
+ request.on('data', (chunk) => {
6271
+ const buffer = Buffer.from(chunk);
6277
6272
  size += buffer.byteLength;
6278
6273
  if (size > MOCK_NETWORK_PROXY_BODY_LIMIT) {
6279
6274
  reject(createMockNetworkError(413, 'REQUEST_ENTITY_TOO_LARGE', 'mock network proxy request body too large'));
@@ -6283,6 +6278,9 @@ function readJsonRequestBody(request) {
6283
6278
  chunks.push(buffer);
6284
6279
  });
6285
6280
  request.on('error', reject);
6281
+ request.on('aborted', () => {
6282
+ reject('mock network proxy request aborted');
6283
+ });
6286
6284
  request.on('end', () => {
6287
6285
  const rawBody = Buffer.concat(chunks).toString('utf8');
6288
6286
  if (!rawBody.trim()) {
@@ -6323,10 +6321,10 @@ function createMockNetworkError(status, code, message) {
6323
6321
  return error;
6324
6322
  }
6325
6323
  function readMockNetworkErrorStatus(error) {
6326
- return isRecord$1(error) && typeof error.status === 'number' ? error.status : 500;
6324
+ return isObjectLike(error) && typeof error.status === 'number' ? error.status : 500;
6327
6325
  }
6328
6326
  function toMockNetworkErrorPayload(error) {
6329
- if (isRecord$1(error) && typeof error.code === 'string' && typeof error.message === 'string') {
6327
+ if (isObjectLike(error) && typeof error.code === 'string' && typeof error.message === 'string') {
6330
6328
  return {
6331
6329
  code: error.code,
6332
6330
  message: error.message,
@@ -6337,6 +6335,9 @@ function toMockNetworkErrorPayload(error) {
6337
6335
  message: readErrorMessage(error),
6338
6336
  };
6339
6337
  }
6338
+ function isObjectLike(value) {
6339
+ return (typeof value === 'object' || typeof value === 'function') && value !== null;
6340
+ }
6340
6341
  function resolveStaticPath(root, pathname) {
6341
6342
  const safePathname = pathname === '/' ? '/index.html' : pathname;
6342
6343
  const normalizedRoot = path.normalize(root);
@@ -6364,9 +6365,9 @@ function readContentType(filePath) {
6364
6365
  }
6365
6366
  return 'application/octet-stream';
6366
6367
  }
6367
- async function listenHttpServer(server, options) {
6368
+ async function listenHttpServer(server, options, getPortImpl) {
6368
6369
  const maxAttempts = 20;
6369
- const port = await getPorts({
6370
+ const port = await getPortImpl({
6370
6371
  host: options.host,
6371
6372
  port: createPortCandidates(options.port, maxAttempts),
6372
6373
  });
@@ -6387,35 +6388,218 @@ async function listenHttpServer(server, options) {
6387
6388
  server.listen(port, options.host);
6388
6389
  });
6389
6390
  const address = server.address();
6390
- return typeof address === 'object' && address ? address.port : port;
6391
+ return address.port;
6391
6392
  }
6392
6393
  function createPortCandidates(startPort, count) {
6393
6394
  return Array.from({ length: count }, (_, index) => startPort + index);
6394
6395
  }
6395
6396
  function createMockHostUrl(options) {
6396
6397
  const url = new URL(`http://${toDisplayHost(options.host)}:${options.port}/`);
6397
- url.searchParams.set(MOCK_HOST_QUERY_PARAM, options.appUrl);
6398
+ url.searchParams.set(MINI_PROGRAM_URL_QUERY_PARAM, options.appUrl);
6398
6399
  return url.toString();
6399
6400
  }
6401
+ async function withManagedDevOutputEnv(action, env = process.env) {
6402
+ const previous = env[MANAGED_DEV_OUTPUT_ENV];
6403
+ env[MANAGED_DEV_OUTPUT_ENV] = '1';
6404
+ try {
6405
+ return await action();
6406
+ }
6407
+ finally {
6408
+ if (previous === undefined) {
6409
+ delete env[MANAGED_DEV_OUTPUT_ENV];
6410
+ }
6411
+ else {
6412
+ env[MANAGED_DEV_OUTPUT_ENV] = previous;
6413
+ }
6414
+ }
6415
+ }
6400
6416
  function toDisplayHost(host) {
6401
6417
  if (!host || host === '0.0.0.0' || host === '::') {
6402
6418
  return 'localhost';
6403
6419
  }
6404
6420
  return host;
6405
6421
  }
6406
- function installShutdownHandlers(closers) {
6422
+ function installShutdownHandlers(closers, processLike) {
6407
6423
  const shutdown = async () => {
6408
- await Promise.allSettled(closers.map(close => close()));
6409
- process.exit(0);
6424
+ await Promise.allSettled(closers.map((close) => close()));
6425
+ processLike.exit(0);
6410
6426
  };
6411
- process.once('SIGINT', () => {
6427
+ processLike.once('SIGINT', () => {
6412
6428
  void shutdown();
6413
6429
  });
6414
- process.once('SIGTERM', () => {
6430
+ processLike.once('SIGTERM', () => {
6415
6431
  void shutdown();
6416
6432
  });
6417
6433
  }
6418
6434
 
6435
+ const PACKAGE_NAME$1 = '@heybox/hb-sdk';
6436
+ const SKILL_NAME = 'hb-sdk';
6437
+ const DEFAULT_SKILL_SOURCE = 'https://open.xiaoheihe.cn/agent-skills/hb-sdk';
6438
+ const DEFAULT_REMOTE_INDEX_URL = 'https://open.xiaoheihe.cn/agent-skills/.well-known/agent-skills/index.json';
6439
+ const DEFAULT_TIMEOUT_MS$1 = 3000;
6440
+ const SKILL_INSTALL_COMMAND = `npx skills add ${DEFAULT_SKILL_SOURCE}`;
6441
+ const PACKAGE_JSON_CANDIDATES$2 = [
6442
+ path.resolve(__dirname, '..', 'package.json'),
6443
+ path.resolve(__dirname, '..', '..', 'package.json'),
6444
+ path.resolve(__dirname, '..', '..', '..', 'package.json'),
6445
+ ];
6446
+ async function runDoctorCommand(runtime = {}) {
6447
+ const output = runtime.console ?? console;
6448
+ const result = await getDoctorResult(runtime);
6449
+ printDoctorResult(result, output);
6450
+ printDoctorNextStep(result, output);
6451
+ return result;
6452
+ }
6453
+ async function getDoctorResult(runtime = {}) {
6454
+ const currentSdkVersion = await readCurrentSdkVersion$2(runtime.packageJsonFiles);
6455
+ const localSkillJsonPath = resolveLocalSkillJsonPath(runtime);
6456
+ let remoteSkill;
6457
+ try {
6458
+ remoteSkill = await fetchRemoteSkill(runtime);
6459
+ }
6460
+ catch {
6461
+ return {
6462
+ currentSdkVersion,
6463
+ localSkillJsonPath,
6464
+ status: 'REMOTE_UNAVAILABLE',
6465
+ };
6466
+ }
6467
+ if (remoteSkill?.sdk?.version !== currentSdkVersion) {
6468
+ return {
6469
+ currentSdkVersion,
6470
+ localSkillJsonPath,
6471
+ remoteSkill,
6472
+ status: 'SDK_MISMATCH',
6473
+ };
6474
+ }
6475
+ const localSkill = await readLocalSkillManifest(localSkillJsonPath);
6476
+ if (!localSkill.exists) {
6477
+ return {
6478
+ currentSdkVersion,
6479
+ localSkillJsonPath,
6480
+ remoteSkill,
6481
+ status: 'SKILL_MISSING',
6482
+ };
6483
+ }
6484
+ if (!localSkill.manifest ||
6485
+ localSkill.manifest.name !== SKILL_NAME ||
6486
+ localSkill.manifest.skillVersion !== remoteSkill.version ||
6487
+ localSkill.manifest.sdk?.version !== remoteSkill.sdk?.version) {
6488
+ return {
6489
+ currentSdkVersion,
6490
+ localSkillJsonPath,
6491
+ localSkillVersion: localSkill.manifest?.skillVersion,
6492
+ remoteSkill,
6493
+ status: 'SKILL_OUTDATED',
6494
+ };
6495
+ }
6496
+ return {
6497
+ currentSdkVersion,
6498
+ localSkillJsonPath,
6499
+ localSkillVersion: localSkill.manifest.skillVersion,
6500
+ remoteSkill,
6501
+ status: 'OK',
6502
+ };
6503
+ }
6504
+ function printDoctorResult(result, output) {
6505
+ output.log('[hb-sdk] Doctor');
6506
+ output.log(`SDK version: ${result.currentSdkVersion}`);
6507
+ output.log(`Remote skill: ${result.remoteSkill?.version ? `${result.remoteSkill.version} (sdk ${result.remoteSkill.sdk?.version ?? 'unknown'})` : 'unavailable'}`);
6508
+ output.log(`Local skill: ${result.localSkillVersion ?? 'missing'} (${result.localSkillJsonPath})`);
6509
+ output.log(`Status: ${result.status}`);
6510
+ }
6511
+ function printDoctorNextStep(result, output) {
6512
+ if (result.status === 'OK') {
6513
+ output.log('[hb-sdk] Skill is up to date.');
6514
+ return;
6515
+ }
6516
+ if (result.status === 'SDK_MISMATCH') {
6517
+ output.log(`[hb-sdk] Current SDK does not match the latest skill metadata. Upgrade with: npm i -D ${PACKAGE_NAME$1}@latest`);
6518
+ output.log(`[hb-sdk] Then install or refresh the skill manually: ${formatSkillInstallCommand(result)}`);
6519
+ return;
6520
+ }
6521
+ if (result.status === 'REMOTE_UNAVAILABLE') {
6522
+ output.log('[hb-sdk] Remote skill metadata is unavailable. Try again later.');
6523
+ output.log(`[hb-sdk] Manual install command: ${SKILL_INSTALL_COMMAND}`);
6524
+ return;
6525
+ }
6526
+ output.log(`[hb-sdk] Install or refresh the skill manually: ${formatSkillInstallCommand(result)}`);
6527
+ }
6528
+ function formatSkillInstallCommand(result) {
6529
+ return `npx skills add ${result.remoteSkill?.source || DEFAULT_SKILL_SOURCE}`;
6530
+ }
6531
+ async function fetchRemoteSkill(runtime) {
6532
+ const fetchImpl = runtime.fetchImpl ?? fetch;
6533
+ const controller = new AbortController();
6534
+ const timeout = setTimeout(() => controller.abort(), runtime.timeoutMs ?? DEFAULT_TIMEOUT_MS$1);
6535
+ try {
6536
+ const response = await fetchImpl(runtime.remoteIndexUrl ?? DEFAULT_REMOTE_INDEX_URL, {
6537
+ headers: {
6538
+ accept: 'application/json',
6539
+ },
6540
+ signal: controller.signal,
6541
+ });
6542
+ if (!response.ok) {
6543
+ throw new Error(`remote skill index returned ${response.status}`);
6544
+ }
6545
+ const index = (await response.json());
6546
+ const skill = index.skills?.find(item => item.name === SKILL_NAME);
6547
+ if (!skill?.version || skill.sdk?.package !== PACKAGE_NAME$1 || !skill.sdk.version) {
6548
+ throw new Error('remote hb-sdk skill metadata is incomplete');
6549
+ }
6550
+ return skill;
6551
+ }
6552
+ finally {
6553
+ clearTimeout(timeout);
6554
+ }
6555
+ }
6556
+ async function readLocalSkillManifest(skillJsonPath) {
6557
+ if (!(await pathExists$2(skillJsonPath))) {
6558
+ return {
6559
+ exists: false,
6560
+ };
6561
+ }
6562
+ try {
6563
+ return {
6564
+ exists: true,
6565
+ manifest: JSON.parse(await fs$2.readFile(skillJsonPath, 'utf8')),
6566
+ };
6567
+ }
6568
+ catch {
6569
+ return {
6570
+ exists: true,
6571
+ };
6572
+ }
6573
+ }
6574
+ async function readCurrentSdkVersion$2(packageJsonCandidates = PACKAGE_JSON_CANDIDATES$2) {
6575
+ for (const candidate of packageJsonCandidates) {
6576
+ if (!(await pathExists$2(candidate))) {
6577
+ continue;
6578
+ }
6579
+ const packageJson = JSON.parse(await fs$2.readFile(candidate, 'utf8'));
6580
+ if (typeof packageJson.version === 'string' && packageJson.version) {
6581
+ return packageJson.version;
6582
+ }
6583
+ }
6584
+ throw new Error('未能读取 @heybox/hb-sdk 当前版本号');
6585
+ }
6586
+ function resolveLocalSkillJsonPath(runtime) {
6587
+ if (runtime.localSkillJsonPath) {
6588
+ return runtime.localSkillJsonPath;
6589
+ }
6590
+ const codexHome = runtime.codexHome ?? runtime.env?.CODEX_HOME ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex');
6591
+ return path.join(codexHome, 'skills', SKILL_NAME, 'skill.json');
6592
+ }
6593
+ async function pathExists$2(filePath) {
6594
+ try {
6595
+ await fs$2.access(filePath, fs$3.constants.F_OK);
6596
+ return true;
6597
+ }
6598
+ catch {
6599
+ return false;
6600
+ }
6601
+ }
6602
+
6419
6603
  const HEYBOX_LOGIN_URL = 'https://login.xiaoheihe.cn/';
6420
6604
  const HEYBOX_CALLBACK_HOST = '127.0.0.1';
6421
6605
  const HEYBOX_CALLBACK_PATH = '/heybox/callback';
@@ -6446,26 +6630,27 @@ async function waitForHeyboxBrowserCallback(options = {}) {
6446
6630
  const expectedState = node_crypto.randomBytes(16).toString('hex');
6447
6631
  const timeoutMs = options.timeoutMs ?? DEFAULT_HEYBOX_CALLBACK_TIMEOUT_MS;
6448
6632
  const openBrowser = options.openBrowser ?? openExternalUrl;
6449
- let server = null;
6450
6633
  let timer = null;
6634
+ const loginCallback = {};
6635
+ const server = node_http.createServer((request, response) => {
6636
+ try {
6637
+ const requestUrl = new URL(request.url, `http://${HEYBOX_CALLBACK_HOST}`);
6638
+ if (requestUrl.pathname === HEYBOX_CALLBACK_DONE_PATH) {
6639
+ sendBrowserResponse(response, 200, 'Heybox 登录成功,可以回到终端继续。');
6640
+ return;
6641
+ }
6642
+ const callbackPayload = parseHeyboxCallbackUrl(request.url, expectedState);
6643
+ redirectToCleanCallbackPage(response, () => loginCallback.resolve(callbackPayload));
6644
+ }
6645
+ catch (error) {
6646
+ sendBrowserResponse(response, 400, `Heybox 登录失败: ${readErrorMessage(error)}`);
6647
+ loginCallback.reject(error);
6648
+ }
6649
+ });
6451
6650
  try {
6452
6651
  const payload = await new Promise((resolve, reject) => {
6453
- server = node_http.createServer((request, response) => {
6454
- try {
6455
- const requestUrl = new URL(request.url || '/', `http://${HEYBOX_CALLBACK_HOST}`);
6456
- if (requestUrl.pathname === HEYBOX_CALLBACK_DONE_PATH) {
6457
- sendBrowserResponse(response, 200, 'Heybox 登录成功,可以回到终端继续。');
6458
- return;
6459
- }
6460
- const callbackPayload = parseHeyboxCallbackUrl(request.url || '', expectedState);
6461
- redirectToCleanCallbackPage(response, () => resolve(callbackPayload));
6462
- }
6463
- catch (error) {
6464
- const message = error instanceof Error ? error.message : String(error);
6465
- sendBrowserResponse(response, 400, `Heybox 登录失败: ${message}`);
6466
- reject(error);
6467
- }
6468
- });
6652
+ loginCallback.resolve = resolve;
6653
+ loginCallback.reject = reject;
6469
6654
  server.once('error', reject);
6470
6655
  server.listen(0, HEYBOX_CALLBACK_HOST, () => {
6471
6656
  void handleServerListening(server, expectedState, {
@@ -6488,10 +6673,7 @@ async function waitForHeyboxBrowserCallback(options = {}) {
6488
6673
  }
6489
6674
  }
6490
6675
  async function handleServerListening(server, expectedState, options) {
6491
- const address = server?.address();
6492
- if (!address || typeof address === 'string') {
6493
- throw new Error('无法启动 Heybox 登录回调服务');
6494
- }
6676
+ const address = server.address();
6495
6677
  const callbackUrl = `http://${HEYBOX_CALLBACK_HOST}:${address.port}${HEYBOX_CALLBACK_PATH}`;
6496
6678
  const loginUrl = createHeyboxLoginUrl(callbackUrl, expectedState);
6497
6679
  options.onLoginUrl?.(loginUrl);
@@ -6519,11 +6701,7 @@ function redirectToCleanCallbackPage(response, onSent) {
6519
6701
  response.end(onSent);
6520
6702
  }
6521
6703
  function closeServer(server) {
6522
- return new Promise(resolve => {
6523
- if (!server?.listening) {
6524
- resolve();
6525
- return;
6526
- }
6704
+ return new Promise((resolve) => {
6527
6705
  server.close(() => resolve());
6528
6706
  });
6529
6707
  }
@@ -9436,9 +9614,9 @@ var libExports = /*@__PURE__*/ requireLib();
9436
9614
  var fs = /*@__PURE__*/getDefaultExportFromCjs(libExports);
9437
9615
 
9438
9616
  const AUTH_CACHE_VERSION = 1;
9439
- const paths = envPaths('hb-sdk', { suffix: '' });
9617
+ const paths$1 = envPaths('hb-sdk', { suffix: '' });
9440
9618
  function getAuthCacheFilePath(options = {}) {
9441
- return options.cacheFile ?? path.join(paths.cache, 'auth.json');
9619
+ return options.cacheFile ?? path.join(paths$1.cache, 'auth.json');
9442
9620
  }
9443
9621
  function createHeyboxAuthSession(payload, options = {}) {
9444
9622
  return {
@@ -9531,105 +9709,402 @@ function isRecord(value) {
9531
9709
  return Object.prototype.toString.call(value) === '[object Object]';
9532
9710
  }
9533
9711
 
9534
- async function loginToHeybox() {
9535
- const payload = await waitForHeyboxBrowserCallback({
9712
+ async function loginToHeybox(options = {}) {
9713
+ const output = options.console ?? console;
9714
+ const waitForBrowserCallback = options.waitForBrowserCallback ?? waitForHeyboxBrowserCallback;
9715
+ const payload = await waitForBrowserCallback({
9716
+ openBrowser: options.openBrowser,
9536
9717
  onLoginUrl(loginUrl) {
9537
- console.log('Heybox 浏览器登录');
9538
- console.log(`浏览器入口: ${loginUrl}`);
9539
- console.log('请在打开的页面完成登录,成功后 hb-sdk 会自动接收登录态。');
9718
+ output.log('Heybox 浏览器登录');
9719
+ output.log(`浏览器入口: ${loginUrl}`);
9720
+ output.log('请在打开的页面完成登录,成功后 hb-sdk 会自动接收登录态。');
9540
9721
  },
9541
9722
  onBrowserOpenResult(opened, loginUrl) {
9542
9723
  if (opened) {
9543
- console.log('已打开 Heybox 登录页。');
9724
+ output.log('已打开 Heybox 登录页。');
9544
9725
  return;
9545
9726
  }
9546
- console.log('当前环境无法直接打开浏览器。');
9547
- console.log('请打开这个链接完成 Heybox 登录:');
9548
- console.log(loginUrl);
9727
+ output.log('当前环境无法直接打开浏览器。');
9728
+ output.log('请打开这个链接完成 Heybox 登录:');
9729
+ output.log(loginUrl);
9549
9730
  },
9731
+ timeoutMs: options.timeoutMs,
9550
9732
  });
9551
- const session = createHeyboxAuthSession(payload);
9552
- await writeHeyboxAuthSession(session);
9553
- console.log(`Heybox 登录成功: ${session.heyboxId}`);
9554
- console.log(`cache: ${getAuthCacheFilePath()}`);
9555
- console.log('验证: hb-sdk login status');
9556
- }
9557
- async function printLoginStatus() {
9558
- const status = await readRedactedHeyboxAuthStatus();
9733
+ const session = createHeyboxAuthSession(payload, options);
9734
+ await writeHeyboxAuthSession(session, options);
9735
+ output.log(`Heybox 登录成功: ${session.heyboxId}`);
9736
+ output.log(`cache: ${getAuthCacheFilePath(options)}`);
9737
+ output.log('验证: hb-sdk login status');
9738
+ }
9739
+ async function printLoginStatus(options = {}) {
9740
+ const output = options.console ?? console;
9741
+ const status = await readRedactedHeyboxAuthStatus(options);
9559
9742
  if (!status.loggedIn) {
9560
- console.log('Heybox 登录态: 未登录');
9561
- console.log('下一步: hb-sdk login');
9562
- console.log(`cache: ${status.cacheFile}`);
9743
+ output.log('Heybox 登录态: 未登录');
9744
+ output.log('下一步: hb-sdk login');
9745
+ output.log(`cache: ${status.cacheFile}`);
9563
9746
  return;
9564
9747
  }
9565
- console.log('Heybox 登录态: 已登录');
9566
- console.log(`heyboxId: ${status.heyboxId}`);
9567
- console.log(`loggedInAt: ${status.loggedInAt}`);
9568
- console.log(`cache: ${status.cacheFile}`);
9748
+ output.log('Heybox 登录态: 已登录');
9749
+ output.log(`heyboxId: ${status.heyboxId}`);
9750
+ output.log(`loggedInAt: ${status.loggedInAt}`);
9751
+ output.log(`cache: ${status.cacheFile}`);
9752
+ }
9753
+ async function clearLoginStatus(options = {}) {
9754
+ const output = options.console ?? console;
9755
+ await clearHeyboxAuthSession(options);
9756
+ output.log('已清理 Heybox 登录态');
9757
+ output.log(`cache: ${getAuthCacheFilePath(options)}`);
9758
+ }
9759
+
9760
+ const PACKAGE_NAME = '@heybox/hb-sdk';
9761
+ const UPDATE_CHECK_CACHE_VERSION = 1;
9762
+ const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
9763
+ const DEFAULT_TIMEOUT_MS = 1500;
9764
+ const UPDATE_CHECK_CACHE_FILE = 'update-check.json';
9765
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}`;
9766
+ const paths = envPaths('hb-sdk', { suffix: '' });
9767
+ const PACKAGE_JSON_CANDIDATES$1 = [
9768
+ path.resolve(__dirname, '..', 'package.json'),
9769
+ path.resolve(__dirname, '..', '..', 'package.json'),
9770
+ path.resolve(__dirname, '..', '..', '..', 'package.json'),
9771
+ ];
9772
+ async function printUpdateReminder(options = {}) {
9773
+ try {
9774
+ const reminder = await getUpdateReminder(options);
9775
+ if (!reminder) {
9776
+ return;
9777
+ }
9778
+ const output = options.stderr ?? process.stderr;
9779
+ output.write(`${formatUpdateReminder(reminder)}\n`);
9780
+ }
9781
+ catch {
9782
+ // Version checks must never affect the actual hb-sdk command.
9783
+ }
9784
+ }
9785
+ async function getUpdateReminder(options = {}) {
9786
+ if (isUpdateCheckDisabled(options.env ?? process.env)) {
9787
+ return null;
9788
+ }
9789
+ const now = options.now?.() ?? new Date();
9790
+ const currentVersion = options.currentVersion ?? (await readCurrentSdkVersion$1(options.packageJsonFiles));
9791
+ const cached = await readFreshUpdateCheckCache(getUpdateCheckCacheFilePath(options), now);
9792
+ if (cached) {
9793
+ return createUpdateReminder(currentVersion, cached.latestVersion);
9794
+ }
9795
+ let latestVersion;
9796
+ try {
9797
+ latestVersion = await fetchLatestVersion(options);
9798
+ }
9799
+ catch {
9800
+ await writeUpdateCheckCache({
9801
+ version: UPDATE_CHECK_CACHE_VERSION,
9802
+ checkedAt: now.toISOString(),
9803
+ }, options).catch(() => undefined);
9804
+ return null;
9805
+ }
9806
+ await writeUpdateCheckCache({
9807
+ version: UPDATE_CHECK_CACHE_VERSION,
9808
+ checkedAt: now.toISOString(),
9809
+ latestVersion,
9810
+ }, options).catch(() => undefined);
9811
+ return createUpdateReminder(currentVersion, latestVersion);
9812
+ }
9813
+ function formatUpdateReminder(reminder) {
9814
+ return `[hb-sdk] New version available: ${reminder.currentVersion} -> ${reminder.latestVersion}. Upgrade with: npm i -D ${PACKAGE_NAME}@latest`;
9815
+ }
9816
+ function isVersionGreater(version, baseline) {
9817
+ const compared = compareSemver(version, baseline);
9818
+ return compared !== null && compared > 0;
9819
+ }
9820
+ function createUpdateReminder(currentVersion, latestVersion) {
9821
+ if (!latestVersion || !isVersionGreater(latestVersion, currentVersion)) {
9822
+ return null;
9823
+ }
9824
+ return {
9825
+ currentVersion,
9826
+ latestVersion,
9827
+ };
9828
+ }
9829
+ function getUpdateCheckCacheFilePath(options = {}) {
9830
+ return options.cacheFile ?? path.join(paths.cache, UPDATE_CHECK_CACHE_FILE);
9569
9831
  }
9570
- async function clearLoginStatus() {
9571
- await clearHeyboxAuthSession();
9572
- console.log('已清理 Heybox 登录态');
9573
- console.log(`cache: ${getAuthCacheFilePath()}`);
9832
+ async function readFreshUpdateCheckCache(cacheFile, now) {
9833
+ const cache = await readUpdateCheckCache(cacheFile);
9834
+ if (!cache) {
9835
+ return null;
9836
+ }
9837
+ const checkedAt = Date.parse(cache.checkedAt);
9838
+ if (!Number.isFinite(checkedAt) || now.getTime() - checkedAt >= UPDATE_CHECK_TTL_MS) {
9839
+ return null;
9840
+ }
9841
+ return cache;
9842
+ }
9843
+ async function readUpdateCheckCache(cacheFile) {
9844
+ if (!(await pathExists$1(cacheFile))) {
9845
+ return null;
9846
+ }
9847
+ try {
9848
+ const parsed = JSON.parse(await fs$2.readFile(cacheFile, 'utf8'));
9849
+ if (parsed.version !== UPDATE_CHECK_CACHE_VERSION || typeof parsed.checkedAt !== 'string') {
9850
+ return null;
9851
+ }
9852
+ return {
9853
+ version: UPDATE_CHECK_CACHE_VERSION,
9854
+ checkedAt: parsed.checkedAt,
9855
+ ...(typeof parsed.latestVersion === 'string' ? { latestVersion: parsed.latestVersion } : {}),
9856
+ };
9857
+ }
9858
+ catch {
9859
+ return null;
9860
+ }
9861
+ }
9862
+ async function writeUpdateCheckCache(cache, options = {}) {
9863
+ const cacheFile = getUpdateCheckCacheFilePath(options);
9864
+ await fs$2.mkdir(path.dirname(cacheFile), { recursive: true });
9865
+ await fs$2.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8');
9866
+ }
9867
+ async function fetchLatestVersion(options) {
9868
+ const fetchImpl = options.fetchImpl ?? fetch;
9869
+ const controller = new AbortController();
9870
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
9871
+ try {
9872
+ const response = await fetchImpl(options.registryUrl ?? NPM_REGISTRY_URL, {
9873
+ headers: {
9874
+ accept: 'application/vnd.npm.install-v1+json',
9875
+ },
9876
+ signal: controller.signal,
9877
+ });
9878
+ if (!response.ok) {
9879
+ throw new Error(`npm registry returned ${response.status}`);
9880
+ }
9881
+ const metadata = (await response.json());
9882
+ const latestVersion = metadata['dist-tags']?.latest;
9883
+ if (typeof latestVersion !== 'string' || !latestVersion) {
9884
+ throw new Error('npm registry response missing latest dist-tag');
9885
+ }
9886
+ return latestVersion;
9887
+ }
9888
+ finally {
9889
+ clearTimeout(timeout);
9890
+ }
9891
+ }
9892
+ async function readCurrentSdkVersion$1(packageJsonCandidates = PACKAGE_JSON_CANDIDATES$1) {
9893
+ for (const candidate of packageJsonCandidates) {
9894
+ if (!(await pathExists$1(candidate))) {
9895
+ continue;
9896
+ }
9897
+ const packageJson = JSON.parse(await fs$2.readFile(candidate, 'utf8'));
9898
+ if (typeof packageJson.version === 'string' && packageJson.version) {
9899
+ return packageJson.version;
9900
+ }
9901
+ }
9902
+ throw new Error('未能读取 @heybox/hb-sdk 当前版本号');
9903
+ }
9904
+ function isUpdateCheckDisabled(env) {
9905
+ return env.HB_SDK_NO_UPDATE_CHECK === '1' || env.CI === 'true';
9906
+ }
9907
+ function compareSemver(version, baseline) {
9908
+ const parsedVersion = parseSemver(version);
9909
+ const parsedBaseline = parseSemver(baseline);
9910
+ if (!parsedVersion || !parsedBaseline) {
9911
+ return null;
9912
+ }
9913
+ for (const key of ['major', 'minor', 'patch']) {
9914
+ if (parsedVersion[key] !== parsedBaseline[key]) {
9915
+ return parsedVersion[key] > parsedBaseline[key] ? 1 : -1;
9916
+ }
9917
+ }
9918
+ return comparePrerelease(parsedVersion.prerelease, parsedBaseline.prerelease);
9919
+ }
9920
+ function parseSemver(version) {
9921
+ const matched = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-.]+))?(?:\+[0-9A-Za-z-.]+)?$/);
9922
+ if (!matched) {
9923
+ return null;
9924
+ }
9925
+ return {
9926
+ major: Number(matched[1]),
9927
+ minor: Number(matched[2]),
9928
+ patch: Number(matched[3]),
9929
+ prerelease: matched[4]?.split('.') ?? [],
9930
+ };
9931
+ }
9932
+ function comparePrerelease(versionPrerelease, baselinePrerelease) {
9933
+ if (versionPrerelease.length === 0 && baselinePrerelease.length === 0) {
9934
+ return 0;
9935
+ }
9936
+ if (versionPrerelease.length === 0) {
9937
+ return 1;
9938
+ }
9939
+ if (baselinePrerelease.length === 0) {
9940
+ return -1;
9941
+ }
9942
+ const length = Math.max(versionPrerelease.length, baselinePrerelease.length);
9943
+ for (let index = 0; index < length; index += 1) {
9944
+ const left = versionPrerelease[index];
9945
+ const right = baselinePrerelease[index];
9946
+ if (left === undefined) {
9947
+ return -1;
9948
+ }
9949
+ if (right === undefined) {
9950
+ return 1;
9951
+ }
9952
+ const compared = comparePrereleaseIdentifier(left, right);
9953
+ if (compared !== 0) {
9954
+ return compared;
9955
+ }
9956
+ }
9957
+ return 0;
9958
+ }
9959
+ function comparePrereleaseIdentifier(left, right) {
9960
+ const leftNumeric = isNumericPrereleaseIdentifier(left);
9961
+ const rightNumeric = isNumericPrereleaseIdentifier(right);
9962
+ if (leftNumeric && rightNumeric) {
9963
+ const compared = Number(left) - Number(right);
9964
+ return compared === 0 ? 0 : compared > 0 ? 1 : -1;
9965
+ }
9966
+ if (leftNumeric) {
9967
+ return -1;
9968
+ }
9969
+ if (rightNumeric) {
9970
+ return 1;
9971
+ }
9972
+ if (left === right) {
9973
+ return 0;
9974
+ }
9975
+ return left > right ? 1 : -1;
9976
+ }
9977
+ function isNumericPrereleaseIdentifier(value) {
9978
+ return /^\d+$/.test(value);
9979
+ }
9980
+ async function pathExists$1(filePath) {
9981
+ try {
9982
+ await fs$2.access(filePath, fs$3.constants.F_OK);
9983
+ return true;
9984
+ }
9985
+ catch {
9986
+ return false;
9987
+ }
9988
+ }
9989
+
9990
+ const CLI_VERSION_PLACEHOLDER = ['__HB', 'SDK', 'CLI', 'VERSION__'].join('_');
9991
+ const BUILT_CLI_VERSION = '0.2.0-alpha.1';
9992
+ const PACKAGE_JSON_CANDIDATES = [
9993
+ path.resolve(__dirname, '..', '..', 'package.json'),
9994
+ path.resolve(__dirname, '..', 'package.json'),
9995
+ path.resolve(__dirname, '..', '..', '..', 'package.json'),
9996
+ ];
9997
+ function getCliVersion(packageJsonCandidates = PACKAGE_JSON_CANDIDATES) {
9998
+ if (BUILT_CLI_VERSION !== CLI_VERSION_PLACEHOLDER) {
9999
+ return BUILT_CLI_VERSION;
10000
+ }
10001
+ return readCurrentSdkVersion(packageJsonCandidates);
10002
+ }
10003
+ function readCurrentSdkVersion(packageJsonCandidates) {
10004
+ for (const candidate of packageJsonCandidates) {
10005
+ if (!pathExists(candidate)) {
10006
+ continue;
10007
+ }
10008
+ const packageJson = JSON.parse(fs$3.readFileSync(candidate, 'utf8'));
10009
+ if (typeof packageJson.version === 'string' && packageJson.version) {
10010
+ return packageJson.version;
10011
+ }
10012
+ }
10013
+ throw new Error('未能读取 @heybox/hb-sdk 当前版本号');
10014
+ }
10015
+ function pathExists(filePath) {
10016
+ try {
10017
+ fs$3.accessSync(filePath, fs$3.constants.F_OK);
10018
+ return true;
10019
+ }
10020
+ catch {
10021
+ return false;
10022
+ }
9574
10023
  }
9575
10024
 
9576
- main().catch(error => {
9577
- if (error instanceof CommanderError && error.exitCode === 0) {
9578
- process.exit(0);
10025
+ async function runCli(options = {}) {
10026
+ const processLike = options.process ?? process;
10027
+ try {
10028
+ await createCliProgram(options).parseAsync(options.argv ?? process.argv);
9579
10029
  }
9580
- if (!(error instanceof CommanderError)) {
9581
- printError(error);
10030
+ catch (error) {
10031
+ if (error instanceof CommanderError && error.exitCode === 0) {
10032
+ processLike.exit(0);
10033
+ return;
10034
+ }
10035
+ if (!(error instanceof CommanderError)) {
10036
+ (options.printError ?? printError)(error);
10037
+ }
10038
+ processLike.exit(error instanceof CommanderError ? error.exitCode : 1);
9582
10039
  }
9583
- process.exit(error instanceof CommanderError ? error.exitCode : 1);
9584
- });
9585
- async function main() {
9586
- await createCliProgram().parseAsync(process.argv);
9587
10040
  }
9588
- function createCliProgram() {
10041
+ function createCliProgram(overrides = {}) {
10042
+ const handlers = {
10043
+ clearLoginStatus: clearLoginStatus,
10044
+ loginToHeybox: loginToHeybox,
10045
+ printLoginStatus: printLoginStatus,
10046
+ printUpdateReminder: printUpdateReminder,
10047
+ runCreateCommand: runCreateCommand,
10048
+ runDevCommand: runDevCommand,
10049
+ runDoctorCommand: runDoctorCommand,
10050
+ ...overrides,
10051
+ };
9589
10052
  const program = new Command();
9590
10053
  program
9591
10054
  .name('hb-sdk')
9592
10055
  .description('hb-sdk developer tools')
10056
+ .version(getCliVersion())
9593
10057
  .showHelpAfterError()
9594
10058
  .exitOverride();
9595
10059
  program
9596
10060
  .command('create')
9597
10061
  .description('创建外部小程序项目开发模板')
9598
10062
  .argument('<project-name>', '项目目录名,例如 my-miniapp')
9599
- .action(async (projectName) => {
9600
- await runCreateCommand(projectName);
9601
- });
10063
+ .action(withUpdateReminder(async (projectName) => {
10064
+ await handlers.runCreateCommand(projectName);
10065
+ }, handlers.printUpdateReminder));
9602
10066
  program
9603
10067
  .command('dev')
9604
- .description('启动当前小程序项目的 Vite dev server')
9605
- .option('--mock', '启动内置浏览器 mock runtime host', false)
10068
+ .description('启动当前小程序项目的 Vite dev server 和浏览器 mock runtime host')
9606
10069
  .option('--port <port>', 'App dev server 端口', parsePositivePort)
9607
10070
  .option('--mock-port <port>', 'Mock host 端口', parsePositivePort)
9608
10071
  .option('--host <host>', '传给 Vite 和 mock host 的 host')
9609
- .option('--open', '打开 app URL 或 mock host URL', false)
9610
- .action(async (options) => {
9611
- await runDevCommand(options);
10072
+ .option('--no-open', '不自动打开浏览器调试页')
10073
+ .action(withUpdateReminder(async (options) => {
10074
+ await handlers.runDevCommand(options);
10075
+ }, handlers.printUpdateReminder));
10076
+ program
10077
+ .command('doctor')
10078
+ .description('诊断 hb-sdk 本地环境和 Agent Skill 版本')
10079
+ .action(async () => {
10080
+ await handlers.runDoctorCommand();
9612
10081
  });
9613
10082
  const login = program
9614
10083
  .command('login')
9615
10084
  .description('登录 Heybox 并缓存 CLI 登录态')
9616
- .action(async () => {
9617
- await loginToHeybox();
9618
- });
10085
+ .action(withUpdateReminder(async () => {
10086
+ await handlers.loginToHeybox();
10087
+ }, handlers.printUpdateReminder));
9619
10088
  login
9620
10089
  .command('status')
9621
10090
  .description('查看脱敏后的 Heybox 登录态')
9622
- .action(async () => {
9623
- await printLoginStatus();
9624
- });
10091
+ .action(withUpdateReminder(async () => {
10092
+ await handlers.printLoginStatus();
10093
+ }, handlers.printUpdateReminder));
9625
10094
  login
9626
10095
  .command('clear')
9627
10096
  .description('清理 hb-sdk 命名空间中的 Heybox 登录态')
9628
- .action(async () => {
9629
- await clearLoginStatus();
9630
- });
10097
+ .action(withUpdateReminder(async () => {
10098
+ await handlers.clearLoginStatus();
10099
+ }, handlers.printUpdateReminder));
9631
10100
  return program;
9632
10101
  }
10102
+ function withUpdateReminder(action, printUpdateReminder) {
10103
+ return async (...args) => {
10104
+ await action(...args);
10105
+ await printUpdateReminder();
10106
+ };
10107
+ }
9633
10108
  function parsePositivePort(value) {
9634
10109
  const parsed = Number(value);
9635
10110
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -9637,3 +10112,6 @@ function parsePositivePort(value) {
9637
10112
  }
9638
10113
  return parsed;
9639
10114
  }
10115
+
10116
+ exports.createCliProgram = createCliProgram;
10117
+ exports.runCli = runCli;