yaver-cli 1.0.1 → 1.95.5

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.
@@ -0,0 +1,329 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { spawn } = require('child_process');
5
+ const { pipeline } = require('stream/promises');
6
+ const https = require('https');
7
+ const semver = require('semver');
8
+
9
+ const PACKAGE = require('../package.json');
10
+
11
+ const DEFAULT_REPO = process.env.YAVER_AGENT_REPO || 'kivanccakmak/yaver.io';
12
+ const WINDOWS_REPO = process.env.YAVER_WINDOWS_AGENT_REPO || 'kivanccakmak/yaver-cli';
13
+ const CACHE_ROOT = process.env.YAVER_AGENT_CACHE_DIR || path.join(os.homedir(), '.yaver', 'bin');
14
+ let resolvedAgentVersionPromise = null;
15
+ const resolvedAssetPromiseByKey = new Map();
16
+
17
+ async function ensureAgentBinary({ quiet = false } = {}) {
18
+ const asset = await resolveAsset();
19
+ const localAgentPath = resolveLocalAgentBinary(asset);
20
+ if (localAgentPath) {
21
+ return localAgentPath;
22
+ }
23
+ const installDir = path.join(CACHE_ROOT, asset.version, asset.cacheKey);
24
+ const binaryPath = path.join(installDir, asset.binaryName);
25
+
26
+ if (fs.existsSync(binaryPath)) {
27
+ if (process.platform !== 'win32') {
28
+ fs.chmodSync(binaryPath, 0o755);
29
+ }
30
+ return binaryPath;
31
+ }
32
+
33
+ fs.mkdirSync(installDir, { recursive: true });
34
+ const tmpPath = path.join(installDir, asset.downloadName);
35
+
36
+ if (!quiet) {
37
+ console.error(`Installing Yaver agent ${asset.version} for ${asset.cacheKey}...`);
38
+ }
39
+
40
+ await downloadToFile(asset.url, tmpPath);
41
+
42
+ if (asset.archiveType === 'tar.gz') {
43
+ await extractTarball(tmpPath, installDir);
44
+ fs.rmSync(tmpPath, { force: true });
45
+ if (!fs.existsSync(binaryPath) && asset.extractedBinaryName) {
46
+ const extractedPath = path.join(installDir, asset.extractedBinaryName);
47
+ if (fs.existsSync(extractedPath)) {
48
+ fs.renameSync(extractedPath, binaryPath);
49
+ }
50
+ }
51
+ } else {
52
+ fs.renameSync(tmpPath, binaryPath);
53
+ }
54
+
55
+ if (!fs.existsSync(binaryPath)) {
56
+ throw new Error(`agent binary missing after install: ${binaryPath}`);
57
+ }
58
+ if (process.platform !== 'win32') {
59
+ fs.chmodSync(binaryPath, 0o755);
60
+ }
61
+ return binaryPath;
62
+ }
63
+
64
+ async function runAgentCommand(args, options = {}) {
65
+ const localAgent = resolveLocalAgentCommand(args);
66
+ const spawnSpec = localAgent
67
+ ? localAgent
68
+ : { command: await ensureAgentBinary(options), args };
69
+ const child = spawn(spawnSpec.command, spawnSpec.args, {
70
+ stdio: 'inherit',
71
+ env: process.env,
72
+ cwd: spawnSpec.cwd || process.cwd(),
73
+ });
74
+
75
+ await new Promise((resolve, reject) => {
76
+ child.on('error', reject);
77
+ child.on('exit', (code, signal) => {
78
+ if (signal) {
79
+ reject(new Error(`agent terminated by signal ${signal}`));
80
+ return;
81
+ }
82
+ if (code && code !== 0) {
83
+ const error = new Error(`agent exited with code ${code}`);
84
+ error.exitCode = code;
85
+ reject(error);
86
+ return;
87
+ }
88
+ resolve();
89
+ });
90
+ });
91
+ }
92
+
93
+ function resolveAgentInfo() {
94
+ return {
95
+ version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
96
+ repo: DEFAULT_REPO,
97
+ note: 'Run a real agent command once to resolve the current published agent release.',
98
+ };
99
+ }
100
+
101
+ function resolveLocalAgentBinary(asset) {
102
+ if (process.env.YAVER_AGENT_BIN && fs.existsSync(process.env.YAVER_AGENT_BIN)) {
103
+ return process.env.YAVER_AGENT_BIN;
104
+ }
105
+ const repoBinary = path.join(repoRoot(), 'desktop', 'agent', asset.downloadName.replace('.tar.gz', '').replace('.exe', '.exe'));
106
+ if (fs.existsSync(repoBinary)) {
107
+ return repoBinary;
108
+ }
109
+ return null;
110
+ }
111
+
112
+ function resolveLocalAgentCommand(agentArgs) {
113
+ if (process.env.YAVER_AGENT_BIN && fs.existsSync(process.env.YAVER_AGENT_BIN)) {
114
+ return { command: process.env.YAVER_AGENT_BIN, args: agentArgs };
115
+ }
116
+
117
+ const asset = resolveLocalAsset();
118
+ const localBinary = resolveLocalAgentBinary(asset);
119
+ if (localBinary) {
120
+ return { command: localBinary, args: agentArgs };
121
+ }
122
+
123
+ const agentDir = path.join(repoRoot(), 'desktop', 'agent');
124
+ const goMod = path.join(agentDir, 'go.mod');
125
+ if (fs.existsSync(goMod)) {
126
+ return {
127
+ command: 'go',
128
+ args: ['run', '.', ...agentArgs],
129
+ cwd: agentDir,
130
+ };
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ function resolveLocalAsset() {
137
+ const platform = process.platform;
138
+ const arch = process.arch;
139
+ const goArch = arch === 'x64' ? 'amd64' : arch === 'arm64' ? 'arm64' : null;
140
+
141
+ if (!goArch) {
142
+ throw new Error(`unsupported architecture for npm bootstrap: ${arch}`);
143
+ }
144
+
145
+ if (platform === 'win32') {
146
+ return {
147
+ cacheKey: `${platform}-${goArch}`,
148
+ binaryName: 'yaver.exe',
149
+ downloadName: `yaver-windows-${goArch}.exe`,
150
+ archiveType: 'exe',
151
+ url: '',
152
+ version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
153
+ };
154
+ }
155
+
156
+ if (platform === 'darwin' || platform === 'linux') {
157
+ return {
158
+ cacheKey: `${platform}-${goArch}`,
159
+ binaryName: 'yaver',
160
+ extractedBinaryName: `yaver-${platform}-${goArch}`,
161
+ downloadName: `yaver-${platform}-${goArch}.tar.gz`,
162
+ archiveType: 'tar.gz',
163
+ url: '',
164
+ version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
165
+ };
166
+ }
167
+
168
+ throw new Error(`unsupported platform for npm bootstrap: ${platform}`);
169
+ }
170
+
171
+ async function resolveAsset() {
172
+ const platform = process.platform;
173
+ const arch = process.arch;
174
+ const goArch = arch === 'x64' ? 'amd64' : arch === 'arm64' ? 'arm64' : null;
175
+
176
+ if (!goArch) {
177
+ throw new Error(`unsupported architecture for npm bootstrap: ${arch}`);
178
+ }
179
+
180
+ const cacheKey = `${platform}-${goArch}`;
181
+ if (!resolvedAssetPromiseByKey.has(cacheKey)) {
182
+ resolvedAssetPromiseByKey.set(cacheKey, fetchRemoteAsset(platform, goArch));
183
+ }
184
+ return resolvedAssetPromiseByKey.get(cacheKey);
185
+ }
186
+
187
+ async function fetchRemoteAsset(platform, goArch) {
188
+ if (platform === 'win32') {
189
+ const release = await fetchLatestRelease(WINDOWS_REPO);
190
+ const asset = findReleaseAsset(release, [`yaver-windows-${goArch}.exe`]);
191
+ if (!asset) {
192
+ throw new Error(`could not find Windows agent asset for ${goArch} in ${WINDOWS_REPO}`);
193
+ }
194
+ return {
195
+ cacheKey: `${platform}-${goArch}`,
196
+ binaryName: 'yaver.exe',
197
+ downloadName: asset.name,
198
+ archiveType: 'exe',
199
+ url: asset.browser_download_url,
200
+ version: release.tag_name.replace(/^v/, ''),
201
+ };
202
+ }
203
+
204
+ if (platform === 'darwin' || platform === 'linux') {
205
+ const release = await fetchLatestRelease(DEFAULT_REPO);
206
+ const version = release.tag_name.replace(/^v/, '');
207
+ const asset = findReleaseAsset(release, [
208
+ `yaver-v${version}-${platform}-${goArch}.tar.gz`,
209
+ `yaver-${platform}-${goArch}.tar.gz`,
210
+ ]);
211
+ if (!asset) {
212
+ throw new Error(`could not find ${platform}/${goArch} agent tarball in ${DEFAULT_REPO}`);
213
+ }
214
+ return {
215
+ cacheKey: `${platform}-${goArch}`,
216
+ binaryName: 'yaver',
217
+ extractedBinaryName: `yaver-${platform}-${goArch}`,
218
+ downloadName: asset.name,
219
+ archiveType: 'tar.gz',
220
+ url: asset.browser_download_url,
221
+ version,
222
+ };
223
+ }
224
+
225
+ throw new Error(`unsupported platform for npm bootstrap: ${platform}`);
226
+ }
227
+
228
+ async function fetchLatestRelease(repo) {
229
+ const response = await request(`https://api.github.com/repos/${repo}/releases?per_page=20`, {
230
+ headers: {
231
+ 'User-Agent': `yaver-cli/${PACKAGE.version}`,
232
+ Accept: 'application/vnd.github+json',
233
+ },
234
+ });
235
+ const body = await readResponseBody(response);
236
+ if (response.statusCode && response.statusCode >= 400) {
237
+ throw new Error(`failed to resolve latest release (${response.statusCode}) from ${repo}`);
238
+ }
239
+
240
+ let releases;
241
+ try {
242
+ releases = JSON.parse(body);
243
+ } catch (error) {
244
+ throw new Error(`invalid release metadata from ${repo}: ${error.message}`);
245
+ }
246
+
247
+ const latest = Array.isArray(releases)
248
+ ? releases.find((release) => !release.draft && !release.prerelease && semver.valid(String(release.tag_name || '').replace(/^v/, '')))
249
+ : null;
250
+
251
+ if (!latest || !latest.tag_name) {
252
+ throw new Error(`could not find a published semver release in ${repo}`);
253
+ }
254
+
255
+ return latest;
256
+ }
257
+
258
+ function findReleaseAsset(release, names) {
259
+ const byName = new Map((release.assets || []).map((asset) => [asset.name, asset]));
260
+ for (const name of names) {
261
+ const asset = byName.get(name);
262
+ if (asset) return asset;
263
+ }
264
+ return null;
265
+ }
266
+
267
+ async function downloadToFile(url, destPath) {
268
+ const response = await request(url);
269
+ if (response.statusCode && response.statusCode >= 400) {
270
+ throw new Error(`download failed (${response.statusCode}) from ${url}. Publish the matching CLI release assets or set YAVER_AGENT_BIN.`);
271
+ }
272
+ await pipeline(response, fs.createWriteStream(destPath));
273
+ }
274
+
275
+ function request(url, options = {}) {
276
+ return new Promise((resolve, reject) => {
277
+ https.get(url, options, (response) => {
278
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
279
+ resolve(request(response.headers.location, options));
280
+ return;
281
+ }
282
+ resolve(response);
283
+ }).on('error', reject);
284
+ });
285
+ }
286
+
287
+ function readResponseBody(response) {
288
+ return new Promise((resolve, reject) => {
289
+ let body = '';
290
+ response.setEncoding('utf8');
291
+ response.on('data', (chunk) => {
292
+ body += chunk;
293
+ });
294
+ response.on('end', () => resolve(body));
295
+ response.on('error', reject);
296
+ });
297
+ }
298
+
299
+ async function extractTarball(archivePath, destDir) {
300
+ const child = spawn('tar', ['-xzf', archivePath, '-C', destDir], {
301
+ stdio: ['ignore', 'pipe', 'pipe'],
302
+ });
303
+
304
+ let stderr = '';
305
+ child.stderr.on('data', (chunk) => {
306
+ stderr += chunk.toString();
307
+ });
308
+
309
+ await new Promise((resolve, reject) => {
310
+ child.on('error', reject);
311
+ child.on('exit', (code) => {
312
+ if (code !== 0) {
313
+ reject(new Error(stderr.trim() || `tar exited with code ${code}`));
314
+ return;
315
+ }
316
+ resolve();
317
+ });
318
+ });
319
+ }
320
+
321
+ module.exports = {
322
+ ensureAgentBinary,
323
+ resolveAgentInfo,
324
+ runAgentCommand,
325
+ };
326
+
327
+ function repoRoot() {
328
+ return path.resolve(__dirname, '..', '..');
329
+ }
package/src/analyzer.js CHANGED
@@ -73,7 +73,7 @@ function analyzeProject(packageJson, sdkManifest) {
73
73
  if (PURE_JS_PACKAGES.has(name)) continue;
74
74
  if (FALSE_POSITIVE_NATIVE.has(name)) continue;
75
75
  if (packageJson.devDependencies?.[name] && !packageJson.dependencies?.[name]) continue;
76
- if (!looksLikeNativeModule(name)) continue;
76
+ if (!looksLikeNativeModule(name, sdkManifest)) continue;
77
77
 
78
78
  const sdkVersion = sdkManifest.nativeModules[name];
79
79
 
@@ -89,10 +89,10 @@ function analyzeProject(packageJson, sdkManifest) {
89
89
 
90
90
  const localParsed = semver.coerce(cleanLocal);
91
91
  const sdkParsed = semver.coerce(sdkVersion);
92
- if (localParsed && sdkParsed && semver.major(localParsed) !== semver.major(sdkParsed)) {
92
+ if (localParsed && sdkParsed && hasPotentiallyBreakingVersionDrift(localParsed, sdkParsed)) {
93
93
  warnings.push({
94
94
  type: 'version_mismatch', module: name,
95
- message: `"${name}": project ${version}, yaver ${sdkVersion}. Major version differs.`,
95
+ message: `"${name}": project ${version}, yaver ${sdkVersion}. Version differs at a potentially breaking boundary.`,
96
96
  });
97
97
  }
98
98
  }
@@ -101,7 +101,8 @@ function analyzeProject(packageJson, sdkManifest) {
101
101
  return { reactNativeVersion: projectRN, errors, warnings, availableModules, missingModules };
102
102
  }
103
103
 
104
- function looksLikeNativeModule(name) {
104
+ function looksLikeNativeModule(name, sdkManifest) {
105
+ if (sdkManifest?.nativeModules?.[name]) return true;
105
106
  return name.startsWith('react-native-') ||
106
107
  name.startsWith('@react-native-') ||
107
108
  name.startsWith('@react-native/') ||
@@ -114,4 +115,12 @@ function cleanVersion(v) {
114
115
  return (v || '').replace(/[\^~>=<\s]/g, '');
115
116
  }
116
117
 
118
+ function hasPotentiallyBreakingVersionDrift(projectVersion, sdkVersion) {
119
+ if (semver.major(projectVersion) !== semver.major(sdkVersion)) return true;
120
+ if (semver.major(projectVersion) === 0 && semver.minor(projectVersion) !== semver.minor(sdkVersion)) {
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
117
126
  module.exports = { analyzeProject, PURE_JS_PACKAGES, FALSE_POSITIVE_NATIVE };
package/src/bundler.js CHANGED
@@ -103,13 +103,13 @@ async function compileHermes({ inputPath, outputPath }) {
103
103
 
104
104
  function readBytecodeVersion(hbcPath) {
105
105
  const buf = fs.readFileSync(hbcPath);
106
- if (buf.length < 8) return null;
106
+ if (buf.length < 12) return null;
107
107
 
108
- // Hermes magic: 0x1F1903C1 (little-endian)
109
- const magic = buf.readUInt32LE(0);
108
+ // Hermes HBC format: magic at offset 4, BC version at offset 8
109
+ const magic = buf.readUInt32LE(4);
110
110
  if (magic !== 0x1F1903C1) return null;
111
111
 
112
- return buf.readUInt32LE(4);
112
+ return buf.readUInt32LE(8);
113
113
  }
114
114
 
115
115
  module.exports = { bundle, compileHermes, readBytecodeVersion, getHermescPath };
@@ -16,7 +16,7 @@ async function devices() {
16
16
  for (const d of found) {
17
17
  console.log(` 📱 ${d.name} (${d.platform})`);
18
18
  console.log(` IP: ${d.ip}:${d.port}`);
19
- console.log(` Push: yaver-push push --device ${d.ip}\n`);
19
+ console.log(` Push: yaver push --device ${d.ip}\n`);
20
20
  }
21
21
  }
22
22
 
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const { analyzeProject } = require('../analyzer');
3
+ const { loadSDKManifest } = require('../sdk-manifest');
3
4
 
4
5
  async function doctor() {
5
6
  if (!fs.existsSync('package.json')) {
@@ -8,7 +9,7 @@ async function doctor() {
8
9
  }
9
10
 
10
11
  const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
11
- const sdkManifest = require('../../sdk-manifest.json');
12
+ const sdkManifest = loadSDKManifest();
12
13
  const analysis = analyzeProject(pkg, sdkManifest);
13
14
 
14
15
  console.log('\n📋 Yaver Compatibility Report\n');
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
2
  const { analyzeProject } = require('../analyzer');
3
+ const { loadSDKManifest } = require('../sdk-manifest');
4
4
 
5
5
  async function init() {
6
6
  if (!fs.existsSync('package.json')) {
@@ -16,7 +16,7 @@ async function init() {
16
16
 
17
17
  console.log('🔍 Analyzing your project...\n');
18
18
 
19
- const sdkManifest = require('../../sdk-manifest.json');
19
+ const sdkManifest = loadSDKManifest();
20
20
  const analysis = analyzeProject(pkg, sdkManifest);
21
21
 
22
22
  printAnalysis(analysis, sdkManifest);
@@ -37,12 +37,12 @@ async function init() {
37
37
 
38
38
  if (analysis.errors.filter(e => e.type === 'missing_module').length > 0) {
39
39
  console.log('\n⚠️ Your project has compatibility issues (see above).');
40
- console.log(' You can still push with: yaver-push push --ignore-missing');
40
+ console.log(' You can still push with: yaver push --ignore-missing');
41
41
  console.log(' Features using missing modules will crash.\n');
42
42
  } else if (analysis.errors.length > 0) {
43
43
  console.log('\n🚫 Incompatible — see errors above.\n');
44
44
  } else {
45
- console.log('\n🎉 Fully compatible! Run: yaver-push push\n');
45
+ console.log('\n🎉 Fully compatible! Run: yaver push\n');
46
46
  }
47
47
  }
48
48
 
@@ -1,5 +1,6 @@
1
1
  async function modules() {
2
- const sdkManifest = require('../../sdk-manifest.json');
2
+ const { loadSDKManifest } = require('../sdk-manifest');
3
+ const sdkManifest = loadSDKManifest();
3
4
 
4
5
  console.log(`\n📦 Yaver SDK v${sdkManifest.sdkVersion} — Native Modules\n`);
5
6
  console.log(` React Native: ${sdkManifest.reactNative}`);
@@ -3,13 +3,14 @@ const path = require('path');
3
3
  const { analyzeProject } = require('../analyzer');
4
4
  const { bundle, compileHermes, readBytecodeVersion } = require('../bundler');
5
5
  const { discoverDevice, fetchHealth } = require('../discovery');
6
+ const { loadSDKManifest } = require('../sdk-manifest');
6
7
  const { pushBundle, pushAssets } = require('../transport');
7
8
 
8
9
  async function push(options = {}) {
9
10
  const startTime = Date.now();
10
11
  const quiet = options.quiet;
11
12
  const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
12
- const sdkManifest = require('../../sdk-manifest.json');
13
+ const sdkManifest = loadSDKManifest();
13
14
 
14
15
  // Find device
15
16
  if (!quiet) console.log('📡 Finding device...');
@@ -22,7 +23,7 @@ async function push(options = {}) {
22
23
 
23
24
  if (health.hermes?.bytecodeVersion !== sdkManifest.hermes.bytecodeVersion) {
24
25
  console.error(`�� Hermes BC mismatch: device BC${health.hermes?.bytecodeVersion}, CLI BC${sdkManifest.hermes.bytecodeVersion}`);
25
- console.error(' Update yaver-cli or yaver.io app.');
26
+ console.error(' Update the npm package or the yaver.io app.');
26
27
  process.exit(1);
27
28
  }
28
29
 
@@ -42,7 +43,7 @@ async function push(options = {}) {
42
43
  console.warn(`\n⚠��� ${analysis.missingModules.length} native module(s) NOT in yaver SDK:`);
43
44
  analysis.missingModules.forEach(m => console.warn(` • ${m.name}@${m.version}`));
44
45
  console.warn('\n App will crash if it calls these modules.');
45
- console.warn(' Push anyway: yaver-push push --ignore-missing\n');
46
+ console.warn(' Push anyway: yaver push --ignore-missing\n');
46
47
  if (!options.force) process.exit(1);
47
48
  }
48
49
 
@@ -14,7 +14,7 @@ async function status(options = {}) {
14
14
  console.log(` Missing: ${yj.missingModules?.length || 0} native modules`);
15
15
  console.log(` Analyzed: ${yj.analyzedAt}`);
16
16
  } else {
17
- console.log(' No yaver.json found. Run: yaver-push init');
17
+ console.log(' No yaver.json found. Run: yaver push init');
18
18
  }
19
19
 
20
20
  // Device status
package/src/discovery.js CHANGED
@@ -24,7 +24,7 @@ async function discoverDevice(manualIp) {
24
24
  throw new Error(
25
25
  'No yaver.io device found on network.\n' +
26
26
  ' Make sure the yaver.io app is open on your phone (same WiFi).\n' +
27
- ' Or specify device IP: yaver-push push --device <ip>'
27
+ ' Or specify device IP: yaver push --device <ip>'
28
28
  );
29
29
  }
30
30
 
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ const PACKAGE = require('../package.json');
2
+ const { resolveAgentInfo, runAgentCommand } = require('./agent-runtime');
1
3
  const { init } = require('./commands/init');
2
4
  const { push } = require('./commands/push');
3
5
  const { doctor } = require('./commands/doctor');
@@ -6,8 +8,8 @@ const { modules } = require('./commands/modules');
6
8
  const { reset } = require('./commands/reset');
7
9
  const { status } = require('./commands/status');
8
10
 
9
- const HELP = `
10
- yaver-push — Push existing React Native projects to yaver.io
11
+ const PUSH_HELP = `
12
+ yaver push — Push existing React Native projects to the Yaver mobile host
11
13
 
12
14
  Commands:
13
15
  init Analyze project, show compatibility, create yaver.json
@@ -27,12 +29,43 @@ Options:
27
29
  --help Show this help
28
30
  `;
29
31
 
30
- async function run(args) {
32
+ const UNIFIED_HELP = `
33
+ yaver — single npm install for the Go agent and the RN push CLI
34
+
35
+ Agent commands:
36
+ yaver serve Start the Yaver agent (downloads platform binary if needed)
37
+ yaver auth Sign in and configure the agent
38
+ yaver version Print agent version
39
+ yaver <agent-command> Forward any other command to the Go agent
40
+
41
+ Push-to-device commands:
42
+ yaver push Bundle + validate + push current RN/Expo project
43
+ yaver push init Analyze current project and create yaver.json
44
+ yaver push doctor Deep compatibility report for current project
45
+ yaver push modules List native modules compiled into the mobile host
46
+ yaver push devices Scan LAN for Yaver mobile hosts
47
+ yaver push reset Clear pushed bundle on the selected device
48
+ yaver push status Show project + device push status
49
+
50
+ Compatibility:
51
+ yaver-push <command> Legacy alias for the push subcommands above
52
+
53
+ Options:
54
+ --help Show this help
55
+ `;
56
+
57
+ const PUSH_SUBCOMMANDS = new Set(['init', 'push', 'doctor', 'devices', 'modules', 'reset', 'status']);
58
+ const DIRECT_PUSH_ALIASES = new Map([
59
+ ['init', 'init'],
60
+ ['modules', 'modules'],
61
+ ]);
62
+
63
+ async function runPushCli(args) {
31
64
  const command = args[0];
32
65
  const options = parseArgs(args.slice(1));
33
66
 
34
- if (!command || command === '--help' || command === '-h') {
35
- console.log(HELP);
67
+ if (!command || command === '--help' || command === '-h' || args.includes('--help') || args.includes('-h')) {
68
+ console.log(PUSH_HELP);
36
69
  process.exit(0);
37
70
  }
38
71
 
@@ -61,7 +94,7 @@ async function run(args) {
61
94
  break;
62
95
  default:
63
96
  console.error(`Unknown command: ${command}`);
64
- console.log(HELP);
97
+ console.log(PUSH_HELP);
65
98
  process.exit(1);
66
99
  }
67
100
  } catch (err) {
@@ -71,6 +104,53 @@ async function run(args) {
71
104
  }
72
105
  }
73
106
 
107
+ async function runUnified(args) {
108
+ const command = args[0];
109
+
110
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
111
+ console.log(UNIFIED_HELP);
112
+ process.exit(0);
113
+ }
114
+
115
+ if (command === 'push') {
116
+ const next = args[1];
117
+ if (!next || next.startsWith('-')) {
118
+ await runPushCli(['push', ...args.slice(1)]);
119
+ return;
120
+ }
121
+ if (PUSH_SUBCOMMANDS.has(next)) {
122
+ const subcommand = next === 'push' ? 'push' : next;
123
+ await runPushCli([subcommand, ...args.slice(2)]);
124
+ return;
125
+ }
126
+ console.error(`Unknown push subcommand: ${next}`);
127
+ console.log(PUSH_HELP);
128
+ process.exit(1);
129
+ }
130
+
131
+ if (DIRECT_PUSH_ALIASES.has(command)) {
132
+ await runPushCli([DIRECT_PUSH_ALIASES.get(command), ...args.slice(1)]);
133
+ return;
134
+ }
135
+
136
+ if (command === 'npm-agent-info') {
137
+ const info = resolveAgentInfo();
138
+ console.log(JSON.stringify(info, null, 2));
139
+ return;
140
+ }
141
+
142
+ try {
143
+ await runAgentCommand(args);
144
+ } catch (err) {
145
+ if (typeof err.exitCode === 'number') {
146
+ process.exit(err.exitCode);
147
+ }
148
+ console.error(`\n❌ ${err.message}`);
149
+ if (process.env.YAVER_DEBUG) console.error(err.stack);
150
+ process.exit(1);
151
+ }
152
+ }
153
+
74
154
  function parseArgs(args) {
75
155
  const opts = {};
76
156
  for (let i = 0; i < args.length; i++) {
@@ -90,4 +170,4 @@ function parseArgs(args) {
90
170
  return opts;
91
171
  }
92
172
 
93
- module.exports = { run };
173
+ module.exports = { runPushCli, runUnified };
@@ -0,0 +1,9 @@
1
+ const path = require('path');
2
+
3
+ function loadSDKManifest() {
4
+ const manifestPath = path.resolve(__dirname, '..', 'sdk-manifest.json');
5
+ delete require.cache[manifestPath];
6
+ return require(manifestPath);
7
+ }
8
+
9
+ module.exports = { loadSDKManifest };