johankit 0.1.3 → 0.4.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 +26 -58
- package/dist/cli/commands/sync.js +38 -59
- package/dist/core/validation.js +7 -20
- package/dist/core/write.js +10 -9
- package/dist/src/cli/commands/copy.js +11 -0
- package/dist/src/cli/commands/paste.js +120 -0
- package/dist/src/cli/commands/prompt.js +54 -0
- package/dist/src/cli/commands/sync.js +173 -0
- package/dist/src/core/clipboard.js +64 -0
- package/dist/src/core/config.js +39 -0
- package/dist/{core → src/core}/diff.js +10 -14
- package/dist/{core → src/core}/git.js +1 -2
- package/dist/src/core/scan.js +70 -0
- package/dist/src/core/schema.js +18 -0
- package/dist/src/core/validation.js +11 -0
- package/dist/src/core/write.js +24 -0
- package/dist/src/index.js +34 -0
- package/dist/src/tests/cleanCodeBlock.test.js +23 -0
- package/dist/src/tests/scan.test.js +35 -0
- package/dist/src/tests/schema.test.js +22 -0
- package/dist/src/utils/cleanCodeBlock.js +21 -0
- package/dist/types.js +1 -0
- package/johankit.yml +6 -0
- package/package.json +20 -10
- package/src/cli/commands/copy.ts +6 -19
- package/src/cli/commands/paste.ts +70 -31
- package/src/cli/commands/prompt.ts +24 -64
- package/src/cli/commands/sync.ts +121 -73
- package/src/core/clipboard.ts +46 -80
- package/src/core/config.ts +20 -32
- package/src/core/diff.ts +10 -21
- package/src/core/scan.ts +43 -40
- package/src/core/schema.ts +17 -34
- package/src/core/validation.ts +8 -27
- package/src/core/write.ts +11 -17
- package/src/index.ts +38 -77
- package/src/types.ts +4 -50
- package/src/utils/cleanCodeBlock.ts +17 -8
- package/tsconfig.json +14 -5
- package/Readme.md +0 -56
- package/dist/cli/commands/copy.js +0 -29
- package/dist/cli/commands/paste.js +0 -49
- package/dist/cli/commands/prompt.js +0 -89
- package/dist/cli/commands/three.js +0 -106
- package/dist/cli/commands/tree.js +0 -107
- package/dist/core/clipboard.js +0 -89
- package/dist/core/config.js +0 -52
- package/dist/core/scan.js +0 -67
- package/dist/core/schema.js +0 -41
- package/dist/index.js +0 -72
- package/dist/services/JohankitService.js +0 -59
- package/dist/utils/cleanCodeBlock.js +0 -12
- package/dist/utils/createAsciiTree.js +0 -46
- package/johankit.yaml +0 -2
- package/src/cli/commands/tree.ts +0 -119
- package/src/services/JohankitService.ts +0 -70
- package/src/utils/createAsciiTree.ts +0 -53
- package/types.ts +0 -11
- /package/{types.js → dist/src/types.js} +0 -0
package/src/core/config.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// src/core/config.ts
|
|
2
1
|
import path from "path";
|
|
3
|
-
import { readFileSync } from "fs";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
4
3
|
import { load } from "js-yaml";
|
|
5
4
|
import { Config } from "../types";
|
|
6
5
|
|
|
7
|
-
const CONFIG_FILENAME = "johankit.yaml";
|
|
8
6
|
const DEFAULT_IGNORE = [
|
|
9
7
|
".git",
|
|
10
8
|
"node_modules",
|
|
@@ -15,36 +13,26 @@ const DEFAULT_IGNORE = [
|
|
|
15
13
|
"temp",
|
|
16
14
|
];
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
* Tenta carregar as configurações do arquivo johankit.yaml na basePath.
|
|
20
|
-
* Retorna um objeto Config com defaults se o arquivo não for encontrado.
|
|
21
|
-
* @param basePath O diretório base para procurar o arquivo de configuração.
|
|
22
|
-
* @returns O objeto de configuração.
|
|
23
|
-
*/
|
|
24
16
|
export function loadConfig(basePath: string): Config {
|
|
25
|
-
const
|
|
17
|
+
const configFilenames = ["johankit.yaml", "johankit.yml"];
|
|
18
|
+
let loadedConfig: Partial<Config> = {};
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (error instanceof Error && (error as any).code === "ENOENT") {
|
|
39
|
-
// Arquivo não encontrado, retorna configuração padrão
|
|
40
|
-
return {
|
|
41
|
-
ignore: DEFAULT_IGNORE,
|
|
42
|
-
};
|
|
20
|
+
for (const filename of configFilenames) {
|
|
21
|
+
const configPath = path.join(basePath, filename);
|
|
22
|
+
if (existsSync(configPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(configPath, "utf8");
|
|
25
|
+
loadedConfig = (load(content) as Partial<Config>) || {};
|
|
26
|
+
break;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn(`[johankit] Erro ao ler ${filename}, tentando próximo...`);
|
|
29
|
+
}
|
|
43
30
|
}
|
|
44
|
-
|
|
45
|
-
console.warn(`[johankit] Aviso: Falha ao carregar ${CONFIG_FILENAME}. Usando defaults.`, error);
|
|
46
|
-
return {
|
|
47
|
-
ignore: DEFAULT_IGNORE,
|
|
48
|
-
};
|
|
49
31
|
}
|
|
50
|
-
|
|
32
|
+
|
|
33
|
+
const ignoreSet = new Set([...DEFAULT_IGNORE, ...(loadedConfig.ignore || [])]);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
ignore: Array.from(ignoreSet),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/src/core/diff.ts
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
// src/core/diff.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { PatchItem } from "./schema";
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
type: "modify" | "create" | "delete";
|
|
7
|
-
path: string;
|
|
8
|
-
content?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function applyDiff(basePath: string, patches: DiffPatch[]) {
|
|
6
|
+
export function applyDiff(basePath: string, patches: PatchItem[]) {
|
|
12
7
|
for (const patch of patches) {
|
|
8
|
+
if (!patch.path) continue;
|
|
13
9
|
const fullPath = path.join(basePath, patch.path);
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
fs.unlinkSync(fullPath);
|
|
19
|
-
}
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
case "create":
|
|
24
|
-
case "modify": {
|
|
25
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
26
|
-
fs.writeFileSync(fullPath, patch.content ?? "", "utf8");
|
|
27
|
-
break;
|
|
11
|
+
if (patch.content === null || patch.content === undefined) {
|
|
12
|
+
if (fs.existsSync(fullPath)) {
|
|
13
|
+
fs.unlinkSync(fullPath);
|
|
28
14
|
}
|
|
15
|
+
} else {
|
|
16
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
17
|
+
fs.writeFileSync(fullPath, patch.content, "utf8");
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
|
-
}
|
|
20
|
+
}
|
package/src/core/scan.ts
CHANGED
|
@@ -1,70 +1,73 @@
|
|
|
1
|
-
// src/core/scan.ts
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import path from "path";
|
|
4
|
-
import { FileSnapshot, ScanOptions } from "../types";
|
|
3
|
+
import { FileSnapshot, ScanOptions, Config } from "../types";
|
|
4
|
+
import { loadConfig } from "./config";
|
|
5
5
|
|
|
6
|
-
export function scanDir(
|
|
7
|
-
basePath: string,
|
|
8
|
-
options: ScanOptions = {}
|
|
9
|
-
): FileSnapshot[] {
|
|
6
|
+
export function scanDir(basePath: string, options: ScanOptions = {}): FileSnapshot[] {
|
|
10
7
|
const result: FileSnapshot[] = [];
|
|
11
8
|
const base = path.resolve(basePath);
|
|
9
|
+
const exts = options.extensions?.map(e => e.startsWith('.') ? e : `.${e}`);
|
|
12
10
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
);
|
|
11
|
+
const config: Config = loadConfig(base);
|
|
12
|
+
const ignorePatterns = new Set(config.ignore);
|
|
16
13
|
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
"node_modules", ".git", "dist", "build", ".DS_Store", "coverage", ".env", "yarn.lock",
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
// Read .gitignore if exists
|
|
23
|
-
const gitignorePath = path.join(base, ".gitignore");
|
|
14
|
+
// Lê .gitignore e adiciona ao Set
|
|
15
|
+
const gitignorePath = path.join(base, '.gitignore');
|
|
24
16
|
if (fs.existsSync(gitignorePath)) {
|
|
25
17
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
18
|
+
fs.readFileSync(gitignorePath, 'utf8')
|
|
19
|
+
.split('\n')
|
|
20
|
+
.forEach(line => {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
23
|
+
// Normaliza o caminho do gitignore para o padrão do scanner
|
|
24
|
+
ignorePatterns.add(trimmed.replace(/^\//, '').replace(/\/$/, ''));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
} catch { }
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const pattern of ignoreSet) {
|
|
41
|
-
if (pattern.startsWith("*") && name.endsWith(pattern.slice(1))) return true;
|
|
42
|
-
if (name.startsWith(pattern + "/")) return true;
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
30
|
+
// PERFORMANCE: Converte o Set em Array UMA VEZ antes de iniciar o loop
|
|
31
|
+
const finalIgnoreList = Array.from(ignorePatterns);
|
|
46
32
|
|
|
47
33
|
function loop(currentPath: string) {
|
|
48
34
|
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
49
35
|
|
|
50
36
|
for (const entry of entries) {
|
|
51
|
-
|
|
37
|
+
const isBinary = entry.name.match(/\.(png|jpg|jpeg|gif|pdf|zip|exe|dll|so|db)$/i);
|
|
38
|
+
if (isBinary) continue;
|
|
52
39
|
|
|
53
40
|
const fullPath = path.join(currentPath, entry.name);
|
|
41
|
+
const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
|
|
42
|
+
|
|
43
|
+
// PERFORMANCE: O some() agora opera sobre um array fixo, sem Array.from() interno
|
|
44
|
+
const shouldIgnore = finalIgnoreList.some(p =>
|
|
45
|
+
relPath === p || relPath.startsWith(p + '/')
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (shouldIgnore) continue;
|
|
54
49
|
|
|
55
50
|
if (entry.isDirectory()) {
|
|
56
51
|
loop(fullPath);
|
|
57
52
|
} else {
|
|
58
53
|
if (exts && !exts.includes(path.extname(entry.name))) continue;
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
try {
|
|
56
|
+
// PERFORMANCE: Pular arquivos muito grandes evita travar o clipboard
|
|
57
|
+
const stats = fs.statSync(fullPath);
|
|
58
|
+
if (stats.size > 1024 * 300) continue; // Reduzi para 300KB para ser mais seguro
|
|
59
|
+
|
|
60
|
+
result.push({
|
|
61
|
+
path: relPath,
|
|
62
|
+
content: fs.readFileSync(fullPath, 'utf8')
|
|
63
|
+
});
|
|
64
|
+
} catch (e) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
loop(base);
|
|
69
72
|
return result;
|
|
70
|
-
}
|
|
73
|
+
}
|
package/src/core/schema.ts
CHANGED
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
// src/core/schema.ts
|
|
2
|
-
import { DiffPatch } from "./diff";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (typeof patch !== "object" || patch === null) return false;
|
|
10
|
-
if (typeof patch.path !== "string" || patch.path.length === 0) return false;
|
|
11
|
-
|
|
12
|
-
const validTypes = ["modify", "create", "delete"];
|
|
13
|
-
if (!validTypes.includes(patch.type)) return false;
|
|
14
|
-
|
|
15
|
-
if (patch.type === "delete") {
|
|
16
|
-
return patch.content === undefined;
|
|
17
|
-
} else {
|
|
18
|
-
return typeof patch.content === "string";
|
|
19
|
-
}
|
|
3
|
+
export interface PatchItem {
|
|
4
|
+
path?: string;
|
|
5
|
+
content?: string | null;
|
|
6
|
+
type?: 'console';
|
|
7
|
+
command?: string;
|
|
20
8
|
}
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* @returns O array de patches se for válido.
|
|
26
|
-
* @throws Um erro se a validação falhar.
|
|
27
|
-
*/
|
|
28
|
-
export function validatePatches(patches: any): DiffPatch[] {
|
|
29
|
-
if (!Array.isArray(patches)) {
|
|
30
|
-
throw new Error("O patch deve ser um array JSON válido");
|
|
10
|
+
export function validatePatches(json: any): PatchItem[] {
|
|
11
|
+
if (!Array.isArray(json)) {
|
|
12
|
+
throw new Error("Input must be a valid JSON array");
|
|
31
13
|
}
|
|
14
|
+
// Validação permissiva: ou tem path (arquivo) ou tem type console + command
|
|
15
|
+
return json.map((item, index) => {
|
|
16
|
+
const isFile = typeof item.path === 'string';
|
|
17
|
+
const isCommand = item.type === 'console' && typeof item.command === 'string';
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
throw new Error(`Patch inválido no índice ${index}: ${JSON.stringify(patch, null, 2)}.\nEsperado: { type: 'modify'|'create'|'delete', path: string, content?: string }`);
|
|
19
|
+
if (!isFile && !isCommand) {
|
|
20
|
+
throw new Error(`Item at index ${index} is invalid. Must have 'path' or 'type: console'`);
|
|
36
21
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return patches as DiffPatch[];
|
|
41
|
-
}
|
|
22
|
+
return item as PatchItem;
|
|
23
|
+
});
|
|
24
|
+
}
|
package/src/core/validation.ts
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
// src/core/validation.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (typeof item !== "object" || item === null) {
|
|
11
|
-
throw new Error(`Validation Error: Item at index ${index} is not an object.`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (!["modify", "create", "delete"].includes(item.type)) {
|
|
15
|
-
throw new Error(`Validation Error: Invalid type '${item.type}' at index ${index}.`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (typeof item.path !== "string" || !item.path.trim()) {
|
|
19
|
-
throw new Error(`Validation Error: Invalid or missing path at index ${index}.`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (item.type !== "delete" && typeof item.content !== "string") {
|
|
23
|
-
throw new Error(`Validation Error: Missing content for '${item.type}' at index ${index}.`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return item as DiffPatch;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
2
|
+
import { validatePatches as sharedValidate } from "./schema";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Use validatePatches from core/schema instead.
|
|
6
|
+
*/
|
|
7
|
+
export function validatePatches(json: any): any[] {
|
|
8
|
+
return sharedValidate(json);
|
|
9
|
+
}
|
package/src/core/write.ts
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
// src/core/write.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { FileSnapshot } from "../types";
|
|
5
4
|
import { ensureGitCommit } from "./git";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use applyDiff from core/diff for more flexibility (supports deletes and console commands).
|
|
8
|
+
*/
|
|
9
|
+
export function writeFiles(basePath: string, files: any, commit = true) {
|
|
10
|
+
// if (commit && files.length > 0) {
|
|
11
|
+
// ensureGitCommit("johankit: before write");
|
|
12
|
+
// }
|
|
15
13
|
|
|
16
14
|
for (const file of files) {
|
|
15
|
+
if (!file.path) continue;
|
|
17
16
|
const fullPath = path.join(basePath, file.path);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!fs.existsSync(dir)) {
|
|
21
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fs.writeFileSync(fullPath, file.content, "utf8");
|
|
17
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
18
|
+
fs.writeFileSync(fullPath, file.content || "", "utf8");
|
|
25
19
|
}
|
|
26
|
-
}
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,78 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(output);
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
default:
|
|
45
|
-
showHelp();
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (error instanceof Error) {
|
|
50
|
-
console.error("Error:", error.message);
|
|
51
|
-
} else {
|
|
52
|
-
console.error("Unexpected error:", error);
|
|
53
|
-
}
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function showHelp() {
|
|
59
|
-
console.log(`
|
|
60
|
-
Usage:
|
|
61
|
-
johankit copy <dir>
|
|
62
|
-
johankit paste <dir>
|
|
63
|
-
johankit prompt <dir> "<user request>" [--diff]
|
|
64
|
-
johankit sync <dir>
|
|
65
|
-
johankit tree <dir>
|
|
66
|
-
|
|
67
|
-
Examples:
|
|
68
|
-
johankit copy src
|
|
69
|
-
johankit paste src
|
|
70
|
-
johankit prompt src "refactor to async/await"
|
|
71
|
-
johankit sync src
|
|
72
|
-
johankit tree src
|
|
73
|
-
`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
main();
|
|
77
|
-
|
|
78
|
-
export { JohankitService };
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { copy } from "./cli/commands/copy";
|
|
4
|
+
import { paste } from "./cli/commands/paste";
|
|
5
|
+
import { prompt } from "./cli/commands/prompt";
|
|
6
|
+
import { sync } from "./cli/commands/sync";
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('johankit')
|
|
12
|
+
.description('Developer-friendly CLI for codebase snapshots and AI vibe-coding')
|
|
13
|
+
.version('0.0.3');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('copy [dir] [exts]')
|
|
17
|
+
.action((dir = '.', exts) => copy(dir, exts?.split(',')));
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('paste [dir]')
|
|
21
|
+
.option('--run', 'execute console commands')
|
|
22
|
+
.option('-y', 'auto accept commands without confirmation')
|
|
23
|
+
.option('--dry-run', 'list changes without applying them')
|
|
24
|
+
.option('--diff', 'show diff and ask for confirmation for each file')
|
|
25
|
+
.action((dir = '.', opts) => paste(dir, !!opts.run, !!opts.dryRun, !!opts.diff));
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('prompt [dir] <request...>')
|
|
29
|
+
.action((dir = '.', request) => prompt(dir, request.join(' ')));
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('sync [dir]')
|
|
33
|
+
.option('--run', 'execute console commands')
|
|
34
|
+
.option('-y', 'auto accept commands without confirmation')
|
|
35
|
+
.option('--dry-run', 'list changes without applying them')
|
|
36
|
+
.option('--diff', 'show diff and ask for confirmation for each file')
|
|
37
|
+
.action((dir = '.', opts) => sync(dir, !!opts.run, !!opts.dryRun, !!opts.diff));
|
|
38
|
+
|
|
39
|
+
program.parse();
|
package/src/types.ts
CHANGED
|
@@ -1,59 +1,13 @@
|
|
|
1
1
|
// src/types.ts
|
|
2
2
|
export interface FileSnapshot {
|
|
3
3
|
path: string;
|
|
4
|
-
content: string;
|
|
4
|
+
content: string | null;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export interface ScanOptions {
|
|
8
|
-
extensions?: string[];
|
|
9
|
-
// A opção 'ignore' foi movida para o arquivo de configuração e 'loadConfig'
|
|
8
|
+
extensions?: string[];
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export interface Config {
|
|
13
|
-
ignore: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type FileKind =
|
|
17
|
-
| "entry"
|
|
18
|
-
| "cli"
|
|
19
|
-
| "domain"
|
|
20
|
-
| "infra"
|
|
21
|
-
| "util"
|
|
22
|
-
| "unknown";
|
|
23
|
-
|
|
24
|
-
export interface VariableInfo {
|
|
25
|
-
name: string;
|
|
26
|
-
type?: string;
|
|
27
|
-
scope: "global" | "local";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface FunctionInfo {
|
|
31
|
-
name: string;
|
|
32
|
-
params: string[];
|
|
33
|
-
returnType?: string;
|
|
34
|
-
scope: "global" | "class";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ClassInfo {
|
|
38
|
-
name: string;
|
|
39
|
-
methods: FunctionInfo[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface ExportInfo {
|
|
43
|
-
name: string;
|
|
44
|
-
kind: "function" | "class" | "variable" | "type" | "unknown";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface FileTree {
|
|
48
|
-
path: string;
|
|
49
|
-
kind: FileKind;
|
|
50
|
-
|
|
51
|
-
imports: string[];
|
|
52
|
-
|
|
53
|
-
classes: ClassInfo[];
|
|
54
|
-
functions: FunctionInfo[];
|
|
55
|
-
variables: VariableInfo[];
|
|
56
|
-
|
|
57
|
-
exports: ExportInfo[];
|
|
58
|
-
mainExport?: string;
|
|
59
|
-
}
|
|
12
|
+
ignore: string[];
|
|
13
|
+
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
// src/utils/cleanCodeBlock.ts
|
|
1
2
|
export default function cleanCodeBlock(content: string) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
// Regex robusta para capturar o primeiro array JSON dentro ou fora de blocos de código markdown
|
|
4
|
+
const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/;
|
|
5
|
+
const match = content.match(codeBlockRegex);
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
let cleaned = match ? match[1] : content;
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
// Remove caracteres invisíveis como BOM (Byte Order Mark) e espaços extras
|
|
10
|
+
cleaned = cleaned.trim().replace(/\uFEFF/g, "");
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
// Se ainda assim parecer que tem texto antes do array, tenta encontrar o início do array
|
|
13
|
+
if (!cleaned.startsWith("[")) {
|
|
14
|
+
const arrayStart = cleaned.indexOf("[");
|
|
15
|
+
const arrayEnd = cleaned.lastIndexOf("]");
|
|
16
|
+
if (arrayStart !== -1 && arrayEnd !== -1) {
|
|
17
|
+
cleaned = cleaned.substring(arrayStart, arrayEnd + 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
}
|
|
21
|
+
return { cleaned };
|
|
22
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
"target": "ES2020",
|
|
4
4
|
"module": "NodeNext",
|
|
5
5
|
"moduleResolution": "NodeNext",
|
|
6
|
-
"baseUrl": "
|
|
6
|
+
"baseUrl": ".",
|
|
7
7
|
"paths": {
|
|
8
|
-
"*": ["
|
|
8
|
+
"*": ["src/*", "src/core/core/*", "cli/*"]
|
|
9
9
|
},
|
|
10
10
|
"outDir": "dist",
|
|
11
|
-
"rootDir": "
|
|
11
|
+
"rootDir": ".",
|
|
12
12
|
"esModuleInterop": true,
|
|
13
13
|
"strict": true,
|
|
14
14
|
"skipLibCheck": true
|
|
15
|
-
}
|
|
16
|
-
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/**/*",
|
|
18
|
+
"src/core/core/**/*",
|
|
19
|
+
"cli/**/*"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist"
|
|
24
|
+
]
|
|
25
|
+
}
|
package/Readme.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# JohanKit CLI
|
|
2
|
-
|
|
3
|
-
JohanKit é uma ferramenta para copiar, colar e sincronizar snapshots de código de forma inteligente usando AI.
|
|
4
|
-
|
|
5
|
-
## Comandos
|
|
6
|
-
|
|
7
|
-
### Copy
|
|
8
|
-
Copia um diretório ou arquivos específicos para o clipboard em formato JSON.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
johankit copy <dir> [exts]
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
- `<dir>`: Diretório a copiar (padrão `.`)
|
|
15
|
-
- `[exts]`: Extensões separadas por vírgula (ex: `ts,js`)
|
|
16
|
-
|
|
17
|
-
### Paste
|
|
18
|
-
Aplica o conteúdo do clipboard em um diretório.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
johankit paste <dir>
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
- `<dir>`: Diretório de destino (padrão `.`)
|
|
25
|
-
|
|
26
|
-
### Prompt
|
|
27
|
-
Gera um prompt com o snapshot atual do diretório para AI, copiando para o clipboard.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
johankit prompt <dir> "<user request>"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Sync
|
|
34
|
-
Aplica patches gerados pela AI em um diretório e atualiza o clipboard com o novo snapshot.
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
johankit sync <dir>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Exemplo de Uso
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
johankit prompt src "refatorar para async/await"
|
|
44
|
-
johankit sync src
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Configuração
|
|
48
|
-
Crie um arquivo `johankit.yaml` na raiz do projeto para customizar os diretórios e arquivos a ignorar.
|
|
49
|
-
|
|
50
|
-
```yaml
|
|
51
|
-
ignore:
|
|
52
|
-
- dist
|
|
53
|
-
- node_modules
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Por padrão, os seguintes diretórios já são ignorados: `.git`, `node_modules`, `dist`, `build`, `coverage`, `tmp`, `temp`.
|