@lobehub/cli 0.0.1 → 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,36 +28139,202 @@ 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
- ".DS_Store",
28144
- ".openclaw",
28145
- "node_modules",
28142
+ const DEFAULT_AGENT_NAME = "OpenClaw";
28143
+ const IDENTITY_FILES = ["IDENTITY.md", "SOUL.md"];
28144
+ const DEFAULT_IGNORE_RULES = [
28146
28145
  ".git",
28146
+ ".svn",
28147
+ ".hg",
28148
+ ".openclaw",
28149
+ ".DS_Store",
28150
+ "Thumbs.db",
28151
+ "desktop.ini",
28152
+ ".idea",
28147
28153
  ".vscode",
28154
+ ".fleet",
28155
+ ".cursor",
28156
+ ".zed",
28157
+ "*.swp",
28158
+ "*.swo",
28159
+ "*~",
28160
+ "node_modules",
28161
+ ".pnp",
28162
+ ".yarn",
28163
+ "bower_components",
28164
+ "vendor",
28165
+ "jspm_packages",
28166
+ ".venv",
28167
+ "venv",
28168
+ "env",
28148
28169
  "__pycache__",
28149
- ".cache"
28150
- ]);
28151
- const OPENCLAW_AGENT_NAME = "OpenClaw";
28170
+ "*.pyc",
28171
+ "*.pyo",
28172
+ ".mypy_cache",
28173
+ ".ruff_cache",
28174
+ ".pytest_cache",
28175
+ ".tox",
28176
+ ".eggs",
28177
+ "*.egg-info",
28178
+ ".bundle",
28179
+ "target",
28180
+ "go.sum",
28181
+ ".gradle",
28182
+ ".m2",
28183
+ "bin",
28184
+ "obj",
28185
+ "packages",
28186
+ ".cache",
28187
+ ".parcel-cache",
28188
+ ".next",
28189
+ ".nuxt",
28190
+ ".turbo",
28191
+ ".output",
28192
+ "dist",
28193
+ "build",
28194
+ "out",
28195
+ ".sass-cache",
28196
+ ".env",
28197
+ ".env.*",
28198
+ "coverage",
28199
+ ".nyc_output",
28200
+ ".terraform",
28201
+ "tmp",
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
+ ];
28152
28263
  /**
28153
- * Recursively collect all files under `dir`, skipping excluded directories/files.
28264
+ * Try to extract the agent name, description, and avatar emoji from
28265
+ * IDENTITY.md or SOUL.md. Falls back to "OpenClaw" if neither file
28266
+ * exists or parsing fails.
28267
+ */
28268
+ function readAgentProfile(workspacePath) {
28269
+ for (const filename of IDENTITY_FILES) {
28270
+ const filePath = path.join(workspacePath, filename);
28271
+ if (!fs.existsSync(filePath)) continue;
28272
+ const content = fs.readFileSync(filePath, "utf8");
28273
+ const nameMatch = content.match(/\*{0,2}Name:?\*{0,2}\s*(.+)/i);
28274
+ const title = nameMatch ? nameMatch[1].trim() : DEFAULT_AGENT_NAME;
28275
+ const descMatch = content.match(/\*{0,2}(?:Creature|Vibe|Description):?\*{0,2}\s*(.+)/i);
28276
+ const description = descMatch ? descMatch[1].trim() : void 0;
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);
28280
+ return {
28281
+ avatar: rawAvatar && !isPlaceholder ? rawAvatar : void 0,
28282
+ description,
28283
+ title
28284
+ };
28285
+ }
28286
+ return { title: DEFAULT_AGENT_NAME };
28287
+ }
28288
+ /**
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.
28154
28301
  * Returns paths relative to `baseDir`.
28155
28302
  */
28156
- function collectFiles(dir, baseDir) {
28303
+ function collectFiles(dir, baseDir, ig) {
28157
28304
  const results = [];
28158
28305
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
28159
- 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;
28160
28309
  const fullPath = path.join(dir, entry.name);
28161
- if (entry.isDirectory()) results.push(...collectFiles(fullPath, baseDir));
28162
- 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);
28163
28312
  }
28164
28313
  return results;
28165
28314
  }
28166
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
+ /**
28167
28334
  * Resolve the target agent ID.
28168
- * Priority: --agent-id > --slug > create new "OpenClaw" agent.
28335
+ * Priority: --agent-id > --slug > create new agent from workspace profile.
28169
28336
  */
