@crystalplatforms/adm 0.2.3 → 0.3.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/LICENSE +21 -21
- package/README.md +154 -161
- package/bin/adm +18 -21
- package/package.json +9 -22
- package/scripts/completion/adm.bash +12 -55
- package/scripts/completion/adm.zsh +21 -58
- package/scripts/install.js +8 -8
- package/src/commands/config.js +66 -0
- package/src/commands/dotfiles.js +49 -32
- package/src/commands/feedback.js +12 -0
- package/src/commands/help.js +33 -0
- package/src/commands/uninstall.js +73 -54
- package/src/commands/version.js +7 -0
- package/src/commands.js +26 -0
- package/src/config/index.js +32 -32
- package/src/data/extensions.json +358 -358
- package/src/engines.js +38 -0
- package/src/install/executor.js +263 -0
- package/src/install/hints.js +94 -0
- package/src/install/npm-prefix.js +110 -0
- package/src/install/plan.js +75 -0
- package/src/install/report.js +46 -0
- package/src/install/run.js +88 -0
- package/src/install/shell-paths.js +129 -0
- package/src/legacy-cleanup.js +96 -0
- package/src/pkg/builders.js +220 -0
- package/src/pkg/managers.js +59 -0
- package/src/pkg/package-names.js +229 -0
- package/src/pkg/script-urls.js +100 -0
- package/src/platform/index.js +129 -0
- package/src/platform/os-release.js +27 -0
- package/src/platform/shell.js +111 -0
- package/src/reports.js +37 -0
- package/src/router.js +157 -0
- package/src/setup/catalog.js +13 -13
- package/src/setup/git-step.js +128 -0
- package/src/setup/loader.js +108 -0
- package/src/setup/prompts.js +259 -0
- package/src/setup/registry.js +48 -0
- package/src/setup/spinner.js +67 -0
- package/src/setup/wizard.js +319 -195
- package/src/upgrade/index.js +499 -0
- package/src/utils/closest-match.js +36 -0
- package/src/utils/dotfiles-sync.js +89 -89
- package/src/utils/file-conflict-handler.js +30 -30
- package/bin/adm-tui +0 -13
- package/src/ai/knowledge.js +0 -61
- package/src/commands/connect.js +0 -76
- package/src/commands/issue.js +0 -80
- package/src/commands/pr.js +0 -55
- package/src/commands/theme.js +0 -103
- package/src/commands/upgrade.js +0 -114
- package/src/installer/git-config.js +0 -34
- package/src/installer/node-installer.js +0 -50
- package/src/installer/package-manager-installer.js +0 -31
- package/src/installer/ssh-setup.js +0 -31
- package/src/installer/system-packages.js +0 -49
- package/src/installers/index.js +0 -66
- package/src/integrations/ai-backend.js +0 -60
- package/src/integrations/ai-providers/registry.js +0 -261
- package/src/integrations/github.js +0 -107
- package/src/integrations/gitlab.js +0 -121
- package/src/plugins/context.js +0 -64
- package/src/plugins/loader.js +0 -50
- package/src/plugins/runner.js +0 -25
- package/src/setup/installer.js +0 -192
- package/src/shell/detector.js +0 -10
- package/src/tui/app-state.js +0 -374
- package/src/tui/app.js +0 -442
- package/src/tui/autocomplete.js +0 -57
- package/src/tui/commands/registry.js +0 -823
- package/src/tui/components/SetupScreen.js +0 -292
- package/src/ui/animations.js +0 -24
- package/src/ui/spinner.js +0 -34
- package/src/ui/theme/cyberpunk.js +0 -14
- package/src/ui/theme/dark.js +0 -14
- package/src/ui/theme/forest.js +0 -14
- package/src/ui/theme/light.js +0 -14
- package/src/ui/theme/monokai.js +0 -14
- package/src/ui/theme/nord.js +0 -14
- package/src/ui/theme.js +0 -49
- package/src/utils/command-history.js +0 -43
- package/src/utils/error-handler.js +0 -69
- package/src/utils/fuzzy-search.js +0 -20
- package/src/utils/github-error-handler.js +0 -26
- package/src/utils/keychain.js +0 -97
- package/src/utils/open-url.js +0 -15
- package/src/utils/terminal-detection.js +0 -26
- package/src/utils/token-encryption.js +0 -96
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
#compdef adm
|
|
2
|
+
# v0.3 CLI surface — src/commands.js is the source of truth
|
|
2
3
|
|
|
3
4
|
_adm() {
|
|
4
|
-
local -a commands
|
|
5
|
+
local -a commands
|
|
5
6
|
|
|
6
7
|
commands=(
|
|
7
|
-
'help:Show
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'model:Show or switch AI provider'
|
|
15
|
-
'setup:Launch extension setup wizard'
|
|
16
|
-
'connect:Connect to GitHub or GitLab'
|
|
17
|
-
'pr:Pull request operations'
|
|
18
|
-
'mr:Merge request operations (GitLab)'
|
|
19
|
-
'issue:List issues from connected platform'
|
|
20
|
-
'commit:Commit subcommands'
|
|
21
|
-
'clock:Show ASCII clock'
|
|
22
|
-
'dotfiles:Sync dotfiles from repo'
|
|
23
|
-
'uninstall:Remove ADM CLI config'
|
|
24
|
-
'plugins:List loaded plugins'
|
|
8
|
+
'help:Show available commands'
|
|
9
|
+
'-v:Print ADM version'
|
|
10
|
+
'feedback:Show how to report bugs and ideas'
|
|
11
|
+
'config:Show ADM config and detected environment'
|
|
12
|
+
'uninstall:Remove ADM data (~/.adm) and shell PATH entries'
|
|
13
|
+
'upgrade:Self-update ADM (alias: update)'
|
|
14
|
+
'dotfiles:Sync dotfiles from a git repo'
|
|
25
15
|
)
|
|
26
16
|
|
|
27
17
|
_arguments -C \
|
|
@@ -34,53 +24,26 @@ _adm() {
|
|
|
34
24
|
;;
|
|
35
25
|
args)
|
|
36
26
|
case $words[1] in
|
|
37
|
-
|
|
38
|
-
_arguments '--
|
|
27
|
+
config)
|
|
28
|
+
_arguments '--json[Machine-readable output]'
|
|
39
29
|
;;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
_describe 'subcommand' connect_subs
|
|
45
|
-
else
|
|
46
|
-
case $words[2] in
|
|
47
|
-
github|gitlab) _arguments '--token[Access token]:token:' ;;
|
|
48
|
-
esac
|
|
49
|
-
fi
|
|
50
|
-
;;
|
|
51
|
-
pr)
|
|
52
|
-
local -a pr_subs
|
|
53
|
-
pr_subs=('list:List open pull requests' 'draft:Create a draft PR' 'comment:Comment on a pull request')
|
|
54
|
-
if (( CURRENT == 2 )); then
|
|
55
|
-
_describe 'subcommand' pr_subs
|
|
56
|
-
fi
|
|
57
|
-
;;
|
|
58
|
-
mr)
|
|
59
|
-
local -a mr_subs
|
|
60
|
-
mr_subs=('list:List open merge requests' 'draft:Create a draft MR' 'comment:Comment on a merge request')
|
|
61
|
-
if (( CURRENT == 2 )); then
|
|
62
|
-
_describe 'subcommand' mr_subs
|
|
63
|
-
fi
|
|
64
|
-
;;
|
|
65
|
-
issue)
|
|
66
|
-
_arguments '1:subcommand:(list)'
|
|
67
|
-
;;
|
|
68
|
-
commit)
|
|
69
|
-
_arguments '1:subcommand:(suggest)'
|
|
30
|
+
upgrade|update)
|
|
31
|
+
_arguments \
|
|
32
|
+
'--check[Only check whether a newer version exists]' \
|
|
33
|
+
'--dry-run[Print the upgrade plan without changing anything]'
|
|
70
34
|
;;
|
|
71
35
|
dotfiles)
|
|
72
36
|
local -a dotfiles_subs
|
|
73
|
-
dotfiles_subs=('sync:
|
|
37
|
+
dotfiles_subs=('sync:Sync dotfiles from a git repo into your home dir')
|
|
74
38
|
if (( CURRENT == 2 )); then
|
|
75
39
|
_describe 'subcommand' dotfiles_subs
|
|
40
|
+
else
|
|
41
|
+
_arguments \
|
|
42
|
+
'--repo[Dotfiles git repo URL]:url:' \
|
|
43
|
+
'--copy[Copy files instead of symlinking]' \
|
|
44
|
+
'--only[Comma-separated file list]:names:'
|
|
76
45
|
fi
|
|
77
46
|
;;
|
|
78
|
-
theme)
|
|
79
|
-
_arguments '1:theme:(dark light cyberpunk nord forest monokai)'
|
|
80
|
-
;;
|
|
81
|
-
model)
|
|
82
|
-
_arguments '1:provider:(glm-free glm-pro openai anthropic ollama list)'
|
|
83
|
-
;;
|
|
84
47
|
esac
|
|
85
48
|
;;
|
|
86
49
|
esac
|
package/scripts/install.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const chalk = require('chalk');
|
|
3
|
-
console.log('');
|
|
4
|
-
console.log(chalk.green(' ADM CLI installed successfully!'));
|
|
5
|
-
console.log('');
|
|
6
|
-
console.log(' Run ' + chalk.bold('adm') + ' to launch the TUI.');
|
|
7
|
-
console.log(' Type ' + chalk.bold('/download') + ' inside ADM to set up your dev environment.');
|
|
8
|
-
console.log('');
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
console.log('');
|
|
4
|
+
console.log(chalk.green(' ADM CLI installed successfully!'));
|
|
5
|
+
console.log('');
|
|
6
|
+
console.log(' Run ' + chalk.bold('adm') + ' to launch the TUI.');
|
|
7
|
+
console.log(' Type ' + chalk.bold('/download') + ' inside ADM to set up your dev environment.');
|
|
8
|
+
console.log('');
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { detectEnvironment, formatEnvironment } = require('../platform');
|
|
2
|
+
const { configDir, readConfig } = require('../config');
|
|
3
|
+
const { getVersion } = require('./version');
|
|
4
|
+
|
|
5
|
+
// `adm config` — human-readable view.
|
|
6
|
+
// `adm config --json` — machine-readable view with a frozen key set:
|
|
7
|
+
// version, configDir, configFile, os, distro, distroVersion,
|
|
8
|
+
// manager, arch, isWSL, shell, rcFile
|
|
9
|
+
async function runConfig({ json, log, err } = {}) {
|
|
10
|
+
log = log || (s => process.stdout.write(s + '\n'));
|
|
11
|
+
err = err || (s => process.stderr.write(s + '\n'));
|
|
12
|
+
|
|
13
|
+
let saved = {};
|
|
14
|
+
try {
|
|
15
|
+
saved = await readConfig();
|
|
16
|
+
} catch {
|
|
17
|
+
saved = {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dir = configDir();
|
|
21
|
+
const file = require('path').join(dir, 'config.json');
|
|
22
|
+
const env = detectEnvironment();
|
|
23
|
+
|
|
24
|
+
if (json) {
|
|
25
|
+
log(JSON.stringify({
|
|
26
|
+
version: getVersion(),
|
|
27
|
+
configDir: dir,
|
|
28
|
+
configFile: file,
|
|
29
|
+
os: env.os,
|
|
30
|
+
distro: env.distro,
|
|
31
|
+
distroVersion: env.distroVersion,
|
|
32
|
+
manager: env.manager,
|
|
33
|
+
arch: env.arch,
|
|
34
|
+
isWSL: env.isWSL,
|
|
35
|
+
shell: env.shell,
|
|
36
|
+
rcFile: env.rcFile,
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const lines = [
|
|
42
|
+
`ADM v${getVersion()}`,
|
|
43
|
+
'',
|
|
44
|
+
'Paths:',
|
|
45
|
+
` Config dir: ${dir}`,
|
|
46
|
+
` Config file: ${file}`,
|
|
47
|
+
'',
|
|
48
|
+
'Environment:',
|
|
49
|
+
formatEnvironment(env),
|
|
50
|
+
'',
|
|
51
|
+
'Saved config:',
|
|
52
|
+
];
|
|
53
|
+
const keys = Object.keys(saved);
|
|
54
|
+
if (keys.length === 0) {
|
|
55
|
+
lines.push(' (empty)');
|
|
56
|
+
} else {
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
const value = JSON.stringify(saved[key]);
|
|
59
|
+
lines.push(` ${key}: ${value.length > 80 ? value.slice(0, 77) + '...' : value}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
log(lines.join('\n'));
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { runConfig };
|
package/src/commands/dotfiles.js
CHANGED
|
@@ -2,71 +2,88 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const ora = require('ora');
|
|
5
|
-
const
|
|
6
|
-
const { readConfig, ensureConfigDir } = require('../config');
|
|
5
|
+
const { readConfig } = require('../config');
|
|
7
6
|
const { syncDotfiles } = require('../utils/dotfiles-sync');
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* only: string[] — file filter (e.g. ['bashrc', 'zshrc'])
|
|
16
|
-
*/
|
|
17
|
-
async function syncDotfilesCommand(options = {}) {
|
|
8
|
+
// `adm dotfiles sync [--repo <url>] [--copy] [--only <name,name>]`
|
|
9
|
+
// Port of the old logic: clones/pulls the dotfiles repo into ~/.adm/dotfiles,
|
|
10
|
+
// then symlinks (or copies) known files into the home directory.
|
|
11
|
+
async function runDotfilesSync(flags = [], { log, err } = {}) {
|
|
12
|
+
log = log || (s => process.stdout.write(s + '\n'));
|
|
13
|
+
err = err || (s => process.stderr.write(s + '\n'));
|
|
18
14
|
const configDir = process.env.ADM_CONFIG_DIR || path.join(require('os').homedir(), '.adm');
|
|
19
15
|
const dotfilesDir = path.join(configDir, 'dotfiles');
|
|
20
16
|
|
|
21
|
-
//
|
|
17
|
+
// ── parse flags ──────────────────────────────────────────────
|
|
18
|
+
const options = {};
|
|
19
|
+
const bad = [];
|
|
20
|
+
for (let i = 0; i < flags.length; i++) {
|
|
21
|
+
const f = flags[i];
|
|
22
|
+
if (f === '--help' || f === '-h') {
|
|
23
|
+
log('Usage: adm dotfiles sync [--repo <url>] [--copy] [--only <name,name>]');
|
|
24
|
+
log('');
|
|
25
|
+
log(' --repo <url> dotfiles git repo (or set dotfiles.repo in the config)');
|
|
26
|
+
log(' --copy copy files instead of symlinking');
|
|
27
|
+
log(' --only <name,name> sync only these files (e.g. --only bashrc,zshrc)');
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
if (f === '--repo') options.repo = flags[++i];
|
|
31
|
+
else if (f === '--copy') options.copy = true;
|
|
32
|
+
else if (f === '--only') options.only = String(flags[++i] || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
33
|
+
else bad.push(f);
|
|
34
|
+
}
|
|
35
|
+
if (bad.length > 0) {
|
|
36
|
+
err(`Unknown flags for "dotfiles sync": ${bad.join(', ')}\nValid flags: --repo <url>, --copy, --only <name,name>`);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── resolve repo URL ─────────────────────────────────────────
|
|
22
41
|
let repoUrl = options.repo;
|
|
23
42
|
if (!repoUrl) {
|
|
24
43
|
const config = await readConfig();
|
|
25
44
|
repoUrl = config?.dotfiles?.repo;
|
|
26
45
|
}
|
|
27
46
|
if (!repoUrl) {
|
|
28
|
-
|
|
47
|
+
err('No dotfiles repo URL. Pass --repo <url> or set it in the config (dotfiles.repo).\nExample: adm dotfiles sync --repo https://github.com/you/dotfiles');
|
|
48
|
+
return 1;
|
|
29
49
|
}
|
|
30
50
|
|
|
31
|
-
//
|
|
51
|
+
// ── clone or pull ────────────────────────────────────────────
|
|
52
|
+
const { execFileSync } = require('child_process');
|
|
32
53
|
const s = ora('Syncing dotfiles...').start();
|
|
33
54
|
try {
|
|
34
55
|
if (fs.existsSync(path.join(dotfilesDir, '.git'))) {
|
|
35
56
|
s.text = 'Pulling updates...';
|
|
36
|
-
|
|
57
|
+
execFileSync('git', ['pull'], { cwd: dotfilesDir, stdio: 'pipe' });
|
|
37
58
|
} else {
|
|
38
|
-
// Remove empty directory if it exists
|
|
39
59
|
if (fs.existsSync(dotfilesDir)) {
|
|
40
60
|
fs.rmSync(dotfilesDir, { recursive: true, force: true });
|
|
41
61
|
}
|
|
42
62
|
s.text = 'Cloning dotfiles repo...';
|
|
43
|
-
|
|
63
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
64
|
+
execFileSync('git', ['clone', repoUrl, dotfilesDir], { stdio: 'pipe' });
|
|
44
65
|
}
|
|
45
66
|
s.succeed('Dotfiles repo ready');
|
|
46
|
-
} catch (
|
|
47
|
-
s.fail(`Git error: ${
|
|
48
|
-
|
|
67
|
+
} catch (e) {
|
|
68
|
+
s.fail(`Git error: ${e.message}`);
|
|
69
|
+
return 2;
|
|
49
70
|
}
|
|
50
71
|
|
|
51
|
-
//
|
|
52
|
-
|
|
72
|
+
// ── sync files ───────────────────────────────────────────────
|
|
73
|
+
log(chalk.bold('\n Syncing files:'));
|
|
53
74
|
const result = await syncDotfiles({
|
|
54
75
|
repo: repoUrl,
|
|
55
76
|
copy: options.copy,
|
|
56
77
|
only: options.only,
|
|
57
78
|
});
|
|
58
79
|
|
|
59
|
-
// Summary
|
|
60
80
|
const total = result.symlinked.length + result.copied.length;
|
|
61
|
-
|
|
62
|
-
if (result.skipped.length > 0) {
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
if (result.errors.length > 0) {
|
|
66
|
-
console.log(chalk.red(` Errors: ${result.errors.map(e => e.file).join(', ')}`));
|
|
67
|
-
}
|
|
81
|
+
log(chalk.bold(`\n Done: ${total} file${total === 1 ? '' : 's'} synced`));
|
|
82
|
+
if (result.skipped.length > 0) log(chalk.yellow(` Skipped: ${result.skipped.join(', ')}`));
|
|
83
|
+
if (result.backedUp.length > 0) log(chalk.blue(` Backed up: ${result.backedUp.join(', ')}`));
|
|
84
|
+
if (result.errors.length > 0) log(chalk.red(` Errors: ${result.errors.map(e => e.file).join(', ')}`));
|
|
68
85
|
|
|
69
|
-
return result;
|
|
86
|
+
return result.errors.length > 0 ? 2 : 0;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
|
-
module.exports = {
|
|
89
|
+
module.exports = { runDotfilesSync };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const REPO_ISSUES = 'https://github.com/CrystalPlatforms/ADM-CLI/issues';
|
|
2
|
+
|
|
3
|
+
function runFeedback({ log } = {}) {
|
|
4
|
+
log = log || (s => process.stdout.write(s + '\n'));
|
|
5
|
+
log('Found a bug or an idea? Open an issue:');
|
|
6
|
+
log(` ${REPO_ISSUES}`);
|
|
7
|
+
log('');
|
|
8
|
+
log('Include: OS + distro, shell, the command you ran, and the full output.');
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { runFeedback, REPO_ISSUES };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { COMMANDS, AUTOMATION_HINTS } = require('../commands');
|
|
2
|
+
|
|
3
|
+
// Renders one " <name> <summary>" line per COMMANDS table entry.
|
|
4
|
+
// This is the only place help text is produced — the table is the single
|
|
5
|
+
// source of truth (anti-drift).
|
|
6
|
+
function renderCommandLines() {
|
|
7
|
+
const width = Math.max(...COMMANDS.map(c => c.name.length));
|
|
8
|
+
const flagsWidth = Math.max(...COMMANDS.map(c => c.flags ? c.flags.join('|').length : 0));
|
|
9
|
+
const labelWidth = width + (flagsWidth > 0 ? flagsWidth + 3 : 0);
|
|
10
|
+
return COMMANDS.map(c => {
|
|
11
|
+
const flags = c.flags ? ` [${c.flags.join('|')}]` : '';
|
|
12
|
+
return ` ${(c.name + flags).padEnd(labelWidth)} ${c.summary}`;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function showHelp() {
|
|
17
|
+
const lines = [
|
|
18
|
+
'ADM — lightweight, multi-platform developer environment installer',
|
|
19
|
+
'',
|
|
20
|
+
'Usage:',
|
|
21
|
+
' adm <command> [flags]',
|
|
22
|
+
'',
|
|
23
|
+
'Commands:',
|
|
24
|
+
...renderCommandLines(),
|
|
25
|
+
'',
|
|
26
|
+
...AUTOMATION_HINTS,
|
|
27
|
+
'',
|
|
28
|
+
'Docs: https://github.com/CrystalPlatforms/ADM-CLI#readme',
|
|
29
|
+
];
|
|
30
|
+
return lines.join('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { showHelp, renderCommandLines };
|
|
@@ -1,72 +1,91 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
1
|
+
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
4
|
+
const { saveReport } = require('../reports');
|
|
5
|
+
const { getVersion } = require('../commands/version');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Removes ADM's footprint:
|
|
8
|
+
// 1. the config dir (~/.adm, or ADM_CONFIG_DIR)
|
|
9
|
+
// 2. PATH entries ADM wrote into shell rc files (marker lines)
|
|
10
|
+
// Idempotent by contract: removing nothing still exits 0 ("already removed").
|
|
11
|
+
const RC_FILES = ['.bashrc', '.bash_profile', '.zprofile', '.zshrc', '.profile'];
|
|
12
|
+
const RC_MARKERS = ['# adm-cli', 'adm completion', 'adm-cli npm path'];
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Remove config directory
|
|
14
|
+
function cleanRcFile(rcPath) {
|
|
15
|
+
let content;
|
|
13
16
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
content = fs.readFileSync(rcPath, 'utf8');
|
|
18
|
+
} catch {
|
|
19
|
+
return { cleaned: false, lines: 0 };
|
|
20
|
+
}
|
|
21
|
+
const lines = content.split('\n');
|
|
22
|
+
const kept = [];
|
|
23
|
+
let removed = 0;
|
|
24
|
+
for (let i = 0; i < lines.length; i++) {
|
|
25
|
+
if (RC_MARKERS.some(m => lines[i].includes(m))) {
|
|
26
|
+
removed++;
|
|
27
|
+
// The PATH line ADM appends right after its marker goes too —
|
|
28
|
+
// leave zero trace (v0.3 installer layout).
|
|
29
|
+
if (lines[i + 1] && /^\s*export\s+PATH=/.test(lines[i + 1])) {
|
|
30
|
+
i++;
|
|
31
|
+
removed++;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
19
34
|
}
|
|
35
|
+
kept.push(lines[i]);
|
|
20
36
|
}
|
|
37
|
+
if (removed === 0) {
|
|
38
|
+
return { cleaned: false, lines: 0 };
|
|
39
|
+
}
|
|
40
|
+
fs.writeFileSync(rcPath, kept.join('\n'), 'utf8');
|
|
41
|
+
return { cleaned: true, lines: removed };
|
|
42
|
+
}
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.log(chalk.green(' Removed npm package: @crystalgames/adm'));
|
|
28
|
-
} else {
|
|
29
|
-
console.log(chalk.yellow(' npm uninstall failed — package may have been installed differently'));
|
|
30
|
-
}
|
|
31
|
-
resolve();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
44
|
+
async function runUninstall({ log, err, configDir: cfgDir, home } = {}) {
|
|
45
|
+
log = log || (s => process.stdout.write(s + '\n'));
|
|
46
|
+
err = err || (s => process.stderr.write(s + '\n'));
|
|
47
|
+
const dir = cfgDir || process.env.ADM_CONFIG_DIR || path.join(home || os.homedir(), '.adm');
|
|
48
|
+
const homeDir = home || os.homedir();
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.log(chalk.yellow(` Could not remove binary: ${err.message}`));
|
|
50
|
+
const dirExisted = fs.existsSync(dir);
|
|
51
|
+
if (dirExisted) {
|
|
52
|
+
try {
|
|
53
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
54
|
+
} catch (e) {
|
|
55
|
+
err(`ADM uninstall: failed to remove ${dir}: ${e.message}`);
|
|
56
|
+
return 2;
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
rcFiles.push(path.join(os.homedir(), '.bashrc'), path.join(os.homedir(), '.bash_profile'));
|
|
51
|
-
} else if (shell.includes('zsh')) {
|
|
52
|
-
rcFiles.push(path.join(os.homedir(), '.zshrc'));
|
|
60
|
+
const cleanedRcs = [];
|
|
61
|
+
for (const rc of RC_FILES) {
|
|
62
|
+
const result = cleanRcFile(path.join(homeDir, rc));
|
|
63
|
+
if (result.cleaned) cleanedRcs.push(`${rc} (${result.lines} line${result.lines === 1 ? '' : 's'})`);
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await fs.writeFile(rc, cleaned, 'utf8');
|
|
63
|
-
console.log(chalk.green(` Cleaned ADM references from ${rc}`));
|
|
64
|
-
}
|
|
65
|
-
} catch { /* skip missing rc files */ }
|
|
66
|
+
if (dirExisted) {
|
|
67
|
+
log(`Removed ${dir}`);
|
|
68
|
+
} else {
|
|
69
|
+
log(`${dir} already removed`);
|
|
70
|
+
}
|
|
71
|
+
if (cleanedRcs.length > 0) {
|
|
72
|
+
for (const rc of cleanedRcs) log(`Cleaned PATH entries from ~/${rc}`);
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
log('ADM data removed. To uninstall the program itself, remove the npm package: npm rm -g @crystalplatforms/adm');
|
|
76
|
+
|
|
77
|
+
// Persist the uninstall report (owner decision: all reports are saved) —
|
|
78
|
+
// but the config dir is GONE by design, so the report lands in the OS
|
|
79
|
+
// temp dir instead of resurrecting ~/.adm.
|
|
80
|
+
const body = [
|
|
81
|
+
`ADM uninstall report — ${new Date().toISOString()}`,
|
|
82
|
+
`ADM ${getVersion()}`,
|
|
83
|
+
`config dir: ${dirExisted ? `removed (${dir})` : `already removed (${dir})`}`,
|
|
84
|
+
cleanedRcs.length ? `cleaned rc entries: ${cleanedRcs.join(', ')}` : 'no rc entries cleaned',
|
|
85
|
+
].join('\n') + '\n';
|
|
86
|
+
const saved = saveReport('uninstall', body, { configDir: os.tmpdir() });
|
|
87
|
+
if (saved.file) log(`Uninstall report saved: ${saved.file}`);
|
|
88
|
+
return 0;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
module.exports = {
|
|
91
|
+
module.exports = { runUninstall, RC_FILES, RC_MARKERS };
|
package/src/commands.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// ── Single source of truth for the CLI surface (frozen contract) ──
|
|
2
|
+
// Exit codes: 0 = success (incl. "nothing to do")
|
|
3
|
+
// 1 = usage error (message enumerates the valid set)
|
|
4
|
+
// 2 = execution failure
|
|
5
|
+
const COMMANDS = [
|
|
6
|
+
{ name: 'help', aliases: ['--help', '-h'], summary: 'Show available commands' },
|
|
7
|
+
{ name: '-v', aliases: ['--version'], summary: 'Print ADM version' },
|
|
8
|
+
{ name: 'feedback', summary: 'Show how to report bugs and ideas' },
|
|
9
|
+
{ name: 'config', summary: 'Show ADM config and detected environment', flags: ['--json'] },
|
|
10
|
+
{ name: 'uninstall', summary: 'Remove ADM data (~/.adm) and shell PATH entries' },
|
|
11
|
+
{ name: 'upgrade', aliases: ['update'], summary: 'Self-update ADM (bundle: atomic swap · npm: prints the npm command)',
|
|
12
|
+
flags: ['--check', '--dry-run'] },
|
|
13
|
+
{ name: 'dotfiles sync', summary: 'Sync dotfiles from a git repo into your home dir',
|
|
14
|
+
usage: 'adm dotfiles sync [--repo <url>] [--copy] [--only <name,name>]' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const AUTOMATION_HINTS = [
|
|
18
|
+
'Automation:',
|
|
19
|
+
' ADM_TOOLS=<id,id> adm install the given tools (no prompts)',
|
|
20
|
+
' ADM_DRY_RUN=1 ADM_TOOLS=<ids> adm print the install plan only (no changes)',
|
|
21
|
+
...(process.platform === 'win32' ? [
|
|
22
|
+
' PowerShell: $env:ADM_TOOLS="typescript,node"; adm (then: $env:ADM_TOOLS=$null)',
|
|
23
|
+
] : []),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
module.exports = { COMMANDS, AUTOMATION_HINTS };
|
package/src/config/index.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
function configDir() {
|
|
6
|
-
return process.env.ADM_CONFIG_DIR || path.join(os.homedir(), '.adm');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function ensureConfigDir(dir) {
|
|
10
|
-
await fs.mkdir(dir, { recursive: true });
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function readConfig() {
|
|
14
|
-
const dir = configDir();
|
|
15
|
-
const file = path.join(dir, 'config.json');
|
|
16
|
-
try {
|
|
17
|
-
const data = await fs.readFile(file, 'utf8');
|
|
18
|
-
return JSON.parse(data);
|
|
19
|
-
} catch (err) {
|
|
20
|
-
if (err.code === 'ENOENT') return {};
|
|
21
|
-
throw err;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function writeConfig(obj) {
|
|
26
|
-
const dir = configDir();
|
|
27
|
-
await ensureConfigDir(dir);
|
|
28
|
-
const file = path.join(dir, 'config.json');
|
|
29
|
-
await fs.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
module.exports = { configDir, readConfig, writeConfig, ensureConfigDir };
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
function configDir() {
|
|
6
|
+
return process.env.ADM_CONFIG_DIR || path.join(os.homedir(), '.adm');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function ensureConfigDir(dir) {
|
|
10
|
+
await fs.mkdir(dir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function readConfig(dirOverride) {
|
|
14
|
+
const dir = dirOverride || configDir();
|
|
15
|
+
const file = path.join(dir, 'config.json');
|
|
16
|
+
try {
|
|
17
|
+
const data = await fs.readFile(file, 'utf8');
|
|
18
|
+
return JSON.parse(data);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.code === 'ENOENT') return {};
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function writeConfig(obj, dirOverride) {
|
|
26
|
+
const dir = dirOverride || configDir();
|
|
27
|
+
await ensureConfigDir(dir);
|
|
28
|
+
const file = path.join(dir, 'config.json');
|
|
29
|
+
await fs.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { configDir, readConfig, writeConfig, ensureConfigDir };
|