linage-cli 1.0.1 → 1.1.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/README.md +32 -3
- package/commands/config.js +44 -0
- package/commands/doctor.js +68 -0
- package/commands/scan.js +99 -0
- package/index.js +29 -7
- package/package.json +2 -2
- package/utils/ui.js +27 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Li'nage CLI • v1.1.0
|
|
2
2
|
|
|
3
|
-
The Official CLI for Li'nage Cloud - Automate Dependency Discovery & Lineage Mapping.
|
|
3
|
+
The Official CLI for [Li'nage Cloud](https://linage.cloud) - Automate Dependency Discovery & Lineage Mapping with a premium Neo-Design interface.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ npm install -g linage-cli
|
|
|
12
12
|
|
|
13
13
|
### Login
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Authenticate with your API Key from the Li'nage Cloud dashboard:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
linage-cli login
|
|
@@ -26,6 +26,32 @@ Scan your current directory and upload your dependency graph:
|
|
|
26
26
|
linage-cli ingest
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Recursive Scan [NEW]
|
|
30
|
+
|
|
31
|
+
Recursively find and ingest all dependency files up to 3 levels deep:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
linage-cli scan
|
|
35
|
+
linage-cli scan --all # Ingest all detected nodes automatically
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### System Diagnostics [NEW]
|
|
39
|
+
|
|
40
|
+
Run health checks for your environment, connectivity, and credentials:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
linage-cli doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Manage Configuration [NEW]
|
|
47
|
+
|
|
48
|
+
View or clear your local CLI state and API keys:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
linage-cli config list
|
|
52
|
+
linage-cli config clear
|
|
53
|
+
```
|
|
54
|
+
|
|
29
55
|
### View Projects
|
|
30
56
|
|
|
31
57
|
List all projects in your organization:
|
|
@@ -58,6 +84,9 @@ Open your dashboard in the browser:
|
|
|
58
84
|
linage-cli open
|
|
59
85
|
```
|
|
60
86
|
|
|
87
|
+
## Neo-Design Matrix
|
|
88
|
+
This version introduces the **Neo-Design Matrix** aesthetic, featuring custom brand gradients and a polished interface that matches the [linage.cloud](https://linage.cloud) experience.
|
|
89
|
+
|
|
61
90
|
## License
|
|
62
91
|
|
|
63
92
|
ISC
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const config = require('../utils/config');
|
|
2
|
+
const { log, printBox, COLORS } = require('../utils/ui');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
module.exports = async (subcommand, options) => {
|
|
6
|
+
if (subcommand === 'list') {
|
|
7
|
+
const apiKey = config.getApiKey();
|
|
8
|
+
const apiUrl = config.getApiUrl();
|
|
9
|
+
|
|
10
|
+
printBox(
|
|
11
|
+
`${chalk.bold.hex(COLORS.cyan)('Local Configuration Matrix')}\n\n` +
|
|
12
|
+
`API Endpoint: ${apiUrl}\n` +
|
|
13
|
+
`API Key: ${apiKey ? chalk.green('SET') : chalk.red('NOT SET')}\n` +
|
|
14
|
+
`Environment: ${apiUrl.includes('localhost') ? 'Development' : 'Cloud'}`,
|
|
15
|
+
{ borderColor: COLORS.orange }
|
|
16
|
+
);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (subcommand === 'clear') {
|
|
21
|
+
if (options.force || confirm('Are you sure you want to clear ALL local CLI settings?')) {
|
|
22
|
+
config.setApiKey('');
|
|
23
|
+
log.success('Local configuration purged.');
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (subcommand === 'set') {
|
|
29
|
+
if (options.apiKey) {
|
|
30
|
+
config.setApiKey(options.apiKey);
|
|
31
|
+
log.success('API Key updated.');
|
|
32
|
+
} else {
|
|
33
|
+
log.error('Please specify a value to set (e.g., --api-key <key>).');
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Default: show help for config
|
|
39
|
+
log.info('Usage: linage-cli config <subcommand>');
|
|
40
|
+
console.log(' Subcommands:');
|
|
41
|
+
console.log(' list - View current settings');
|
|
42
|
+
console.log(' clear - Delete all local state');
|
|
43
|
+
console.log(' set - Manually update settings');
|
|
44
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
const config = require('../utils/config');
|
|
6
|
+
const fetch = require('node-fetch');
|
|
7
|
+
const { log, COLORS, printBox } = require('../utils/ui');
|
|
8
|
+
|
|
9
|
+
module.exports = async () => {
|
|
10
|
+
log.info('Starting Li\'nage System Diagnostics...');
|
|
11
|
+
console.log('');
|
|
12
|
+
|
|
13
|
+
const spinner = ora('Checking environment...').start();
|
|
14
|
+
|
|
15
|
+
// 1. System Info
|
|
16
|
+
const systemInfo = {
|
|
17
|
+
node: process.version,
|
|
18
|
+
os: `${os.type()} ${os.release()} (${os.arch()})`,
|
|
19
|
+
cli: require('../package.json').version
|
|
20
|
+
};
|
|
21
|
+
spinner.succeed(`System Environment: Node ${systemInfo.node} on ${systemInfo.os}`);
|
|
22
|
+
|
|
23
|
+
// 2. Local Config
|
|
24
|
+
spinner.start('Checking local configuration...');
|
|
25
|
+
const apiKey = config.getApiKey();
|
|
26
|
+
const apiUrl = config.getApiUrl();
|
|
27
|
+
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
spinner.warn('API Key: Not found locally. Use "linage-cli login" to authenticate.');
|
|
30
|
+
} else {
|
|
31
|
+
spinner.succeed(`API Key: Secured (${apiKey.substring(0, 4)}...${apiKey.substring(apiKey.length - 4)})`);
|
|
32
|
+
}
|
|
33
|
+
spinner.info(`Endpoint: ${apiUrl}`);
|
|
34
|
+
|
|
35
|
+
// 3. Network & Auth
|
|
36
|
+
spinner.start('Verifying connection to Li\'nage Cloud...');
|
|
37
|
+
try {
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
// Check connectivity without key
|
|
40
|
+
const res = await fetch(`${apiUrl}/api/health`);
|
|
41
|
+
if (res.ok) {
|
|
42
|
+
spinner.succeed('Connectivity: Reachable (Unauthenticated)');
|
|
43
|
+
} else {
|
|
44
|
+
throw new Error('Endpoint returned error status');
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Check connectivity with key
|
|
48
|
+
const whoami = await api.validateKey(apiKey);
|
|
49
|
+
if (whoami && whoami.success) {
|
|
50
|
+
spinner.succeed(`Authentication: Verified (Org: ${whoami.organization.name})`);
|
|
51
|
+
} else {
|
|
52
|
+
spinner.fail('Authentication: Invalid or expired API Key.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail('Connectivity: Failed to reach linage.cloud');
|
|
57
|
+
log.error(error.message);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Summary
|
|
61
|
+
console.log('');
|
|
62
|
+
printBox(
|
|
63
|
+
`${chalk.bold.hex(COLORS.cyan)('Diagnostics Complete')}\n\n` +
|
|
64
|
+
`Version: ${systemInfo.cli}\n` +
|
|
65
|
+
`Status: ${apiKey ? chalk.green('READY') : chalk.yellow('SETUP REQUIRED')}`,
|
|
66
|
+
{ borderColor: COLORS.magenta }
|
|
67
|
+
);
|
|
68
|
+
};
|
package/commands/scan.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ora = require('ora');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
const { log, COLORS } = require('../utils/ui');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
const SUPPORTED_FILES = {
|
|
9
|
+
'package.json': 'node',
|
|
10
|
+
'package-lock.json': 'node',
|
|
11
|
+
'yarn.lock': 'node',
|
|
12
|
+
'requirements.txt': 'python',
|
|
13
|
+
'Pipfile': 'python',
|
|
14
|
+
'pyproject.toml': 'python',
|
|
15
|
+
'Cargo.toml': 'rust',
|
|
16
|
+
'go.mod': 'go'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function findFiles(dir, depth = 0, maxDepth = 3) {
|
|
20
|
+
if (depth > maxDepth) return [];
|
|
21
|
+
|
|
22
|
+
let results = [];
|
|
23
|
+
const list = fs.readdirSync(dir);
|
|
24
|
+
|
|
25
|
+
for (const file of list) {
|
|
26
|
+
if (file === 'node_modules' || file === '.git' || file === 'venv') continue;
|
|
27
|
+
|
|
28
|
+
const fullPath = path.resolve(dir, file);
|
|
29
|
+
const stat = fs.statSync(fullPath);
|
|
30
|
+
|
|
31
|
+
if (stat && stat.isDirectory()) {
|
|
32
|
+
results = results.concat(await findFiles(fullPath, depth + 1, maxDepth));
|
|
33
|
+
} else if (SUPPORTED_FILES[file]) {
|
|
34
|
+
results.push({
|
|
35
|
+
file,
|
|
36
|
+
path: fullPath,
|
|
37
|
+
type: SUPPORTED_FILES[file],
|
|
38
|
+
relative: path.relative(process.cwd(), fullPath)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = async (options) => {
|
|
46
|
+
const spinner = ora('Scanning for architectures...').start();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const files = await findFiles(process.cwd());
|
|
50
|
+
|
|
51
|
+
if (files.length === 0) {
|
|
52
|
+
spinner.fail('No dependency matrices detected.');
|
|
53
|
+
log.dim('Supported: node, python, rust, go');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.succeed(`Detected ${files.length} resource ${files.length === 1 ? 'node' : 'nodes'}:`);
|
|
58
|
+
console.log('');
|
|
59
|
+
|
|
60
|
+
for (const fileItem of files) {
|
|
61
|
+
console.log(chalk.hex(COLORS.cyan)(' ⬡ ') + chalk.bold(fileItem.relative) + chalk.dim(` [${fileItem.type}]`));
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
|
|
65
|
+
if (!options.all) {
|
|
66
|
+
log.info('Hint: Run with --all to ingest all detected nodes automatically.');
|
|
67
|
+
log.info('Currently ingesting the root node only...');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// For now, we'll just ingest the first one if not --all, or loop if --all
|
|
71
|
+
const toIngest = options.all ? files : [files[0]];
|
|
72
|
+
|
|
73
|
+
for (const fileItem of toIngest) {
|
|
74
|
+
const ingestSpinner = ora(`Ingesting ${fileItem.relative}...`).start();
|
|
75
|
+
try {
|
|
76
|
+
const content = fs.readFileSync(fileItem.path, 'utf-8');
|
|
77
|
+
const projectCode = options.project || path.basename(fileItem.path === 'package.json' ? path.dirname(fileItem.path) : fileItem.path);
|
|
78
|
+
|
|
79
|
+
await api.post('/api/ingest/cli', {
|
|
80
|
+
projectCode,
|
|
81
|
+
fileName: fileItem.file,
|
|
82
|
+
ecosystem: fileItem.type,
|
|
83
|
+
content,
|
|
84
|
+
isRecursive: true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
ingestSpinner.succeed(`Ingested ${fileItem.relative}`);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
ingestSpinner.fail(`Failed ${fileItem.relative}: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
log.success('Scan protocol completed.');
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
spinner.fail('Scan failed.');
|
|
97
|
+
log.error(error.message);
|
|
98
|
+
}
|
|
99
|
+
};
|
package/index.js
CHANGED
|
@@ -11,6 +11,9 @@ const whoamiCmd = require('./commands/whoami');
|
|
|
11
11
|
const logoutCmd = require('./commands/logout');
|
|
12
12
|
const openCmd = require('./commands/open');
|
|
13
13
|
const statsCmd = require('./commands/stats');
|
|
14
|
+
const doctorCmd = require('./commands/doctor');
|
|
15
|
+
const configCmd = require('./commands/config');
|
|
16
|
+
const scanCmd = require('./commands/scan');
|
|
14
17
|
|
|
15
18
|
// Initial Setup
|
|
16
19
|
printHeader();
|
|
@@ -18,7 +21,7 @@ printHeader();
|
|
|
18
21
|
program
|
|
19
22
|
.name('linage-cli')
|
|
20
23
|
.description("The Official CLI for Li'nage Cloud - Automate Dependency Discovery")
|
|
21
|
-
.version('1.
|
|
24
|
+
.version('1.1.0');
|
|
22
25
|
|
|
23
26
|
program
|
|
24
27
|
.command('login')
|
|
@@ -61,21 +64,40 @@ program
|
|
|
61
64
|
.description('Clear saved credentials')
|
|
62
65
|
.action(logoutCmd);
|
|
63
66
|
|
|
67
|
+
program
|
|
68
|
+
.command('doctor')
|
|
69
|
+
.description('Run system diagnostics and verify connectivity')
|
|
70
|
+
.action(doctorCmd);
|
|
71
|
+
|
|
72
|
+
program
|
|
73
|
+
.command('config <subcommand>')
|
|
74
|
+
.description('Manage local CLI configuration')
|
|
75
|
+
.option('-f, --force', 'Force action without confirmation')
|
|
76
|
+
.option('-k, --api-key <key>', 'Set API Key manually')
|
|
77
|
+
.action(configCmd);
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command('scan')
|
|
81
|
+
.description('Recursively find and ingest all dependency files')
|
|
82
|
+
.option('-a, --all', 'Ingest all detected files automatically')
|
|
83
|
+
.option('-p, --project <name>', 'Override Project Code Name')
|
|
84
|
+
.action(scanCmd);
|
|
85
|
+
|
|
64
86
|
// Normalize arguments to handle case-insensitivity
|
|
65
|
-
const knownCommands = ['login', 'ingest', 'projects', 'whoami', 'logout', 'help', 'open', 'stats'];
|
|
87
|
+
const knownCommands = ['login', 'ingest', 'projects', 'whoami', 'logout', 'help', 'open', 'stats', 'doctor', 'config', 'scan'];
|
|
66
88
|
const knownFlags = ['--key', '--project', '--version', '--help'];
|
|
67
89
|
|
|
68
90
|
const args = process.argv.map((arg, index) => {
|
|
69
91
|
if (index < 2) return arg; // Skip node and script path
|
|
70
|
-
|
|
92
|
+
|
|
71
93
|
const lowerArg = arg.toLowerCase();
|
|
72
|
-
|
|
94
|
+
|
|
73
95
|
// 1. Map -v to -V for version
|
|
74
96
|
if (arg === '-v') return '-V';
|
|
75
|
-
|
|
97
|
+
|
|
76
98
|
// 2. Lowercase known commands
|
|
77
99
|
if (knownCommands.includes(lowerArg)) return lowerArg;
|
|
78
|
-
|
|
100
|
+
|
|
79
101
|
// 3. Handle long flags (e.g., --KEY or --PROJECT)
|
|
80
102
|
if (arg.startsWith('--')) {
|
|
81
103
|
const parts = arg.split('=');
|
|
@@ -84,7 +106,7 @@ const args = process.argv.map((arg, index) => {
|
|
|
84
106
|
return parts.length > 1 ? `${flagName}=${parts.slice(1).join('=')}` : flagName;
|
|
85
107
|
}
|
|
86
108
|
}
|
|
87
|
-
|
|
109
|
+
|
|
88
110
|
return arg;
|
|
89
111
|
});
|
|
90
112
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linage-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "The Official CLI for Li'nage Cloud - Automate Dependency Discovery",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": "index.js",
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=14.0.0"
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
package/utils/ui.js
CHANGED
|
@@ -3,19 +3,35 @@ const figlet = require('figlet');
|
|
|
3
3
|
const gradient = require('gradient-string');
|
|
4
4
|
const boxen = require('boxen');
|
|
5
5
|
|
|
6
|
+
// Neo-Design Palette
|
|
7
|
+
const COLORS = {
|
|
8
|
+
orange: '#D4622B', // Neo-Orange
|
|
9
|
+
cyan: '#00D2D3', // Neo-Cyan
|
|
10
|
+
magenta: '#FF00FF',
|
|
11
|
+
zinc: '#7A756E',
|
|
12
|
+
black: '#2D2A26'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const neoGradient = gradient(COLORS.orange, COLORS.cyan);
|
|
16
|
+
|
|
6
17
|
const printHeader = () => {
|
|
7
18
|
console.log('');
|
|
8
|
-
console.log(
|
|
9
|
-
|
|
19
|
+
console.log(neoGradient.multiline(figlet.textSync("LI'NAGE", {
|
|
20
|
+
font: 'Slant',
|
|
21
|
+
horizontalLayout: 'fitted'
|
|
22
|
+
})));
|
|
23
|
+
console.log(chalk.bold.hex(COLORS.cyan)(' ⬡ ') + chalk.hex(COLORS.zinc)("Automated Dependency Discovery & Lineage Mapping"));
|
|
24
|
+
console.log(chalk.hex(COLORS.zinc)(' v1.0.1 • linage.cloud'));
|
|
10
25
|
console.log('');
|
|
11
26
|
};
|
|
12
27
|
|
|
13
28
|
const log = {
|
|
14
|
-
info: (msg) => console.log(chalk.
|
|
15
|
-
success: (msg) => console.log(chalk.
|
|
16
|
-
warning: (msg) => console.log(chalk.
|
|
17
|
-
error: (msg) => console.log(chalk.
|
|
18
|
-
dim: (msg) => console.log(chalk.
|
|
29
|
+
info: (msg) => console.log(chalk.hex(COLORS.cyan)('◈'), msg),
|
|
30
|
+
success: (msg) => console.log(chalk.hex('#10B981')('✓'), msg),
|
|
31
|
+
warning: (msg) => console.log(chalk.hex('#F59E0B')('⚠️'), msg),
|
|
32
|
+
error: (msg) => console.log(chalk.hex('#EF4444')('🗙'), msg),
|
|
33
|
+
dim: (msg) => console.log(chalk.hex(COLORS.zinc)(msg)),
|
|
34
|
+
brand: (msg) => console.log(neoGradient(msg))
|
|
19
35
|
};
|
|
20
36
|
|
|
21
37
|
const printBox = (text, options = {}) => {
|
|
@@ -23,7 +39,7 @@ const printBox = (text, options = {}) => {
|
|
|
23
39
|
padding: 1,
|
|
24
40
|
margin: 1,
|
|
25
41
|
borderStyle: 'round',
|
|
26
|
-
borderColor:
|
|
42
|
+
borderColor: COLORS.cyan,
|
|
27
43
|
...options
|
|
28
44
|
}));
|
|
29
45
|
};
|
|
@@ -31,5 +47,7 @@ const printBox = (text, options = {}) => {
|
|
|
31
47
|
module.exports = {
|
|
32
48
|
printHeader,
|
|
33
49
|
log,
|
|
34
|
-
printBox
|
|
50
|
+
printBox,
|
|
51
|
+
COLORS,
|
|
52
|
+
neoGradient
|
|
35
53
|
};
|