@lobehub/cli 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import os from "node:os";
9
9
  import crypto, { randomUUID } from "node:crypto";
10
10
  import { createInterface } from "node:readline";
11
11
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
12
+ import ignore from "ignore";
12
13
  //#region \0rolldown/runtime.js
13
14
  var __create$2 = Object.create;
14
15
  var __defProp$2 = Object.defineProperty;
@@ -5121,7 +5122,7 @@ async function getAuthAndServer() {
5121
5122
  }
5122
5123
  const result = await getValidToken();
5123
5124
  if (!result) {
5124
- log$1.error(`No authentication found. Run 'lh login' first, or set ${CLI_API_KEY_ENV}.`);
5125
+ log$1.error(`No authentication found. Run 'lh login' (or 'npx -y @lobehub/cli login') first, or set ${CLI_API_KEY_ENV}.`);
5125
5126
  process.exit(1);
5126
5127
  }
5127
5128
  const serverUrl = resolveServerUrl();
@@ -28138,52 +28139,127 @@ function registerMessageCommand(program) {
28138
28139
  }
28139
28140
  //#endregion
28140
28141
  //#region src/commands/migrate/openclaw.ts
28141
- const EXCLUDED_NAMES = new Set([
28142
- ".idea",
28143
- ".vscode",
28144
- ".fleet",
28145
- ".cursor",
28146
- ".zed",
28142
+ const DEFAULT_AGENT_NAME = "OpenClaw";
28143
+ const IDENTITY_FILES = ["IDENTITY.md", "SOUL.md"];
28144
+ const DEFAULT_IGNORE_RULES = [
28147
28145
  ".git",
28148
28146
  ".svn",
28149
28147
  ".hg",
28148
+ ".openclaw",
28150
28149
  ".DS_Store",
28151
28150
  "Thumbs.db",
28152
28151
  "desktop.ini",
28153
- ".openclaw",
28152
+ ".idea",
28153
+ ".vscode",
28154
+ ".fleet",
28155
+ ".cursor",
28156
+ ".zed",
28157
+ "*.swp",
28158
+ "*.swo",
28159
+ "*~",
28154
28160
  "node_modules",
28155
28161
  ".pnp",
28162
+ ".yarn",
28156
28163
  "bower_components",
28157
28164
  "vendor",
28165
+ "jspm_packages",
28158
28166
  ".venv",
28159
28167
  "venv",
28168
+ "env",
28160
28169
  "__pycache__",
28170
+ "*.pyc",
28171
+ "*.pyo",
28161
28172
  ".mypy_cache",
28162
28173
  ".ruff_cache",
28163
28174
  ".pytest_cache",
28164
28175
  ".tox",
28165
28176
  ".eggs",
28166
28177
  "*.egg-info",
28178
+ ".bundle",
28179
+ "target",
28180
+ "go.sum",
28181
+ ".gradle",
28182
+ ".m2",
28183
+ "bin",
28184
+ "obj",
28185
+ "packages",
28167
28186
  ".cache",
28168
28187
  ".parcel-cache",
28169
28188
  ".next",
28170
28189
  ".nuxt",
28171
28190
  ".turbo",
28191
+ ".output",
28172
28192
  "dist",
28173
28193
  "build",
28174
28194
  "out",
28175
- ".output",
28195
+ ".sass-cache",
28176
28196
  ".env",
28177
- ".env.local",
28197
+ ".env.*",
28178
28198
  "coverage",
28179
28199
  ".nyc_output",
28180
28200
  ".terraform",
28181
- ".sass-cache",
28182
28201
  "tmp",
28183
- ".tmp"
28184
- ]);
28185
- const DEFAULT_AGENT_NAME = "OpenClaw";
28186
- const IDENTITY_FILES = ["IDENTITY.md", "SOUL.md"];
28202
+ ".tmp",
28203
+ "*.log",
28204
+ "logs",
28205
+ "*.sqlite",
28206
+ "*.sqlite3",
28207
+ "*.db",
28208
+ "*.db-shm",
28209
+ "*.db-wal",
28210
+ "*.ldb",
28211
+ "*.mdb",
28212
+ "*.accdb",
28213
+ "*.zip",
28214
+ "*.tar",
28215
+ "*.tar.gz",
28216
+ "*.tgz",
28217
+ "*.gz",
28218
+ "*.bz2",
28219
+ "*.xz",
28220
+ "*.rar",
28221
+ "*.7z",
28222
+ "*.jar",
28223
+ "*.war",
28224
+ "*.dll",
28225
+ "*.so",
28226
+ "*.dylib",
28227
+ "*.exe",
28228
+ "*.bin",
28229
+ "*.o",
28230
+ "*.a",
28231
+ "*.lib",
28232
+ "*.class",
28233
+ "*.png",
28234
+ "*.jpg",
28235
+ "*.jpeg",
28236
+ "*.gif",
28237
+ "*.bmp",
28238
+ "*.ico",
28239
+ "*.webp",
28240
+ "*.svg",
28241
+ "*.mp3",
28242
+ "*.mp4",
28243
+ "*.wav",
28244
+ "*.avi",
28245
+ "*.mov",
28246
+ "*.mkv",
28247
+ "*.flac",
28248
+ "*.ogg",
28249
+ "*.pdf",
28250
+ "*.woff",
28251
+ "*.woff2",
28252
+ "*.ttf",
28253
+ "*.otf",
28254
+ "*.eot",
28255
+ "package-lock.json",
28256
+ "yarn.lock",
28257
+ "pnpm-lock.yaml",
28258
+ "Gemfile.lock",
28259
+ "Cargo.lock",
28260
+ "poetry.lock",
28261
+ "composer.lock"
28262
+ ];
28187
28263
  /**
28188
28264
  * Try to extract the agent name, description, and avatar emoji from
28189
28265
  * IDENTITY.md or SOUL.md. Falls back to "OpenClaw" if neither file
@@ -28199,8 +28275,10 @@ function readAgentProfile(workspacePath) {
28199
28275
  const descMatch = content.match(/\*{0,2}(?:Creature|Vibe|Description):?\*{0,2}\s*(.+)/i);
28200
28276
  const description = descMatch ? descMatch[1].trim() : void 0;
28201
28277
  const emojiMatch = content.match(/\*{0,2}Emoji:?\*{0,2}\s*(.+)/i);
28278
+ const rawAvatar = emojiMatch ? emojiMatch[1].trim() : void 0;
28279
+ const isPlaceholder = rawAvatar && /^[_*((].*[))_*]$|^(?:tbd|todo|n\/?a|none|待定|未定)$/i.test(rawAvatar);
28202
28280
  return {
28203
- avatar: emojiMatch ? emojiMatch[1].trim() : void 0,
28281
+ avatar: rawAvatar && !isPlaceholder ? rawAvatar : void 0,
28204
28282
  description,
28205
28283
  title
28206
28284
  };
@@ -28208,20 +28286,51 @@ function readAgentProfile(workspacePath) {
28208
28286
  return { title: DEFAULT_AGENT_NAME };
28209
28287
  }
28210
28288
  /**
28211
- * Recursively collect all files under `dir`, skipping excluded directories/files.
28289
+ * Build an ignore filter for the workspace. Uses .gitignore if present,
28290
+ * otherwise falls back to a comprehensive default rule set.
28291
+ */
28292
+ function buildIgnoreFilter(workspacePath) {
28293
+ const ig = ignore();
28294
+ const gitignorePath = path.join(workspacePath, ".gitignore");
28295
+ if (fs.existsSync(gitignorePath)) ig.add(fs.readFileSync(gitignorePath, "utf8"));
28296
+ ig.add(DEFAULT_IGNORE_RULES);
28297
+ return ig;
28298
+ }
28299
+ /**
28300
+ * Recursively collect all files under `dir`, filtered by ignore rules.
28212
28301
  * Returns paths relative to `baseDir`.
28213
28302
  */
