gent-cli 2.1.0 → 5.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.
@@ -1,131 +1,115 @@
1
1
  /**
2
- * Remote Command - Manage remote repositories
3
- * Add, remove, and list remote repository configurations
2
+ * ============================================================================
3
+ * Remote Command - Manage remote repository connections
4
+ * ============================================================================
5
+ *
6
+ * PURPOSE:
7
+ * Configure remote backend server URLs for push/pull. Like `git remote`.
8
+ *
9
+ * USAGE:
10
+ * gent remote → List remotes
11
+ * gent remote add <name> <url> → Add a remote (e.g. origin)
12
+ * gent remote remove <name> → Remove a remote
13
+ * gent remote set-url <name> <url> → Update remote URL
14
+ *
15
+ * STORAGE:
16
+ * Stored in .gent/config.json under "remotes" key:
17
+ * { "origin": { "url": "https://gent-api.onrender.com/api/repos/my-repo/" } }
18
+ *
19
+ * BACKEND EXPECTATIONS:
20
+ * The URL is the base endpoint for a repository resource:
21
+ * GET <url>/ → Repo metadata
22
+ * POST <url>/push/ → Push commits/objects
23
+ * GET <url>/pull/ → Pull commits/objects
24
+ * GET <url>/refs/ → List remote branch refs
25
+ *
26
+ * ============================================================================
4
27
  */
5
28
 
6
- const chalk = require('chalk');
7
29
  const path = require('path');
8
- const ora = require('ora');
9
- const { getRemoteConfig, addRemote, removeRemote } = require('../utils/cloud-sync');
10
- const { GENT_DIR } = require('../utils/constants');
11
- const { pathExists } = require('../utils/fileSystem');
12
- const repoService = require('../services/repo-service');
13
- const authStorage = require('../utils/auth-storage');
30
+ const chalk = require('chalk');
31
+ const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
32
+ const { CONFIG_FILE } = require('../utils/constants');
14
33
 
15
34
  /**
16
- * Manage remote repositories
17
- * @param {string} action - Action to perform (add, remove, or list)
18
- * @param {string} name - Remote name
19
- * @param {string} url - Remote URL (owner_id/repo_name)
20
- * @param {Object} options - Command options
35
+ * Manage remotes
36
+ * @param {String} subcommand - add|remove|set-url (null = list)
37
+ * @param {Array} args
38
+ * @param {Object} options
21
39
  */
