monoai 0.2.0 ā 0.2.2
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 +33 -0
- package/dist/commands/login.js +4 -1
- package/dist/commands/push.js +138 -0
- package/dist/index.js +3 -6
- package/package.json +6 -1
- package/scripts/cognee_bridge.py +101 -0
- package/src/commands/git-commit.ts +0 -87
- package/src/commands/login.ts +0 -73
- package/src/commands/sync.ts +0 -111
- package/src/index.ts +0 -20
- package/src/utils/ast-extractor.ts +0 -131
- package/src/utils/config.ts +0 -28
- package/test-redaction.js +0 -5
- package/tsconfig.json +0 -21
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# MonoAI CLI
|
|
2
|
+
> **Decision Support Engine**
|
|
3
|
+
|
|
4
|
+
MonoAI CLI is a strategic guide tool that measures alignment between product requirements and implementation, proposing a **list of issues the user should address right now**.
|
|
5
|
+
|
|
6
|
+
## Key Features
|
|
7
|
+
|
|
8
|
+
### 1. monoai login
|
|
9
|
+
- Authenticate via [monoai.space](https://monoai.space) to access the central intelligence system.
|
|
10
|
+
|
|
11
|
+
### 2. monoai push
|
|
12
|
+
- **Evidence Extraction**: Extracts AST via `ts-morph` and constructs a Knowledge Graph via `Cognee` upon execution, transmitting clear evidence of the current implementation state to the server.
|
|
13
|
+
- **Issue Proposal**: Analyzes alignment by comparing transmitted data with the active PRD, deriving and reporting a **list of issues** that the user should prioritize and execute immediately.
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
|
|
17
|
+
### Requirements
|
|
18
|
+
* Language: Official support for **TypeScript** (.ts, .tsx) codebases.
|
|
19
|
+
* Environment: Node.js 18+ and Git-based projects.
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
Execute immediately via `npx` without any installation.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Login (First time only)
|
|
26
|
+
npx monoai login
|
|
27
|
+
|
|
28
|
+
# Analyze and push issue checklist
|
|
29
|
+
npx monoai push
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
package/dist/commands/login.js
CHANGED
|
@@ -5,7 +5,9 @@ import chalk from 'chalk';
|
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
const config = new Conf({ projectName: 'monoai' });
|
|
8
|
+
// Production URLs
|
|
8
9
|
const CONVEX_SITE_URL = 'https://majestic-crane-609.convex.site';
|
|
10
|
+
const WEB_URL = 'https://monoai.space';
|
|
9
11
|
export const loginCommand = new Command('login')
|
|
10
12
|
.description('Authenticate with MonoAI')
|
|
11
13
|
.action(async () => {
|
|
@@ -17,7 +19,8 @@ export const loginCommand = new Command('login')
|
|
|
17
19
|
deviceDescription: process.platform
|
|
18
20
|
});
|
|
19
21
|
initSpinner.succeed();
|
|
20
|
-
const { tempCode
|
|
22
|
+
const { tempCode } = initRes.data;
|
|
23
|
+
const loginUrl = `${WEB_URL}/auth/cli?code=${tempCode}`;
|
|
21
24
|
console.log(chalk.yellow(`\nš Verification Code: ${chalk.bold(tempCode)}`));
|
|
22
25
|
console.log(chalk.dim(` Opening browser... if it doesn't open, visit:`));
|
|
23
26
|
console.log(chalk.underline(loginUrl));
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const git = simpleGit();
|
|
13
|
+
const config = new Conf({ projectName: 'monoai' });
|
|
14
|
+
export const pushCommand = 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
|
+
// 0. Auth Check
|
|
20
|
+
const token = config.get('auth_token');
|
|
21
|
+
if (!token) {
|
|
22
|
+
console.error(chalk.red('ā Not Authenticated. Please run:'));
|
|
23
|
+
console.error(chalk.white(' npx monoai login'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const isRepo = await git.checkIsRepo();
|
|
27
|
+
if (!isRepo) {
|
|
28
|
+
console.error(chalk.red('ā Not a git repository.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// 1. Git Metadata (Zero-HITL Intent)
|
|
32
|
+
const log = await git.log({ maxCount: 1 });
|
|
33
|
+
const lastCommit = log.latest;
|
|
34
|
+
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
35
|
+
if (!lastCommit) {
|
|
36
|
+
console.error(chalk.red('ā No commits found.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(chalk.dim(` Branch: ${chalk.white(branch)}`));
|
|
40
|
+
console.log(chalk.dim(` Commit: ${chalk.white(lastCommit.hash.substring(0, 7))}`));
|
|
41
|
+
// 2. Scan & Extract AST Skeleton
|
|
42
|
+
console.log(chalk.blue('š Analyzing structural integrity (AST)...'));
|
|
43
|
+
const ig = ignore();
|
|
44
|
+
if (fs.existsSync('.gitignore')) {
|
|
45
|
+
ig.add(fs.readFileSync('.gitignore').toString());
|
|
46
|
+
}
|
|
47
|
+
// Hardcoded safety
|
|
48
|
+
ig.add(['node_modules', '.git', 'dist', '.env', 'build']);
|
|
49
|
+
const filesToAnalyze = [];
|
|
50
|
+
const scanDir = (dir) => {
|
|
51
|
+
const items = fs.readdirSync(dir);
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const fullPath = path.join(dir, item);
|
|
54
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
55
|
+
if (ig.ignores(relativePath))
|
|
56
|
+
continue;
|
|
57
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
58
|
+
scanDir(fullPath);
|
|
59
|
+
}
|
|
60
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(item)) {
|
|
61
|
+
filesToAnalyze.push(fullPath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
scanDir(process.cwd());
|
|
66
|
+
const skeleton = extractSkeleton(filesToAnalyze);
|
|
67
|
+
const CONVEX_SITE_URL = config.get('convex_url') || 'https://majestic-crane-609.convex.site';
|
|
68
|
+
// 3. Central System Intelligence (Auth required)
|
|
69
|
+
console.log(chalk.blue('š Retrieving system intelligence credentials...'));
|
|
70
|
+
let centralKey = '';
|
|
71
|
+
try {
|
|
72
|
+
const keyResponse = await axios.post(`${CONVEX_SITE_URL}/cli/system/key`, {}, {
|
|
73
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
74
|
+
});
|
|
75
|
+
centralKey = keyResponse.data.key;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.warn(chalk.yellow('ā ļø Failed to fetch central key, using local settings if available.'));
|
|
79
|
+
}
|
|
80
|
+
// 4. Cognee Knowledge Graph Bridge
|
|
81
|
+
let graphData = { nodes: [], edges: [] };
|
|
82
|
+
if (centralKey) {
|
|
83
|
+
console.log(chalk.blue('š§ Building Knowledge Graph via Cognee...'));
|
|
84
|
+
try {
|
|
85
|
+
const tempDir = path.join(process.cwd(), '.monoai_temp');
|
|
86
|
+
if (!fs.existsSync(tempDir))
|
|
87
|
+
fs.mkdirSync(tempDir);
|
|
88
|
+
const astPath = path.join(tempDir, 'ast_skeleton.json');
|
|
89
|
+
fs.writeFileSync(astPath, JSON.stringify(skeleton));
|
|
90
|
+
// Locate bridge script (assuming it's in the same package)
|
|
91
|
+
const bridgePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../scripts/cognee_bridge.py');
|
|
92
|
+
// Fallback to project root scripts if not found in package
|
|
93
|
+
const finalBridgePath = fs.existsSync(bridgePath) ? bridgePath : 'scripts/cognee_bridge.py';
|
|
94
|
+
console.log(chalk.dim(` Running Cognee on ${filesToAnalyze.length} files...`));
|
|
95
|
+
const output = execSync(`python3 ${finalBridgePath} ${astPath} ${centralKey}`, { encoding: 'utf8' });
|
|
96
|
+
graphData = JSON.parse(output);
|
|
97
|
+
// Cleanup
|
|
98
|
+
fs.unlinkSync(astPath);
|
|
99
|
+
if (graphData.status === 'success') {
|
|
100
|
+
console.log(chalk.green('ā
Knowledge Graph constructed successfully.'));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.warn(chalk.yellow('ā ļø Cognee analysis skipped or failed:'), err.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// 5. Payload Construction
|
|
108
|
+
const payload = {
|
|
109
|
+
name: path.basename(process.cwd()),
|
|
110
|
+
branch: branch,
|
|
111
|
+
commitId: lastCommit.hash.substring(0, 7),
|
|
112
|
+
commitMessage: lastCommit.message,
|
|
113
|
+
structure: JSON.stringify(skeleton), // Structured AST
|
|
114
|
+
graphData: graphData, // Knowledge Graph Data
|
|
115
|
+
syncStatus: 'success',
|
|
116
|
+
};
|
|
117
|
+
// 6. Send to Navigator (Convex)
|
|
118
|
+
console.log(chalk.blue('š” Transmitting to Value Engine...'));
|
|
119
|
+
await axios.post(`${CONVEX_SITE_URL}/cli/git-commit`, {
|
|
120
|
+
codebaseData: payload
|
|
121
|
+
}, {
|
|
122
|
+
headers: {
|
|
123
|
+
'Authorization': `Bearer ${token}`
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
console.log(chalk.green('⨠[Navigator] Push complete! Check your dashboard for Alignment Analysis.'));
|
|
127
|
+
console.log(chalk.dim(` Message: ${lastCommit.message.split('\n')[0]}`));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error.response?.status === 401) {
|
|
131
|
+
console.error(chalk.red('ā Authentication Expired. Please run:'));
|
|
132
|
+
console.error(chalk.white(' npx monoai login'));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.error(chalk.red('ā Sync failed:'), error.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { pushCommand } from './commands/push.js';
|
|
4
4
|
import { loginCommand } from './commands/login.js';
|
|
5
5
|
const program = new Command();
|
|
6
6
|
program
|
|
7
7
|
.name('monoai')
|
|
8
8
|
.description('MonoAI CLI - Strategic Navigator')
|
|
9
|
-
.version('0.
|
|
10
|
-
// Git sub-command group
|
|
11
|
-
const git = new Command('git').description('Git related operations');
|
|
12
|
-
git.addCommand(syncCommand.name('push'));
|
|
9
|
+
.version('0.2.2');
|
|
13
10
|
program.addCommand(loginCommand);
|
|
14
|
-
program.addCommand(
|
|
11
|
+
program.addCommand(pushCommand);
|
|
15
12
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monoai",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"description": "MonoAI CLI for syncing codebase history",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"monoai": "./dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"scripts",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
10
15
|
"scripts": {
|
|
11
16
|
"build": "tsc",
|
|
12
17
|
"start": "node dist/index.js"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from typing import List, Dict
|
|
6
|
+
|
|
7
|
+
# Cognee ė¼ģ“ėøė¬ė¦¬ ė”ė ģė
|
|
8
|
+
try:
|
|
9
|
+
import cognee
|
|
10
|
+
except ImportError:
|
|
11
|
+
# PoC ķź²½ģģ ķØķ¤ģ§ź° ģģ ź²½ģ° ģė¬ ė©ģģ§ ė°ķ
|
|
12
|
+
print(json.dumps({
|
|
13
|
+
"error": "cognee library not found. Please install it with 'pip install cognee' to enable graph-based analysis.",
|
|
14
|
+
"nodes": [],
|
|
15
|
+
"edges": []
|
|
16
|
+
}))
|
|
17
|
+
sys.exit(0)
|
|
18
|
+
|
|
19
|
+
async def process_ast_to_graph(ast_data: Dict, api_key: str):
|
|
20
|
+
"""
|
|
21
|
+
AST JSON ė°ģ“ķ°ė„¼ Cognee ģ§ģ ź·øėķė” ė³ķķź³ ė¶ģķ©ėė¤.
|
|
22
|
+
"""
|
|
23
|
+
# ģ¤ģ API ķ¤ ģ¤ģ (Cognee ģģ§ģ 주ģ
)
|
|
24
|
+
os.environ["OPENAI_API_KEY"] = api_key
|
|
25
|
+
|
|
26
|
+
# 1. ģØķØė”ģ§ ģ ģ (MVPģ© ź°ėµķ)
|
|
27
|
+
# Cogneeģ cognify ķė”ģøģ¤ė„¼ ķµķ“ ģ½ė ź°ģ ģ¤ģ 'ģėÆøė” ģ ź“ź³'넼 ģ¶ģ¶ķ©ėė¤.
|
|
28
|
+
|
|
29
|
+
nodes = []
|
|
30
|
+
edges = []
|
|
31
|
+
|
|
32
|
+
# AST ė°ģ“ķ° ķģ± (ts-morph ģ¶ģ¶ė³ø źø°ģ¤)
|
|
33
|
+
files = ast_data.get("files", [])
|
|
34
|
+
|
|
35
|
+
for file in files:
|
|
36
|
+
file_id = file.get("path")
|
|
37
|
+
nodes.append({
|
|
38
|
+
"id": file_id,
|
|
39
|
+
"type": "codebase",
|
|
40
|
+
"name": os.path.basename(file_id)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
# ķ“ėģ¤ ė° ķØģ ź“ź³ ģ¶ģ¶
|
|
44
|
+
for item in file.get("items", []):
|
|
45
|
+
item_id = f"{file_id}:{item.get('name')}"
|
|
46
|
+
nodes.append({
|
|
47
|
+
"id": item_id,
|
|
48
|
+
"type": item.get("type", "unknown"),
|
|
49
|
+
"name": item.get("name")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# ź“ź³ ģģ± (File -> Item)
|
|
53
|
+
edges.append({
|
|
54
|
+
"sourceId": file_id,
|
|
55
|
+
"targetId": item_id,
|
|
56
|
+
"relationType": "implemented_in"
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
# ģģ”“ģ± ź“ź³ ģ¶ģ¶ (ģ¶ķ Cognee ģģ§ģ“ ķ
ģ¤ķø ė¶ģģ¼ė” 볓ź°ķ ģģ)
|
|
60
|
+
for dep in item.get("dependencies", []):
|
|
61
|
+
edges.append({
|
|
62
|
+
"sourceId": item_id,
|
|
63
|
+
"targetId": dep,
|
|
64
|
+
"relationType": "depends_on"
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
# 2. Cognee ģģ§ģ ķµķ ź·øėķ ź³ ėķ
|
|
68
|
+
# ģ¤ģ 구ķ ģ:
|
|
69
|
+
# await cognee.add(ast_data).cognify()
|
|
70
|
+
# graph = await cognee.get_graph()
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"nodes": nodes,
|
|
74
|
+
"edges": edges,
|
|
75
|
+
"status": "success",
|
|
76
|
+
"processor": "cognee-bridge-v1"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
if len(sys.argv) < 3:
|
|
81
|
+
print(json.dumps({"error": "Missing arguments. Usage: python cognee_bridge.py <ast_path> <api_key>"}))
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
ast_path = sys.argv[1]
|
|
85
|
+
api_key = sys.argv[2]
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
if not os.path.exists(ast_path):
|
|
89
|
+
raise FileNotFoundError(f"AST file not found: {ast_path}")
|
|
90
|
+
|
|
91
|
+
with open(ast_path, 'r') as f:
|
|
92
|
+
ast_data = json.load(f)
|
|
93
|
+
|
|
94
|
+
result = await process_ast_to_graph(ast_data, api_key)
|
|
95
|
+
print(json.dumps(result))
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(json.dumps({"error": str(e), "nodes": [], "edges": []}))
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
asyncio.run(main())
|
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
});
|
package/src/commands/login.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
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
|
-
});
|
package/src/commands/sync.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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();
|
|
@@ -1,131 +0,0 @@
|
|
|
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
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
};
|
package/test-redaction.js
DELETED
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|