@whyshu/argent 0.1.4 → 0.1.6

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/bin/argent.mjs CHANGED
@@ -9,13 +9,14 @@
9
9
  *
10
10
  * 公共参数:--dsh-home <dir> --profile <name> --repo <dir> --json --dry-run
11
11
  */
12
- import { spawn } from 'node:child_process';
13
- import { mkdirSync, writeFileSync, rmSync, existsSync, copyFileSync, chmodSync } from 'node:fs';
12
+ import { spawn, spawnSync } from 'node:child_process';
13
+ import { mkdirSync, writeFileSync, rmSync, existsSync, copyFileSync, chmodSync, readFileSync } from 'node:fs';
14
14
  import { join, dirname } from 'node:path';
15
15
  import { homedir } from 'node:os';
16
16
  import {
17
17
  DSH_EXACT_VERSION, PROFILE_NAME, PROD_BUNDLES, buildLaunchdPlist, buildProfile, dshHomeOf, runnerScriptPath,
18
18
  depsInstalled, health, installDeps, loginWithPhone, pairRunner, probeDshVersion, readAuthToken, repoRoot, runnerConfigPath, sendLoginCode, versionMatches,
19
+ engineRoot, materializeRuntime,
19
20
  } from '../lib/index.mjs';
20
21
 
21
22
  const argv = process.argv.slice(2);
@@ -26,9 +27,9 @@ const DRY_RUN = has('--dry-run');
26
27
 
27
28
  const command = (() => {
28
29
  const positional = argv.filter((a) => !a.startsWith('--') && !['-h'].includes(a));
29
- const i = argv.findIndex((a) => a === 'web' || a === 'health' || a === 'setup' || a === 'pair' || a === 'login' || a === 'service');
30
+ const i = argv.findIndex((a) => a === 'web' || a === 'health' || a === 'setup' || a === 'pair' || a === 'login' || a === 'service' || a === 'runtime');
30
31
  if (i < 0) return '';
31
- if (argv[i] === 'service') return `service ${argv[i + 1] || ''}`.trim();
32
+ if (argv[i] === 'service' || argv[i] === 'runtime') return `${argv[i]} ${argv[i + 1] || ''}`.trim();
32
33
  return argv[i];
33
34
  })();
34
35
 
@@ -37,6 +38,7 @@ const opts = {
37
38
  profile: val('--profile', PROFILE_NAME),
38
39
  repo: repoRoot({ repo: val('--repo') }),
39
40
  port: Number(val('--port', '3080')) || 3080,
41
+ target: val('--target', join(homedir(), '.argent-dsh')),
40
42
  extraBundles: (() => { const raw = val('--bundles', '').split(',').map((x) => x.trim()).filter(Boolean); return raw.flatMap((x) => (x === 'prod' ? PROD_BUNDLES : [x])); })(),
41
43
  };
42
44
 
