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 CHANGED
@@ -1,6 +1,6 @@
1
- # linage-cli
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
- First, authenticate with your API Key from the Li'nage Cloud dashboard:
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
+ };
@@ -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.0.0');
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.1",
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(gradient.pastel.multiline(figlet.textSync("LI'NAGE", { horizontalLayout: 'full' })));
9
- console.log(chalk.dim(" Automated Dependency Discovery & Lineage Mapping by Li'nage"));
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.blue(''), msg),
15
- success: (msg) => console.log(chalk.green(''), msg),
16
- warning: (msg) => console.log(chalk.yellow(''), msg),
17
- error: (msg) => console.log(chalk.red(''), msg),
18
- dim: (msg) => console.log(chalk.dim(msg)),
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: 'cyan',
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
  };