memoir-cli 1.2.0 → 1.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/bin/memoir.js +18 -11
- package/package.json +1 -1
- package/src/adapters/restore.js +29 -4
- package/src/commands/init.js +61 -51
- package/src/commands/push.js +46 -12
- package/src/commands/restore.js +22 -8
- package/src/commands/status.js +33 -26
package/bin/memoir.js
CHANGED
|
@@ -8,16 +8,23 @@ import { pushCommand } from '../src/commands/push.js';
|
|
|
8
8
|
import { restoreCommand } from '../src/commands/restore.js';
|
|
9
9
|
import { statusCommand } from '../src/commands/status.js';
|
|
10
10
|
|
|
11
|
-
const VERSION = '1.
|
|
11
|
+
const VERSION = '1.2.0';
|
|
12
|
+
|
|
13
|
+
// Custom help banner
|
|
14
|
+
program.addHelpText('beforeAll', '\n' + boxen(
|
|
15
|
+
gradient.pastel.multiline(' memoir ') + '\n' +
|
|
16
|
+
chalk.gray(' Your AI remembers everything.'),
|
|
17
|
+
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderStyle: 'round', borderColor: 'cyan', dimBorder: true }
|
|
18
|
+
) + '\n');
|
|
12
19
|
|
|
13
20
|
program
|
|
14
21
|
.name('memoir')
|
|
15
|
-
.description('
|
|
22
|
+
.description(chalk.white('Sync your AI memory across every device.'))
|
|
16
23
|
.version(VERSION);
|
|
17
24
|
|
|
18
25
|
program
|
|
19
26
|
.command('init')
|
|
20
|
-
.description('
|
|
27
|
+
.description('Set up memoir with your storage provider')
|
|
21
28
|
.action(async () => {
|
|
22
29
|
try {
|
|
23
30
|
await initCommand();
|
|
@@ -30,7 +37,7 @@ program
|
|
|
30
37
|
program
|
|
31
38
|
.command('push')
|
|
32
39
|
.alias('remember')
|
|
33
|
-
.description('
|
|
40
|
+
.description('Back up your AI memory to the cloud')
|
|
34
41
|
.action(async () => {
|
|
35
42
|
try {
|
|
36
43
|
await pushCommand();
|
|
@@ -43,7 +50,7 @@ program
|
|
|
43
50
|
program
|
|
44
51
|
.command('restore')
|
|
45
52
|
.alias('pull')
|
|
46
|
-
.description('Restore your AI memory
|
|
53
|
+
.description('Restore your AI memory on this machine')
|
|
47
54
|
.action(async () => {
|
|
48
55
|
try {
|
|
49
56
|
await restoreCommand();
|
|
@@ -55,7 +62,7 @@ program
|
|
|
55
62
|
|
|
56
63
|
program
|
|
57
64
|
.command('status')
|
|
58
|
-
.description('
|
|
65
|
+
.description('See what AI tools are on this machine')
|
|
59
66
|
.action(async () => {
|
|
60
67
|
try {
|
|
61
68
|
await statusCommand();
|
|
@@ -67,13 +74,13 @@ program
|
|
|
67
74
|
|
|
68
75
|
program
|
|
69
76
|
.command('migrate')
|
|
70
|
-
.description('
|
|
77
|
+
.description('Translate memory between AI providers')
|
|
71
78
|
.action(() => {
|
|
72
79
|
console.log('\n' + boxen(
|
|
73
|
-
gradient.pastel('memoir migrate
|
|
74
|
-
chalk.white('
|
|
75
|
-
chalk.white('
|
|
76
|
-
chalk.cyan('
|
|
80
|
+
gradient.pastel(' memoir migrate ') + '\n\n' +
|
|
81
|
+
chalk.white('Instantly translate your context between') + '\n' +
|
|
82
|
+
chalk.white('Claude, Gemini, Codex, and more.') + '\n\n' +
|
|
83
|
+
chalk.cyan.bold('Coming soon.'),
|
|
77
84
|
{ padding: 1, borderStyle: 'round', borderColor: 'yellow', align: 'center' }
|
|
78
85
|
) + '\n');
|
|
79
86
|
});
|
package/package.json
CHANGED
package/src/adapters/restore.js
CHANGED
|
@@ -5,6 +5,25 @@ import os from 'os';
|
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { adapters } from '../adapters/index.js';
|
|
7
7
|
|
|
8
|
+
async function copyMissing(src, dest, spinner) {
|
|
9
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const srcPath = path.join(src, entry.name);
|
|
12
|
+
const destPath = path.join(dest, entry.name);
|
|
13
|
+
|
|
14
|
+
if (entry.isDirectory()) {
|
|
15
|
+
await fs.ensureDir(destPath);
|
|
16
|
+
await copyMissing(srcPath, destPath, spinner);
|
|
17
|
+
} else {
|
|
18
|
+
if (await fs.pathExists(destPath)) {
|
|
19
|
+
// skip existing files
|
|
20
|
+
} else {
|
|
21
|
+
await fs.copy(srcPath, destPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
export async function restoreMemories(sourceDir, spinner) {
|
|
9
28
|
let restoredAny = false;
|
|
10
29
|
|
|
@@ -19,7 +38,7 @@ export async function restoreMemories(sourceDir, spinner) {
|
|
|
19
38
|
{
|
|
20
39
|
type: 'confirm',
|
|
21
40
|
name: 'confirm',
|
|
22
|
-
message: `Restore ${adapter.name} memory?
|
|
41
|
+
message: `Restore ${adapter.name} memory? (only adds missing files, won't overwrite)`,
|
|
23
42
|
default: false
|
|
24
43
|
}
|
|
25
44
|
]);
|
|
@@ -28,16 +47,22 @@ export async function restoreMemories(sourceDir, spinner) {
|
|
|
28
47
|
|
|
29
48
|
if (confirm) {
|
|
30
49
|
if (adapter.customExtract) {
|
|
31
|
-
// Restore individual files
|
|
50
|
+
// Restore individual files — only add missing ones
|
|
32
51
|
const files = await fs.readdir(backupDir);
|
|
33
52
|
for (const file of files) {
|
|
34
53
|
const dest = path.join(adapter.source, file);
|
|
35
|
-
await fs.
|
|
54
|
+
if (await fs.pathExists(dest)) {
|
|
55
|
+
spinner.info(chalk.gray(` Skipped ${file} (already exists)`));
|
|
56
|
+
spinner.start();
|
|
57
|
+
} else {
|
|
58
|
+
await fs.copy(path.join(backupDir, file), dest);
|
|
59
|
+
}
|
|
36
60
|
}
|
|
37
61
|
} else {
|
|
38
62
|
spinner.text = `Restoring ${chalk.cyan(adapter.name)} memory to ${adapter.source}...`;
|
|
39
63
|
await fs.ensureDir(adapter.source);
|
|
40
|
-
|
|
64
|
+
// Merge — only copy files that don't already exist
|
|
65
|
+
await copyMissing(backupDir, adapter.source, spinner);
|
|
41
66
|
}
|
|
42
67
|
restoredAny = true;
|
|
43
68
|
} else {
|
package/src/commands/init.js
CHANGED
|
@@ -4,78 +4,88 @@ import open from 'open';
|
|
|
4
4
|
import boxen from 'boxen';
|
|
5
5
|
import gradient from 'gradient-string';
|
|
6
6
|
import { saveConfig } from '../config.js';
|
|
7
|
+
import { pushCommand } from './push.js';
|
|
8
|
+
import { restoreCommand } from './restore.js';
|
|
7
9
|
|
|
8
10
|
export async function initCommand() {
|
|
9
11
|
const title = gradient.pastel.multiline('memoir \\nYour AI Remembers Everything');
|
|
10
|
-
console.log('\\n' + boxen(title, {
|
|
11
|
-
padding: 1,
|
|
12
|
-
margin: 1,
|
|
13
|
-
borderStyle: 'round',
|
|
12
|
+
console.log('\\n' + boxen(title, {
|
|
13
|
+
padding: 1,
|
|
14
|
+
margin: 1,
|
|
15
|
+
borderStyle: 'round',
|
|
14
16
|
borderColor: 'cyan',
|
|
15
17
|
align: 'center'
|
|
16
18
|
}));
|
|
17
19
|
|
|
18
|
-
console.log(chalk.gray("Let's
|
|
20
|
+
console.log(chalk.gray("Let's get your AI memory set up.\\n"));
|
|
19
21
|
|
|
20
|
-
const
|
|
22
|
+
const { direction } = await inquirer.prompt([
|
|
21
23
|
{
|
|
22
24
|
type: 'list',
|
|
23
|
-
name: '
|
|
24
|
-
message: '
|
|
25
|
+
name: 'direction',
|
|
26
|
+
message: 'What do you want to do?',
|
|
25
27
|
choices: [
|
|
26
|
-
{ name: '
|
|
27
|
-
{ name: '
|
|
28
|
+
{ name: '⬆️ Upload ' + chalk.gray('(backup this machine\'s AI memory)'), value: 'upload' },
|
|
29
|
+
{ name: '⬇️ Download ' + chalk.gray('(restore AI memory to this machine)'), value: 'download' }
|
|
28
30
|
]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
message: 'Enter the full path to your sync directory ' + chalk.gray('(e.g., ~/Dropbox/memoir):'),
|
|
34
|
-
when: (answers) => answers.provider === 'local',
|
|
35
|
-
validate: (input) => input.trim() !== '' ? true : chalk.red('✖ Path is required')
|
|
36
|
-
},
|
|
31
|
+
}
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const { provider } = await inquirer.prompt([
|
|
37
35
|
{
|
|
38
|
-
type: '
|
|
39
|
-
name: '
|
|
40
|
-
message: '
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
type: 'list',
|
|
37
|
+
name: 'provider',
|
|
38
|
+
message: 'Where do you want to store it?',
|
|
39
|
+
choices: [
|
|
40
|
+
{ name: '☁️ GitHub ' + chalk.gray('(sync across computers)'), value: 'git' },
|
|
41
|
+
{ name: '📂 Local Directory ' + chalk.gray('(Dropbox, iCloud, etc.)'), value: 'local' }
|
|
42
|
+
]
|
|
43
43
|
}
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
console.log(chalk.cyan('\\n↗ Opening GitHub... Create an empty private repository, then return here.\\n'));
|
|
48
|
-
await open('https://github.com/new');
|
|
49
|
-
}
|
|
46
|
+
let config = { provider };
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
if (provider === 'local') {
|
|
49
|
+
const { localPath } = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'input',
|
|
52
|
+
name: 'localPath',
|
|
53
|
+
message: 'Path to sync directory ' + chalk.gray('(e.g., ~/Dropbox/memoir):'),
|
|
54
|
+
validate: (input) => input.trim() !== '' ? true : chalk.red('✖ Path is required')
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
57
|
+
config.localPath = localPath;
|
|
58
|
+
} else {
|
|
59
|
+
const { repoInput } = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'repoInput',
|
|
63
|
+
message: 'GitHub repo ' + chalk.gray('(e.g., camgitt/brain):'),
|
|
64
|
+
validate: (input) => {
|
|
65
|
+
if (input.trim() === '') return chalk.red('✖ Repo is required');
|
|
66
|
+
return true;
|
|
61
67
|
}
|
|
62
|
-
return true;
|
|
63
68
|
}
|
|
64
|
-
|
|
65
|
-
]);
|
|
69
|
+
]);
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
// Accept shorthand like "camgitt/brain" or full URLs
|
|
72
|
+
let gitRepo = repoInput.trim();
|
|
73
|
+
if (!gitRepo.includes('github.com') && !gitRepo.includes('gitlab.com')) {
|
|
74
|
+
gitRepo = `https://github.com/${gitRepo}.git`;
|
|
75
|
+
}
|
|
76
|
+
config.gitRepo = gitRepo;
|
|
77
|
+
}
|
|
72
78
|
|
|
73
79
|
await saveConfig(config);
|
|
74
80
|
|
|
75
|
-
console.log('\\n' +
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
console.log('\\n' + chalk.green('✔ Configuration saved!'));
|
|
82
|
+
|
|
83
|
+
// Immediately run the chosen action
|
|
84
|
+
if (direction === 'upload') {
|
|
85
|
+
console.log(chalk.cyan('\\n↗ Uploading your AI memory...\\n'));
|
|
86
|
+
await pushCommand();
|
|
87
|
+
} else {
|
|
88
|
+
console.log(chalk.cyan('\\n↙ Downloading your AI memory...\\n'));
|
|
89
|
+
await restoreCommand();
|
|
90
|
+
}
|
|
81
91
|
}
|
package/src/commands/push.js
CHANGED
|
@@ -3,48 +3,82 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
+
import boxen from 'boxen';
|
|
7
|
+
import gradient from 'gradient-string';
|
|
6
8
|
import { getConfig } from '../config.js';
|
|
7
|
-
import { extractMemories } from '../adapters/index.js';
|
|
9
|
+
import { extractMemories, adapters } from '../adapters/index.js';
|
|
8
10
|
import { syncToLocal, syncToGit } from '../providers/index.js';
|
|
9
11
|
|
|
10
12
|
export async function pushCommand() {
|
|
11
13
|
const config = await getConfig();
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
if (!config) {
|
|
14
|
-
console.log('
|
|
15
|
-
|
|
16
|
+
console.log('\n' + boxen(
|
|
17
|
+
chalk.red('✖ Not configured yet\n\n') +
|
|
18
|
+
chalk.white('Run ') + chalk.cyan.bold('memoir init') + chalk.white(' to get started.'),
|
|
19
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'red' }
|
|
20
|
+
) + '\n');
|
|
16
21
|
return;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
console.log();
|
|
20
|
-
const spinner = ora('
|
|
25
|
+
const spinner = ora({ text: chalk.gray('Scanning for AI tools...'), spinner: 'dots' }).start();
|
|
21
26
|
|
|
22
|
-
// Create a temporary staging directory
|
|
23
27
|
const stagingDir = path.join(os.tmpdir(), `memoir-staging-${Date.now()}`);
|
|
24
28
|
await fs.ensureDir(stagingDir);
|
|
25
29
|
|
|
26
30
|
try {
|
|
27
|
-
spinner.text = 'Scanning system for AI configurations...';
|
|
28
|
-
|
|
29
|
-
// Pass spinner so adapter can update it
|
|
30
31
|
const foundAny = await extractMemories(stagingDir, spinner);
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
if (!foundAny) {
|
|
33
|
-
spinner.
|
|
34
|
+
spinner.stop();
|
|
35
|
+
console.log('\n' + boxen(
|
|
36
|
+
chalk.yellow('No AI tools detected on this machine.\n\n') +
|
|
37
|
+
chalk.gray('Supported: Claude, Gemini, Codex, Cursor, Copilot, Windsurf, Aider'),
|
|
38
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'yellow' }
|
|
39
|
+
) + '\n');
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
// Count what was found
|
|
44
|
+
const found = [];
|
|
45
|
+
for (const adapter of adapters) {
|
|
46
|
+
if (adapter.customExtract) {
|
|
47
|
+
for (const file of adapter.files) {
|
|
48
|
+
if (await fs.pathExists(path.join(adapter.source, file))) {
|
|
49
|
+
found.push(adapter.name);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else if (await fs.pathExists(adapter.source)) {
|
|
54
|
+
found.push(adapter.name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
spinner.text = chalk.gray('Uploading to ' + (config.provider === 'git' ? 'GitHub' : 'local storage') + '...');
|
|
59
|
+
|
|
37
60
|
if (config.provider === 'local' || config.provider.includes('local')) {
|
|
38
61
|
await syncToLocal(config, stagingDir, spinner);
|
|
39
62
|
} else if (config.provider === 'git' || config.provider.includes('git')) {
|
|
40
63
|
await syncToGit(config, stagingDir, spinner);
|
|
41
64
|
} else {
|
|
42
65
|
spinner.fail(chalk.red(`Unknown provider: ${config.provider}`));
|
|
66
|
+
return;
|
|
43
67
|
}
|
|
68
|
+
|
|
69
|
+
spinner.stop();
|
|
70
|
+
|
|
71
|
+
// Success output
|
|
72
|
+
const toolList = found.map(t => chalk.cyan(' ✔ ' + t)).join('\n');
|
|
73
|
+
console.log('\n' + boxen(
|
|
74
|
+
gradient.pastel(' Backed up! ') + '\n\n' +
|
|
75
|
+
toolList + '\n\n' +
|
|
76
|
+
chalk.gray(`${found.length} tool${found.length !== 1 ? 's' : ''} synced to ${config.provider === 'git' ? 'GitHub' : 'local storage'}`),
|
|
77
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
78
|
+
) + '\n');
|
|
44
79
|
} catch (error) {
|
|
45
80
|
spinner.fail(chalk.red('Sync failed: ') + error.message);
|
|
46
81
|
} finally {
|
|
47
|
-
// Clean up staging directory
|
|
48
82
|
await fs.remove(stagingDir);
|
|
49
83
|
}
|
|
50
84
|
}
|
package/src/commands/restore.js
CHANGED
|
@@ -3,22 +3,26 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
+
import boxen from 'boxen';
|
|
7
|
+
import gradient from 'gradient-string';
|
|
6
8
|
import { getConfig } from '../config.js';
|
|
7
9
|
import { fetchFromLocal, fetchFromGit } from '../providers/restore.js';
|
|
8
10
|
|
|
9
11
|
export async function restoreCommand() {
|
|
10
12
|
const config = await getConfig();
|
|
11
|
-
|
|
13
|
+
|
|
12
14
|
if (!config) {
|
|
13
|
-
console.log('
|
|
14
|
-
|
|
15
|
+
console.log('\n' + boxen(
|
|
16
|
+
chalk.red('✖ Not configured yet\n\n') +
|
|
17
|
+
chalk.white('Run ') + chalk.cyan.bold('memoir init') + chalk.white(' to get started.'),
|
|
18
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'red' }
|
|
19
|
+
) + '\n');
|
|
15
20
|
return;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
console.log();
|
|
19
|
-
const spinner = ora('
|
|
24
|
+
const spinner = ora({ text: chalk.gray('Fetching memories from ' + (config.provider === 'git' ? 'GitHub' : 'local storage') + '...'), spinner: 'dots' }).start();
|
|
20
25
|
|
|
21
|
-
// Create a temporary staging directory to hold the downloaded files
|
|
22
26
|
const stagingDir = path.join(os.tmpdir(), `memoir-restore-${Date.now()}`);
|
|
23
27
|
await fs.ensureDir(stagingDir);
|
|
24
28
|
|
|
@@ -34,16 +38,26 @@ export async function restoreCommand() {
|
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
spinner.stop();
|
|
42
|
+
|
|
37
43
|
if (restored) {
|
|
38
|
-
|
|
44
|
+
console.log('\n' + boxen(
|
|
45
|
+
gradient.pastel(' Restored! ') + '\n\n' +
|
|
46
|
+
chalk.white('Your AI tools have their memories back.') + '\n' +
|
|
47
|
+
chalk.gray('They remember everything.'),
|
|
48
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
49
|
+
) + '\n');
|
|
39
50
|
} else {
|
|
40
|
-
|
|
51
|
+
console.log('\n' + boxen(
|
|
52
|
+
chalk.yellow('No memories were restored.\n\n') +
|
|
53
|
+
chalk.gray('Run ') + chalk.cyan('memoir push') + chalk.gray(' on another machine first.'),
|
|
54
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'yellow' }
|
|
55
|
+
) + '\n');
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
} catch (error) {
|
|
44
59
|
spinner.fail(chalk.red('Restore failed: ') + error.message);
|
|
45
60
|
} finally {
|
|
46
|
-
// Clean up staging directory
|
|
47
61
|
await fs.remove(stagingDir);
|
|
48
62
|
}
|
|
49
63
|
}
|
package/src/commands/status.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import boxen from 'boxen';
|
|
5
|
+
import gradient from 'gradient-string';
|
|
4
6
|
import { getConfig } from '../config.js';
|
|
5
7
|
import { adapters } from '../adapters/index.js';
|
|
6
8
|
|
|
@@ -10,47 +12,52 @@ export async function statusCommand() {
|
|
|
10
12
|
console.log();
|
|
11
13
|
|
|
12
14
|
// Config status
|
|
15
|
+
let configLine;
|
|
13
16
|
if (config) {
|
|
14
|
-
const provider = config.provider === 'git'
|
|
15
|
-
|
|
17
|
+
const provider = config.provider === 'git'
|
|
18
|
+
? chalk.cyan(config.gitRepo)
|
|
19
|
+
: chalk.cyan(config.localPath);
|
|
20
|
+
configLine = chalk.green('✔ Connected') + chalk.gray(' → ') + provider;
|
|
16
21
|
} else {
|
|
17
|
-
|
|
18
|
-
console.log();
|
|
19
|
-
return;
|
|
22
|
+
configLine = chalk.red('✖ Not configured') + chalk.gray(' → run ') + chalk.cyan('memoir init');
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
console.log();
|
|
23
|
-
|
|
24
25
|
// Detected tools
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const lines = [];
|
|
27
27
|
let detected = 0;
|
|
28
|
+
|
|
28
29
|
for (const adapter of adapters) {
|
|
30
|
+
let found = false;
|
|
29
31
|
if (adapter.customExtract) {
|
|
30
|
-
let hasFiles = false;
|
|
31
32
|
for (const file of adapter.files) {
|
|
32
33
|
if (await fs.pathExists(path.join(adapter.source, file))) {
|
|
33
|
-
|
|
34
|
+
found = true;
|
|
34
35
|
break;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
if (hasFiles) {
|
|
38
|
-
console.log(chalk.green(' ✔ ') + adapter.name);
|
|
39
|
-
detected++;
|
|
40
|
-
} else {
|
|
41
|
-
console.log(chalk.gray(' ○ ') + chalk.gray(adapter.name + ' — not found'));
|
|
42
|
-
}
|
|
43
38
|
} else {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
found = await fs.pathExists(adapter.source);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (found) {
|
|
43
|
+
lines.push(chalk.green(' ✔ ') + chalk.white(adapter.name));
|
|
44
|
+
detected++;
|
|
45
|
+
} else {
|
|
46
|
+
lines.push(chalk.gray(' ○ ' + adapter.name));
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const summary = detected > 0
|
|
51
|
+
? chalk.white(`${detected} tool${detected !== 1 ? 's' : ''} ready to sync`)
|
|
52
|
+
: chalk.yellow('No AI tools detected');
|
|
53
|
+
|
|
54
|
+
console.log(boxen(
|
|
55
|
+
gradient.pastel(' memoir status ') + '\n\n' +
|
|
56
|
+
configLine + '\n\n' +
|
|
57
|
+
chalk.bold.white('AI Tools') + '\n' +
|
|
58
|
+
lines.join('\n') + '\n\n' +
|
|
59
|
+
chalk.gray('─'.repeat(30)) + '\n' +
|
|
60
|
+
summary,
|
|
61
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'cyan', dimBorder: true }
|
|
62
|
+
) + '\n');
|
|
56
63
|
}
|