28214
- function collectFiles(dir, baseDir) {
28303
+ function collectFiles(dir, baseDir, ig) {
28215
28304
  const results = [];
28216
28305
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
28217
- if (EXCLUDED_NAMES.has(entry.name)) continue;
28306
+ const relativePath = path.relative(baseDir, path.join(dir, entry.name));
28307
+ const testPath = entry.isDirectory() ? `${relativePath}/` : relativePath;
28308
+ if (ig.ignores(testPath)) continue;
28218
28309
  const fullPath = path.join(dir, entry.name);
28219
- if (entry.isDirectory()) results.push(...collectFiles(fullPath, baseDir));
28220
- else if (entry.isFile()) results.push(path.relative(baseDir, fullPath));
28310
+ if (entry.isDirectory()) results.push(...collectFiles(fullPath, baseDir, ig));
28311
+ else if (entry.isFile()) results.push(relativePath);
28221
28312
  }
28222
28313
  return results;
28223
28314
  }
28224
28315
  /**
28316
+ * Quick check: read the first 8KB and look for null bytes.
28317
+ * If found, the file is likely binary and should be skipped.
28318
+ */
28319
+ function isBinaryFile(filePath) {
28320
+ const fd = fs.openSync(filePath, "r");
28321
+ try {
28322
+ const buf = Buffer.alloc(8192);
28323
+ const bytesRead = fs.readSync(fd, buf, 0, 8192, 0);
28324
+ for (let i = 0; i < bytesRead; i++) if (buf[i] === 0) return true;
28325
+ return false;
28326
+ } finally {
28327
+ fs.closeSync(fd);
28328
+ }
28329
+ }
28330
+ function formatAgentLabel(profile) {
28331
+ return profile.avatar ? `${profile.avatar} ${profile.title}` : profile.title;
28332
+ }
28333
+ /**
28225
28334
  * Resolve the target agent ID.
28226
28335
  * Priority: --agent-id > --slug > create new agent from workspace profile.
28227
28336
  */