22
- async function remote(action, name, url, options) {
40
+ async function remote(subcommand, args, options) {
23
41
  try {
24
- const cwd = process.cwd();
25
- const gentPath = path.join(cwd, GENT_DIR);
26
-
27
- // Check if in a gent repository
28
- if (!(await pathExists(gentPath))) {
29
- console.error(chalk.red('Error: Not a gent repository'));
30
- console.log(chalk.yellow('Run'), chalk.cyan('gent init'), chalk.yellow('to initialize a repository'));
31
- process.exit(1);
32
- }
33
-
34
- // List remotes (default action)
35
- if (!action || action === 'list' || options.verbose) {
36
- const config = await getRemoteConfig(cwd);
37
- const remotes = Object.keys(config.remotes || {});
38
-
39
- if (remotes.length === 0) {
40
- console.log(chalk.yellow('No remotes configured'));
41
- console.log(chalk.gray('Add a remote with:'), chalk.cyan('gent remote add <name> <owner_id>/<repo_name>'));
42
- return;
43
- }
44
-
45
- console.log(chalk.cyan('Configured remotes:\n'));
46
- for (const remoteName of remotes) {
47
- const remote = config.remotes[remoteName];
48
- if (options.verbose) {
49
- console.log(chalk.bold(remoteName));
50
- console.log(chalk.gray(` Owner ID: ${remote.owner_id}`));
51
- console.log(chalk.gray(` Repository: ${remote.repo_name}`));
52
- console.log(chalk.gray(` URL: ${remote.owner_id}/${remote.repo_name}\n`));
53
- } else {
54
- console.log(`${remoteName}\t${remote.owner_id}/${remote.repo_name}`);
42
+ const gentPath = await getGentPath();
43
+ const configPath = path.join(gentPath, CONFIG_FILE);
44
+ const config = await readJSON(configPath);
45
+ config.remotes = config.remotes || {};
46
+
47
+ switch (subcommand) {
48
+ case 'add': {
49
+ const [name, url] = args || [];
50
+ if (!name || !url) {
51
+ console.error(chalk.red('Usage: gent remote add <name> <url>'));
52
+ return;
55
53
  }
54
+ if (config.remotes[name]) {
55
+ console.error(chalk.red(`Remote '${name}' already exists`));
56
+ return;
57
+ }
58
+ config.remotes[name] = { url };
59
+ await writeJSON(configPath, config);
60
+ console.log(chalk.green(`Added remote '${name}' → ${url}`));
61
+ break;
56
62
  }
57
- return;
58
- }
59
-
60
- // Add remote
61
- if (action === 'add') {
62
- if (!name || !url) {
63
- console.error(chalk.red('Error: Remote name and URL are required'));
64
- console.log(chalk.yellow('Usage:'), chalk.cyan('gent remote add <name> <owner_id>/<repo_name>'));
65
- process.exit(1);
66
- }
67
-
68
- // Parse owner_id/repo_name
69
- const parts = url.split('/');
70
- if (parts.length !== 2) {
71
- console.error(chalk.red('Error: Invalid remote URL format'));
72
- console.log(chalk.yellow('Expected:'), chalk.cyan('<owner_id>/<repo_name>'));
73
- process.exit(1);
74
- }
75
-
76
- const ownerId = parseInt(parts[0]);
77
- const repoName = parts[1];
78
-
79
- if (isNaN(ownerId)) {
80
- console.error(chalk.red('Error: Owner ID must be a number'));
81
- process.exit(1);
82
- }
83
-
84
- // Check authentication
85
- const user = await authStorage.getUser();
86
- if (!user) {
87
- console.error(chalk.red('Error: You must be logged in to add a remote'));
88
- console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
89
- process.exit(1);
63
+ case 'remove': {
64
+ const name = args && args[0];
65
+ if (!name) {
66
+ console.error(chalk.red('Usage: gent remote remove <name>'));
67
+ return;
68
+ }
69
+ if (!config.remotes[name]) {
70
+ console.error(chalk.red(`Remote '${name}' not found`));
71
+ return;
72
+ }
73
+ delete config.remotes[name];
74
+ await writeJSON(configPath, config);
75
+ console.log(chalk.green(`Removed remote '${name}'`));
76
+ break;
90
77
  }
91
-
92
- // Verify repository exists
93
- const spinner = ora('Verifying repository...').start();
94
- try {
95
- await repoService.getRepository(ownerId, repoName);
96
- spinner.succeed('Repository verified');
97
- } catch (error) {
98
- spinner.fail('Repository not found or access denied');
99
- throw error;
78
+ case 'set-url': {
79
+ const [name, url] = args || [];
80
+ if (!name || !url) {
81
+ console.error(chalk.red('Usage: gent remote set-url <name> <url>'));
82
+ return;
83
+ }
84
+ if (!config.remotes[name]) {
85
+ console.error(chalk.red(`Remote '${name}' not found`));
86
+ return;
87
+ }
88
+ config.remotes[name].url = url;
89
+ await writeJSON(configPath, config);
90
+ console.log(chalk.green(`Updated '${name}' → ${url}`));
91
+ break;
100
92
  }
101
-
102
- // Add remote
103
- await addRemote(name, ownerId, repoName, cwd);
104
- console.log(chalk.green(`✓ Remote '${name}' added: ${ownerId}/${repoName}`));
105
- return;
106
- }
107
-
108
- // Remove remote
109
- if (action === 'remove' || action === 'rm') {
110
- if (!name) {
111
- console.error(chalk.red('Error: Remote name is required'));
112
- console.log(chalk.yellow('Usage:'), chalk.cyan('gent remote remove <name>'));
113
- process.exit(1);
93
+ default: {
94
+ // List remotes
95
+ const names = Object.keys(config.remotes);
96
+ if (names.length === 0) {
97
+ console.log(chalk.gray('No remotes configured'));
98
+ console.log(chalk.yellow('Use "gent remote add origin <url>" to add one'));
99
+ return;
100
+ }
101
+ for (const name of names) {
102
+ const verbose = options.verbose ? chalk.gray(` → ${config.remotes[name].url}`) : '';
103
+ console.log(chalk.cyan(name) + verbose);
104
+ }
114
105
  }
115
-
116
- await removeRemote(name, cwd);
117
- console.log(chalk.green(`✓ Remote '${name}' removed`));
118
- return;
119
106
  }
120
-
121
- // Unknown action
122
- console.error(chalk.red(`Error: Unknown action '${action}'`));
123
- console.log(chalk.yellow('Available actions:'), chalk.cyan('add, remove, list'));
124
- process.exit(1);
125
-
126
107
  } catch (error) {
127
- console.error(chalk.red('Failed to manage remote'));
128
- console.error(chalk.red('Error:'), error.message);
108
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
109
+ console.error(chalk.red('Error: Not a gent repository'));
110
+ } else {
111
+ console.error(chalk.red('Error:'), error.message);
112
+ }
129
113
  process.exit(1);
130
114
  }
131
115
  }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * ============================================================================
3
+ * Reset Command - Unstage files or reset HEAD to a previous commit
4
+ * ============================================================================
5
+ *
6
+ * PURPOSE:
7
+ * Undo staging (soft) or move branch pointer back (hard). Like `git reset`.
8
+ *
9
+ * USAGE:
10
+ * gent reset <file...> → Unstage specific file(s) (keep working tree)
11
+ * gent reset → Unstage all files
12
+ * gent reset --hard <hash> → Move HEAD to commit, discard changes
13
+ * gent reset --soft <hash> → Move HEAD to commit, keep staging
14
+ *
15
+ * ALGORITHM:
16
+ * Soft: removes entries from staging.entries matching given paths.
17
+ * Hard: resets commits.json branch pointer + restores working tree blobs.
18
+ *
19
+ * BACKEND EXPECTATIONS:
20
+ * POST /api/repos/:id/reset/ { mode, targetHash }
21
+ * Backend should update remote HEAD and prune unreachable commits.
22
+ *
23
+ * ============================================================================
24
+ */
25
+
26
+ const fs = require('fs').promises;
27
+ const path = require('path');
28
+ const chalk = require('chalk');
29
+ const ora = require('ora');
30
+ const { getGentPath, readJSON, writeJSON, pathExists } = require('../utils/fileSystem');
31
+ const { STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
32
+ const { readBlobAsString } = require('../utils/hash-engine');
33
+
34
+ /**
35
+ * Reset staging or HEAD
36
+ * @param {Array} files - Files to unstage (empty = all)
37
+ * @param {Object} options
38
+ */
39
+ async function reset(files, options) {
40
+ try {
41
+ const gentPath = await getGentPath();
42
+ const cwd = process.cwd();
43
+
44
+ if (options.hard || options.soft) {
45
+ await resetHead(gentPath, cwd, files, options);
46
+ } else {
47
+ await unstageFiles(gentPath, files);
48
+ }
49
+ } catch (error) {
50
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
51
+ console.error(chalk.red('Error: Not a gent repository'));
52
+ } else {
53
+ console.error(chalk.red('Error:'), error.message);
54
+ }
55
+ process.exit(1);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Unstage files from staging area
61
+ */
62
+ async function unstageFiles(gentPath, files) {
63
+ const stagingPath = path.join(gentPath, STAGING_FILE);
64
+ const staging = await readJSON(stagingPath);
65
+
66
+ if (!staging.entries || staging.entries.length === 0) {
67
+ console.log(chalk.yellow('Nothing to unstage'));
68
+ return;
69
+ }
70
+
71
+ let removed = 0;
72
+
73
+ if (!files || files.length === 0) {
74
+ removed = staging.entries.length;
75
+ staging.entries = [];
76
+ staging.files = [];
77
+ } else {
78
+ const removeSet = new Set(files);
79
+ const before = staging.entries.length;
80
+ staging.entries = staging.entries.filter(e => !removeSet.has(e.path));
81
+ staging.files = staging.entries.map(e => e.path);
82
+ removed = before - staging.entries.length;
83
+ }
84
+
85
+ await writeJSON(stagingPath, staging);
86
+ console.log(chalk.green(`Unstaged ${removed} file(s)`));
87
+ }
88
+
89
+ /**
90
+ * Reset HEAD to specific commit
91
+ */
92
+ async function resetHead(gentPath, cwd, args, options) {
93
+ const targetHash = args && args.length > 0 ? args[0] : null;
94
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
95
+ const currentBranch = repository.currentBranch;
96
+ const commits = repository.commits || [];
97
+
98
+ if (!targetHash) {
99
+ console.error(chalk.red('Provide a commit hash to reset to'));
100
+ return;
101
+ }
102
+
103
+ // Find target commit (support short hashes)
104
+ const target = commits.find(c =>
105
+ c.hash === targetHash || c.hash.startsWith(targetHash)
106
+ );
107
+
108
+ if (!target) {
109
+ console.error(chalk.red(`Commit '${targetHash}' not found`));
110
+ return;
111
+ }
112
+
113
+ const spinner = ora(`Resetting to ${target.hash.substring(0, 7)}...`).start();
114
+
115
+ // Move branch pointer
116
+ repository.branches[currentBranch] = target.hash;
117
+ await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
118
+
119
+ if (options.hard) {
120
+ // Restore working tree from target commit
121
+ const tree = target.tree || (target.files || []).map(f => ({
122
+ name: f.path || f.name, hash: f.hash
123
+ }));
124
+
125
+ for (const entry of tree) {
126
+ try {
127
+ const content = await readBlobAsString(gentPath, entry.hash);
128
+ const fullPath = path.join(cwd, entry.name || entry.path);
129
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
130
+ await fs.writeFile(fullPath, content, 'utf-8');
131
+ } catch {
132
+ // Blob may not exist for legacy commits
133
+ }
134
+ }
135
+
136
+ // Clear staging
137
+ const stagingPath = path.join(gentPath, STAGING_FILE);
138
+ await writeJSON(stagingPath, { entries: [], files: [] });
139
+
140
+ spinner.succeed(chalk.green(`HEAD is now at ${target.hash.substring(0, 7)} (hard reset)`));
141
+ console.log(chalk.gray(` ${target.message}`));
142
+ } else {
143
+ // Soft reset: keep staging
144
+ spinner.succeed(chalk.green(`HEAD is now at ${target.hash.substring(0, 7)} (soft reset)`));
145
+ console.log(chalk.gray(` Staging area preserved. ${target.message}`));
146
+ }
147
+ }
148
+
149
+ module.exports = reset;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * ============================================================================
3
+ * Rm Command - Remove files from working tree and staging
4
+ * ============================================================================
5
+ *
6
+ * PURPOSE:
7
+ * Remove files from tracking and optionally from disk. Like `git rm`.
8
+ *
9
+ * USAGE:
10
+ * gent rm <file...> → Remove file(s) from tracking + disk
11
+ * gent rm --cached <file...> → Remove from staging only (keep on disk)
12
+ *
13
+ * ALGORITHM:
14
+ * Adds a "deleted" entry to staging so commit records the removal.
15
+ * Without --cached, also deletes the file from the filesystem.
16
+ *
17
+ * BACKEND EXPECTATIONS:
18
+ * Deletion is recorded as absence in the commit tree. Backend removes
19
+ * file entry from tree when processing the push.
20
+ *
21
+ * ============================================================================
22
+ */
23
+
24
+ const fs = require('fs').promises;
25
+ const path = require('path');
26
+ const chalk = require('chalk');
27
+ const { getGentPath, readJSON, writeJSON, pathExists } = require('../utils/fileSystem');
28
+ const { STAGING_FILE } = require('../utils/constants');
29
+
30
+ /**
31
+ * Remove files
32
+ * @param {Array} files
33
+ * @param {Object} options
34
+ */
35
+ async function rm(files, options) {
36
+ try {
37
+ const gentPath = await getGentPath();
38
+ const cwd = process.cwd();
39
+ const stagingPath = path.join(gentPath, STAGING_FILE);
40
+ const staging = await readJSON(stagingPath);
41
+
42
+ const entries = staging.entries || [];
43
+ const entryMap = new Map(entries.map(e => [e.path, e]));
44
+ let removed = 0;
45
+
46
+ for (const file of files) {
47
+ const relPath = path.relative(cwd, path.resolve(cwd, file));
48
+
49
+ // Stage deletion
50
+ entryMap.set(relPath, {
51
+ path: relPath,
52
+ hash: null,
53
+ status: 'deleted',
54
+ binary: false,
55
+ stats: { insertions: 0, deletions: 0 }
56
+ });
57
+
58
+ // Delete from disk unless --cached
59
+ if (!options.cached) {
60
+ const fullPath = path.join(cwd, relPath);
61
+ if (await pathExists(fullPath)) {
62
+ await fs.unlink(fullPath);
63
+ }
64
+ }
65
+
66
+ removed++;
67
+ console.log(chalk.red(` rm ${relPath}`));
68
+ }
69
+
70
+ staging.entries = Array.from(entryMap.values());
71
+ staging.files = staging.entries.filter(e => e.status !== 'deleted').map(e => e.path);
72
+ await writeJSON(stagingPath, staging);
73
+
74
+ console.log(chalk.green(`\nRemoved ${removed} file(s)`));
75
+ } catch (error) {
76
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
77
+ console.error(chalk.red('Error: Not a gent repository'));
78
+ } else {
79
+ console.error(chalk.red('Error:'), error.message);
80
+ }
81
+ process.exit(1);
82
+ }
83
+ }
84
+
85
+ module.exports = rm;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * ============================================================================
3
+ * Show Command - Show details of a commit, tag, or tree
4
+ * ============================================================================
5
+ *
6
+ * PURPOSE:
7
+ * Display detailed information about a specific commit including author,
8
+ * message, tree hash, parent, and full diff. Like `git show`.
9
+ *
10
+ * USAGE:
11
+ * gent show → Show HEAD commit details with diff
12
+ * gent show <hash> → Show specific commit
13
+ * gent show <tag> → Show commit referenced by tag
14
+ *
15
+ * ALGORITHM:
16
+ * Retrieves commit from commits.json, reads blob content from object store,
17
+ * computes diff against parent commit's tree, and displays unified diff.
18
+ *
19
+ * BACKEND EXPECTATIONS:
20
+ * GET /api/repos/:id/commits/:hash/ — returns commit object with tree
21
+ *
22
+ * ============================================================================
23
+ */
24
+
25
+ const path = require('path');
26
+ const chalk = require('chalk');
27
+ const { getGentPath, readJSON } = require('../utils/fileSystem');
28
+ const { COMMITS_FILE } = require('../utils/constants');
29
+ const { readBlobAsString } = require('../utils/hash-engine');
30
+ const { formatUnifiedDiff } = require('../utils/diff-engine');
31
+
32
+ /**
33
+ * Show commit details
34
+ * @param {String} ref - Commit hash, tag name, or empty for HEAD
35
+ * @param {Object} options
36
+ */
37
+ async function show(ref, options) {
38
+ try {
39
+ const gentPath = await getGentPath();
40
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
41
+ const commits = repository.commits || [];
42
+ const tags = repository.tags || {};
43
+
44
+ let targetHash = ref;
45
+
46
+ // Default to HEAD
47
+ if (!targetHash) {
48
+ targetHash = repository.branches[repository.currentBranch];
49
+ if (!targetHash) {
50
+ console.log(chalk.yellow('No commits yet'));
51
+ return;
52
+ }
53
+ }
54
+
55
+ // Resolve tag name → hash
56
+ if (tags[targetHash]) {
57
+ targetHash = tags[targetHash].hash;
58
+ }
59
+
60
+ // Find commit (support short hashes)
61
+ const commit = commits.find(c =>
62
+ c.hash === targetHash || c.hash.startsWith(targetHash)
63
+ );
64
+
65
+ if (!commit) {
66
+ console.error(chalk.red(`Commit '${ref || 'HEAD'}' not found`));
67
+ return;
68
+ }
69
+
70
+ // Display commit header
71
+ console.log(chalk.yellow(`commit ${commit.hash}`));
72
+ if (commit.mergeParent) {
73
+ console.log(chalk.gray(`Merge: ${commit.parent?.substring(0, 7)} ${commit.mergeParent.substring(0, 7)}`));
74
+ }
75
+ console.log(chalk.white(`Author: ${commit.author.name} <${commit.author.email}>`));
76
+ console.log(chalk.white(`Date: ${new Date(commit.timestamp).toLocaleString()}`));
77
+ if (commit.treeHash) {
78
+ console.log(chalk.gray(`Tree: ${commit.treeHash.substring(0, 7)}`));
79
+ }
80
+ console.log('');
81
+ console.log(chalk.white(` ${commit.message}`));
82
+ console.log('');
83
+
84
+ if (commit.stats) {
85
+ console.log(chalk.gray(` ${commit.stats.filesChanged} file(s), `) +
86
+ chalk.green(`+${commit.stats.insertions}`) + ' ' +
87
+ chalk.red(`-${commit.stats.deletions}`));
88
+ console.log('');
89
+ }
90
+
91
+ // Show diff against parent
92
+ if (!options.noPatch) {
93
+ await showCommitDiff(gentPath, commits, commit);
94
+ }
95
+
96
+ } catch (error) {
97
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
98
+ console.error(chalk.red('Error: Not a gent repository'));
99
+ } else {
100
+ console.error(chalk.red('Error:'), error.message);
101
+ }
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Display diff between commit and its parent
108
+ */
109
+ async function showCommitDiff(gentPath, commits, commit) {
110
+ const parentCommit = commit.parent
111
+ ? commits.find(c => c.hash === commit.parent)
112
+ : null;
113
+
114
+ const currentTree = commit.tree || (commit.files || []).map(f => ({
115
+ name: f.path || f.name, hash: f.hash
116
+ }));
117
+ const parentTree = parentCommit
118
+ ? (parentCommit.tree || (parentCommit.files || []).map(f => ({
119
+ name: f.path || f.name, hash: f.hash
120
+ })))
121
+ : [];
122
+
123
+ const parentMap = new Map(parentTree.map(f => [f.name || f.path, f.hash]));
124
+ const currentMap = new Map(currentTree.map(f => [f.name || f.path, f.hash]));
125
+
126
+ // Files changed
127
+ const allPaths = new Set([...parentMap.keys(), ...currentMap.keys()]);
128
+
129
+ for (const filePath of allPaths) {
130
+ const oldHash = parentMap.get(filePath);
131
+ const newHash = currentMap.get(filePath);
132
+
133
+ if (oldHash === newHash) continue;
134
+
135
+ try {
136
+ const oldContent = oldHash ? await readBlobAsString(gentPath, oldHash) : '';
137
+ const newContent = newHash ? await readBlobAsString(gentPath, newHash) : '';
138
+
139
+ const unified = formatUnifiedDiff(filePath, oldContent, newContent);
140
+ if (unified) {
141
+ printColorizedDiff(unified);
142
+ }
143
+ } catch {
144
+ // Blob missing for legacy commits
145
+ if (!oldHash && newHash) {
146
+ console.log(chalk.green(`+ new file: ${filePath}`));
147
+ } else if (oldHash && !newHash) {
148
+ console.log(chalk.red(`- deleted: ${filePath}`));
149
+ } else {
150
+ console.log(chalk.yellow(`~ modified: ${filePath} (blob not available)`));
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ function printColorizedDiff(unifiedDiff) {
157
+ for (const line of unifiedDiff.split('\n')) {
158
+ if (line.startsWith('---') || line.startsWith('+++')) console.log(chalk.bold(line));
159
+ else if (line.startsWith('@@')) console.log(chalk.cyan(line));
160
+ else if (line.startsWith('+')) console.log(chalk.green(line));
161
+ else if (line.startsWith('-')) console.log(chalk.red(line));
162
+ else console.log(line);
163
+ }
164
+ console.log('');
165
+ }
166
+
167
+ module.exports = show;