cleanyourandroid 0.0.0-stage → 1.0.1
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/LICENSE +22 -0
- package/README.md +170 -2
- package/bin/cya.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +422 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -4
- package/src/index.ts +452 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cleanyourandroid",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "CleanYourAndroid CLI — open-source Android customization and debloating toolkit",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cleanyourandroid": "./bin/cya.js",
|
|
8
|
+
"cya": "./bin/cya.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -b",
|
|
20
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
21
|
+
"dev": "node ./bin/cya.js"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@cleanyourandroid/shared": "workspace:*",
|
|
25
|
+
"@cleanyourandroid/core": "workspace:*",
|
|
26
|
+
"commander": "^13.1.0",
|
|
27
|
+
"chalk": "^5.4.1",
|
|
28
|
+
"inquirer": "^12.4.2",
|
|
29
|
+
"ora": "^8.2.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/inquirer": "^9.0.7",
|
|
33
|
+
"typescript": "^5.7.3"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import {
|
|
6
|
+
createDeviceManager,
|
|
7
|
+
THEME_PRESETS,
|
|
8
|
+
ThemeIdentifier,
|
|
9
|
+
DeviceNotFoundError,
|
|
10
|
+
CleanYourAndroidError
|
|
11
|
+
} from '@cleanyourandroid/core';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
const manager = createDeviceManager();
|
|
15
|
+
|
|
16
|
+
async function resolveTargetDevice(explicitDevice?: string): Promise<string> {
|
|
17
|
+
if (explicitDevice) return explicitDevice;
|
|
18
|
+
|
|
19
|
+
const devices = await manager.getConnectedDevices();
|
|
20
|
+
if (devices.length === 0) {
|
|
21
|
+
console.log(chalk.red('✕ No Android devices detected.'));
|
|
22
|
+
console.log(chalk.gray(' Run "cya doctor" for connection troubleshooting or connect via USB.'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (devices.length === 1) {
|
|
27
|
+
return devices[0].serial;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const answer = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
name: 'selected',
|
|
34
|
+
message: 'Multiple devices detected. Select a target device:',
|
|
35
|
+
choices: devices.map((d) => ({
|
|
36
|
+
name: `${d.model || d.product || 'Android Device'} (${d.serial}) [${d.state}]`,
|
|
37
|
+
value: d.serial
|
|
38
|
+
}))
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
return answer.selected;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.name('cya')
|
|
47
|
+
.description(chalk.bold('CleanYourAndroid') + ' — Open-Source Android Customization & Debloating Toolkit')
|
|
48
|
+
.version('1.0.0');
|
|
49
|
+
|
|
50
|
+
// cya devices
|
|
51
|
+
program
|
|
52
|
+
.command('devices')
|
|
53
|
+
.description('List all connected USB and wireless Android devices')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
const spinner = ora('Detecting connected devices...').start();
|
|
56
|
+
try {
|
|
57
|
+
const devices = await manager.getConnectedDevices();
|
|
58
|
+
spinner.stop();
|
|
59
|
+
if (devices.length === 0) {
|
|
60
|
+
console.log(chalk.yellow('No Android devices found. Make sure USB Debugging is ON.'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.bold.cyan(`\n📱 Connected Devices (${devices.length}):`));
|
|
65
|
+
console.log('─'.repeat(60));
|
|
66
|
+
for (const d of devices) {
|
|
67
|
+
const stateColor = d.state === 'device' ? chalk.green : chalk.red;
|
|
68
|
+
console.log(
|
|
69
|
+
`• Serial: ${chalk.bold(d.serial)}\n` +
|
|
70
|
+
` State: ${stateColor(d.state.toUpperCase())}\n` +
|
|
71
|
+
` Transport: ${chalk.gray(d.transport)}\n` +
|
|
72
|
+
` Model: ${d.model || 'Unknown'}\n`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
spinner.fail('Failed to query devices');
|
|
77
|
+
console.error(chalk.red((err as Error).message));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// cya connect <host> [port]
|
|
82
|
+
program
|
|
83
|
+
.command('connect <host> [port]')
|
|
84
|
+
.description('Connect to a wireless ADB device via IP address')
|
|
85
|
+
.action(async (host: string, portStr?: string) => {
|
|
86
|
+
const port = portStr ? parseInt(portStr, 10) : 5555;
|
|
87
|
+
const spinner = ora(`Connecting to ${host}:${port}...`).start();
|
|
88
|
+
try {
|
|
89
|
+
const res = await manager.connectDevice(host, port);
|
|
90
|
+
if (res.success) {
|
|
91
|
+
spinner.succeed(chalk.green(`Successfully connected to ${host}:${port}`));
|
|
92
|
+
} else {
|
|
93
|
+
spinner.fail(chalk.yellow(`Connection failed: ${res.message}`));
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
spinner.fail('Connection error');
|
|
97
|
+
console.error(chalk.red((err as Error).message));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// cya pair <host> <port> <code>
|
|
102
|
+
program
|
|
103
|
+
.command('pair <host> <port> <code>')
|
|
104
|
+
.description('Pair with Android 11+ Wireless Debugging pairing code')
|
|
105
|
+
.action(async (host: string, portStr: string, code: string) => {
|
|
106
|
+
const port = parseInt(portStr, 10);
|
|
107
|
+
const spinner = ora(`Pairing with ${host}:${port}...`).start();
|
|
108
|
+
try {
|
|
109
|
+
const res = await manager.pairDevice(host, port, code);
|
|
110
|
+
if (res.success) {
|
|
111
|
+
spinner.succeed(chalk.green(`Device successfully paired!`));
|
|
112
|
+
} else {
|
|
113
|
+
spinner.fail(chalk.red(`Pairing failed: ${res.message}`));
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
spinner.fail('Pairing failed');
|
|
117
|
+
console.error(chalk.red((err as Error).message));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// cya info
|
|
122
|
+
program
|
|
123
|
+
.command('info')
|
|
124
|
+
.description('Display detailed hardware, software, and battery information')
|
|
125
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
126
|
+
.action(async (opts) => {
|
|
127
|
+
try {
|
|
128
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
129
|
+
const spinner = ora('Reading device hardware and telemetry...').start();
|
|
130
|
+
const details = await manager.getDeviceInfo(serial);
|
|
131
|
+
spinner.succeed('Device telemetry retrieved');
|
|
132
|
+
|
|
133
|
+
console.log(chalk.bold.green(`\n📊 Device Information [${details.serial}]`));
|
|
134
|
+
console.log('─'.repeat(55));
|
|
135
|
+
console.log(chalk.bold('Hardware:'));
|
|
136
|
+
console.log(` Manufacturer: ${details.hardware.manufacturer}`);
|
|
137
|
+
console.log(` Model: ${details.hardware.model}`);
|
|
138
|
+
console.log(` Screen: ${details.hardware.screenResolution || 'N/A'} @ ${details.hardware.screenDensityDpi || 'N/A'} DPI`);
|
|
139
|
+
console.log(` Battery: ${details.hardware.batteryLevelPercent}% (${details.hardware.batteryStatus || 'N/A'}) - ${details.hardware.batteryTemperatureC ?? 'N/A'}°C`);
|
|
140
|
+
|
|
141
|
+
console.log(chalk.bold('\nSoftware:'));
|
|
142
|
+
console.log(` Android Version: ${details.software.androidVersion} (API ${details.software.apiLevel})`);
|
|
143
|
+
console.log(` Build ID: ${details.software.buildId}`);
|
|
144
|
+
console.log(` Security Patch: ${details.software.securityPatch}`);
|
|
145
|
+
console.log(` Root Access: ${details.software.isRooted ? chalk.green('Yes') : chalk.gray('No (Standard Non-Root ADB)')}`);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(chalk.red((err as Error).message));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// cya apps
|
|
152
|
+
program
|
|
153
|
+
.command('apps')
|
|
154
|
+
.description('List installed packages on the device')
|
|
155
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
156
|
+
.option('-f, --filter <type>', 'Filter: all, system, 3rd-party, disabled', 'all')
|
|
157
|
+
.action(async (opts) => {
|
|
158
|
+
try {
|
|
159
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
160
|
+
const spinner = ora(`Fetching packages (${opts.filter})...`).start();
|
|
161
|
+
const apps = await manager.getInstalledApps(serial, opts.filter);
|
|
162
|
+
spinner.stop();
|
|
163
|
+
|
|
164
|
+
console.log(chalk.bold.cyan(`\n📦 Installed Packages (${apps.length}):`));
|
|
165
|
+
console.log('─'.repeat(60));
|
|
166
|
+
for (const app of apps.slice(0, 50)) {
|
|
167
|
+
const stateStr = app.isEnabled ? chalk.green('ENABLED') : chalk.gray('DISABLED');
|
|
168
|
+
console.log(`• ${chalk.bold(app.packageName)} [${stateStr}]`);
|
|
169
|
+
}
|
|
170
|
+
if (apps.length > 50) {
|
|
171
|
+
console.log(chalk.gray(`... and ${apps.length - 50} more. Use --filter to narrow down.`));
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(chalk.red((err as Error).message));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// cya debloat
|
|
179
|
+
program
|
|
180
|
+
.command('debloat')
|
|
181
|
+
.description('Inspect and safely debloat manufacturer bloatware')
|
|
182
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
183
|
+
.option('--dry-run', 'Simulate debloating actions without removing packages', false)
|
|
184
|
+
.action(async (opts) => {
|
|
185
|
+
try {
|
|
186
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
187
|
+
const spinner = ora('Detecting device OEM profile & bloatware...').start();
|
|
188
|
+
const details = await manager.getDeviceInfo(serial);
|
|
189
|
+
const bloat = await manager.inspectBloatware(serial, details.hardware.manufacturer);
|
|
190
|
+
spinner.stop();
|
|
191
|
+
|
|
192
|
+
if (bloat.length === 0) {
|
|
193
|
+
console.log(chalk.green(`\n✓ No known bloatware detected for ${details.hardware.manufacturer}!`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(chalk.bold.yellow(`\n⚠️ Detected ${bloat.length} Bloatware candidates for ${details.hardware.manufacturer}:`));
|
|
198
|
+
console.log('─'.repeat(70));
|
|
199
|
+
for (const b of bloat) {
|
|
200
|
+
console.log(
|
|
201
|
+
`• ${chalk.bold(b.applicationLabel)} (${chalk.cyan(b.packageName)})\n` +
|
|
202
|
+
` Risk: ${b.riskLevel.toUpperCase()} | Category: ${b.category}\n` +
|
|
203
|
+
` Desc: ${b.description || 'No description'}\n`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const answers = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: 'checkbox',
|
|
210
|
+
name: 'selected',
|
|
211
|
+
message: 'Select packages to remove for current user:',
|
|
212
|
+
choices: bloat.map((b) => ({
|
|
213
|
+
name: `${b.applicationLabel} [${b.riskLevel}]`,
|
|
214
|
+
value: b.packageName,
|
|
215
|
+
checked: b.riskLevel === 'safe'
|
|
216
|
+
}))
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: 'confirm',
|
|
220
|
+
name: 'confirm',
|
|
221
|
+
message: `Are you sure you want to proceed with ${opts.dryRun ? 'DRY-RUN' : 'safe user-space uninstall'}?`,
|
|
222
|
+
default: false
|
|
223
|
+
}
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
if (!answers.confirm || answers.selected.length === 0) {
|
|
227
|
+
console.log(chalk.gray('Debloating canceled.'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(chalk.blue(`\nProcessing ${answers.selected.length} packages...`));
|
|
232
|
+
for (const pkg of answers.selected) {
|
|
233
|
+
const res = await manager.uninstallPackage(serial, pkg, opts.dryRun);
|
|
234
|
+
if (res.success) {
|
|
235
|
+
console.log(chalk.green(` ✓ [UNINSTALLED] ${pkg}`));
|
|
236
|
+
} else {
|
|
237
|
+
console.log(chalk.red(` ✕ [FAILED] ${pkg}: ${res.error}`));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log(chalk.green('\nDebloating batch complete. All operations were user-space reversible.'));
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error(chalk.red((err as Error).message));
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// cya themes
|
|
247
|
+
program
|
|
248
|
+
.command('themes')
|
|
249
|
+
.description('List and apply aesthetic customization themes')
|
|
250
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
251
|
+
.option('-a, --apply <themeId>', 'Apply theme directly (nothing-os, pixel-material-you, etc.)')
|
|
252
|
+
.action(async (opts) => {
|
|
253
|
+
try {
|
|
254
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
255
|
+
|
|
256
|
+
if (opts.apply) {
|
|
257
|
+
const spinner = ora(`Applying theme '${opts.apply}'...`).start();
|
|
258
|
+
await manager.applyTheme(serial, opts.apply as ThemeIdentifier);
|
|
259
|
+
spinner.succeed(chalk.green(`Theme '${opts.apply}' successfully applied!`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const themes = Object.values(THEME_PRESETS);
|
|
264
|
+
console.log(chalk.bold.magenta('\n🎨 Available Aesthetic Themes:'));
|
|
265
|
+
console.log('─'.repeat(60));
|
|
266
|
+
for (const t of themes) {
|
|
267
|
+
console.log(`• ${chalk.bold.cyan(t.name)} (ID: ${chalk.yellow(t.id)})`);
|
|
268
|
+
console.log(` ${t.description}`);
|
|
269
|
+
console.log(` Vibe: ${chalk.gray(t.designVibe)}\n`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const answer = await inquirer.prompt([
|
|
273
|
+
{
|
|
274
|
+
type: 'list',
|
|
275
|
+
name: 'chosenTheme',
|
|
276
|
+
message: 'Choose a theme to apply immediately:',
|
|
277
|
+
choices: themes.map((t) => ({ name: t.name, value: t.id }))
|
|
278
|
+
}
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
const spinner = ora(`Applying ${answer.chosenTheme}...`).start();
|
|
282
|
+
await manager.applyTheme(serial, answer.chosenTheme as ThemeIdentifier);
|
|
283
|
+
spinner.succeed(chalk.green(`Applied ${answer.chosenTheme} aesthetic!`));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(chalk.red((err as Error).message));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// cya customize
|
|
290
|
+
program
|
|
291
|
+
.command('customize')
|
|
292
|
+
.description('Interactively adjust animation speeds, display density, and dark mode')
|
|
293
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
294
|
+
.action(async (opts) => {
|
|
295
|
+
try {
|
|
296
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
297
|
+
const current = await manager.customizer.getCurrentSettings(serial);
|
|
298
|
+
|
|
299
|
+
const answers = await inquirer.prompt([
|
|
300
|
+
{
|
|
301
|
+
type: 'list',
|
|
302
|
+
name: 'animSpeed',
|
|
303
|
+
message: 'Set System Animation Scale:',
|
|
304
|
+
choices: [
|
|
305
|
+
{ name: '0.25x (Lightning snappy)', value: 0.25 },
|
|
306
|
+
{ name: '0.5x (Recommended Nothing OS speed)', value: 0.5 },
|
|
307
|
+
{ name: '1.0x (Android Default)', value: 1.0 },
|
|
308
|
+
{ name: 'Keep current', value: current.windowAnimationScale }
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: 'confirm',
|
|
313
|
+
name: 'darkMode',
|
|
314
|
+
message: 'Enable System Dark Mode?',
|
|
315
|
+
default: current.darkModeEnabled ?? true
|
|
316
|
+
}
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
const spinner = ora('Applying customization adjustments...').start();
|
|
320
|
+
await manager.customizer.setAnimationScales(serial, {
|
|
321
|
+
window: answers.animSpeed,
|
|
322
|
+
transition: answers.animSpeed,
|
|
323
|
+
animator: answers.animSpeed
|
|
324
|
+
});
|
|
325
|
+
await manager.customizer.setDarkMode(serial, answers.darkMode);
|
|
326
|
+
spinner.succeed(chalk.green('Customization settings applied!'));
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error(chalk.red((err as Error).message));
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// cya backup
|
|
333
|
+
program
|
|
334
|
+
.command('backup')
|
|
335
|
+
.description('Snapshot installed packages, state, and appearance settings')
|
|
336
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
337
|
+
.option('-o, --output <dir>', 'Output directory', './cya-backups')
|
|
338
|
+
.action(async (opts) => {
|
|
339
|
+
try {
|
|
340
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
341
|
+
const spinner = ora('Creating backup snapshot...').start();
|
|
342
|
+
const res = await manager.createBackup(serial, opts.output);
|
|
343
|
+
spinner.succeed(chalk.green(`Backup saved to ${res.manifestPath}`));
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.error(chalk.red((err as Error).message));
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// cya restore <file>
|
|
350
|
+
program
|
|
351
|
+
.command('restore <file>')
|
|
352
|
+
.description('Restore settings and packages from a backup manifest')
|
|
353
|
+
.option('-d, --device <serial>', 'Target device serial')
|
|
354
|
+
.action(async (file: string, opts) => {
|
|
355
|
+
try {
|
|
356
|
+
const serial = await resolveTargetDevice(opts.device);
|
|
357
|
+
const spinner = ora(`Loading manifest ${file}...`).start();
|
|
358
|
+
const manifest = await manager.backup.loadBackup(file);
|
|
359
|
+
spinner.text = 'Restoring packages and settings...';
|
|
360
|
+
const result = await manager.backup.restoreFromManifest(serial, manifest);
|
|
361
|
+
spinner.succeed(chalk.green(`Restored ${result.reinstalledCount} packages! Settings: ${result.settingsRestored ? 'Restored' : 'Skipped'}`));
|
|
362
|
+
} catch (err) {
|
|
363
|
+
console.error(chalk.red((err as Error).message));
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// cya doctor
|
|
368
|
+
program
|
|
369
|
+
.command('doctor')
|
|
370
|
+
.description('Diagnose ADB binary, driver status, device authorization, and permissions')
|
|
371
|
+
.action(async () => {
|
|
372
|
+
console.log(chalk.bold.cyan('\n🩺 CleanYourAndroid Diagnostic Doctor'));
|
|
373
|
+
console.log('═'.repeat(60));
|
|
374
|
+
|
|
375
|
+
// 1. ADB check
|
|
376
|
+
console.log(`Checking ADB platform tools:`);
|
|
377
|
+
console.log(` Path: ${chalk.yellow(manager.adb.adbPath)}`);
|
|
378
|
+
try {
|
|
379
|
+
const versionRes = await manager.adb.execute(['version']);
|
|
380
|
+
console.log(` Version: ${chalk.green(versionRes.stdout.split('\n')[0])}`);
|
|
381
|
+
} catch {
|
|
382
|
+
console.log(chalk.red(' ✕ ADB binary not executable or not found.'));
|
|
383
|
+
console.log(chalk.gray(' Fix: Install Android SDK Platform-Tools or set ADB_PATH environment variable.'));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 2. Connected devices
|
|
387
|
+
console.log('\nChecking Connected Devices:');
|
|
388
|
+
try {
|
|
389
|
+
const devices = await manager.getConnectedDevices();
|
|
390
|
+
if (devices.length === 0) {
|
|
391
|
+
console.log(chalk.yellow(' ⚠️ No devices detected over USB or Wi-Fi.'));
|
|
392
|
+
console.log(chalk.gray(' Troubleshooting Checklist:'));
|
|
393
|
+
console.log(' 1. Open Settings -> About Phone -> Tap "Build Number" 7 times.');
|
|
394
|
+
console.log(' 2. Go to Developer Options -> Enable "USB Debugging".');
|
|
395
|
+
console.log(' 3. Re-plug the USB cable and select "File Transfer / MTP" mode.');
|
|
396
|
+
} else {
|
|
397
|
+
for (const d of devices) {
|
|
398
|
+
if (d.state === 'unauthorized') {
|
|
399
|
+
console.log(chalk.yellow(` ⚠️ Device ${d.serial} is UNAUTHORIZED.`));
|
|
400
|
+
console.log(chalk.gray(' Unlock your phone screen and tap "Always allow" on the popup.'));
|
|
401
|
+
} else {
|
|
402
|
+
console.log(chalk.green(` ✓ Device ${d.serial} is CONNECTED & AUTHORIZED.`));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.log(chalk.red(` ✕ Error querying devices: ${(err as Error).message}`));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.log('\n' + '═'.repeat(60));
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Default interactive CLI if called with no arguments
|
|
414
|
+
if (process.argv.length <= 2) {
|
|
415
|
+
(async () => {
|
|
416
|
+
console.log(chalk.bold.cyan(`
|
|
417
|
+
_____ _ __ __
|
|
418
|
+
/ ____| | \\ \\ / /
|
|
419
|
+
| | | | ___ __ _ _ __ \\ \\_/ /__ _ _ _ __
|
|
420
|
+
| | | |/ _ \\/ _\` | '_ \\ \\ / _ \\| | | | '__|
|
|
421
|
+
| |____| | __/ (_| | | | | | | (_) | |_| | |
|
|
422
|
+
\\_____|_|\\___|\\__,_|_| |_| |_|\\___/ \\__,_|_|
|
|
423
|
+
Android Customization Toolkit v1.0.0
|
|
424
|
+
`));
|
|
425
|
+
|
|
426
|
+
const answer = await inquirer.prompt([
|
|
427
|
+
{
|
|
428
|
+
type: 'list',
|
|
429
|
+
name: 'action',
|
|
430
|
+
message: 'What would you like to do?',
|
|
431
|
+
choices: [
|
|
432
|
+
{ name: '1. List Connected Devices', value: 'devices' },
|
|
433
|
+
{ name: '2. Inspect Device Info & Battery', value: 'info' },
|
|
434
|
+
{ name: '3. Inspect & Debloat Bloatware', value: 'debloat' },
|
|
435
|
+
{ name: '4. Apply Design Themes (Nothing OS / Pixel / One UI)', value: 'themes' },
|
|
436
|
+
{ name: '5. Fast Customization (Animations & Dark Mode)', value: 'customize' },
|
|
437
|
+
{ name: '6. Backup Device Settings', value: 'backup' },
|
|
438
|
+
{ name: '7. Run Diagnostic Doctor', value: 'doctor' },
|
|
439
|
+
{ name: '8. Exit', value: 'exit' }
|
|
440
|
+
]
|
|
441
|
+
}
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
if (answer.action === 'exit') process.exit(0);
|
|
445
|
+
|
|
446
|
+
const cmdArgs = [process.argv[0], process.argv[1], answer.action];
|
|
447
|
+
await program.parseAsync(cmdArgs);
|
|
448
|
+
})();
|
|
449
|
+
} else {
|
|
450
|
+
program.parseAsync(process.argv);
|
|
451
|
+
}
|
|
452
|
+
|
package/tsconfig.json
ADDED