monoai 0.2.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.
@@ -0,0 +1,73 @@
1
+ import { Command } from 'commander';
2
+ import { simpleGit } from 'simple-git';
3
+ import chalk from 'chalk';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ const git = simpleGit();
7
+ export const gitCommitCommand = new Command('git-commit')
8
+ .description('Sync local codebase with MonoAI with git metadata')
9
+ .action(async () => {
10
+ try {
11
+ console.log(chalk.blue('šŸ” Analyzing codebase and git status...'));
12
+ const isRepo = await git.checkIsRepo();
13
+ if (!isRepo) {
14
+ console.error(chalk.red('āŒ Not a git repository. Please run this inside a git project.'));
15
+ return;
16
+ }
17
+ // 1. Get Git Metadata
18
+ const log = await git.log({ maxCount: 1 });
19
+ const lastCommit = log.latest;
20
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
21
+ if (!lastCommit) {
22
+ console.error(chalk.red('āŒ No git commits found. Please commit your changes first.'));
23
+ return;
24
+ }
25
+ console.log(chalk.green(`āœ… Found commit: ${lastCommit.hash.substring(0, 7)} on branch ${branch}`));
26
+ // 2. Scan Directory Structure (Simplified for demo)
27
+ // In real implementation, this would be a deep traversal respecting .gitignore
28
+ const structure = {
29
+ name: path.basename(process.cwd()),
30
+ totalFiles: 0,
31
+ files: []
32
+ };
33
+ const scanDir = (dir, base = '') => {
34
+ const items = fs.readdirSync(dir);
35
+ for (const item of items) {
36
+ if (item === 'node_modules' || item === '.git' || item === 'dist')
37
+ continue;
38
+ const fullPath = path.join(dir, item);
39
+ const relativePath = path.join(base, item);
40
+ if (fs.statSync(fullPath).isDirectory()) {
41
+ scanDir(fullPath, relativePath);
42
+ }
43
+ else {
44
+ structure.totalFiles++;
45
+ structure.files.push(relativePath);
46
+ }
47
+ }
48
+ };
49
+ scanDir(process.cwd());
50
+ // 3. Send to MonoAI (Convex)
51
+ console.log(chalk.blue('šŸš€ Syncing with MonoAI...'));
52
+ const payload = {
53
+ commitId: lastCommit.hash.substring(0, 7),
54
+ commitMessage: lastCommit.message,
55
+ branch: branch,
56
+ structure: JSON.stringify(structure),
57
+ syncStatus: 'success',
58
+ lastSyncedAt: Date.now()
59
+ };
60
+ // TODO: Get actual CONVEX_SITE_URL and user token from config
61
+ const CONVEX_SITE_URL = 'http://localhost:5173'; // Placeholder
62
+ console.log(chalk.yellow(`šŸ“” Sending data to MonoAI (${CONVEX_SITE_URL}/cli/git-commit)...`));
63
+ // Simulating successful response for demo verification
64
+ await new Promise(resolve => setTimeout(resolve, 1000));
65
+ console.log(chalk.green('✨ [Simulation] Successfully synced to MonoAI!'));
66
+ console.log(chalk.dim(` Branch: ${branch}`));
67
+ console.log(chalk.dim(` Commit: ${lastCommit.hash.substring(0, 7)}`));
68
+ console.log(chalk.dim(` Message: ${lastCommit.message}`));
69
+ }
70
+ catch (error) {
71
+ console.error(chalk.red('āŒ Error during sync:'), error.message);
72
+ }
73
+ });
@@ -0,0 +1,65 @@
1
+ import { Command } from 'commander';
2
+ import Conf from 'conf';
3
+ import axios from 'axios';
4
+ import chalk from 'chalk';
5
+ import open from 'open';
6
+ import ora from 'ora';
7
+ const config = new Conf({ projectName: 'monoai' });
8
+ const CONVEX_SITE_URL = 'https://majestic-crane-609.convex.site';
9
+ export const loginCommand = new Command('login')
10
+ .description('Authenticate with MonoAI')
11
+ .action(async () => {
12
+ console.log(chalk.blue('šŸ” Starting MonoAI Login...'));
13
+ try {
14
+ // 1. Init Session
15
+ const initSpinner = ora('Initializing auth session...').start();
16
+ const initRes = await axios.post(`${CONVEX_SITE_URL}/cli/auth/init`, {
17
+ deviceDescription: process.platform
18
+ });
19
+ initSpinner.succeed();
20
+ const { tempCode, loginUrl } = initRes.data;
21
+ console.log(chalk.yellow(`\nšŸ‘‰ Verification Code: ${chalk.bold(tempCode)}`));
22
+ console.log(chalk.dim(` Opening browser... if it doesn't open, visit:`));
23
+ console.log(chalk.underline(loginUrl));
24
+ console.log('\n');
25
+ await open(loginUrl);
26
+ // 2. Poll Status
27
+ const pollSpinner = ora('Waiting for approval in browser...').start();
28
+ let attempts = 0;
29
+ const maxAttempts = 60; // 2 minutes (2s * 60)
30
+ const pollInterval = setInterval(async () => {
31
+ try {
32
+ const pollRes = await axios.post(`${CONVEX_SITE_URL}/cli/auth/poll`, { tempCode });
33
+ const { status, token, userId } = pollRes.data;
34
+ if (status === 'approved' && token) {
35
+ clearInterval(pollInterval);
36
+ config.set('auth_token', token);
37
+ config.set('user_id', userId);
38
+ config.set('convex_url', CONVEX_SITE_URL); // Store for future use
39
+ pollSpinner.succeed(chalk.green('āœ… Login Successful!'));
40
+ console.log(chalk.dim(` Token saved to ${config.path}`));
41
+ process.exit(0);
42
+ }
43
+ else if (status === 'expired' || status === 'rejected') {
44
+ clearInterval(pollInterval);
45
+ pollSpinner.fail(chalk.red(`āŒ Session ${status}. Please try again.`));
46
+ process.exit(1);
47
+ }
48
+ else {
49
+ attempts++;
50
+ if (attempts >= maxAttempts) {
51
+ clearInterval(pollInterval);
52
+ pollSpinner.fail(chalk.red('āŒ Timed out.'));
53
+ process.exit(1);
54
+ }
55
+ }
56
+ }
57
+ catch (err) {
58
+ // Ignore poll errors (network blips)
59
+ }
60
+ }, 2000);
61
+ }
62
+ catch (error) {
63
+ console.error(chalk.red('\nāŒ Login failed:'), error.message);
64
+ }
65
+ });
@@ -0,0 +1,96 @@
1
+ import { Command } from 'commander';
2
+ import { simpleGit } from 'simple-git';
3
+ import axios from 'axios';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import ignore from 'ignore';
8
+ import Conf from 'conf';
9
+ import { extractSkeleton } from '../utils/ast-extractor.js';
10
+ const git = simpleGit();
11
+ const config = new Conf({ projectName: 'monoai' });
12
+ export const syncCommand = new Command('push')
13
+ .description('Push codebase integrity and AST skeleton to MonoAI')
14
+ .action(async () => {
15
+ try {
16
+ console.log(chalk.blue('šŸŽļø Starting MonoAI Strategic Push...'));
17
+ // 0. Auth Check
18
+ const token = config.get('auth_token');
19
+ if (!token) {
20
+ console.error(chalk.red('āŒ Not Authenticated. Please run:'));
21
+ console.error(chalk.white(' npx monoai login'));
22
+ return;
23
+ }
24
+ const isRepo = await git.checkIsRepo();
25
+ if (!isRepo) {
26
+ console.error(chalk.red('āŒ Not a git repository.'));
27
+ return;
28
+ }
29
+ // 1. Git Metadata (Zero-HITL Intent)
30
+ const log = await git.log({ maxCount: 1 });
31
+ const lastCommit = log.latest;
32
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
33
+ if (!lastCommit) {
34
+ console.error(chalk.red('āŒ No commits found.'));
35
+ return;
36
+ }
37
+ console.log(chalk.dim(` Branch: ${chalk.white(branch)}`));
38
+ console.log(chalk.dim(` Commit: ${chalk.white(lastCommit.hash.substring(0, 7))}`));
39
+ // 2. Scan & Extract AST Skeleton
40
+ console.log(chalk.blue('šŸ” Analyzing structural integrity (AST)...'));
41
+ const ig = ignore();
42
+ if (fs.existsSync('.gitignore')) {
43
+ ig.add(fs.readFileSync('.gitignore').toString());
44
+ }
45
+ // Hardcoded safety
46
+ ig.add(['node_modules', '.git', 'dist', '.env', 'build']);
47
+ const filesToAnalyze = [];
48
+ const scanDir = (dir) => {
49
+ const items = fs.readdirSync(dir);
50
+ for (const item of items) {
51
+ const fullPath = path.join(dir, item);
52
+ const relativePath = path.relative(process.cwd(), fullPath);
53
+ if (ig.ignores(relativePath))
54
+ continue;
55
+ if (fs.statSync(fullPath).isDirectory()) {
56
+ scanDir(fullPath);
57
+ }
58
+ else if (/\.(ts|tsx|js|jsx)$/.test(item)) {
59
+ filesToAnalyze.push(fullPath);
60
+ }
61
+ }
62
+ };
63
+ scanDir(process.cwd());
64
+ const skeleton = extractSkeleton(filesToAnalyze);
65
+ // 3. Payload Construction
66
+ const payload = {
67
+ name: path.basename(process.cwd()),
68
+ branch: branch,
69
+ commitId: lastCommit.hash.substring(0, 7),
70
+ commitMessage: lastCommit.message,
71
+ structure: JSON.stringify(skeleton), // Now structured AST
72
+ syncStatus: 'success',
73
+ };
74
+ // 4. Send to Navigator (Convex)
75
+ console.log(chalk.blue('šŸ“” Transmitting to Value Engine...'));
76
+ const CONVEX_SITE_URL = config.get('convex_url') || 'https://majestic-crane-609.convex.site';
77
+ await axios.post(`${CONVEX_SITE_URL}/cli/git-commit`, {
78
+ codebaseData: payload
79
+ }, {
80
+ headers: {
81
+ 'Authorization': `Bearer ${token}`
82
+ }
83
+ });
84
+ console.log(chalk.green('✨ [Navigator] Push complete! Check your dashboard for Navigator alignment.'));
85
+ console.log(chalk.dim(` Message: ${lastCommit.message.split('\n')[0]}`));
86
+ }
87
+ catch (error) {
88
+ if (error.response?.status === 401) {
89
+ console.error(chalk.red('āŒ Authentication Expired. Please run:'));
90
+ console.error(chalk.white(' npx monoai login'));
91
+ }
92
+ else {
93
+ console.error(chalk.red('āŒ Sync failed:'), error.message);
94
+ }
95
+ }
96
+ });
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { syncCommand } from './commands/sync.js';
4
+ import { loginCommand } from './commands/login.js';
5
+ const program = new Command();
6
+ program
7
+ .name('monoai')
8
+ .description('MonoAI CLI - Strategic Navigator')
9
+ .version('0.1.2');
10
+ // Git sub-command group
11
+ const git = new Command('git').description('Git related operations');
12
+ git.addCommand(syncCommand.name('push'));
13
+ program.addCommand(loginCommand);
14
+ program.addCommand(git);
15
+ program.parse();
@@ -0,0 +1,112 @@
1
+ import { Project, SyntaxKind } from 'ts-morph';
2
+ import path from 'path';
3
+ // šŸ›”ļø Security: Redaction Patterns
4
+ const SECRET_PATTERNS = [
5
+ /sk-[a-zA-Z0-9-_]{20,}/g, // OpenAI / Stripe style
6
+ /eyJ[a-zA-Z0-9-_]{20,}/g, // JWT style
7
+ /AIza[0-9A-Za-z-_]{35}/g, // Google Cloud style
8
+ /ghp_[a-zA-Z0-9]{36}/g // GitHub Personal Access Token
9
+ ];
10
+ export function extractSkeleton(filePaths) {
11
+ const project = new Project();
12
+ // šŸ›”ļø Security: File Filter
13
+ const safePaths = filePaths.filter(p => {
14
+ const base = path.basename(p);
15
+ if (base.startsWith('.env'))
16
+ return false;
17
+ if (base === '.DS_Store')
18
+ return false;
19
+ if (p.includes('node_modules'))
20
+ return false;
21
+ if (p.includes('.git/'))
22
+ return false;
23
+ return true;
24
+ });
25
+ project.addSourceFilesAtPaths(safePaths);
26
+ const result = {};
27
+ let totalPayloadSize = 0;
28
+ project.getSourceFiles().forEach(sourceFile => {
29
+ // šŸ›”ļø Security: Secret Redaction (Active Scanning)
30
+ sourceFile.forEachDescendant((node) => {
31
+ if (node.getKind() === SyntaxKind.StringLiteral) {
32
+ const sl = node;
33
+ const text = sl.getLiteralText();
34
+ let redacted = text;
35
+ let found = false;
36
+ for (const pattern of SECRET_PATTERNS) {
37
+ if (pattern.test(text)) {
38
+ redacted = '[REDACTED_SECRET]';
39
+ found = true;
40
+ break;
41
+ }
42
+ }
43
+ if (found) {
44
+ // AST Rewrite (Memory only, does not save to disk)
45
+ sl.setLiteralValue(redacted);
46
+ }
47
+ }
48
+ });
49
+ const filePath = sourceFile.getFilePath();
50
+ const skeleton = {
51
+ functions: [],
52
+ classes: [],
53
+ interfaces: [],
54
+ types: []
55
+ };
56
+ // Extract Functions
57
+ sourceFile.getFunctions().forEach(f => {
58
+ if (f.isExported()) {
59
+ skeleton.functions.push({
60
+ name: f.getName(),
61
+ parameters: f.getParameters().map(p => ({
62
+ name: p.getName(),
63
+ type: p.getType().getText()
64
+ })),
65
+ returnType: f.getReturnType().getText(),
66
+ jsDoc: f.getJsDocs().map(d => d.getCommentText()).join('\n')
67
+ });
68
+ }
69
+ });
70
+ // Extract Classes
71
+ sourceFile.getClasses().forEach(c => {
72
+ if (c.isExported()) {
73
+ skeleton.classes.push({
74
+ name: c.getName(),
75
+ methods: c.getMethods().map(m => ({
76
+ name: m.getName(),
77
+ parameters: m.getParameters().map(p => ({
78
+ name: p.getName(),
79
+ type: p.getType().getText()
80
+ })),
81
+ returnType: m.getReturnType().getText()
82
+ })),
83
+ jsDoc: c.getJsDocs().map(d => d.getCommentText()).join('\n')
84
+ });
85
+ }
86
+ });
87
+ // Extract Interfaces
88
+ sourceFile.getInterfaces().forEach(i => {
89
+ if (i.isExported()) {
90
+ skeleton.interfaces.push({
91
+ name: i.getName(),
92
+ jsDoc: i.getJsDocs().map(d => d.getCommentText()).join('\n')
93
+ });
94
+ }
95
+ });
96
+ // Extract Types
97
+ sourceFile.getTypeAliases().forEach(t => {
98
+ if (t.isExported()) {
99
+ skeleton.types.push({
100
+ name: t.getName(),
101
+ });
102
+ }
103
+ });
104
+ result[filePath] = skeleton;
105
+ });
106
+ // šŸ›”ļø Security: Payload Size Limit (DoS Prevention)
107
+ const payloadString = JSON.stringify(result);
108
+ if (payloadString.length > 5 * 1024 * 1024) { // 5MB
109
+ throw new Error("Payload too large. Security limit exceeded (5MB).");
110
+ }
111
+ return result;
112
+ }
@@ -0,0 +1,19 @@
1
+ import Conf from 'conf';
2
+ const config = new Conf({
3
+ projectName: 'monoai',
4
+ projectSuffix: 'cli'
5
+ });
6
+ export const saveCredentials = (token, url) => {
7
+ config.set('authToken', token);
8
+ config.set('convexUrl', url);
9
+ };
10
+ export const getCredentials = () => {
11
+ return {
12
+ authToken: config.get('authToken'),
13
+ convexUrl: config.get('convexUrl')
14
+ };
15
+ };
16
+ export const clearCredentials = () => {
17
+ config.delete('authToken');
18
+ config.delete('convexUrl');
19
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "monoai",
3
+ "type": "module",
4
+ "version": "0.2.0",
5
+ "description": "MonoAI CLI for syncing codebase history",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "monoai": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "axios": "^1.6.2",
16
+ "chalk": "^4.1.2",
17
+ "commander": "^11.1.0",
18
+ "conf": "^15.1.0",
19
+ "ignore": "^7.0.5",
20
+ "open": "^9.1.0",
21
+ "ora": "^7.0.1",
22
+ "simple-git": "^3.21.0",
23
+ "ts-morph": "^27.0.2"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.19.31",
27
+ "typescript": "^5.3.2"
28
+ }
29
+ }
@@ -0,0 +1,87 @@
1
+ import { Command } from 'commander';
2
+ import { simpleGit } from 'simple-git';
3
+ import axios from 'axios';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+
8
+ const git = simpleGit();
9
+
10
+ export const gitCommitCommand = new Command('git-commit')
11
+ .description('Sync local codebase with MonoAI with git metadata')
12
+ .action(async () => {
13
+ try {
14
+ console.log(chalk.blue('šŸ” Analyzing codebase and git status...'));
15
+
16
+ const isRepo = await git.checkIsRepo();
17
+ if (!isRepo) {
18
+ console.error(chalk.red('āŒ Not a git repository. Please run this inside a git project.'));
19
+ return;
20
+ }
21
+
22
+ // 1. Get Git Metadata
23
+ const log = await git.log({ maxCount: 1 });
24
+ const lastCommit = log.latest;
25
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
26
+
27
+ if (!lastCommit) {
28
+ console.error(chalk.red('āŒ No git commits found. Please commit your changes first.'));
29
+ return;
30
+ }
31
+
32
+ console.log(chalk.green(`āœ… Found commit: ${lastCommit.hash.substring(0, 7)} on branch ${branch}`));
33
+
34
+ // 2. Scan Directory Structure (Simplified for demo)
35
+ // In real implementation, this would be a deep traversal respecting .gitignore
36
+ const structure = {
37
+ name: path.basename(process.cwd()),
38
+ totalFiles: 0,
39
+ files: [] as string[]
40
+ };
41
+
42
+ const scanDir = (dir: string, base: string = '') => {
43
+ const items = fs.readdirSync(dir);
44
+ for (const item of items) {
45
+ if (item === 'node_modules' || item === '.git' || item === 'dist') continue;
46
+ const fullPath = path.join(dir, item);
47
+ const relativePath = path.join(base, item);
48
+ if (fs.statSync(fullPath).isDirectory()) {
49
+ scanDir(fullPath, relativePath);
50
+ } else {
51
+ structure.totalFiles++;
52
+ structure.files.push(relativePath);
53
+ }
54
+ }
55
+ };
56
+
57
+ scanDir(process.cwd());
58
+
59
+ // 3. Send to MonoAI (Convex)
60
+ console.log(chalk.blue('šŸš€ Syncing with MonoAI...'));
61
+
62
+ const payload = {
63
+ commitId: lastCommit.hash.substring(0, 7),
64
+ commitMessage: lastCommit.message,
65
+ branch: branch,
66
+ structure: JSON.stringify(structure),
67
+ syncStatus: 'success',
68
+ lastSyncedAt: Date.now()
69
+ };
70
+
71
+ // TODO: Get actual CONVEX_SITE_URL and user token from config
72
+ const CONVEX_SITE_URL = 'http://localhost:5173'; // Placeholder
73
+
74
+ console.log(chalk.yellow(`šŸ“” Sending data to MonoAI (${CONVEX_SITE_URL}/cli/git-commit)...`));
75
+
76
+ // Simulating successful response for demo verification
77
+ await new Promise(resolve => setTimeout(resolve, 1000));
78
+
79
+ console.log(chalk.green('✨ [Simulation] Successfully synced to MonoAI!'));
80
+ console.log(chalk.dim(` Branch: ${branch}`));
81
+ console.log(chalk.dim(` Commit: ${lastCommit.hash.substring(0, 7)}`));
82
+ console.log(chalk.dim(` Message: ${lastCommit.message}`));
83
+
84
+ } catch (error: any) {
85
+ console.error(chalk.red('āŒ Error during sync:'), error.message);
86
+ }
87
+ });
@@ -0,0 +1,73 @@
1
+ import { Command } from 'commander';
2
+ import Conf from 'conf';
3
+ import axios from 'axios';
4
+ import chalk from 'chalk';
5
+ import open from 'open';
6
+ import ora from 'ora';
7
+
8
+ const config = new Conf({ projectName: 'monoai' });
9
+ const CONVEX_SITE_URL = 'https://majestic-crane-609.convex.site';
10
+
11
+ export const loginCommand = new Command('login')
12
+ .description('Authenticate with MonoAI')
13
+ .action(async () => {
14
+ console.log(chalk.blue('šŸ” Starting MonoAI Login...'));
15
+
16
+ try {
17
+ // 1. Init Session
18
+ const initSpinner = ora('Initializing auth session...').start();
19
+ const initRes = await axios.post(`${CONVEX_SITE_URL}/cli/auth/init`, {
20
+ deviceDescription: process.platform
21
+ });
22
+ initSpinner.succeed();
23
+
24
+ const { tempCode, loginUrl } = initRes.data;
25
+
26
+ console.log(chalk.yellow(`\nšŸ‘‰ Verification Code: ${chalk.bold(tempCode)}`));
27
+ console.log(chalk.dim(` Opening browser... if it doesn't open, visit:`));
28
+ console.log(chalk.underline(loginUrl));
29
+ console.log('\n');
30
+
31
+ await open(loginUrl);
32
+
33
+ // 2. Poll Status
34
+ const pollSpinner = ora('Waiting for approval in browser...').start();
35
+
36
+ let attempts = 0;
37
+ const maxAttempts = 60; // 2 minutes (2s * 60)
38
+
39
+ const pollInterval = setInterval(async () => {
40
+ try {
41
+ const pollRes = await axios.post(`${CONVEX_SITE_URL}/cli/auth/poll`, { tempCode });
42
+ const { status, token, userId } = pollRes.data;
43
+
44
+ if (status === 'approved' && token) {
45
+ clearInterval(pollInterval);
46
+ config.set('auth_token', token);
47
+ config.set('user_id', userId);
48
+ config.set('convex_url', CONVEX_SITE_URL); // Store for future use
49
+
50
+ pollSpinner.succeed(chalk.green('āœ… Login Successful!'));
51
+ console.log(chalk.dim(` Token saved to ${config.path}`));
52
+ process.exit(0);
53
+ } else if (status === 'expired' || status === 'rejected') {
54
+ clearInterval(pollInterval);
55
+ pollSpinner.fail(chalk.red(`āŒ Session ${status}. Please try again.`));
56
+ process.exit(1);
57
+ } else {
58
+ attempts++;
59
+ if (attempts >= maxAttempts) {
60
+ clearInterval(pollInterval);
61
+ pollSpinner.fail(chalk.red('āŒ Timed out.'));
62
+ process.exit(1);
63
+ }
64
+ }
65
+ } catch (err) {
66
+ // Ignore poll errors (network blips)
67
+ }
68
+ }, 2000);
69
+
70
+ } catch (error: any) {
71
+ console.error(chalk.red('\nāŒ Login failed:'), error.message);
72
+ }
73
+ });
@@ -0,0 +1,111 @@
1
+ import { Command } from 'commander';
2
+ import { simpleGit } from 'simple-git';
3
+ import axios from 'axios';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import ignore from 'ignore';
8
+ import Conf from 'conf';
9
+ import { extractSkeleton } from '../utils/ast-extractor.js';
10
+
11
+ const git = simpleGit();
12
+ const config = new Conf({ projectName: 'monoai' });
13
+
14
+ export const syncCommand = new Command('push')
15
+ .description('Push codebase integrity and AST skeleton to MonoAI')
16
+ .action(async () => {
17
+ try {
18
+ console.log(chalk.blue('šŸŽļø Starting MonoAI Strategic Push...'));
19
+
20
+ // 0. Auth Check
21
+ const token = config.get('auth_token');
22
+ if (!token) {
23
+ console.error(chalk.red('āŒ Not Authenticated. Please run:'));
24
+ console.error(chalk.white(' npx monoai login'));
25
+ return;
26
+ }
27
+
28
+ const isRepo = await git.checkIsRepo();
29
+ if (!isRepo) {
30
+ console.error(chalk.red('āŒ Not a git repository.'));
31
+ return;
32
+ }
33
+
34
+ // 1. Git Metadata (Zero-HITL Intent)
35
+ const log = await git.log({ maxCount: 1 });
36
+ const lastCommit = log.latest;
37
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
38
+
39
+ if (!lastCommit) {
40
+ console.error(chalk.red('āŒ No commits found.'));
41
+ return;
42
+ }
43
+
44
+ console.log(chalk.dim(` Branch: ${chalk.white(branch)}`));
45
+ console.log(chalk.dim(` Commit: ${chalk.white(lastCommit.hash.substring(0, 7))}`));
46
+
47
+ // 2. Scan & Extract AST Skeleton
48
+ console.log(chalk.blue('šŸ” Analyzing structural integrity (AST)...'));
49
+
50
+ const ig = ignore();
51
+ if (fs.existsSync('.gitignore')) {
52
+ ig.add(fs.readFileSync('.gitignore').toString());
53
+ }
54
+ // Hardcoded safety
55
+ ig.add(['node_modules', '.git', 'dist', '.env', 'build']);
56
+
57
+ const filesToAnalyze: string[] = [];
58
+ const scanDir = (dir: string) => {
59
+ const items = fs.readdirSync(dir);
60
+ for (const item of items) {
61
+ const fullPath = path.join(dir, item);
62
+ const relativePath = path.relative(process.cwd(), fullPath);
63
+
64
+ if (ig.ignores(relativePath)) continue;
65
+
66
+ if (fs.statSync(fullPath).isDirectory()) {
67
+ scanDir(fullPath);
68
+ } else if (/\.(ts|tsx|js|jsx)$/.test(item)) {
69
+ filesToAnalyze.push(fullPath);
70
+ }
71
+ }
72
+ };
73
+
74
+ scanDir(process.cwd());
75
+ const skeleton = extractSkeleton(filesToAnalyze);
76
+
77
+ // 3. Payload Construction
78
+ const payload = {
79
+ name: path.basename(process.cwd()),
80
+ branch: branch,
81
+ commitId: lastCommit.hash.substring(0, 7),
82
+ commitMessage: lastCommit.message,
83
+ structure: JSON.stringify(skeleton), // Now structured AST
84
+ syncStatus: 'success' as const,
85
+ };
86
+
87
+ // 4. Send to Navigator (Convex)
88
+ console.log(chalk.blue('šŸ“” Transmitting to Value Engine...'));
89
+
90
+ const CONVEX_SITE_URL = config.get('convex_url') as string || 'https://majestic-crane-609.convex.site';
91
+
92
+ await axios.post(`${CONVEX_SITE_URL}/cli/git-commit`, {
93
+ codebaseData: payload
94
+ }, {
95
+ headers: {
96
+ 'Authorization': `Bearer ${token}`
97
+ }
98
+ });
99
+
100
+ console.log(chalk.green('✨ [Navigator] Push complete! Check your dashboard for Navigator alignment.'));
101
+ console.log(chalk.dim(` Message: ${lastCommit.message.split('\n')[0]}`));
102
+
103
+ } catch (error: any) {
104
+ if (error.response?.status === 401) {
105
+ console.error(chalk.red('āŒ Authentication Expired. Please run:'));
106
+ console.error(chalk.white(' npx monoai login'));
107
+ } else {
108
+ console.error(chalk.red('āŒ Sync failed:'), error.message);
109
+ }
110
+ }
111
+ });
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { syncCommand } from './commands/sync.js';
4
+ import { loginCommand } from './commands/login.js';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('monoai')
10
+ .description('MonoAI CLI - Strategic Navigator')
11
+ .version('0.1.2');
12
+
13
+ // Git sub-command group
14
+ const git = new Command('git').description('Git related operations');
15
+ git.addCommand(syncCommand.name('push'));
16
+
17
+ program.addCommand(loginCommand);
18
+ program.addCommand(git);
19
+
20
+ program.parse();
@@ -0,0 +1,131 @@
1
+ import { Project, SyntaxKind, StringLiteral } from 'ts-morph';
2
+ import path from 'path';
3
+
4
+ export interface CodeSkeleton {
5
+ functions: any[];
6
+ classes: any[];
7
+ interfaces: any[];
8
+ types: any[];
9
+ }
10
+
11
+ // šŸ›”ļø Security: Redaction Patterns
12
+ const SECRET_PATTERNS = [
13
+ /sk-[a-zA-Z0-9-_]{20,}/g, // OpenAI / Stripe style
14
+ /eyJ[a-zA-Z0-9-_]{20,}/g, // JWT style
15
+ /AIza[0-9A-Za-z-_]{35}/g, // Google Cloud style
16
+ /ghp_[a-zA-Z0-9]{36}/g // GitHub Personal Access Token
17
+ ];
18
+
19
+ export function extractSkeleton(filePaths: string[]): Record<string, CodeSkeleton> {
20
+ const project = new Project();
21
+
22
+ // šŸ›”ļø Security: File Filter
23
+ const safePaths = filePaths.filter(p => {
24
+ const base = path.basename(p);
25
+ if (base.startsWith('.env')) return false;
26
+ if (base === '.DS_Store') return false;
27
+ if (p.includes('node_modules')) return false;
28
+ if (p.includes('.git/')) return false;
29
+ return true;
30
+ });
31
+
32
+ project.addSourceFilesAtPaths(safePaths);
33
+
34
+ const result: Record<string, CodeSkeleton> = {};
35
+ let totalPayloadSize = 0;
36
+
37
+ project.getSourceFiles().forEach(sourceFile => {
38
+ // šŸ›”ļø Security: Secret Redaction (Active Scanning)
39
+ sourceFile.forEachDescendant((node) => {
40
+ if (node.getKind() === SyntaxKind.StringLiteral) {
41
+ const sl = node as StringLiteral;
42
+ const text = sl.getLiteralText();
43
+ let redacted = text;
44
+ let found = false;
45
+
46
+ for (const pattern of SECRET_PATTERNS) {
47
+ if (pattern.test(text)) {
48
+ redacted = '[REDACTED_SECRET]';
49
+ found = true;
50
+ break;
51
+ }
52
+ }
53
+
54
+ if (found) {
55
+ // AST Rewrite (Memory only, does not save to disk)
56
+ sl.setLiteralValue(redacted);
57
+ }
58
+ }
59
+ });
60
+
61
+ const filePath = sourceFile.getFilePath();
62
+ const skeleton: CodeSkeleton = {
63
+ functions: [],
64
+ classes: [],
65
+ interfaces: [],
66
+ types: []
67
+ };
68
+
69
+ // Extract Functions
70
+ sourceFile.getFunctions().forEach(f => {
71
+ if (f.isExported()) {
72
+ skeleton.functions.push({
73
+ name: f.getName(),
74
+ parameters: f.getParameters().map(p => ({
75
+ name: p.getName(),
76
+ type: p.getType().getText()
77
+ })),
78
+ returnType: f.getReturnType().getText(),
79
+ jsDoc: f.getJsDocs().map(d => d.getCommentText()).join('\n')
80
+ });
81
+ }
82
+ });
83
+
84
+ // Extract Classes
85
+ sourceFile.getClasses().forEach(c => {
86
+ if (c.isExported()) {
87
+ skeleton.classes.push({
88
+ name: c.getName(),
89
+ methods: c.getMethods().map(m => ({
90
+ name: m.getName(),
91
+ parameters: m.getParameters().map(p => ({
92
+ name: p.getName(),
93
+ type: p.getType().getText()
94
+ })),
95
+ returnType: m.getReturnType().getText()
96
+ })),
97
+ jsDoc: c.getJsDocs().map(d => d.getCommentText()).join('\n')
98
+ });
99
+ }
100
+ });
101
+
102
+ // Extract Interfaces
103
+ sourceFile.getInterfaces().forEach(i => {
104
+ if (i.isExported()) {
105
+ skeleton.interfaces.push({
106
+ name: i.getName(),
107
+ jsDoc: i.getJsDocs().map(d => d.getCommentText()).join('\n')
108
+ });
109
+ }
110
+ });
111
+
112
+ // Extract Types
113
+ sourceFile.getTypeAliases().forEach(t => {
114
+ if (t.isExported()) {
115
+ skeleton.types.push({
116
+ name: t.getName(),
117
+ });
118
+ }
119
+ });
120
+
121
+ result[filePath] = skeleton;
122
+ });
123
+
124
+ // šŸ›”ļø Security: Payload Size Limit (DoS Prevention)
125
+ const payloadString = JSON.stringify(result);
126
+ if (payloadString.length > 5 * 1024 * 1024) { // 5MB
127
+ throw new Error("Payload too large. Security limit exceeded (5MB).");
128
+ }
129
+
130
+ return result;
131
+ }
@@ -0,0 +1,28 @@
1
+ import Conf from 'conf';
2
+
3
+ interface CliConfig {
4
+ authToken?: string;
5
+ convexUrl?: string;
6
+ }
7
+
8
+ const config = new Conf<CliConfig>({
9
+ projectName: 'monoai',
10
+ projectSuffix: 'cli'
11
+ });
12
+
13
+ export const saveCredentials = (token: string, url: string) => {
14
+ config.set('authToken', token);
15
+ config.set('convexUrl', url);
16
+ };
17
+
18
+ export const getCredentials = () => {
19
+ return {
20
+ authToken: config.get('authToken'),
21
+ convexUrl: config.get('convexUrl')
22
+ };
23
+ };
24
+
25
+ export const clearCredentials = () => {
26
+ config.delete('authToken');
27
+ config.delete('convexUrl');
28
+ };
@@ -0,0 +1,5 @@
1
+
2
+ import { extractSkeleton } from "./src/utils/ast-extractor.js";
3
+ const result = extractSkeleton(["cli/test-secret.ts"]);
4
+ console.log(JSON.stringify(result, null, 2));
5
+
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "Node16",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "moduleResolution": "node16",
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": [
15
+ "src/**/*"
16
+ ],
17
+ "exclude": [
18
+ "node_modules",
19
+ "dist"
20
+ ]
21
+ }