@@ -28235,7 +28344,8 @@ async function resolveAgentId(client, opts, profile) {
28235
28344
  }
28236
28345
  return agent.id;
28237
28346
  }
28238
- log$1.info(`Creating new agent "${profile.title}"...`);
28347
+ const label = formatAgentLabel(profile);
28348
+ log$1.info(`Creating new agent ${import_picocolors.default.bold(label)}...`);
28239
28349
  const id = (await client.agent.createAgent.mutate({ config: {
28240
28350
  avatar: profile.avatar,
28241
28351
  description: profile.description,
@@ -28245,12 +28355,11 @@ async function resolveAgentId(client, opts, profile) {
28245
28355
  log$1.error("Failed to create agent — no agentId returned.");
28246
28356
  process.exit(1);
28247
28357
  }
28248
- const label = profile.avatar ? `${profile.avatar} ${profile.title}` : profile.title;
28249
28358
  console.log(`${import_picocolors.default.green("✓")} Agent created: ${import_picocolors.default.bold(label)}`);
28250
28359
  return id;
28251
28360
  }
28252
28361
  function registerOpenClawMigration(migrate) {
28253
- migrate.command("openclaw").description("Import OpenClaw workspace files as agent documents into a new \"OpenClaw\" agent").option("--source <path>", "Path to OpenClaw workspace", path.join(process.env.HOME || "~", ".openclaw", "workspace")).option("--agent-id <id>", "Import into an existing agent by ID").option("--slug <slug>", "Import into an existing agent by slug (e.g. \"inbox\")").option("--dry-run", "Preview files without importing").option("--yes", "Skip confirmation prompt").action(async (options) => {
28362
+ migrate.command("openclaw").description("Import OpenClaw workspace files as agent documents").option("--source <path>", "Path to OpenClaw workspace", path.join(process.env.HOME || "~", ".openclaw", "workspace")).option("--agent-id <id>", "Import into an existing agent by ID").option("--slug <slug>", "Import into an existing agent by slug (e.g. \"inbox\")").option("--dry-run", "Preview files without importing").option("--yes", "Skip confirmation prompt").action(async (options) => {
28254
28363
  if (!options.dryRun) await getTrpcClient();
28255
28364
  const workspacePath = path.resolve(options.source);
28256
28365
  if (!fs.existsSync(workspacePath)) {
@@ -28262,7 +28371,8 @@ function registerOpenClawMigration(migrate) {
28262
28371
  process.exit(1);
28263
28372
  }
28264
28373
  const profile = readAgentProfile(workspacePath);
28265
- const files = collectFiles(workspacePath, workspacePath);
28374
+ const label = formatAgentLabel(profile);
28375
+ const files = collectFiles(workspacePath, workspacePath, buildIgnoreFilter(workspacePath));
28266
28376
  if (files.length === 0) {
28267
28377
  log$1.info("No files found in workspace.");
28268
28378
  return;
@@ -28275,8 +28385,7 @@ function registerOpenClawMigration(migrate) {
28275
28385
  return;
28276
28386
  }
28277
28387
  if (!options.yes) {
28278
- const agentLabel = profile.avatar ? `${profile.avatar} ${profile.title}` : profile.title;
28279
- const target = options.agentId ? `agent ${import_picocolors.default.bold(options.agentId)}` : options.slug ? `agent slug "${import_picocolors.default.bold(options.slug)}"` : `a new ${import_picocolors.default.bold(agentLabel)} agent`;
28388
+ const target = options.agentId ? `agent ${import_picocolors.default.bold(options.agentId)}` : options.slug ? `agent slug "${import_picocolors.default.bold(options.slug)}"` : `a new ${import_picocolors.default.bold(label)} agent`;
28280
28389
  if (!await confirm(`Import ${files.length} file(s) as agent documents into ${target}?`)) {
28281
28390
  console.log("Cancelled.");
28282
28391
  return;
@@ -28284,31 +28393,40 @@ function registerOpenClawMigration(migrate) {
28284
28393
  }
28285
28394
  const client = await getTrpcClient();
28286
28395
  const agentId = await resolveAgentId(client, options, profile);
28287
- const displayName = profile.avatar ? `${profile.avatar} ${profile.title}` : profile.title;
28288
- console.log(`\nImporting to ${import_picocolors.default.bold(displayName)}...\n`);
28396
+ console.log(`\nImporting to ${import_picocolors.default.bold(label)}...\n`);
28289
28397
  let success = 0;
28290
28398
  let failed = 0;
28399
+ let skipped = 0;
28291
28400
  for (const relativePath of files) {
28292
28401
  const fullPath = path.join(workspacePath, relativePath);
28402
+ if (isBinaryFile(fullPath)) {
28403
+ console.log(` ${import_picocolors.default.dim("○")} ${relativePath} ${import_picocolors.default.dim("(binary, skipped)")}`);
28404
+ skipped++;
28405
+ continue;
28406
+ }
28293
28407
  const content = fs.readFileSync(fullPath, "utf8");
28294
- const filename = relativePath;
28408
+ const createdAt = fs.statSync(fullPath).mtime;
28295
28409
  try {
28296
28410
  await client.agentDocument.upsertDocument.mutate({
28297
28411
  agentId,
28298
28412
  content,
28299
- filename
28413
+ createdAt,
28414
+ filename: relativePath
28300
28415
  });
28301
- console.log(` ${import_picocolors.default.green("✓")} ${filename}`);
28416
+ console.log(` ${import_picocolors.default.green("✓")} ${relativePath}`);
28302
28417
  success++;
28303
28418
  } catch (err) {
28304
- console.log(` ${import_picocolors.default.red("✗")} ${filename} — ${err.message || err}`);
28419
+ console.log(` ${import_picocolors.default.red("✗")} ${relativePath} — ${err.message || err}`);
28305
28420
  failed++;
28306
28421
  }
28307
28422
  }
28308
- console.log();
28309
- console.log(`${import_picocolors.default.green("✓")} Done: ${import_picocolors.default.bold(String(success))} imported` + (failed > 0 ? `, ${import_picocolors.default.red(String(failed))} failed` : ""));
28310
28423
  const agentUrl = `${resolveServerUrl()}/agent/${agentId}`;
28311
- console.log(`\n${import_picocolors.default.bold("Open agent:")} ${import_picocolors.default.underline(agentUrl)}`);
28424
+ const skippedInfo = skipped > 0 ? `, ${skipped} skipped` : "";
28425
+ console.log();
28426
+ if (failed === 0) console.log(`${import_picocolors.default.green("✓")} Migration complete! ${import_picocolors.default.bold(String(success))} file(s) imported to ${import_picocolors.default.bold(label)}.${skippedInfo}`);
28427
+ else console.log(`${import_picocolors.default.yellow("⚠")} Migration finished with issues: ${import_picocolors.default.bold(String(success))} imported, ${import_picocolors.default.red(String(failed))} failed${skippedInfo}.`);
28428
+ console.log(`\n ${import_picocolors.default.dim("→")} ${import_picocolors.default.underline(agentUrl)}`);
28429
+ console.log();
28312
28430
  });
28313
28431
  }
28314
28432
  //#endregion
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.2" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.3" "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.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "lh": "./dist/index.js",
@@ -27,6 +27,9 @@
27
27
  "test:coverage": "bunx vitest run --config vitest.config.mts --coverage",
28
28
  "type-check": "tsc --noEmit"
29
29
  },
30
+ "dependencies": {
31
+ "ignore": "^7.0.5"
32
+ },
30
33
  "devDependencies": {
31
34
  "@lobechat/device-gateway-client": "workspace:*",
32
35
  "@lobechat/local-file-shell": "workspace:*",