28170
- async function resolveAgentId(client, opts) {
28337
+ async function resolveAgentId(client, opts, profile) {
28171
28338
  if (opts.agentId) return opts.agentId;
28172
28339
  if (opts.slug) {
28173
28340
  const agent = await client.agent.getBuiltinAgent.query({ slug: opts.slug });
@@ -28177,17 +28344,23 @@ async function resolveAgentId(client, opts) {
28177
28344
  }
28178
28345
  return agent.id;
28179
28346
  }
28180
- log$1.info(`Creating new agent "${OPENCLAW_AGENT_NAME}"...`);
28181
- const id = (await client.agent.createAgent.mutate({ config: { title: OPENCLAW_AGENT_NAME } })).agentId;
28347
+ const label = formatAgentLabel(profile);
28348
+ log$1.info(`Creating new agent ${import_picocolors.default.bold(label)}...`);
28349
+ const id = (await client.agent.createAgent.mutate({ config: {
28350
+ avatar: profile.avatar,
28351
+ description: profile.description,
28352
+ title: profile.title
28353
+ } })).agentId;
28182
28354
  if (!id) {
28183
28355
  log$1.error("Failed to create agent — no agentId returned.");
28184
28356
  process.exit(1);
28185
28357
  }
28186
- console.log(`${import_picocolors.default.green("✓")} Agent created: ${import_picocolors.default.bold(id)}`);
28358
+ console.log(`${import_picocolors.default.green("✓")} Agent created: ${import_picocolors.default.bold(label)}`);
28187
28359
  return id;
28188
28360
  }
28189
28361
  function registerOpenClawMigration(migrate) {
28190
- 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) => {
28363
+ if (!options.dryRun) await getTrpcClient();
28191
28364
  const workspacePath = path.resolve(options.source);
28192
28365
  if (!fs.existsSync(workspacePath)) {
28193
28366
  log$1.error(`OpenClaw workspace not found: ${workspacePath}`);
@@ -28197,7 +28370,9 @@ function registerOpenClawMigration(migrate) {
28197
28370
  log$1.error(`Not a directory: ${workspacePath}`);
28198
28371
  process.exit(1);
28199
28372
  }
28200
- const files = collectFiles(workspacePath, workspacePath);
28373
+ const profile = readAgentProfile(workspacePath);
28374
+ const label = formatAgentLabel(profile);
28375
+ const files = collectFiles(workspacePath, workspacePath, buildIgnoreFilter(workspacePath));
28201
28376
  if (files.length === 0) {
28202
28377
  log$1.info("No files found in workspace.");
28203
28378
  return;
@@ -28210,38 +28385,48 @@ function registerOpenClawMigration(migrate) {
28210
28385
  return;
28211
28386
  }
28212
28387
  if (!options.yes) {
28213
- const target = options.agentId ? `agent ${import_picocolors.default.bold(options.agentId)}` : options.slug ? `agent slug "${import_picocolors.default.bold(options.slug)}"` : `a new "${OPENCLAW_AGENT_NAME}" 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`;
28214
28389
  if (!await confirm(`Import ${files.length} file(s) as agent documents into ${target}?`)) {
28215
28390
  console.log("Cancelled.");
28216
28391
  return;
28217
28392
  }
28218
28393
  }
28219
28394
  const client = await getTrpcClient();
28220
- const agentId = await resolveAgentId(client, options);
28221
- console.log(`\nImporting to agent ${import_picocolors.default.bold(agentId)}...\n`);
28395
+ const agentId = await resolveAgentId(client, options, profile);
28396
+ console.log(`\nImporting to ${import_picocolors.default.bold(label)}...\n`);
28222
28397
  let success = 0;
28223
28398
  let failed = 0;
28399
+ let skipped = 0;
28224
28400
  for (const relativePath of files) {
28225
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
+ }
28226
28407
  const content = fs.readFileSync(fullPath, "utf8");
28227
- const filename = relativePath;
28408
+ const createdAt = fs.statSync(fullPath).mtime;
28228
28409
  try {
28229
28410
  await client.agentDocument.upsertDocument.mutate({
28230
28411
  agentId,
28231
28412
  content,
28232
- filename
28413
+ createdAt,
28414
+ filename: relativePath
28233
28415
  });
28234
- console.log(` ${import_picocolors.default.green("✓")} ${filename}`);
28416
+ console.log(` ${import_picocolors.default.green("✓")} ${relativePath}`);
28235
28417
  success++;
28236
28418
  } catch (err) {
28237
- console.log(` ${import_picocolors.default.red("✗")} ${filename} — ${err.message || err}`);
28419
+ console.log(` ${import_picocolors.default.red("✗")} ${relativePath} — ${err.message || err}`);
28238
28420
  failed++;
28239
28421
  }
28240
28422
  }
28241
- console.log();
28242
- console.log(`${import_picocolors.default.green("✓")} Done: ${import_picocolors.default.bold(String(success))} imported` + (failed > 0 ? `, ${import_picocolors.default.red(String(failed))} failed` : ""));
28243
28423
  const agentUrl = `${resolveServerUrl()}/agent/${agentId}`;
28244
- 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();
28245
28430
  });
28246
28431
  }
28247
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.1" "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.1",
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:*",