@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,89 +1,89 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const { handleConflict, createBackup } = require('./file-conflict-handler');
|
|
5
|
-
|
|
6
|
-
const DEFAULT_FILES = ['.bashrc', '.zshrc', '.gitconfig', '.ssh/config'];
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Syncs dotfiles from repo to home directory
|
|
10
|
-
*
|
|
11
|
-
* Options:
|
|
12
|
-
* repo: string — dotfiles repo path/URL
|
|
13
|
-
* homeDir: string — home directory (defaults to os.homedir())
|
|
14
|
-
* copy: boolean — copy instead of symlinking
|
|
15
|
-
* only: string[] — file filter (without dot, e.g. ['bashrc', 'zshrc'])
|
|
16
|
-
* files: string[] — file list (with dot, e.g. ['.bashrc'])
|
|
17
|
-
* onConflict: () => 'skip' | 'backup' — conflict strategy
|
|
18
|
-
*/
|
|
19
|
-
async function syncDotfiles(options = {}) {
|
|
20
|
-
const configDir = process.env.ADM_CONFIG_DIR || path.join(require('os').homedir(), '.adm');
|
|
21
|
-
const dotfilesDir = path.join(configDir, 'dotfiles');
|
|
22
|
-
const homeDir = options.homeDir || process.env.ADM_TEST_HOME || require('os').homedir();
|
|
23
|
-
|
|
24
|
-
// Determine file list to sync
|
|
25
|
-
let files = options.files || DEFAULT_FILES;
|
|
26
|
-
|
|
27
|
-
// --only filter (user provides without dot: 'bashrc' → '.bashrc')
|
|
28
|
-
if (options.only && options.only.length > 0) {
|
|
29
|
-
files = options.only.map(f => f.startsWith('.') ? f : '.' + f);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const result = { symlinked: [], copied: [], skipped: [], backedUp: [], errors: [] };
|
|
33
|
-
|
|
34
|
-
for (const file of files) {
|
|
35
|
-
const sourcePath = path.join(dotfilesDir, file);
|
|
36
|
-
const targetPath = path.join(homeDir, file);
|
|
37
|
-
|
|
38
|
-
// Check if source file exists in repo
|
|
39
|
-
if (!fs.existsSync(sourcePath)) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Create target directory if needed (e.g. .ssh/)
|
|
44
|
-
const targetDir = path.dirname(targetPath);
|
|
45
|
-
if (!fs.existsSync(targetDir)) {
|
|
46
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check for conflict
|
|
50
|
-
const conflict = handleConflict({ targetPath, sourcePath });
|
|
51
|
-
if (conflict === 'conflict') {
|
|
52
|
-
const strategy = options.onConflict ? options.onConflict(file) : 'skip';
|
|
53
|
-
if (strategy === 'skip') {
|
|
54
|
-
result.skipped.push(file);
|
|
55
|
-
console.log(chalk.yellow(` ⚠ Skipped ${file} (conflict)`));
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (strategy === 'backup') {
|
|
59
|
-
createBackup(targetPath);
|
|
60
|
-
result.backedUp.push(file);
|
|
61
|
-
fs.unlinkSync(targetPath);
|
|
62
|
-
console.log(chalk.blue(` ℹ Backup ${file} → ${file}.backup`));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Symlink or copy
|
|
67
|
-
try {
|
|
68
|
-
if (options.copy) {
|
|
69
|
-
fs.copyFileSync(sourcePath, targetPath);
|
|
70
|
-
// Preserve permissions
|
|
71
|
-
const sourceStat = fs.statSync(sourcePath);
|
|
72
|
-
fs.chmodSync(targetPath, sourceStat.mode);
|
|
73
|
-
result.copied.push(file);
|
|
74
|
-
console.log(chalk.green(`
|
|
75
|
-
} else {
|
|
76
|
-
fs.symlinkSync(sourcePath, targetPath);
|
|
77
|
-
result.symlinked.push(file);
|
|
78
|
-
console.log(chalk.green(`
|
|
79
|
-
}
|
|
80
|
-
} catch (err) {
|
|
81
|
-
result.errors.push({ file, error: err.message });
|
|
82
|
-
console.error(chalk.red(` ✖ Error ${file}: ${err.message}`));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
module.exports = { syncDotfiles };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { handleConflict, createBackup } = require('./file-conflict-handler');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_FILES = ['.bashrc', '.zshrc', '.gitconfig', '.ssh/config'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Syncs dotfiles from repo to home directory
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* repo: string — dotfiles repo path/URL
|
|
13
|
+
* homeDir: string — home directory (defaults to os.homedir())
|
|
14
|
+
* copy: boolean — copy instead of symlinking
|
|
15
|
+
* only: string[] — file filter (without dot, e.g. ['bashrc', 'zshrc'])
|
|
16
|
+
* files: string[] — file list (with dot, e.g. ['.bashrc'])
|
|
17
|
+
* onConflict: () => 'skip' | 'backup' — conflict strategy
|
|
18
|
+
*/
|
|
19
|
+
async function syncDotfiles(options = {}) {
|
|
20
|
+
const configDir = process.env.ADM_CONFIG_DIR || path.join(require('os').homedir(), '.adm');
|
|
21
|
+
const dotfilesDir = path.join(configDir, 'dotfiles');
|
|
22
|
+
const homeDir = options.homeDir || process.env.ADM_TEST_HOME || require('os').homedir();
|
|
23
|
+
|
|
24
|
+
// Determine file list to sync
|
|
25
|
+
let files = options.files || DEFAULT_FILES;
|
|
26
|
+
|
|
27
|
+
// --only filter (user provides without dot: 'bashrc' → '.bashrc')
|
|
28
|
+
if (options.only && options.only.length > 0) {
|
|
29
|
+
files = options.only.map(f => f.startsWith('.') ? f : '.' + f);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = { symlinked: [], copied: [], skipped: [], backedUp: [], errors: [] };
|
|
33
|
+
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const sourcePath = path.join(dotfilesDir, file);
|
|
36
|
+
const targetPath = path.join(homeDir, file);
|
|
37
|
+
|
|
38
|
+
// Check if source file exists in repo
|
|
39
|
+
if (!fs.existsSync(sourcePath)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create target directory if needed (e.g. .ssh/)
|
|
44
|
+
const targetDir = path.dirname(targetPath);
|
|
45
|
+
if (!fs.existsSync(targetDir)) {
|
|
46
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check for conflict
|
|
50
|
+
const conflict = handleConflict({ targetPath, sourcePath });
|
|
51
|
+
if (conflict === 'conflict') {
|
|
52
|
+
const strategy = options.onConflict ? options.onConflict(file) : 'skip';
|
|
53
|
+
if (strategy === 'skip') {
|
|
54
|
+
result.skipped.push(file);
|
|
55
|
+
console.log(chalk.yellow(` ⚠ Skipped ${file} (conflict)`));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (strategy === 'backup') {
|
|
59
|
+
createBackup(targetPath);
|
|
60
|
+
result.backedUp.push(file);
|
|
61
|
+
fs.unlinkSync(targetPath);
|
|
62
|
+
console.log(chalk.blue(` ℹ Backup ${file} → ${file}.backup`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Symlink or copy
|
|
67
|
+
try {
|
|
68
|
+
if (options.copy) {
|
|
69
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
70
|
+
// Preserve permissions
|
|
71
|
+
const sourceStat = fs.statSync(sourcePath);
|
|
72
|
+
fs.chmodSync(targetPath, sourceStat.mode);
|
|
73
|
+
result.copied.push(file);
|
|
74
|
+
console.log(chalk.green(` ✓ Copied ${file}`));
|
|
75
|
+
} else {
|
|
76
|
+
fs.symlinkSync(sourcePath, targetPath);
|
|
77
|
+
result.symlinked.push(file);
|
|
78
|
+
console.log(chalk.green(` ✓ Symlink ${file}`));
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
result.errors.push({ file, error: err.message });
|
|
82
|
+
console.error(chalk.red(` ✖ Error ${file}: ${err.message}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { syncDotfiles };
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Detects file conflict — whether target file already exists
|
|
5
|
-
* Returns 'no-conflict' or 'conflict'
|
|
6
|
-
*/
|
|
7
|
-
function handleConflict({ targetPath, sourcePath }) {
|
|
8
|
-
if (!fs.existsSync(targetPath)) {
|
|
9
|
-
return 'no-conflict';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Check if it's a symlink — not a conflict if so
|
|
13
|
-
const stat = fs.lstatSync(targetPath);
|
|
14
|
-
if (stat.isSymbolicLink()) {
|
|
15
|
-
return 'no-conflict';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return 'conflict';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Creates a backup of a file (.backup) and returns the backup path
|
|
23
|
-
*/
|
|
24
|
-
function createBackup(filePath) {
|
|
25
|
-
const backupPath = filePath + '.backup';
|
|
26
|
-
fs.copyFileSync(filePath, backupPath);
|
|
27
|
-
return backupPath;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = { handleConflict, createBackup };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detects file conflict — whether target file already exists
|
|
5
|
+
* Returns 'no-conflict' or 'conflict'
|
|
6
|
+
*/
|
|
7
|
+
function handleConflict({ targetPath, sourcePath }) {
|
|
8
|
+
if (!fs.existsSync(targetPath)) {
|
|
9
|
+
return 'no-conflict';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Check if it's a symlink — not a conflict if so
|
|
13
|
+
const stat = fs.lstatSync(targetPath);
|
|
14
|
+
if (stat.isSymbolicLink()) {
|
|
15
|
+
return 'no-conflict';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return 'conflict';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a backup of a file (.backup) and returns the backup path
|
|
23
|
+
*/
|
|
24
|
+
function createBackup(filePath) {
|
|
25
|
+
const backupPath = filePath + '.backup';
|
|
26
|
+
fs.copyFileSync(filePath, backupPath);
|
|
27
|
+
return backupPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { handleConflict, createBackup };
|
package/bin/adm-tui
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* ADM TUI — Full-screen terminal UI
|
|
4
|
-
* Replaces Commander.js CLI with ink-based TUI for v2.
|
|
5
|
-
*/
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const { boot } = require('../src/tui/app');
|
|
9
|
-
|
|
10
|
-
boot().catch(err => {
|
|
11
|
-
console.error(err.message);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
});
|
package/src/ai/knowledge.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
const KNOWLEDGE_DIR = path.join(os.homedir(), '.adm');
|
|
6
|
-
const KNOWLEDGE_FILE = path.join(KNOWLEDGE_DIR, 'ai-knowledge.json');
|
|
7
|
-
|
|
8
|
-
const SOURCE_DOCS = ['README.md', path.join('plans', 'prd-v2.md')];
|
|
9
|
-
|
|
10
|
-
function buildKnowledge(version, projectRoot) {
|
|
11
|
-
const parts = [];
|
|
12
|
-
|
|
13
|
-
for (const doc of SOURCE_DOCS) {
|
|
14
|
-
const filePath = path.join(projectRoot, doc);
|
|
15
|
-
try {
|
|
16
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
17
|
-
// Take first 500 chars per doc for compactness
|
|
18
|
-
const compact = content.substring(0, 500).trim();
|
|
19
|
-
if (compact) {
|
|
20
|
-
parts.push(`--- ${path.basename(doc)} ---\n${compact}`);
|
|
21
|
-
}
|
|
22
|
-
} catch {
|
|
23
|
-
// doc missing — skip
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (parts.length === 0) return null;
|
|
28
|
-
|
|
29
|
-
const summary = parts.join('\n\n');
|
|
30
|
-
const cache = {
|
|
31
|
-
version,
|
|
32
|
-
summary,
|
|
33
|
-
updatedAt: new Date().toISOString(),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Ensure ~/.adm/ exists
|
|
37
|
-
if (!fs.existsSync(KNOWLEDGE_DIR)) {
|
|
38
|
-
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
fs.writeFileSync(KNOWLEDGE_FILE, JSON.stringify(cache, null, 2), 'utf8');
|
|
41
|
-
|
|
42
|
-
return summary;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getKnowledge(version, projectRoot) {
|
|
46
|
-
// Try reading cache
|
|
47
|
-
try {
|
|
48
|
-
const raw = fs.readFileSync(KNOWLEDGE_FILE, 'utf8');
|
|
49
|
-
const cached = JSON.parse(raw);
|
|
50
|
-
if (cached.version === version) {
|
|
51
|
-
return cached.summary;
|
|
52
|
-
}
|
|
53
|
-
// Version changed — rebuild
|
|
54
|
-
} catch {
|
|
55
|
-
// No cache — build fresh
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return buildKnowledge(version, projectRoot);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
module.exports = { getKnowledge, buildKnowledge, KNOWLEDGE_DIR, KNOWLEDGE_FILE };
|
package/src/commands/connect.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const inquirer = require('inquirer');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
|
-
const ora = require('ora');
|
|
4
|
-
const github = require('../integrations/github');
|
|
5
|
-
const gitlab = require('../integrations/gitlab');
|
|
6
|
-
const { listStoredServices, retrieveToken } = require('../utils/keychain');
|
|
7
|
-
|
|
8
|
-
async function connectGithub({ token } = {}) {
|
|
9
|
-
if (!token) {
|
|
10
|
-
({ token } = await inquirer.prompt([{
|
|
11
|
-
type: 'password',
|
|
12
|
-
name: 'token',
|
|
13
|
-
message: 'Enter GitHub Personal Access Token (PAT):',
|
|
14
|
-
mask: '*',
|
|
15
|
-
}]));
|
|
16
|
-
}
|
|
17
|
-
const s = ora('Validating GitHub token...').start();
|
|
18
|
-
try {
|
|
19
|
-
const result = await github.connect(token);
|
|
20
|
-
s.succeed(`Connected to GitHub as ${chalk.green(result.user.login)}`);
|
|
21
|
-
return { ok: true, user: result.user.login };
|
|
22
|
-
} catch (err) {
|
|
23
|
-
s.fail(err.message);
|
|
24
|
-
throw err;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function listConnections() {
|
|
29
|
-
const services = await listStoredServices();
|
|
30
|
-
if (services.length === 0) {
|
|
31
|
-
console.log(chalk.yellow(' No connected services.'));
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
for (const name of services) {
|
|
35
|
-
const token = await retrieveToken(name);
|
|
36
|
-
const masked = token ? token.substring(0, 4) + '****' + token.slice(-4) : '(none)';
|
|
37
|
-
console.log(` ${chalk.green(name)}: connected (token: ${masked})`);
|
|
38
|
-
}
|
|
39
|
-
return services;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function disconnect(service) {
|
|
43
|
-
if (service === 'github') {
|
|
44
|
-
await github.disconnect();
|
|
45
|
-
console.log(chalk.green(' GitHub disconnected.'));
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
if (service === 'gitlab') {
|
|
49
|
-
await gitlab.disconnect();
|
|
50
|
-
console.log(chalk.green(' GitLab disconnected.'));
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
throw new Error(`Unknown service: ${service}. Supported: github, gitlab`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function connectGitlab({ token } = {}) {
|
|
57
|
-
if (!token) {
|
|
58
|
-
({ token } = await inquirer.prompt([{
|
|
59
|
-
type: 'password',
|
|
60
|
-
name: 'token',
|
|
61
|
-
message: 'Enter GitLab Access Token:',
|
|
62
|
-
mask: '*',
|
|
63
|
-
}]));
|
|
64
|
-
}
|
|
65
|
-
const s = ora('Validating GitLab token...').start();
|
|
66
|
-
try {
|
|
67
|
-
const result = await gitlab.connect(token);
|
|
68
|
-
s.succeed(`Connected to GitLab as ${chalk.green(result.user.username)}`);
|
|
69
|
-
return { ok: true, user: result.user.username };
|
|
70
|
-
} catch (err) {
|
|
71
|
-
s.fail(err.message);
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = { connectGithub, connectGitlab, listConnections, disconnect };
|
package/src/commands/issue.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const ora = require('ora');
|
|
3
|
-
const inquirer = require('inquirer');
|
|
4
|
-
const gitlab = require('../integrations/gitlab');
|
|
5
|
-
const { listStoredServices } = require('../utils/keychain');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Lists issues from connected platforms (GitHub + GitLab)
|
|
9
|
-
* When multiple platforms — interactive selection
|
|
10
|
-
* When --platform given — skips selection
|
|
11
|
-
*/
|
|
12
|
-
async function listIssues(options = {}) {
|
|
13
|
-
const services = await listStoredServices();
|
|
14
|
-
|
|
15
|
-
if (services.length === 0) {
|
|
16
|
-
throw new Error('No connected platforms. Run /connect to connect GitHub or GitLab.');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Filter to platforms supporting issues
|
|
20
|
-
const platforms = services.filter(s => ['github', 'gitlab'].includes(s));
|
|
21
|
-
if (platforms.length === 0) {
|
|
22
|
-
throw new Error('No connected platforms. Run /connect to connect GitHub or GitLab.');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let platform = options.platform;
|
|
26
|
-
|
|
27
|
-
// When --platform given, use it
|
|
28
|
-
if (!platform) {
|
|
29
|
-
if (platforms.length === 1) {
|
|
30
|
-
platform = platforms[0];
|
|
31
|
-
} else {
|
|
32
|
-
// Interactive selection
|
|
33
|
-
const answer = await inquirer.prompt([{
|
|
34
|
-
type: 'list',
|
|
35
|
-
name: 'platform',
|
|
36
|
-
message: 'Which platform to fetch issues from?',
|
|
37
|
-
choices: platforms.map(p => ({ name: p.charAt(0).toUpperCase() + p.slice(1), value: p })),
|
|
38
|
-
}]);
|
|
39
|
-
platform = answer.platform;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const s = ora(`Fetching issues from ${platform}...`).start();
|
|
44
|
-
try {
|
|
45
|
-
let issues;
|
|
46
|
-
if (platform === 'gitlab') {
|
|
47
|
-
issues = await gitlab.listIssues(options);
|
|
48
|
-
} else {
|
|
49
|
-
// GitHub uses search API for issues
|
|
50
|
-
const github = require('../integrations/github');
|
|
51
|
-
const octokit = await github.getClient();
|
|
52
|
-
const { data: user } = await octokit.users.getAuthenticated();
|
|
53
|
-
const { data } = await octokit.search.issuesAndPullRequests({
|
|
54
|
-
q: `author:${user.login} is:issue is:open`,
|
|
55
|
-
sort: 'updated',
|
|
56
|
-
order: 'desc',
|
|
57
|
-
per_page: options.limit || 20,
|
|
58
|
-
});
|
|
59
|
-
issues = data.items.map(issue => ({
|
|
60
|
-
iid: issue.number,
|
|
61
|
-
title: issue.title,
|
|
62
|
-
state: issue.state,
|
|
63
|
-
url: issue.html_url,
|
|
64
|
-
updatedAt: issue.updated_at,
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
s.succeed(`Found ${issues.length} issues on ${platform}`);
|
|
69
|
-
for (const issue of issues) {
|
|
70
|
-
console.log(` ${chalk.bold(`#${issue.iid}`)} ${issue.title}`);
|
|
71
|
-
console.log(` ${chalk.gray(issue.url)}`);
|
|
72
|
-
}
|
|
73
|
-
return issues;
|
|
74
|
-
} catch (err) {
|
|
75
|
-
s.fail(err.message);
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
module.exports = { listIssues };
|
package/src/commands/pr.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const ora = require('ora');
|
|
3
|
-
const github = require('../integrations/github');
|
|
4
|
-
|
|
5
|
-
async function listPRs(options = {}) {
|
|
6
|
-
const s = ora('Fetching pull requests...').start();
|
|
7
|
-
try {
|
|
8
|
-
const prs = await github.listPRs(options);
|
|
9
|
-
s.stop();
|
|
10
|
-
if (prs.length === 0) {
|
|
11
|
-
console.log(chalk.yellow(' No open pull requests found.'));
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
for (const pr of prs) {
|
|
15
|
-
console.log(
|
|
16
|
-
` ${chalk.bold(`#${pr.number}`)} ${pr.title}`,
|
|
17
|
-
chalk.gray(`(${pr.repo})`),
|
|
18
|
-
chalk.green(pr.state),
|
|
19
|
-
);
|
|
20
|
-
console.log(chalk.gray(` ${pr.url}`));
|
|
21
|
-
}
|
|
22
|
-
return prs;
|
|
23
|
-
} catch (err) {
|
|
24
|
-
s.fail(err.message);
|
|
25
|
-
throw err;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function draftPR(title, options = {}) {
|
|
30
|
-
const s = ora(`Creating draft PR "${title}"...`).start();
|
|
31
|
-
try {
|
|
32
|
-
const result = await github.createDraftPR(title, options);
|
|
33
|
-
s.succeed(`Draft PR #${result.number} created`);
|
|
34
|
-
console.log(chalk.gray(` ${result.url}`));
|
|
35
|
-
return result;
|
|
36
|
-
} catch (err) {
|
|
37
|
-
s.fail(err.message);
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function commentPR(prNumber, message, options = {}) {
|
|
43
|
-
const s = ora(`Commenting on PR #${prNumber}...`).start();
|
|
44
|
-
try {
|
|
45
|
-
const result = await github.commentOnPR(prNumber, message, options);
|
|
46
|
-
s.succeed(`Comment added to PR #${prNumber}`);
|
|
47
|
-
console.log(chalk.gray(` ${result.url}`));
|
|
48
|
-
return result;
|
|
49
|
-
} catch (err) {
|
|
50
|
-
s.fail(err.message);
|
|
51
|
-
throw err;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = { listPRs, draftPR, commentPR };
|
package/src/commands/theme.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
const inquirer = require('inquirer');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
|
-
const { resolveTheme, listThemes } = require('../ui/theme');
|
|
4
|
-
const { readConfig, writeConfig } = require('../config');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* /theme — interactive theme selector with live preview.
|
|
8
|
-
* Arrow keys to browse, Enter to apply, theme saved to ~/.adm/config.json.
|
|
9
|
-
*/
|
|
10
|
-
async function themeCommand() {
|
|
11
|
-
const config = await readConfig();
|
|
12
|
-
const currentTheme = config.theme || 'dark';
|
|
13
|
-
const themes = listThemes();
|
|
14
|
-
|
|
15
|
-
// ── Show current theme ────────────────────────────────
|
|
16
|
-
console.log('');
|
|
17
|
-
const cur = resolveTheme(config);
|
|
18
|
-
const cc = cur.colors;
|
|
19
|
-
console.log(
|
|
20
|
-
chalk.hex(cc.primary)(' Current: ') +
|
|
21
|
-
chalk.hex(cc.accent)(cur.name) +
|
|
22
|
-
chalk.hex(cc.muted)(' (' + currentTheme + ')')
|
|
23
|
-
);
|
|
24
|
-
console.log('');
|
|
25
|
-
|
|
26
|
-
// ── Build preview for each theme ──────────────────────
|
|
27
|
-
const choices = themes.map(t => {
|
|
28
|
-
const c = t.colors;
|
|
29
|
-
const block = (hex) => chalk.hex(hex)('██');
|
|
30
|
-
const label =
|
|
31
|
-
' ' +
|
|
32
|
-
block(c.primary) + block(c.accent) + block(c.secondary) +
|
|
33
|
-
' ' +
|
|
34
|
-
chalk.hex(c.primary).bold(t.name.padEnd(10)) +
|
|
35
|
-
chalk.hex(c.muted)('adm> ') +
|
|
36
|
-
chalk.hex(c.text)('hello') + ' ' +
|
|
37
|
-
chalk.hex(c.success)('✓') + ' ' +
|
|
38
|
-
chalk.hex(c.error)('✗') + ' ' +
|
|
39
|
-
chalk.hex(c.warning)('⚠');
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
name: label,
|
|
43
|
-
value: t.name,
|
|
44
|
-
short: t.name,
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// ── Interactive selection ──────────────────────────────
|
|
49
|
-
const { selected } = await inquirer.prompt([
|
|
50
|
-
{
|
|
51
|
-
type: 'list',
|
|
52
|
-
name: 'selected',
|
|
53
|
-
message: 'Choose a theme:',
|
|
54
|
-
choices,
|
|
55
|
-
default: currentTheme,
|
|
56
|
-
pageSize: themes.length,
|
|
57
|
-
loop: false,
|
|
58
|
-
},
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
// ── Save to config ────────────────────────────────────
|
|
62
|
-
if (selected === currentTheme) {
|
|
63
|
-
const t = resolveTheme({ theme: selected });
|
|
64
|
-
console.log('');
|
|
65
|
-
console.log(chalk.hex(t.colors.muted)(' Already using ' + selected + ' theme.'));
|
|
66
|
-
console.log('');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const newConfig = { ...config, theme: selected };
|
|
71
|
-
await writeConfig(newConfig);
|
|
72
|
-
|
|
73
|
-
const t = resolveTheme({ theme: selected });
|
|
74
|
-
const nc = t.colors;
|
|
75
|
-
console.log('');
|
|
76
|
-
console.log(
|
|
77
|
-
chalk.hex(nc.success)(' ✓ Theme saved: ') +
|
|
78
|
-
chalk.hex(nc.primary).bold(selected)
|
|
79
|
-
);
|
|
80
|
-
console.log(
|
|
81
|
-
chalk.hex(nc.muted)(' ~/.adm/config.json updated') +
|
|
82
|
-
chalk.hex(nc.text)(' — restart to see changes everywhere.')
|
|
83
|
-
);
|
|
84
|
-
console.log('');
|
|
85
|
-
|
|
86
|
-
// ── Show preview of new theme ─────────────────────────
|
|
87
|
-
showPreview(nc, selected);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function showPreview(c, name) {
|
|
91
|
-
const border = chalk.hex(c.primary);
|
|
92
|
-
const pad = ' ';
|
|
93
|
-
console.log('');
|
|
94
|
-
console.log(border(' ┌──────────────────────────────────┐'));
|
|
95
|
-
console.log(border(' │ ') + chalk.hex(c.text).bold(' ADM') + chalk.hex(c.muted)(' · ' + name) + pad.slice(0, Math.max(0, 18 - name.length)) + border('│'));
|
|
96
|
-
console.log(border(' │ ') + chalk.hex(c.muted)(' adm> ') + chalk.hex(c.text)('status') + pad.slice(0, 12) + border('│'));
|
|
97
|
-
console.log(border(' │ ') + chalk.hex(c.success)(' ✓ All systems go') + pad.slice(0, 10) + border('│'));
|
|
98
|
-
console.log(border(' │ ') + chalk.hex(c.accent)(' ██ ██') + chalk.hex(c.text)(' 12:34') + pad.slice(0, 10) + border('│'));
|
|
99
|
-
console.log(border(' └──────────────────────────────────┘'));
|
|
100
|
-
console.log('');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = { themeCommand };
|