dsh-lark-bot 0.6.0 → 0.7.0

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/cli.js CHANGED
@@ -134,16 +134,29 @@ var init_dsh_runtime = __esm({
134
134
  // src/adapters/dsh/own-package.ts
135
135
  import { readFileSync } from "fs";
136
136
  import { fileURLToPath } from "url";
137
- import { join as join2 } from "path";
137
+ import { dirname, join as join2 } from "path";
138
138
  function ownPackageInfo() {
139
- const root = fileURLToPath(new URL("../../..", import.meta.url));
140
- let name = "dsh-lark-bot";
141
- try {
142
- const pkg = JSON.parse(readFileSync(join2(root, "package.json"), "utf8"));
143
- if (typeof pkg.name === "string" && pkg.name) name = pkg.name;
144
- } catch {
139
+ return findOwnPackageRoot(dirname(fileURLToPath(import.meta.url)));
140
+ }
141
+ function findOwnPackageRoot(startDir) {
142
+ let dir = startDir;
143
+ for (let depth = 0; depth < 8; depth += 1) {
144
+ const pkgPath = join2(dir, "package.json");
145
+ try {
146
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
147
+ if (typeof pkg.name === "string" && isOwnPackageName(pkg.name)) {
148
+ return { name: pkg.name, root: dir };
149
+ }
150
+ } catch {
151
+ }
152
+ const parent = dirname(dir);
153
+ if (parent === dir) break;
154
+ dir = parent;
145
155
  }
146
- return { name, root };
156
+ return { name: "dsh-lark-bot", root: process.cwd() };
157
+ }
158
+ function isOwnPackageName(name) {
159
+ return name === "dsh-lark-bot" || name === "dsh-feishu-bot" || /^@[^/]+\/(dsh-lark-bot|dsh-feishu-bot)$/.test(name);
147
160
  }
148
161
  var init_own_package = __esm({
149
162
  "src/adapters/dsh/own-package.ts"() {
@@ -153,7 +166,8 @@ var init_own_package = __esm({
153
166
 
154
167
  // src/adapters/dsh/acp-runtime.ts
155
168
  import { spawn as spawn4 } from "child_process";
156
- import { existsSync as existsSync4 } from "fs";
169
+ import { existsSync as existsSync4, realpathSync as realpathSync2 } from "fs";
170
+ import { readFileSync as readFileSync3 } from "fs";
157
171
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
158
172
  import { join as join4 } from "path";
159
173
  function acpProfileRoot(home, profile, env) {
@@ -224,7 +238,17 @@ function acpPluginInstalled(profileRoot) {
224
238
  }
225
239
  function isAcpProfileReady(profileRoot) {
226
240
  const own = ownPackageInfo();
227
- return existsSync4(join4(profileRoot, "package.json")) && existsSync4(join4(profileRoot, "cordis.yml")) && existsSync4(join4(profileRoot, "cordis.patch.yml")) && acpPluginInstalled(profileRoot) && existsSync4(join4(profileRoot, "node_modules", own.name));
241
+ return existsSync4(join4(profileRoot, "package.json")) && existsSync4(join4(profileRoot, "cordis.yml")) && existsSync4(join4(profileRoot, "cordis.patch.yml")) && acpPluginInstalled(profileRoot) && ownPackageLinked2(profileRoot, own.name);
242
+ }
243
+ function ownPackageLinked2(profileRoot, ownName) {
244
+ const linkPath = join4(profileRoot, "node_modules", ownName);
245
+ try {
246
+ const real = realpathSync2(linkPath);
247
+ const pkg = JSON.parse(readFileSync3(join4(real, "package.json"), "utf8"));
248
+ return pkg.name === ownName && pkg.dsh?.bundle?.patch !== void 0;
249
+ } catch {
250
+ return false;
251
+ }
228
252
  }
229
253
  function runPnpmInstall2(profileRoot) {
230
254
  return new Promise((resolve5, reject) => {
@@ -633,8 +657,8 @@ var init_acp_adapter = __esm({
633
657
  });
634
658
 
635
659
  // src/cli.ts
636
- import { readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
637
- import { fileURLToPath as fileURLToPath3 } from "url";
660
+ import { readFileSync as readFileSync4, realpathSync as realpathSync4 } from "fs";
661
+ import { fileURLToPath as fileURLToPath2 } from "url";
638
662
  import { Command } from "commander";
639
663
 
640
664
  // src/cli/commands/doctor.ts
@@ -1235,7 +1259,8 @@ init_dsh_compat();
1235
1259
  init_dsh_runtime();
1236
1260
  init_own_package();
1237
1261
  import { spawn as spawn3 } from "child_process";
1238
- import { existsSync as existsSync3 } from "fs";
1262
+ import { existsSync as existsSync3, realpathSync } from "fs";
1263
+ import { readFileSync as readFileSync2 } from "fs";
1239
1264
  import { mkdir, writeFile } from "fs/promises";
1240
1265
  import { join as join3 } from "path";
1241
1266
  var SDK_SERVER_PACKAGE = "@deepseek-ai/dsh-sdk-jsonrpc-server";
@@ -1310,7 +1335,17 @@ function sdkServerInstalled(profileRoot) {
1310
1335
  }
1311
1336
  function isSdkProfileReady(profileRoot) {
1312
1337
  const own = ownPackageInfo();
1313
- return existsSync3(join3(profileRoot, "package.json")) && existsSync3(join3(profileRoot, "cordis.yml")) && existsSync3(join3(profileRoot, "cordis.patch.yml")) && sdkServerInstalled(profileRoot) && existsSync3(join3(profileRoot, "node_modules", own.name));
1338
+ return existsSync3(join3(profileRoot, "package.json")) && existsSync3(join3(profileRoot, "cordis.yml")) && existsSync3(join3(profileRoot, "cordis.patch.yml")) && sdkServerInstalled(profileRoot) && ownPackageLinked(profileRoot, own.name);
1339
+ }
1340
+ function ownPackageLinked(profileRoot, ownName) {
1341
+ const linkPath = join3(profileRoot, "node_modules", ownName);
1342
+ try {
1343
+ const real = realpathSync(linkPath);
1344
+ const pkg = JSON.parse(readFileSync2(join3(real, "package.json"), "utf8"));
1345
+ return pkg.name === ownName && pkg.dsh?.bundle?.patch !== void 0;
1346
+ } catch {
1347
+ return false;
1348
+ }
1314
1349
  }
1315
1350
  function runPnpmInstall(profileRoot) {
1316
1351
  return new Promise((resolve5, reject) => {
@@ -1446,13 +1481,7 @@ function resolveAppPaths(root = defaultHome()) {
1446
1481
  archivesDir: (profile) => profilePath(profile, "archives"),
1447
1482
  workspacesFile: (profile) => profilePath(profile, "workspaces.json"),
1448
1483
  mediaDir: (profile) => profilePath(profile, "media"),
1449
- logsDir: (profile) => profilePath(profile, "logs"),
1450
- serviceDir: join5(root, "service"),
1451
- serviceEnvFile: join5(root, "service", "service.env"),
1452
- serviceMetadataFile: join5(root, "service", "service.json"),
1453
- serviceLogFile: (profile) => profilePath(profile, "logs", "bot.log"),
1454
- registryFile: join5(root, "registry", "processes.json"),
1455
- locksDir: join5(root, "registry", "locks")
1484
+ logsDir: (profile) => profilePath(profile, "logs")
1456
1485
  };
1457
1486
  }
1458
1487
 
@@ -1612,14 +1641,14 @@ function loadRuntimeEnv(source = process.env) {
1612
1641
  // src/config/profile-store.ts
1613
1642
  import { readFile } from "fs/promises";
1614
1643
  import { mkdir as mkdir4 } from "fs/promises";
1615
- import { dirname as dirname2 } from "path";
1644
+ import { dirname as dirname3 } from "path";
1616
1645
 
1617
1646
  // src/platform/atomic-write.ts
1618
1647
  import { randomBytes } from "crypto";
1619
1648
  import { mkdir as mkdir3, rename, writeFile as writeFile3 } from "fs/promises";
1620
- import { dirname, join as join7 } from "path";
1649
+ import { dirname as dirname2, join as join7 } from "path";
1621
1650
  async function writeFileAtomic(target, data, options = {}) {
1622
- const directory = dirname(target);
1651
+ const directory = dirname2(target);
1623
1652
  await mkdir3(directory, { recursive: true });
1624
1653
  const temporary = join7(directory, `.${randomBytes(8).toString("hex")}.tmp`);
1625
1654
  try {
@@ -1631,9 +1660,9 @@ async function writeFileAtomic(target, data, options = {}) {
1631
1660
  }
1632
1661
  }
1633
1662
  async function rmSilently(path) {
1634
- const { rm: rm4 } = await import("fs/promises");
1663
+ const { rm: rm2 } = await import("fs/promises");
1635
1664
  try {
1636
- await rm4(path, { force: true });
1665
+ await rm2(path, { force: true });
1637
1666
  } catch {
1638
1667
  }
1639
1668
  }
@@ -1725,7 +1754,7 @@ var ConfigStore = class {
1725
1754
  }
1726
1755
  async persist() {
1727
1756
  const data = this.getData();
1728
- await mkdir4(dirname2(this.path), { recursive: true });
1757
+ await mkdir4(dirname3(this.path), { recursive: true });
1729
1758
  await writeFileAtomic(this.path, `${JSON.stringify(data, null, 2)}
1730
1759
  `, {
1731
1760
  mode: 384
@@ -1857,7 +1886,7 @@ var ActiveRuns = class {
1857
1886
  };
1858
1887
 
1859
1888
  // src/config/security.ts
1860
- import { realpathSync } from "fs";
1889
+ import { realpathSync as realpathSync3 } from "fs";
1861
1890
  import { isIP } from "net";
1862
1891
  import { isAbsolute, relative, resolve as resolve3, sep } from "path";
1863
1892
  var REDACTED = "[redacted]";
@@ -1875,7 +1904,7 @@ function redactSecrets(text) {
1875
1904
  }
1876
1905
  function resolveReal(path) {
1877
1906
  try {
1878
- return realpathSync(path);
1907
+ return realpathSync3(path);
1879
1908
  } catch {
1880
1909
  return resolve3(path);
1881
1910
  }
@@ -2959,7 +2988,7 @@ function errorMessage(error) {
2959
2988
  // src/config/dsh-config.ts
2960
2989
  import { mkdir as mkdir5, readFile as readFile3, rm, writeFile as writeFile4 } from "fs/promises";
2961
2990
  import { homedir as homedir5 } from "os";
2962
- import { dirname as dirname3, join as join8 } from "path";
2991
+ import { dirname as dirname4, join as join8 } from "path";
2963
2992
  import { Document, parseDocument } from "yaml";
2964
2993
  init_dsh_runtime();
2965
2994
  var DEEPSEEK_NAMESPACE = "llm-deepseek";
@@ -3315,7 +3344,7 @@ var DshProviderManager = class {
3315
3344
  throw new Error(`\u975E\u6CD5\u51ED\u636E\u5F15\u7528\u540D\uFF1A${ref}\uFF08\u5E94\u4E3A POSIX \u73AF\u5883\u53D8\u91CF\u540D\uFF0C\u5982 OPENAI_API_KEY\uFF09`);
3316
3345
  }
3317
3346
  if (value.length === 0) throw new Error("\u51ED\u636E\u503C\u4E0D\u80FD\u4E3A\u7A7A");
3318
- await mkdir5(dirname3(this.credentialsFile), { recursive: true, mode: 448 });
3347
+ await mkdir5(dirname4(this.credentialsFile), { recursive: true, mode: 448 });
3319
3348
  await withFileLock(this.credentialsFile, async () => {
3320
3349
  const text = await readOptional(this.credentialsFile);
3321
3350
  const root = parseYamlMap(text, this.credentialsFile);
@@ -3345,7 +3374,7 @@ var DshProviderManager = class {
3345
3374
  return removed;
3346
3375
  }
3347
3376
  async writeNamespace(ns, section) {
3348
- await mkdir5(dirname3(this.settingsFile), { recursive: true });
3377
+ await mkdir5(dirname4(this.settingsFile), { recursive: true });
3349
3378
  await withFileLock(this.settingsFile, async () => {
3350
3379
  const text = await readOptional(this.settingsFile);
3351
3380
  const root = parseYamlMap(text, this.settingsFile);
@@ -5206,7 +5235,7 @@ async function safeRemove(path) {
5206
5235
  import { execFile as execFile2 } from "child_process";
5207
5236
  import { randomBytes as randomBytes4 } from "crypto";
5208
5237
  import { access as access2, copyFile, mkdir as mkdir8 } from "fs/promises";
5209
- import { dirname as dirname4, join as join11 } from "path";
5238
+ import { dirname as dirname5, join as join11 } from "path";
5210
5239
  import { promisify as promisify2 } from "util";
5211
5240
  var execFileAsync2 = promisify2(execFile2);
5212
5241
  async function defaultRunGit2(args, cwd) {
@@ -5225,7 +5254,7 @@ async function exists2(path) {
5225
5254
  }
5226
5255
  }
5227
5256
  async function defaultCopyRulesFile(source, target) {
5228
- await mkdir8(dirname4(target), { recursive: true });
5257
+ await mkdir8(dirname5(target), { recursive: true });
5229
5258
  await copyFile(source, target);
5230
5259
  }
5231
5260
  function slugify2(scope) {
@@ -5380,10 +5409,7 @@ async function ensureBotProfile(store, options) {
5380
5409
  }
5381
5410
  if (store.getProfile(profileName)) return true;
5382
5411
  if (!options.allowOnboarding) {
5383
- process.stderr.write(
5384
- "\u672A\u68C0\u6D4B\u5230\u98DE\u4E66 / Lark \u5E94\u7528\u51ED\u636E\u3002\u8BF7\u5148\u5728\u7EC8\u7AEF\u8FD0\u884C `dsh-lark-bot start` \u5B8C\u6210\u626B\u7801\u7ED1\u5B9A\u3002\n"
5385
- );
5386
- return false;
5412
+ throw new Error("\u672A\u68C0\u6D4B\u5230\u98DE\u4E66 / Lark \u5E94\u7528\u51ED\u636E\uFF0C\u4E14\u672A\u5141\u8BB8\u626B\u7801\u7ED1\u5B9A\u3002");
5387
5413
  }
5388
5414
  try {
5389
5415
  const created = await onboardPersonalAgent();
@@ -5403,9 +5429,7 @@ async function ensureBotProfile(store, options) {
5403
5429
  return true;
5404
5430
  } catch (error) {
5405
5431
  log.fail("onboarding", error);
5406
- process.stderr.write("\u626B\u7801\u521B\u5EFA\u5E94\u7528\u5931\u8D25\uFF0C\u672A\u5199\u5165\u672C\u5730\u914D\u7F6E\u3002\n");
5407
- process.exitCode = 1;
5408
- return false;
5432
+ throw new Error(`\u626B\u7801\u521B\u5EFA\u5E94\u7528\u5931\u8D25\uFF1A${errorMessage2(error)}`);
5409
5433
  }
5410
5434
  }
5411
5435
  function mergeStartEnv(options) {
@@ -5417,28 +5441,25 @@ function mergeStartEnv(options) {
5417
5441
  ...options.appSecret ? { DSH_LARK_APP_SECRET: options.appSecret } : {}
5418
5442
  };
5419
5443
  }
5420
- async function runBot(options) {
5421
- const env = loadRuntimeEnv(mergeStartEnv(options));
5444
+ async function startBridgeEngine(options) {
5445
+ const env = options.env;
5422
5446
  const paths = resolveAppPaths(env.home);
5423
- const profileName = options.profile ?? "default";
5447
+ const profileName = options.profileName;
5424
5448
  const configStore = new ConfigStore(paths.configFile);
5425
5449
  await configStore.load();
5426
5450
  const ready = await ensureBotProfile(configStore, {
5427
5451
  env,
5428
5452
  profileName,
5429
- allowOnboarding: false
5453
+ allowOnboarding: options.allowOnboarding
5430
5454
  });
5431
5455
  if (!ready) {
5432
- process.exitCode = 1;
5433
- return;
5456
+ throw new Error("bot profile \u672A\u5C31\u7EEA\u3002");
5434
5457
  }
5435
5458
  const activeProfile = configStore.getProfile(profileName);
5436
5459
  if (!activeProfile) {
5437
- process.stderr.write("\u672C\u5730\u914D\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u8BD5\u3002\n");
5438
- process.exitCode = 1;
5439
- return;
5460
+ throw new Error("\u672C\u5730\u914D\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u8BD5\u3002");
5440
5461
  }
5441
- const defaultWorkspace = options.workspace ?? activeProfile.workspaces.default ?? env.workspace ?? paths.profilePath(profileName, "workspace");
5462
+ const defaultWorkspace = activeProfile.workspaces.default ?? env.workspace ?? paths.profilePath(profileName, "workspace");
5442
5463
  await mkdir9(defaultWorkspace, { recursive: true });
5443
5464
  const sessions = new SessionStore(paths.sessionsFile(profileName));
5444
5465
  const archiver = new SessionArchive(paths.archivesDir(profileName));
@@ -5454,19 +5475,10 @@ async function runBot(options) {
5454
5475
  roleStore.load(),
5455
5476
  scopeDirectory.load()
5456
5477
  ]);
5457
- let adapter;
5458
- try {
5459
- adapter = await buildAgentAdapter(env, {
5460
- stopGraceMs: activeProfile.preferences.stopGraceMs ?? env.stopGraceMs,
5461
- model: activeProfile.preferences.model
5462
- });
5463
- } catch (error) {
5464
- log.fail("adapter", error);
5465
- process.stderr.write(`agent adapter \u521D\u59CB\u5316\u5931\u8D25\uFF1A${errorMessage2(error)}
5466
- `);
5467
- process.exitCode = 1;
5468
- return;
5469
- }
5478
+ const adapter = options.adapter ?? await buildAgentAdapter(env, {
5479
+ stopGraceMs: activeProfile.preferences.stopGraceMs ?? env.stopGraceMs,
5480
+ model: activeProfile.preferences.model
5481
+ });
5470
5482
  const activeRuns = new ActiveRuns();
5471
5483
  const runPolicies = new RunPolicyStore();
5472
5484
  const concurrencyStore = new ConcurrencyStore();
@@ -5582,7 +5594,8 @@ async function runBot(options) {
5582
5594
  accessDefaultDeny: env.accessDefaultDeny,
5583
5595
  eventFreshnessMs: env.eventFreshnessMs,
5584
5596
  allowedUsers: activeProfile.access.allowedUsers,
5585
- allowedChats: activeProfile.access.allowedChats
5597
+ allowedChats: activeProfile.access.allowedChats,
5598
+ ...options.createChannel ? { createChannel: options.createChannel } : {}
5586
5599
  };
5587
5600
  if (activeProfile.preferences.stopGraceMs !== void 0) {
5588
5601
  channelInput.stopGraceMs = activeProfile.preferences.stopGraceMs;
@@ -5592,24 +5605,54 @@ async function runBot(options) {
5592
5605
  larkChannel = bridge.channel;
5593
5606
  await notifyServer.start();
5594
5607
  process.env.DSH_LARK_NOTIFY_URL = notifyServer.url ?? "";
5608
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5595
5609
  log.info("cli", "started", {
5596
5610
  profile: profileName,
5597
5611
  home: paths.root,
5598
5612
  tenant: activeProfile.tenant,
5599
- workspace: defaultWorkspace
5613
+ workspace: defaultWorkspace,
5614
+ mode: "engine"
5615
+ });
5616
+ let stopped = false;
5617
+ return {
5618
+ profile: profileName,
5619
+ home: paths.root,
5620
+ status: () => ({
5621
+ state: stopped ? "stopped" : "running",
5622
+ profile: profileName,
5623
+ home: paths.root,
5624
+ adapterId: adapter.id,
5625
+ startedAt,
5626
+ workspace: defaultWorkspace,
5627
+ notifyUrl: notifyServer.url
5628
+ }),
5629
+ stop: async () => {
5630
+ if (stopped) return;
5631
+ stopped = true;
5632
+ await notifyServer.stop();
5633
+ await bridge.disconnect();
5634
+ await adapter.dispose?.();
5635
+ await Promise.all([
5636
+ sessions.flush(),
5637
+ workspaces.flush(),
5638
+ roleStore.flush(),
5639
+ scopeDirectory.flush()
5640
+ ]);
5641
+ }
5642
+ };
5643
+ }
5644
+ async function runBot(options) {
5645
+ const env = loadRuntimeEnv(mergeStartEnv(options));
5646
+ const profileName = options.profile ?? "default";
5647
+ const engine = await startBridgeEngine({
5648
+ env,
5649
+ profileName,
5650
+ allowOnboarding: false
5600
5651
  });
5601
5652
  process.stdout.write(`dsh-lark-bot \u5DF2\u542F\u52A8\uFF0Cprofile=${profileName}
5602
5653
  `);
5603
5654
  await waitForShutdown();
5604
- await notifyServer.stop();
5605
- await bridge.disconnect();
5606
- await adapter.dispose?.();
5607
- await Promise.all([
5608
- sessions.flush(),
5609
- workspaces.flush(),
5610
- roleStore.flush(),
5611
- scopeDirectory.flush()
5612
- ]);
5655
+ await engine.stop();
5613
5656
  }
5614
5657
  function errorMessage2(error) {
5615
5658
  return error instanceof Error ? error.message : String(error);
@@ -5626,917 +5669,88 @@ function waitForShutdown() {
5626
5669
  });
5627
5670
  }
5628
5671
 
5629
- // src/service/manager.ts
5630
- import { mkdir as mkdir14, readFile as readFile11 } from "fs/promises";
5631
- import { dirname as dirname7 } from "path";
5632
-
5633
- // src/service/command.ts
5634
- import { execFile as execFile3 } from "child_process";
5635
- import { basename, dirname as dirname5, join as join12 } from "path";
5636
- import { fileURLToPath as fileURLToPath2 } from "url";
5637
- var runCommand = (command, args, options) => new Promise((resolve5) => {
5638
- execFile3(
5639
- command,
5640
- [...args],
5641
- {
5642
- env: options?.env,
5643
- maxBuffer: 16 * 1024 * 1024,
5644
- windowsHide: true
5645
- },
5646
- (error, stdout, stderr) => {
5647
- let code = 0;
5648
- if (error) {
5649
- const errorCode = error.code;
5650
- code = typeof errorCode === "number" ? errorCode : 1;
5651
- }
5652
- resolve5({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
5653
- }
5654
- );
5655
- });
5656
- function resolveCliJsPath(metaUrl = import.meta.url) {
5657
- const current = fileURLToPath2(metaUrl);
5658
- const name = basename(current);
5659
- if (name === "cli.js" || name === "cli.mjs" || name === "cli.cjs") return current;
5660
- return join12(dirname5(current), "..", "dist", "cli.js");
5661
- }
5662
- function slugify3(value) {
5663
- const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5664
- return slug || "default";
5665
- }
5666
- function serviceNameFor(profile) {
5667
- return profile === "default" ? "dsh-lark-bot" : `dsh-lark-bot-${slugify3(profile)}`;
5668
- }
5669
- function launchdLabelFor(profile) {
5670
- return profile === "default" ? "io.dsh-lark-bot" : `io.dsh-lark-bot.${slugify3(profile)}`;
5671
- }
5672
-
5673
- // src/service/env-snapshot.ts
5674
- import { readFile as readFile9 } from "fs/promises";
5675
- var EXTRA_KEYS = ["PATH", "HOME", "DEEPSEEK_API_KEY", "DSH_HOME"];
5676
- function snapshotServiceEnv(source = process.env) {
5677
- const env = {};
5678
- for (const [key, value] of Object.entries(source)) {
5679
- if (value === void 0) continue;
5680
- if (key.startsWith("DSH_LARK_") || EXTRA_KEYS.includes(key)) {
5681
- env[key] = value;
5682
- }
5683
- }
5684
- return env;
5685
- }
5686
- function formatEnvFile(env) {
5687
- const lines = [];
5688
- for (const [key, value] of Object.entries(env)) {
5689
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
5690
- throw new Error(`invalid service env key: ${key}`);
5691
- }
5692
- if (/[\r\n]/.test(value)) {
5693
- throw new Error(`service env value for ${key} contains a newline`);
5694
- }
5695
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5696
- lines.push(`${key}="${escaped}"`);
5697
- }
5698
- return `${lines.join("\n")}
5699
- `;
5700
- }
5701
- function parseEnvFile(text) {
5702
- const env = {};
5703
- for (const rawLine of text.split("\n")) {
5704
- const line = rawLine.trim();
5705
- if (!line || line.startsWith("#") || line.startsWith(";")) continue;
5706
- const eq = line.indexOf("=");
5707
- if (eq <= 0) continue;
5708
- const key = line.slice(0, eq).trim();
5709
- let value = line.slice(eq + 1).trim();
5710
- if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
5711
- value = value.slice(1, -1).replace(/\\(.)/g, "$1");
5712
- } else if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
5713
- value = value.slice(1, -1);
5714
- }
5715
- env[key] = value;
5716
- }
5717
- return env;
5718
- }
5719
- async function writeServiceEnv(file, env) {
5720
- await writeFileAtomic(file, formatEnvFile(env), { mode: 384 });
5721
- }
5722
- async function readServiceEnv(file) {
5723
- try {
5724
- return parseEnvFile(await readFile9(file, "utf8"));
5725
- } catch (error) {
5726
- if (error.code === "ENOENT") return {};
5727
- throw error;
5728
- }
5729
- }
5730
-
5731
- // src/service/linux-systemd.ts
5732
- import { mkdir as mkdir10, stat as stat3 } from "fs/promises";
5733
- import { homedir as homedir6 } from "os";
5734
- import { join as join13 } from "path";
5735
- function defaultSystemdUnitDir() {
5736
- return join13(homedir6(), ".config", "systemd", "user");
5737
- }
5738
- function quoteSystemdArg(arg) {
5739
- if (!/[\s"\\]/.test(arg)) return arg;
5740
- return `"${arg.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
5741
- }
5742
- function buildSystemdUnit(spec) {
5743
- const exec = [spec.nodePath, spec.cliJsPath, "run", "--profile", spec.profile].map(quoteSystemdArg).join(" ");
5744
- return [
5745
- "[Unit]",
5746
- "Description=dsh-lark-bot bridge service",
5747
- "After=network-online.target",
5748
- "Wants=network-online.target",
5749
- "",
5750
- "[Service]",
5751
- "Type=simple",
5752
- `EnvironmentFile=${quoteSystemdArg(spec.envFile)}`,
5753
- `ExecStart=${exec}`,
5754
- "Restart=always",
5755
- "RestartSec=5",
5756
- "KillMode=mixed",
5757
- "TimeoutStopSec=30",
5758
- `StandardOutput=append:${spec.logFile}`,
5759
- `StandardError=append:${spec.logFile}`,
5760
- "",
5761
- "[Install]",
5762
- "WantedBy=default.target",
5763
- ""
5764
- ].join("\n");
5765
- }
5766
- async function isSystemdUserAvailable(runner = runCommand) {
5767
- const result = await runner("systemctl", ["--user", "show-environment"]);
5768
- return result.code === 0;
5769
- }
5770
- async function fileExists(path) {
5771
- try {
5772
- return (await stat3(path)).isFile();
5773
- } catch {
5774
- return false;
5775
- }
5776
- }
5777
- function parseMainPid(value) {
5778
- const raw = value?.trim();
5779
- if (!raw || raw === "0" || raw === "4294967295") return void 0;
5780
- const pid = Number(raw);
5781
- return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5782
- }
5783
- function parseNumber(value) {
5784
- const raw = value?.trim();
5785
- if (!raw) return void 0;
5786
- const parsed = Number(raw);
5787
- return Number.isInteger(parsed) ? parsed : void 0;
5788
- }
5789
- var SystemdServiceController = class {
5790
- platform = "linux-systemd";
5791
- run;
5792
- unitDir;
5793
- constructor(options = {}) {
5794
- this.run = options.runCommand ?? runCommand;
5795
- this.unitDir = options.userUnitDir ?? defaultSystemdUnitDir();
5796
- }
5797
- unitPath(spec) {
5798
- return join13(this.unitDir, `${spec.serviceName}.service`);
5799
- }
5800
- unitName(spec) {
5801
- return `${spec.serviceName}.service`;
5802
- }
5803
- async isActive(name) {
5804
- const result = await this.run("systemctl", ["--user", "is-active", name]);
5805
- return result.code === 0;
5806
- }
5807
- async installAndStart(spec) {
5808
- await mkdir10(this.unitDir, { recursive: true });
5809
- await writeFileAtomic(this.unitPath(spec), buildSystemdUnit(spec));
5810
- await this.run("systemctl", ["--user", "daemon-reload"]);
5811
- const name = this.unitName(spec);
5812
- if (await this.isActive(name)) {
5813
- await this.run("systemctl", ["--user", "restart", name]);
5814
- } else {
5815
- await this.run("systemctl", ["--user", "enable", "--now", name]);
5816
- }
5817
- }
5818
- async stop(spec) {
5819
- await this.run("systemctl", ["--user", "disable", "--now", this.unitName(spec)]);
5820
- }
5821
- async restart(spec) {
5822
- await this.run("systemctl", ["--user", "daemon-reload"]);
5823
- await this.run("systemctl", ["--user", "restart", this.unitName(spec)]);
5824
- }
5825
- async status(spec) {
5826
- const name = this.unitName(spec);
5827
- const unitFile = this.unitPath(spec);
5828
- const installed = await fileExists(unitFile);
5829
- let autostartEnabled = false;
5830
- if (installed) {
5831
- const enabled = await this.run("systemctl", ["--user", "is-enabled", name]);
5832
- autostartEnabled = enabled.code === 0 && enabled.stdout.trim() === "enabled";
5833
- }
5834
- const active = await this.run("systemctl", ["--user", "is-active", name]);
5835
- const isActive = active.code === 0;
5836
- let detail = active.stdout.trim() || (isActive ? "active" : "inactive");
5837
- let pid;
5838
- let restarts;
5839
- if (isActive || installed) {
5840
- const show = await this.run(
5841
- "systemctl",
5842
- ["--user", "show", name, "-p", "MainPID", "-p", "ActiveState", "-p", "NRestarts", "--value"]
5843
- );
5844
- const [activeState, mainPid, nRestarts] = show.stdout.trim().split("\n");
5845
- if (activeState?.trim()) detail = activeState.trim();
5846
- pid = parseMainPid(mainPid);
5847
- restarts = parseNumber(nRestarts);
5848
- }
5849
- let state;
5850
- if (!installed) {
5851
- state = "stopped";
5852
- } else if (detail === "failed") {
5853
- state = "error";
5854
- } else if (isActive || detail === "activating" || detail === "deactivating" || detail === "reloading") {
5855
- state = "running";
5856
- } else {
5857
- state = "stopped";
5858
- }
5859
- return {
5860
- name: spec.serviceName,
5861
- platform: this.platform,
5862
- installed,
5863
- autostartEnabled,
5864
- state,
5865
- pid,
5866
- detail,
5867
- restarts
5868
- };
5869
- }
5870
- };
5871
-
5872
- // src/service/macos-launchd.ts
5873
- import { mkdir as mkdir11, rm as rm2, stat as stat4 } from "fs/promises";
5874
- import { homedir as homedir7 } from "os";
5875
- import { join as join14 } from "path";
5876
- function defaultLaunchAgentDir() {
5877
- return join14(homedir7(), "Library", "LaunchAgents");
5878
- }
5879
- function xmlEscape(value) {
5880
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5881
- }
5882
- function buildLaunchdPlist(spec) {
5883
- const envLines = Object.entries(spec.env).filter(([, value]) => value !== void 0).map(
5884
- ([key, value]) => ` <key>${xmlEscape(key)}</key>
5885
- <string>${xmlEscape(value)}</string>`
5886
- ).join("\n");
5887
- return [
5888
- '<?xml version="1.0" encoding="UTF-8"?>',
5889
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
5890
- '<plist version="1.0">',
5891
- "<dict>",
5892
- " <key>Label</key>",
5893
- ` <string>${xmlEscape(spec.label)}</string>`,
5894
- " <key>ProgramArguments</key>",
5895
- " <array>",
5896
- ` <string>${xmlEscape(spec.nodePath)}</string>`,
5897
- ` <string>${xmlEscape(spec.cliJsPath)}</string>`,
5898
- " <string>run</string>",
5899
- " <string>--profile</string>",
5900
- ` <string>${xmlEscape(spec.profile)}</string>`,
5901
- " </array>",
5902
- " <key>RunAtLoad</key>",
5903
- " <true/>",
5904
- " <key>KeepAlive</key>",
5905
- " <true/>",
5906
- " <key>EnvironmentVariables</key>",
5907
- " <dict>",
5908
- envLines,
5909
- " </dict>",
5910
- " <key>StandardOutPath</key>",
5911
- ` <string>${xmlEscape(spec.logFile)}</string>`,
5912
- " <key>StandardErrorPath</key>",
5913
- ` <string>${xmlEscape(spec.logFile)}</string>`,
5914
- " <key>ProcessType</key>",
5915
- " <string>Background</string>",
5916
- "</dict>",
5917
- "</plist>",
5918
- ""
5919
- ].join("\n");
5920
- }
5921
- async function fileExists2(path) {
5922
- try {
5923
- return (await stat4(path)).isFile();
5924
- } catch {
5925
- return false;
5926
- }
5927
- }
5928
- var LaunchdServiceController = class {
5929
- platform = "darwin-launchd";
5930
- run;
5931
- agentDir;
5932
- uid;
5933
- constructor(options = {}) {
5934
- this.run = options.runCommand ?? runCommand;
5935
- this.agentDir = options.launchAgentDir ?? defaultLaunchAgentDir();
5936
- this.uid = options.uid ?? process.getuid?.() ?? -1;
5937
- }
5938
- domain() {
5939
- return `gui/${this.uid}`;
5940
- }
5941
- plistPath(spec) {
5942
- return join14(this.agentDir, `${spec.label}.plist`);
5943
- }
5944
- serviceRef(spec) {
5945
- return `${this.domain()}/${spec.label}`;
5946
- }
5947
- async installAndStart(spec) {
5948
- await mkdir11(this.agentDir, { recursive: true });
5949
- await writeFileAtomic(this.plistPath(spec), buildLaunchdPlist(spec));
5950
- const bootstrap = await this.run("launchctl", ["bootstrap", this.domain(), this.plistPath(spec)]);
5951
- if (bootstrap.code !== 0) {
5952
- await this.run("launchctl", ["kickstart", "-k", this.serviceRef(spec)]);
5953
- }
5954
- }
5955
- async stop(spec) {
5956
- await this.run("launchctl", ["bootout", this.serviceRef(spec)]);
5957
- await rm2(this.plistPath(spec), { force: true });
5958
- }
5959
- async restart(spec) {
5960
- const kickstart = await this.run("launchctl", ["kickstart", "-k", this.serviceRef(spec)]);
5961
- if (kickstart.code !== 0) {
5962
- await this.installAndStart(spec);
5963
- }
5964
- }
5965
- async status(spec) {
5966
- const plistExists = await fileExists2(this.plistPath(spec));
5967
- const printed = await this.run("launchctl", ["print", this.serviceRef(spec)]);
5968
- const loaded = printed.code === 0;
5969
- let pid;
5970
- let state = "stopped";
5971
- let detail = loaded ? "loaded" : "not loaded";
5972
- if (loaded) {
5973
- const pidMatch = /pid = (\d+)/.exec(printed.stdout);
5974
- if (pidMatch?.[1]) pid = Number(pidMatch[1]);
5975
- if (/state = running/.test(printed.stdout)) {
5976
- state = "running";
5977
- detail = "running";
5978
- } else if (/state = exited|state = waiting/.test(printed.stdout)) {
5979
- state = "stopped";
5980
- detail = /state = exited/.test(printed.stdout) ? "exited" : "waiting";
5981
- }
5982
- }
5983
- return {
5984
- name: spec.serviceName,
5985
- platform: this.platform,
5986
- installed: plistExists,
5987
- autostartEnabled: loaded,
5988
- state,
5989
- pid,
5990
- detail,
5991
- restarts: void 0
5992
- };
5993
- }
5994
- };
5995
-
5996
- // src/service/portable.ts
5672
+ // src/cli/commands/setup.ts
5673
+ init_dsh_runtime();
5674
+ init_own_package();
5997
5675
  import { spawn as spawn5 } from "child_process";
5998
- import { mkdir as mkdir12, readFile as readFile10, rm as rm3, stat as stat5, writeFile as writeFile6 } from "fs/promises";
5999
- import { homedir as homedir8 } from "os";
6000
- import { join as join15 } from "path";
6001
- function defaultAutostartDir() {
6002
- return join15(homedir8(), ".config", "autostart");
6003
- }
6004
- function quoteDesktopArg(arg) {
6005
- const escaped = arg.replace(/%/g, "%%").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
6006
- return /[\s"\\]/.test(arg) ? `"${escaped}"` : escaped;
6007
- }
6008
- function buildDesktopEntry(spec) {
6009
- const exec = [spec.nodePath, spec.cliJsPath, "supervise", "--profile", spec.profile].map(quoteDesktopArg).join(" ");
6010
- return [
6011
- "[Desktop Entry]",
6012
- "Type=Application",
6013
- `Name=${spec.serviceName}`,
6014
- "Comment=Keep dsh-lark-bot running in the background",
6015
- `Exec=${exec}`,
6016
- "Terminal=false",
6017
- "X-GNOME-Autostart-enabled=true",
6018
- "Hidden=false",
6019
- ""
6020
- ].join("\n");
6021
- }
6022
- function supervisorStatusFile(serviceDir, serviceName) {
6023
- return join15(serviceDir, `supervisor-${serviceName}.json`);
6024
- }
6025
- function isProcessAlive(pid) {
6026
- try {
6027
- process.kill(pid, 0);
6028
- return true;
6029
- } catch (error) {
6030
- return error.code === "EPERM";
6031
- }
6032
- }
6033
- async function readSupervisorStatus(file) {
6034
- try {
6035
- return JSON.parse(await readFile10(file, "utf8"));
6036
- } catch {
6037
- return void 0;
6038
- }
6039
- }
6040
- function defaultSpawnDetached(nodePath, args) {
6041
- const child = spawn5(nodePath, [...args], {
6042
- detached: true,
6043
- stdio: "ignore",
6044
- env: process.env
6045
- });
6046
- child.unref();
6047
- return child;
6048
- }
6049
- var PortableServiceController = class {
6050
- platform = "linux-portable";
6051
- autostartDir;
6052
- serviceDir;
6053
- spawnDetached;
6054
- constructor(options = {}) {
6055
- this.autostartDir = options.autostartDir ?? defaultAutostartDir();
6056
- this.serviceDir = options.serviceDir ?? "";
6057
- this.spawnDetached = options.spawnDetached ?? defaultSpawnDetached;
6058
- }
6059
- desktopPath(spec) {
6060
- return join15(this.autostartDir, `${spec.serviceName}.desktop`);
6061
- }
6062
- statusFile(spec) {
6063
- return supervisorStatusFile(this.serviceDir, spec.serviceName);
6064
- }
6065
- async sendStopSignal(spec) {
6066
- const status = await readSupervisorStatus(this.statusFile(spec));
6067
- if (!status) return;
6068
- try {
6069
- process.kill(status.pid, "SIGTERM");
6070
- } catch {
6071
- return;
6072
- }
6073
- for (let attempt = 0; attempt < 20; attempt += 1) {
6074
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 250));
6075
- if (!isProcessAlive(status.pid)) return;
6076
- }
6077
- try {
6078
- process.kill(status.pid, "SIGKILL");
6079
- } catch {
6080
- }
6081
- }
6082
- async installAndStart(spec) {
6083
- await this.sendStopSignal(spec);
6084
- await mkdir12(this.autostartDir, { recursive: true });
6085
- await writeFile6(this.desktopPath(spec), buildDesktopEntry(spec), { encoding: "utf8", mode: 420 });
6086
- if (!this.serviceDir) throw new Error("serviceDir is required for the portable supervisor");
6087
- await mkdir12(this.serviceDir, { recursive: true });
6088
- this.spawnDetached(spec.nodePath, [spec.cliJsPath, "supervise", "--profile", spec.profile]);
6089
- }
6090
- async stop(spec) {
6091
- await this.sendStopSignal(spec);
6092
- await rm3(this.desktopPath(spec), { force: true });
6093
- }
6094
- async restart(spec) {
6095
- await this.stop(spec);
6096
- await this.installAndStart(spec);
6097
- }
6098
- async status(spec) {
6099
- let desktopExists = false;
6100
- try {
6101
- desktopExists = (await stat5(this.desktopPath(spec))).isFile();
6102
- } catch {
6103
- desktopExists = false;
6104
- }
6105
- const status = await readSupervisorStatus(this.statusFile(spec));
6106
- const alive = status !== void 0 && isProcessAlive(status.pid);
6107
- return {
6108
- name: spec.serviceName,
6109
- platform: this.platform,
6110
- installed: desktopExists,
6111
- autostartEnabled: desktopExists,
6112
- state: alive ? "running" : "stopped",
6113
- pid: status?.childPid,
6114
- detail: alive ? `supervisor ${status?.pid ?? "?"}` : "stopped",
6115
- restarts: status?.restarts
6116
- };
6117
- }
6118
- };
6119
-
6120
- // src/service/windows-task.ts
6121
- import { mkdir as mkdir13, writeFile as writeFile7 } from "fs/promises";
6122
- import { dirname as dirname6, join as join16 } from "path";
6123
- function psQuote(value) {
6124
- return `'${value.replace(/'/g, "''")}'`;
6125
- }
6126
- function buildScheduledTaskScript(action, spec) {
6127
- const vars = [
6128
- `$taskName = ${psQuote(spec.serviceName)}`,
6129
- `$node = ${psQuote(spec.nodePath)}`,
6130
- `$cli = ${psQuote(spec.cliJsPath)}`,
6131
- `$log = ${psQuote(spec.logFile)}`,
6132
- `$profile = ${psQuote(spec.profile)}`,
6133
- `$workDir = ${psQuote(dirname6(spec.cliJsPath))}`
6134
- ].join("\n");
6135
- const install = [
6136
- `$inner = '"' + $node + '" "' + $cli + '" run --profile "' + $profile + '" >> "' + $log + '" 2>&1'`,
6137
- `$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument ('/c ' + $inner) -WorkingDirectory $workDir`,
6138
- `$trigger = New-ScheduledTaskTrigger -AtLogOn`,
6139
- `$settings = New-ScheduledTaskSettingsSet -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries`,
6140
- `Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue`,
6141
- `Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Force`,
6142
- `Enable-ScheduledTask -TaskName $taskName`,
6143
- `Start-ScheduledTask -TaskName $taskName`
6144
- ].join("\n");
6145
- const stop = [
6146
- `Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
6147
- `Disable-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`
6148
- ].join("\n");
6149
- const restart = [
6150
- `Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
6151
- `Start-ScheduledTask -TaskName $taskName`
6152
- ].join("\n");
6153
- const status = [
6154
- `$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
6155
- `if ($null -eq $task) { Write-Output 'STATE:missing'; Write-Output 'ENABLED:false'; exit 2 }`,
6156
- `$info = Get-ScheduledTaskInfo -TaskName $taskName`,
6157
- `Write-Output ('STATE:' + $task.State)`,
6158
- `Write-Output ('ENABLED:' + $task.Settings.Enabled)`,
6159
- `Write-Output ('LAST_RESULT:' + $info.LastTaskResult)`,
6160
- `Write-Output ('LAST_RUN:' + $info.LastRunTime)`
6161
- ].join("\n");
6162
- const body = {
6163
- install,
6164
- stop,
6165
- restart,
6166
- status
6167
- };
6168
- return [
6169
- `$ErrorActionPreference = 'Stop'`,
6170
- vars,
6171
- body[action],
6172
- ""
6173
- ].join("\n");
6174
- }
6175
- function parseTaskStatusOutput(output) {
6176
- const fields = /* @__PURE__ */ new Map();
6177
- for (const rawLine of output.split(/\r?\n/)) {
6178
- const line = rawLine.trim();
6179
- const eq = line.indexOf(":");
6180
- if (eq <= 0) continue;
6181
- fields.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());
6182
- }
6183
- const stateValue = fields.get("STATE") ?? "missing";
6184
- const enabled = fields.get("ENABLED") === "True";
6185
- const installed = stateValue !== "missing";
6186
- let state = "unknown";
6187
- if (stateValue === "Running") state = "running";
6188
- else if (stateValue === "Ready" || stateValue === "Disabled") state = "stopped";
6189
- else if (stateValue === "Unknown") state = "unknown";
6190
- else if (stateValue === "missing") state = "stopped";
6191
- const lastResultRaw = fields.get("LAST_RESULT");
6192
- const lastResult = lastResultRaw !== void 0 ? Number.parseInt(lastResultRaw, 10) : void 0;
6193
- const lastResultValue = lastResult !== void 0 && Number.isFinite(lastResult) ? lastResult : void 0;
6194
- if (installed && lastResultValue !== void 0 && lastResultValue !== 0 && stateValue === "Ready") {
6195
- state = "error";
5676
+ import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
5677
+ import { homedir as homedir6 } from "os";
5678
+ import { join as join12 } from "path";
5679
+ async function runSetup(options = {}) {
5680
+ const profile = options.profile ?? "dsh-lark";
5681
+ const own = ownPackageInfo();
5682
+ const packageSpec = options.packageSpec ?? process.env.DSH_LARK_SETUP_PACKAGE ?? own.name;
5683
+ const dshHome = options.dshHome ?? resolveDshHome(homedir6(), process.env);
5684
+ const bin = options.bin ?? discoverDshBin(homedir6(), process.env);
5685
+ if (!bin) {
5686
+ throw new Error(
5687
+ "\u672A\u627E\u5230\u672C\u673A dsh \u5B89\u88C5\uFF08`@deepseek-ai/dsh`\uFF09\u3002\u8BF7\u5148\u5B89\u88C5 DeepSeek Harness\uFF0C\u518D\u8FD0\u884C\u672C\u547D\u4EE4\u3002"
5688
+ );
6196
5689
  }
6197
- return {
6198
- installed,
6199
- autostartEnabled: enabled,
6200
- state,
6201
- detail: stateValue,
6202
- lastResult: lastResultValue
6203
- };
5690
+ const profileDir = join12(dshHome, "profiles", profile);
5691
+ await mkdir10(profileDir, { recursive: true });
5692
+ await approveBuilds(profileDir);
5693
+ process.stdout.write(
5694
+ `\u6B63\u5728\u628A ${packageSpec} \u5B89\u88C5\u5230 dsh profile \`${profile}\`\uFF08$DSH_HOME=${dshHome}\uFF09...
5695
+ `
5696
+ );
5697
+ await runDshPlugin(bin, profile, packageSpec);
5698
+ process.stdout.write(
5699
+ [
5700
+ "",
5701
+ "\u2705 \u5B89\u88C5\u5B8C\u6210\u3002\u542F\u52A8\u65B9\u5F0F\uFF08\u552F\u4E00\u7684\u90E8\u7F72\u8DEF\u5F84\uFF09\uFF1A",
5702
+ "",
5703
+ ` dsh --profile ${profile}`,
5704
+ "",
5705
+ "\u9996\u6B21\u542F\u52A8\u4F1A\u5728\u7EC8\u7AEF\u6253\u5370\u4E8C\u7EF4\u7801\uFF0C\u7528\u98DE\u4E66 / Lark \u626B\u7801\u5B8C\u6210\u4E00\u6B21\u6027\u7ED1\u5B9A\uFF1B",
5706
+ "\u7ED1\u5B9A\u540E dsh \u5373\u4EE5\u6807\u51C6\u63D2\u4EF6\u65B9\u5F0F\u52A0\u8F7D dsh-lark-bot \u7684\u6865\u63A5\u5F15\u64CE\u3002",
5707
+ ""
5708
+ ].join("\n")
5709
+ );
6204
5710
  }
6205
- var WindowsTaskServiceController = class {
6206
- platform = "win32-task";
6207
- run;
6208
- scriptDir;
6209
- constructor(options = {}) {
6210
- this.run = options.runCommand ?? runCommand;
6211
- this.scriptDir = options.scriptDir ?? "";
6212
- }
6213
- scriptPath(spec, action) {
6214
- return join16(this.scriptDir, `${action}-${spec.serviceName}.ps1`);
6215
- }
6216
- async runScript(spec, action) {
6217
- const script = this.scriptPath(spec, action);
6218
- if (this.scriptDir) await mkdir13(this.scriptDir, { recursive: true });
6219
- await writeFile7(script, buildScheduledTaskScript(action, spec), { encoding: "utf8" });
6220
- const result = await this.run("powershell.exe", [
6221
- "-NoProfile",
6222
- "-NonInteractive",
6223
- "-ExecutionPolicy",
6224
- "Bypass",
6225
- "-File",
6226
- script
6227
- ]);
6228
- return { result, script };
6229
- }
6230
- async installAndStart(spec) {
6231
- const { result } = await this.runScript(spec, "install");
6232
- if (result.code !== 0) {
6233
- throw new Error(
6234
- `\u65E0\u6CD5\u6CE8\u518C Windows \u8BA1\u5212\u4EFB\u52A1\uFF08exit ${result.code}\uFF09\uFF1A${result.stderr.trim() || result.stdout.trim() || "\u672A\u77E5\u9519\u8BEF"}`
6235
- );
6236
- }
6237
- }
6238
- async stop(spec) {
6239
- const { result } = await this.runScript(spec, "stop");
6240
- if (result.code !== 0) {
6241
- throw new Error(
6242
- `\u65E0\u6CD5\u505C\u6B62 Windows \u8BA1\u5212\u4EFB\u52A1\uFF08exit ${result.code}\uFF09\uFF1A${result.stderr.trim() || result.stdout.trim() || "\u672A\u77E5\u9519\u8BEF"}`
6243
- );
6244
- }
6245
- }
6246
- async restart(spec) {
6247
- const { result } = await this.runScript(spec, "restart");
6248
- if (result.code !== 0) {
6249
- throw new Error(
6250
- `\u65E0\u6CD5\u91CD\u542F Windows \u8BA1\u5212\u4EFB\u52A1\uFF08exit ${result.code}\uFF09\uFF1A${result.stderr.trim() || result.stdout.trim() || "\u672A\u77E5\u9519\u8BEF"}`
6251
- );
6252
- }
6253
- }
6254
- async status(spec) {
6255
- const { result } = await this.runScript(spec, "status");
6256
- const parsed = parseTaskStatusOutput(result.stdout);
6257
- if (result.code !== 0 && result.code !== 2) {
6258
- throw new Error(
6259
- `\u65E0\u6CD5\u67E5\u8BE2 Windows \u8BA1\u5212\u4EFB\u52A1\uFF08exit ${result.code}\uFF09\uFF1A${result.stderr.trim() || result.stdout.trim() || "\u672A\u77E5\u9519\u8BEF"}`
6260
- );
6261
- }
6262
- return {
6263
- name: spec.serviceName,
6264
- platform: this.platform,
6265
- installed: parsed.installed,
6266
- autostartEnabled: parsed.autostartEnabled,
6267
- state: parsed.state,
6268
- pid: void 0,
6269
- detail: parsed.detail,
6270
- restarts: void 0
6271
- };
6272
- }
6273
- };
6274
-
6275
- // src/service/manager.ts
6276
- var ServiceManager = class {
6277
- profile;
6278
- sourceEnv;
6279
- paths;
6280
- version;
6281
- controllerOverride;
6282
- constructor(deps = {}) {
6283
- this.profile = deps.profile ?? "default";
6284
- this.sourceEnv = deps.env ?? process.env;
6285
- this.paths = deps.paths ?? resolveAppPaths();
6286
- this.version = deps.version ?? "0.0.0";
6287
- this.controllerOverride = deps.controller;
6288
- }
6289
- buildSpec() {
6290
- const serviceName = serviceNameFor(this.profile);
6291
- return {
6292
- serviceName,
6293
- label: launchdLabelFor(this.profile),
6294
- profile: this.profile,
6295
- nodePath: process.execPath,
6296
- cliJsPath: resolveCliJsPath(),
6297
- envFile: this.paths.serviceEnvFile,
6298
- logFile: this.paths.serviceLogFile(this.profile),
6299
- env: snapshotServiceEnv(this.sourceEnv)
6300
- };
6301
- }
6302
- async resolveController() {
6303
- if (this.controllerOverride) return this.controllerOverride;
6304
- if (process.platform === "darwin") return new LaunchdServiceController();
6305
- if (process.platform === "win32") return new WindowsTaskServiceController({
6306
- scriptDir: this.paths.serviceDir
5711
+ function runDshPlugin(bin, profile, packageSpec) {
5712
+ return new Promise((resolve5, reject) => {
5713
+ const child = spawn5(
5714
+ "node",
5715
+ [bin, "plugin", "--profile", profile, "add", packageSpec],
5716
+ { stdio: "inherit" }
5717
+ );
5718
+ child.once("error", reject);
5719
+ child.once("exit", (code) => {
5720
+ if (code === 0) resolve5();
5721
+ else reject(new Error(`dsh plugin add exited with code ${String(code)}`));
6307
5722
  });
6308
- const systemd = new SystemdServiceController();
6309
- if (await isSystemdUserAvailable()) return systemd;
6310
- return new PortableServiceController({ serviceDir: this.paths.serviceDir });
6311
- }
6312
- async saveMetadata(platform) {
6313
- const metadata = {
6314
- schemaVersion: 1,
6315
- serviceName: this.buildSpec().serviceName,
6316
- profile: this.profile,
6317
- platform,
6318
- version: this.version,
6319
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
6320
- };
6321
- await writeFileAtomic(this.paths.serviceMetadataFile, `${JSON.stringify(metadata, null, 2)}
6322
- `);
6323
- }
6324
- async readMetadata() {
6325
- try {
6326
- return JSON.parse(await readFile11(this.paths.serviceMetadataFile, "utf8"));
6327
- } catch {
6328
- return void 0;
6329
- }
6330
- }
6331
- async prepareFiles(spec) {
6332
- await mkdir14(dirname7(spec.logFile), { recursive: true });
6333
- await mkdir14(dirname7(spec.envFile), { recursive: true });
6334
- await writeServiceEnv(spec.envFile, spec.env);
6335
- }
6336
- async start() {
6337
- const controller = await this.resolveController();
6338
- const spec = this.buildSpec();
6339
- await this.prepareFiles(spec);
6340
- await controller.installAndStart(spec);
6341
- await this.saveMetadata(controller.platform);
6342
- return controller.status(spec);
6343
- }
6344
- async stop() {
6345
- const controller = await this.resolveController();
6346
- const spec = this.buildSpec();
6347
- await controller.stop(spec);
6348
- return controller.status(spec);
6349
- }
6350
- async restart() {
6351
- const controller = await this.resolveController();
6352
- const spec = this.buildSpec();
6353
- const before = await controller.status(spec);
6354
- if (!before.installed) {
6355
- throw new Error("\u540E\u53F0\u670D\u52A1\u5C1A\u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u8FD0\u884C dsh-lark-bot start");
6356
- }
6357
- await this.prepareFiles(spec);
6358
- await controller.restart(spec);
6359
- await this.saveMetadata(controller.platform);
6360
- return controller.status(spec);
6361
- }
6362
- async status() {
6363
- const controller = await this.resolveController();
6364
- const spec = this.buildSpec();
6365
- return controller.status(spec);
6366
- }
6367
- };
6368
-
6369
- // src/cli/commands/service.ts
6370
- function mergeStartEnv2(options) {
6371
- return {
6372
- ...process.env,
6373
- ...options.workspace ? { DSH_LARK_WORKSPACE: options.workspace } : {},
6374
- ...options.tenant ? { DSH_LARK_TENANT: options.tenant } : {},
6375
- ...options.appId ? { DSH_LARK_APP_ID: options.appId } : {},
6376
- ...options.appSecret ? { DSH_LARK_APP_SECRET: options.appSecret } : {}
6377
- };
6378
- }
6379
- function createManager(options, version) {
6380
- const env = loadRuntimeEnv(mergeStartEnv2(options));
6381
- const paths = resolveAppPaths(env.home);
6382
- return new ServiceManager({
6383
- profile: options.profile ?? "default",
6384
- env: mergeStartEnv2(options),
6385
- paths,
6386
- version
6387
5723
  });
6388
5724
  }
6389
- function printServiceStatus(status, output) {
6390
- const lines = [
6391
- "dsh-lark-bot \u670D\u52A1\u72B6\u6001",
6392
- ` name: ${status.name}`,
6393
- ` platform: ${status.platform}`,
6394
- ` installed: ${status.installed ? "\u662F" : "\u5426"}`,
6395
- ` autostart: ${status.autostartEnabled ? "\u5DF2\u542F\u7528\uFF08\u5F00\u673A\u81EA\u542F\uFF09" : "\u672A\u542F\u7528"}`,
6396
- ` state: ${status.state}`,
6397
- ` detail: ${status.detail}`,
6398
- ` pid: ${status.pid ?? "-"}`,
6399
- ` restarts: ${status.restarts ?? "-"}`
6400
- ];
6401
- output(`${lines.join("\n")}
6402
- `);
6403
- }
6404
- async function runServiceStart(options, deps = {}) {
6405
- const version = deps.version ?? "0.0.0";
6406
- const output = deps.output ?? ((text) => process.stdout.write(text));
6407
- if (!deps.manager) {
6408
- const env = loadRuntimeEnv(mergeStartEnv2(options));
6409
- const paths = resolveAppPaths(env.home);
6410
- const profileName = options.profile ?? "default";
6411
- const store = new ConfigStore(paths.configFile);
6412
- await store.load();
6413
- const ready = await ensureBotProfile(store, {
6414
- env,
6415
- profileName,
6416
- allowOnboarding: true
6417
- });
6418
- if (!ready) {
6419
- process.exitCode = 1;
6420
- return;
6421
- }
6422
- }
6423
- const manager = deps.manager ?? createManager(options, version);
6424
- try {
6425
- const status = await manager.start();
6426
- printServiceStatus(status, output);
6427
- if (status.state !== "running") process.exitCode = 1;
6428
- } catch (error) {
6429
- output(`${error instanceof Error ? error.message : String(error)}
6430
- `);
6431
- process.exitCode = 1;
6432
- }
6433
- }
6434
- async function runServiceCommand(action, options, deps = {}) {
6435
- const output = deps.output ?? ((text) => process.stdout.write(text));
6436
- const manager = deps.manager ?? createManager(options, deps.version ?? "0.0.0");
5725
+ async function approveBuilds(profileDir) {
5726
+ const workspaceFile = join12(profileDir, "pnpm-workspace.yaml");
5727
+ let existing = "";
6437
5728
  try {
6438
- let status;
6439
- if (action === "status") {
6440
- status = await manager.status();
6441
- } else if (action === "restart") {
6442
- status = await manager.restart();
6443
- } else {
6444
- status = await manager.stop();
6445
- }
6446
- printServiceStatus(status, output);
6447
- if (action === "status") {
6448
- if (status.state !== "running") process.exitCode = 1;
6449
- } else if (action === "restart") {
6450
- if (status.state !== "running") process.exitCode = 1;
6451
- } else if (status.state === "running") {
6452
- process.exitCode = 1;
6453
- }
6454
- } catch (error) {
6455
- output(`${error instanceof Error ? error.message : String(error)}
6456
- `);
6457
- process.exitCode = 1;
5729
+ existing = await readFile9(workspaceFile, "utf8");
5730
+ } catch {
6458
5731
  }
6459
- }
5732
+ const allowBuilds = [
5733
+ "protobufjs: true",
5734
+ ...ownPackageInfo().name.startsWith("@") ? [] : [`${ownPackageInfo().name}: true`]
5735
+ ].join("\n");
5736
+ if (!existing.includes("allowBuilds")) {
5737
+ existing = existing.trimEnd() ? `${existing.trimEnd()}
6460
5738
 
6461
- // src/cli/commands/supervise.ts
6462
- import { spawn as spawn6 } from "child_process";
6463
- import { closeSync, mkdirSync, openSync } from "fs";
6464
- import { dirname as dirname8 } from "path";
6465
- var BASE_BACKOFF_MS = 5e3;
6466
- var MAX_BACKOFF_MS = 3e4;
6467
- function computeBackoffMs(restarts) {
6468
- return Math.min(MAX_BACKOFF_MS, BASE_BACKOFF_MS * 2 ** Math.min(restarts, 3));
6469
- }
6470
- function waitForExit(child) {
6471
- return new Promise((resolvePromise) => {
6472
- const done = () => resolvePromise();
6473
- child.once("exit", done);
6474
- child.once("error", done);
6475
- });
6476
- }
6477
- function sleep2(ms) {
6478
- return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
6479
- }
6480
- async function runSupervise(options) {
6481
- const env = loadRuntimeEnv(process.env);
6482
- const paths = resolveAppPaths(env.home);
6483
- const profile = options.profile ?? "default";
6484
- const serviceName = serviceNameFor(profile);
6485
- const statusFile = supervisorStatusFile(paths.serviceDir, serviceName);
6486
- const logFile = paths.serviceLogFile(profile);
6487
- const serviceEnv = await readServiceEnv(paths.serviceEnvFile);
6488
- const childEnv = { ...process.env, ...serviceEnv };
6489
- const cliJsPath = resolveCliJsPath();
6490
- mkdirSync(dirname8(statusFile), { recursive: true });
6491
- mkdirSync(dirname8(logFile), { recursive: true });
6492
- const logFd = openSync(logFile, "a");
6493
- const writeStatus = async (state, childPid, restarts2) => {
6494
- const status = {
6495
- pid: process.pid,
6496
- childPid,
6497
- state,
6498
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6499
- restarts: restarts2,
6500
- profile
6501
- };
6502
- await writeFileAtomic(statusFile, `${JSON.stringify(status)}
5739
+ allowBuilds:
5740
+ ${allowBuilds}
5741
+ ` : `allowBuilds:
5742
+ ${allowBuilds}
5743
+ `;
5744
+ } else if (!existing.includes("protobufjs: true")) {
5745
+ existing = existing.replace(/(allowBuilds:\s*\n)/, `$1 ${allowBuilds}
6503
5746
  `);
6504
- };
6505
- let stopping = false;
6506
- let child;
6507
- const stop = () => {
6508
- stopping = true;
6509
- child?.kill("SIGTERM");
6510
- };
6511
- process.once("SIGTERM", stop);
6512
- process.once("SIGINT", stop);
6513
- let restarts = 0;
6514
- try {
6515
- while (!stopping) {
6516
- await writeStatus("running", void 0, restarts);
6517
- const spawned = spawn6(process.execPath, [cliJsPath, "run", "--profile", profile], {
6518
- env: childEnv,
6519
- stdio: ["ignore", logFd, logFd]
6520
- });
6521
- child = spawned;
6522
- await writeStatus("running", spawned.pid, restarts);
6523
- await waitForExit(spawned);
6524
- child = void 0;
6525
- if (stopping) break;
6526
- restarts += 1;
6527
- await writeStatus("restarting", void 0, restarts);
6528
- const backoff = computeBackoffMs(restarts);
6529
- await sleep2(backoff);
6530
- }
6531
- } finally {
6532
- await writeStatus("stopped", void 0, restarts);
6533
- closeSync(logFd);
6534
5747
  }
5748
+ await writeFile6(workspaceFile, existing, "utf8");
6535
5749
  }
6536
5750
 
6537
5751
  // src/cli.ts
6538
5752
  function packageVersion() {
6539
- const raw = readFileSync2(new URL("../package.json", import.meta.url), "utf8");
5753
+ const raw = readFileSync4(new URL("../package.json", import.meta.url), "utf8");
6540
5754
  const pkg = JSON.parse(raw);
6541
5755
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
6542
5756
  }
@@ -6546,39 +5760,19 @@ function addBotOptions(command) {
6546
5760
  function buildProgram() {
6547
5761
  const program = new Command();
6548
5762
  program.name("dsh-lark-bot").description("Bridge DeepSeek Harness into Feishu / Lark").version(packageVersion(), "-v, --version");
6549
- addBotOptions(
6550
- program.command("start").description("Install and start the background service (autostart + auto-restart)")
6551
- ).action(async (opts) => {
6552
- await runServiceStart(opts, { version: packageVersion() });
5763
+ program.command("setup").description(
5764
+ "Install this package as a standard dsh profile bundle (single install path)"
5765
+ ).option("--profile <name>", "dsh profile to install into (default: dsh-lark)").action(async (opts) => {
5766
+ await runSetup({ ...opts.profile ? { profile: opts.profile } : {} });
6553
5767
  });
6554
- addBotOptions(program.command("status").description("Show background service status")).action(
6555
- async (opts) => {
6556
- await runServiceCommand("status", opts, { version: packageVersion() });
6557
- }
6558
- );
6559
- addBotOptions(program.command("restart").description("Restart the background service")).action(
6560
- async (opts) => {
6561
- await runServiceCommand("restart", opts, { version: packageVersion() });
6562
- }
6563
- );
6564
- addBotOptions(program.command("stop").description("Stop the background service and remove autostart")).action(
6565
- async (opts) => {
6566
- await runServiceCommand("stop", opts, { version: packageVersion() });
6567
- }
6568
- );
6569
5768
  program.command("doctor").description("Run local diagnostics").option("--profile <name>", "profile name").option("--workspace <path>", "initial working directory").option("--app-id <id>", "existing Lark/Feishu app id").option("--app-secret <secret>", "existing Lark/Feishu app secret").option("--tenant <tenant>", "feishu or lark").action(async (opts) => {
6570
5769
  await runDoctor({ ...opts, version: packageVersion() });
6571
5770
  });
6572
5771
  addBotOptions(
6573
- program.command("run", { hidden: true }).description("Run the bridge process (managed by the background service; not for interactive use)")
5772
+ program.command("run", { hidden: true }).description("Run the bridge engine directly (diagnostics; the dsh plugin runs it in-process)")
6574
5773
  ).action(async (opts) => {
6575
5774
  await runBot(opts);
6576
5775
  });
6577
- addBotOptions(
6578
- program.command("supervise", { hidden: true }).description("Supervisor loop for the portable background service; not for interactive use")
6579
- ).action(async (opts) => {
6580
- await runSupervise(opts);
6581
- });
6582
5776
  return program;
6583
5777
  }
6584
5778
  async function main(argv = process.argv) {
@@ -6587,7 +5781,7 @@ async function main(argv = process.argv) {
6587
5781
  function isDirectInvocation(entry = process.argv[1], metaUrl = import.meta.url) {
6588
5782
  if (!entry) return false;
6589
5783
  try {
6590
- return realpathSync2(entry) === fileURLToPath3(metaUrl);
5784
+ return realpathSync4(entry) === fileURLToPath2(metaUrl);
6591
5785
  } catch {
6592
5786
  return false;
6593
5787
  }