@@ -45,6 +47,24 @@ function print(obj) {
45
47
  else for (const [k, v] of Object.entries(obj)) console.log(`${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
46
48
  }
47
49
 
50
+ function ensureBundleDeps() {
51
+ const dir = join(opts.dshHome, 'profiles', opts.profile);
52
+ let pkg;
53
+ try { pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); } catch { return; }
54
+ for (const [name, version] of Object.entries(pkg.dependencies || {})) {
55
+ const depDir = join(dir, 'node_modules', ...name.split('/'));
56
+ if (existsSync(depDir)) continue;
57
+ console.error(`→ 补装缺失 bundle ${name}@${version} …`);
58
+ const r = spawnSync('dsh', ['plugin', '--profile', opts.profile, 'add', `${name}@${version}`], {
59
+ cwd: dir, stdio: 'inherit', env: { ...process.env, DSH_HOME: opts.dshHome },
60
+ });
61
+ if (r.status !== 0) {
62
+ console.error(`❌ 补装失败:${name}(exit ${r.status})`);
63
+ process.exit(2);
64
+ }
65
+ }
66
+ }
67
+
48
68
  function ensureProfile({ install = true } = {}) {
49
69
  const built = buildProfile({ dshHome: opts.dshHome, profile: opts.profile, repo: opts.repo, extraBundles: opts.extraBundles });
50
70
  if (!built.ok) {
@@ -61,6 +81,7 @@ function ensureProfile({ install = true } = {}) {
61
81
  }
62
82
  console.error('✅ profile 依赖装配完成');
63
83
  }
84
+ if (install && !has('--no-install')) ensureBundleDeps();
64
85
  return built;
65
86
  }
66
87
 
@@ -160,6 +181,23 @@ function setupCommand() {
160
181
  print({ ok: true, profile: built.dir, bundles: built.bundles.join(', '), plugins: built.plugins.join(', '), preset: built.preset, depsInstalled: depsInstalled(opts.dshHome, opts.profile) });
161
182
  }
162
183
 
184
+ function runtimeCommand(sub) {
185
+ const engine = engineRoot();
186
+ if (!engine) { console.error('❌ 未找到 @whyshu/argent-engine;请用新版启动器(npm 依赖应随包安装)'); process.exit(2); }
187
+ const version = readFileSync(join(engine, 'VERSION'), 'utf8').trim();
188
+ if (sub === 'status' || sub === '') {
189
+ let current = null;
190
+ try { current = JSON.parse(readFileSync(join(opts.target, 'RUNTIME_VERSION.json'), 'utf8')); } catch { current = null; }
191
+ print({ ok: true, engine, engineVersion: version, target: opts.target, current: current ? current.version : null, installed: Boolean(current) });
192
+ return;
193
+ }
194
+ if (sub !== 'install') { console.error('用法:argent runtime install|status [--target <dir>] [--dry-run]'); process.exit(2); }
195
+ if (DRY_RUN) { print({ ok: true, dryRun: true, engine, version, target: opts.target, action: 'materialize runtime files' }); return; }
196
+ const r = materializeRuntime({ target: opts.target, engine });
197
+ if (!r.ok) { console.error(`❌ runtime 安装失败:${r.message}`); process.exit(2); }
198
+ print({ ok: true, package: '@whyshu/argent-engine', version: r.version, files: r.files, target: r.target, backupDir: r.backupDir });
199
+ }
200
+
163
201
  function serviceCommand(sub) {
164
202
  const bundled = runnerScriptPath({ repo: opts.repo });
165
203
  // 固定 runner 路径:复制进 DSH_HOME,避免 launchd 指向 npx 临时缓存(缓存清理后失效)
@@ -199,13 +237,14 @@ function serviceCommand(sub) {
199
237
 
200
238
  const main = async () => {
201
239
  if (!command || has('-h') || has('--help')) {
202
- console.log('用法:argent <web|health|setup|login|pair|service install|service uninstall> [--dsh-home <dir>] [--json] [--dry-run]');
240
+ console.log('用法:argent <web|health|setup|login|pair|runtime install|runtime status|service install|service uninstall> [--dsh-home <dir>] [--json] [--dry-run]');
203
241
  process.exit(command ? 0 : 2);
204
242
  }
205
243
  if (command === 'health') return healthCommand();
206
244
  if (command === 'setup') return setupCommand();
207
245
  if (command === 'pair') return pairCommand();
208
246
  if (command === 'login') return loginCommand();
247
+ if (command === 'runtime install' || command === 'runtime status') return runtimeCommand(command.split(' ')[1]);
209
248
  if (command === 'service install' || command === 'service uninstall') return serviceCommand(command.split(' ')[1]);
210
249
  return webCommand();
211
250
  };
package/lib/index.mjs CHANGED
@@ -8,7 +8,7 @@
8
8
  * · 状态自检(版本/profile/runner 配置/端口)。
9
9
  * ⛔ 不读写任何凭据内容(runner.json 由首跑引导写,权限 600)。
10
10
  */
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, cpSync, readdirSync } from 'node:fs';
12
12
  import { spawnSync } from 'node:child_process';
13
13
  import { homedir, hostname as osHostname } from 'node:os';
14
14
  import { dirname, join, resolve } from 'node:path';
@@ -83,6 +83,63 @@ export const PUBLISHED_BUNDLE_VERSIONS = {
83
83
  '@linxin666/dsh-client-ui-task-board': '0.4.7',
84
84
  };
85
85
 
86
+
87
+ /** 发布模式随启动器安装的 runtime(@whyshu/argent-engine)根目录;本地联调回退 dist。 */
88
+ export function engineRoot({ env = process.env, launcherLib = dirname(fileURLToPath(import.meta.url)) } = {}) {
89
+ const candidates = [];
90
+ if (env.ARGENT_ENGINE_ROOT) candidates.push(resolve(env.ARGENT_ENGINE_ROOT));
91
+ // npx/global 安装:node_modules/@whyshu/argent-engine/engine
92
+ candidates.push(resolve(launcherLib, '..', '..', '..', '@whyshu', 'argent-engine', 'engine'));
93
+ candidates.push(resolve(launcherLib, '..', '..', 'node_modules', '@whyshu', 'argent-engine', 'engine'));
94
+ // 本地仓库联调:dist/argent-engine/engine
95
+ candidates.push(resolve(launcherLib, '..', '..', 'dist', 'argent-engine', 'engine'));
96
+ for (const c of candidates) {
97
+ if (existsSync(join(c, 'scripts')) && existsSync(join(c, 'VERSION'))) return c;
98
+ }
99
+ return '';
100
+ }
101
+
102
+ function walkFiles(dir, base = dir) {
103
+ const out = [];
104
+ for (const e of readdirSync(dir)) {
105
+ const p = join(dir, e);
106
+ const rel = p.slice(base.length + 1).split('\\').join('/');
107
+ const st = statSync(p);
108
+ if (st.isDirectory()) out.push(...walkFiles(p, base));
109
+ else out.push(rel);
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * 把 npm runtime 包物化到客户机既有资产目录(默认 ~/.argent-dsh)。
116
+ * 只覆盖 engine 包内的文件;客户配置/凭据/.git 不动;被覆盖文件先备份。
117
+ */
118
+ export function materializeRuntime({ target = join(homedir(), '.argent-dsh'), engine = engineRoot(), now = new Date() } = {}) {
119
+ if (!engine) return { ok: false, code: 'engine_missing', message: '未找到 @whyshu/argent-engine(启动器依赖未安装)' };
120
+ if (!existsSync(target)) mkdirSync(target, { recursive: true });
121
+ const version = readFileSync(join(engine, 'VERSION'), 'utf8').trim();
122
+ const stamp = now.toISOString().replace(/[:.]/g, '-');
123
+ const backupDir = join(target, '.runtime-backup', stamp);
124
+ const files = walkFiles(engine);
125
+ let copied = 0;
126
+ for (const rel of files) {
127
+ const src = join(engine, rel);
128
+ const dst = join(target, rel);
129
+ if (existsSync(dst)) {
130
+ const b = join(backupDir, rel);
131
+ mkdirSync(dirname(b), { recursive: true });
132
+ cpSync(dst, b);
133
+ }
134
+ mkdirSync(dirname(dst), { recursive: true });
135
+ cpSync(src, dst);
136
+ copied += 1;
137
+ }
138
+ mkdirSync(join(target, '.runtime-backup'), { recursive: true });
139
+ writeFileSync(join(target, 'RUNTIME_VERSION.json'), JSON.stringify({ package: '@whyshu/argent-engine', version, installedAt: now.toISOString(), files: copied, backupDir }, null, 2) + '\n');
140
+ return { ok: true, version, files: copied, target, backupDir };
141
+ }
142
+
86
143
  export function profileDir(dshHome, profile = PROFILE_NAME) {
87
144
  return join(dshHome, 'profiles', profile);
88
145
  }
@@ -112,13 +169,16 @@ export function buildProfile({ dshHome = dshHomeOf(), profile = PROFILE_NAME, re
112
169
  dsh: { profile: { bundles } },
113
170
  };
114
171
  writeFileSync(join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
115
- const patch = [
172
+ const patchLines = [
116
173
  '# @whyshu/argent 生成(幂等;手工改动会在此文件重生成时被覆盖)',
117
174
  '- id: agent-preset-registry',
118
175
  ' config:',
119
176
  ` default: ${DEFAULT_PRESET}`,
120
- '',
121
- ].join('\n');
177
+ ];
178
+ if (bundles.includes('@linxin666/dsh-client-ui-task-board')) {
179
+ patchLines.push('- insert:', ' - id: ui-task-board', " name: '@linxin666/dsh-client-ui-task-board'");
180
+ }
181
+ const patch = patchLines.join('\n') + '\n';
122
182
  writeFileSync(join(dir, 'cordis.patch.yml'), patch);
123
183
  // pnpm 配置必须与现有 web profile 同口径:否则 pnpm 会尝试从 registry 自动装 peer(含未发布版本)⇒ 安装失败
124
184
  writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - .\nnodeLinker: hoisted\nautoInstallPeers: false\nallowBuilds:\n protobufjs: true\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyshu/argent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Argent 启动器:一条命令启动「打包好的 dsh + Argent 插件/角色预设」,并管理本机 runner 常驻。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@deepseek-ai/dsh": "0.2.1-rc.2"
17
+ "@deepseek-ai/dsh": "0.2.1-rc.2",
18
+ "@whyshu/argent-engine": "0.7.4"
18
19
  },
19
20
  "files": [
20
21
  "bin/",