johankit 0.1.5 → 1.0.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/README.md +45 -62
- package/dist/cli/commands/sync.js +37 -57
- package/dist/core/validation.js +6 -18
- package/dist/core/write.js +9 -7
- package/dist/src/cli/commands/copy.js +11 -0
- package/dist/src/cli/commands/paste.js +128 -0
- package/dist/src/cli/commands/prompt.js +54 -0
- package/dist/src/cli/commands/sync.js +166 -0
- package/dist/src/core/clipboard.js +64 -0
- package/dist/src/core/config.js +41 -0
- package/dist/{core → src/core}/diff.js +9 -12
- package/dist/src/core/scan.js +75 -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 +39 -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 +13 -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 +79 -31
- package/src/cli/commands/prompt.ts +24 -64
- package/src/cli/commands/sync.ts +112 -71
- package/src/core/clipboard.ts +46 -80
- package/src/core/config.ts +22 -32
- package/src/core/diff.ts +10 -21
- package/src/core/scan.ts +52 -43
- 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 +43 -77
- package/src/tests/cleanCodeBlock.test.ts +21 -0
- package/src/tests/scan.test.ts +33 -0
- package/src/tests/schema.test.ts +24 -0
- package/src/types.ts +4 -50
- package/src/utils/cleanCodeBlock.ts +12 -12
- package/tsconfig.json +14 -6
- package/Readme.md +0 -56
- package/dist/cli/commands/copy.js +0 -21
- package/dist/cli/commands/paste.js +0 -48
- package/dist/cli/commands/prompt.js +0 -88
- package/dist/cli/commands/three.js +0 -106
- package/dist/cli/commands/tree.js +0 -106
- package/dist/core/clipboard.js +0 -88
- package/dist/core/config.js +0 -51
- package/dist/core/scan.js +0 -66
- package/dist/core/schema.js +0 -40
- 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/dist/{core → src/core}/git.js +0 -0
- /package/{types.js → dist/src/types.js} +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.sync = sync;
|
|
40
|
+
// src/cli/commands/sync.ts
|
|
41
|
+
const scan_1 = require("../../core/scan");
|
|
42
|
+
const schema_1 = require("../../core/schema");
|
|
43
|
+
const diff_1 = require("../../core/diff");
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const cleanCodeBlock_1 = __importDefault(require("../../utils/cleanCodeBlock"));
|
|
46
|
+
const readline_1 = __importDefault(require("readline"));
|
|
47
|
+
const clipboard_1 = require("../../core/clipboard");
|
|
48
|
+
const fs_1 = __importDefault(require("fs"));
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
const diff = __importStar(require("diff"));
|
|
51
|
+
require("colors");
|
|
52
|
+
const INTERNAL_MARKER = "__JOHANKIT_INTERNAL__";
|
|
53
|
+
async function confirm(msg) {
|
|
54
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
55
|
+
return new Promise(resolve => {
|
|
56
|
+
rl.question(`${msg} (y/N): `, ans => {
|
|
57
|
+
rl.close();
|
|
58
|
+
resolve(ans.toLowerCase() === 'y');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function showDiff(filename, oldContent, newContent) {
|
|
63
|
+
console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
|
|
64
|
+
const patches = diff.diffLines(oldContent, newContent);
|
|
65
|
+
patches.forEach((part) => {
|
|
66
|
+
const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
|
|
67
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
68
|
+
const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
|
|
69
|
+
process.stdout.write((value.split('\n').map(line => line ? `${prefix}${line}` : '').join('\n'))[color]);
|
|
70
|
+
});
|
|
71
|
+
console.log('\n-----------------------');
|
|
72
|
+
}
|
|
73
|
+
async function processInput(input, dir, runAll, dryRun, interactiveDiff) {
|
|
74
|
+
const autoAccept = process.argv.includes('-y');
|
|
75
|
+
const { cleaned } = (0, cleanCodeBlock_1.default)(input);
|
|
76
|
+
let patchesData;
|
|
77
|
+
try {
|
|
78
|
+
patchesData = JSON.parse(cleaned);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const patches = (0, schema_1.validatePatches)(patchesData);
|
|
84
|
+
if (patches.length === 0)
|
|
85
|
+
return false;
|
|
86
|
+
for (const patch of patches) {
|
|
87
|
+
if (patch.type === 'console' && patch.command) {
|
|
88
|
+
if (dryRun) {
|
|
89
|
+
process.stdout.write(`[DRY-RUN] Would execute: ${patch.command}\n`);
|
|
90
|
+
}
|
|
91
|
+
else if (runAll) {
|
|
92
|
+
const ok = autoAccept || await confirm(`> Execute: ${patch.command}`);
|
|
93
|
+
if (ok)
|
|
94
|
+
(0, child_process_1.execSync)(patch.command, { stdio: 'inherit', cwd: dir });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (patch.path) {
|
|
98
|
+
const fullPath = path_1.default.join(dir, patch.path);
|
|
99
|
+
const exists = fs_1.default.existsSync(fullPath);
|
|
100
|
+
const oldContent = exists ? fs_1.default.readFileSync(fullPath, 'utf8') : "";
|
|
101
|
+
const newContent = patch.content || "";
|
|
102
|
+
if (interactiveDiff && patch.content !== null) {
|
|
103
|
+
showDiff(patch.path, oldContent, newContent);
|
|
104
|
+
if (await confirm(`Apply changes to ${patch.path}?`)) {
|
|
105
|
+
(0, diff_1.applyDiff)(dir, [patch]);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(`Skipped: ${patch.path}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (dryRun) {
|
|
112
|
+
const action = patch.content === null ? "Delete" : "Write";
|
|
113
|
+
process.stdout.write(`[DRY-RUN] Would ${action}: ${patch.path}\n`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
(0, diff_1.applyDiff)(dir, [patch]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
async function sync(dir, runAll = false, dryRun = false, interactiveDiff = false, watch = false) {
|
|
123
|
+
try {
|
|
124
|
+
const snapshotBefore = (0, scan_1.scanDir)(dir);
|
|
125
|
+
const systemPrompt = `YOU ARE AN AI SOFTWARE ENGINEER.\nALWAYS RESPOND USING ONLY A JSON PATCH ARRAY.\n\nSNAPSHOT:\n${JSON.stringify(snapshotBefore, null, 2)}`;
|
|
126
|
+
await (0, clipboard_1.copyToClipboard)(INTERNAL_MARKER + systemPrompt);
|
|
127
|
+
process.stdout.write("[johankit] System Prompt + Snapshot copied to clipboard\n");
|
|
128
|
+
if (watch) {
|
|
129
|
+
process.stdout.write("[johankit] Watching clipboard for AI response (Ctrl+C to stop)\n");
|
|
130
|
+
let lastClipboard = await (0, clipboard_1.readClipboard)();
|
|
131
|
+
while (true) {
|
|
132
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
133
|
+
const currentClipboard = await (0, clipboard_1.readClipboard)();
|
|
134
|
+
if (!currentClipboard || currentClipboard === lastClipboard)
|
|
135
|
+
continue;
|
|
136
|
+
lastClipboard = currentClipboard;
|
|
137
|
+
if (currentClipboard.startsWith(INTERNAL_MARKER))
|
|
138
|
+
continue;
|
|
139
|
+
const success = await processInput(currentClipboard, dir, runAll, dryRun, interactiveDiff);
|
|
140
|
+
if (success) {
|
|
141
|
+
process.stdout.write("[johankit] Patch applied from clipboard\n");
|
|
142
|
+
const snapshotAfter = (0, scan_1.scanDir)(dir);
|
|
143
|
+
await (0, clipboard_1.copyToClipboard)(INTERNAL_MARKER + JSON.stringify(snapshotAfter, null, 2));
|
|
144
|
+
process.stdout.write("[johankit] Updated snapshot copied to clipboard\n");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
process.stdout.write("[johankit] Paste AI response into clipboard and press Enter\n");
|
|
150
|
+
await confirm('');
|
|
151
|
+
const input = await (0, clipboard_1.readClipboard)();
|
|
152
|
+
if (!input)
|
|
153
|
+
throw new Error("Clipboard is empty");
|
|
154
|
+
const success = await processInput(input, dir, runAll, dryRun, interactiveDiff);
|
|
155
|
+
if (success && !dryRun) {
|
|
156
|
+
const snapshotAfter = (0, scan_1.scanDir)(dir);
|
|
157
|
+
await (0, clipboard_1.copyToClipboard)(INTERNAL_MARKER + JSON.stringify(snapshotAfter, null, 2));
|
|
158
|
+
process.stdout.write("[johankit] Sync applied and snapshot copied to clipboard\n");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
process.stderr.write(`[johankit] Sync failed: ${e.message}\n`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.copyToClipboard = copyToClipboard;
|
|
7
|
+
exports.readClipboard = readClipboard;
|
|
8
|
+
// src/core/clipboard.ts
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const clipboardy_1 = __importDefault(require("clipboardy"));
|
|
11
|
+
async function copyToClipboard(text) {
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP;
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let command = "";
|
|
16
|
+
let args = [];
|
|
17
|
+
if (isWSL) {
|
|
18
|
+
command = "clip.exe";
|
|
19
|
+
}
|
|
20
|
+
else if (platform === "darwin") {
|
|
21
|
+
command = "pbcopy";
|
|
22
|
+
}
|
|
23
|
+
else if (platform === "win32") {
|
|
24
|
+
command = "clip";
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
command = "xclip";
|
|
28
|
+
args = ["-selection", "clipboard"];
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const child = (0, child_process_1.spawn)(command, args);
|
|
32
|
+
child.on("error", (err) => reject(err));
|
|
33
|
+
child.on("close", (code) => {
|
|
34
|
+
if (code === 0)
|
|
35
|
+
resolve();
|
|
36
|
+
else
|
|
37
|
+
reject(new Error(`Clipboard error: ${code}`));
|
|
38
|
+
});
|
|
39
|
+
child.stdin.write(text);
|
|
40
|
+
child.stdin.end();
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
reject(e);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function readClipboard() {
|
|
48
|
+
try {
|
|
49
|
+
return await clipboardy_1.default.read();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const platform = process.platform;
|
|
54
|
+
let command = platform === "darwin" ? "pbpaste" : platform === "win32" ? "powershell" : "xclip";
|
|
55
|
+
let args = platform === "win32" ? ["-NoProfile", "-Command", "Get-Clipboard"] :
|
|
56
|
+
platform === "linux" ? ["-selection", "clipboard", "-o"] : [];
|
|
57
|
+
const child = (0, child_process_1.spawn)(command, args);
|
|
58
|
+
let output = "";
|
|
59
|
+
child.stdout.on("data", (d) => (output += d.toString()));
|
|
60
|
+
child.on("close", () => resolve(output.trim()));
|
|
61
|
+
child.on("error", () => resolve(""));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const js_yaml_1 = require("js-yaml");
|
|
10
|
+
const DEFAULT_IGNORE = [
|
|
11
|
+
".git",
|
|
12
|
+
"node_modules",
|
|
13
|
+
"dist",
|
|
14
|
+
"build",
|
|
15
|
+
"coverage",
|
|
16
|
+
"tmp",
|
|
17
|
+
"temp",
|
|
18
|
+
];
|
|
19
|
+
function loadConfig(basePath) {
|
|
20
|
+
// Lista de possíveis nomes para o arquivo de configuração
|
|
21
|
+
const configFilenames = ["johankit.yaml", "johankit.yml"];
|
|
22
|
+
let loadedConfig = {};
|
|
23
|
+
for (const filename of configFilenames) {
|
|
24
|
+
const configPath = path_1.default.join(basePath, filename);
|
|
25
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const content = (0, fs_1.readFileSync)(configPath, "utf8");
|
|
28
|
+
loadedConfig = (0, js_yaml_1.load)(content) || {};
|
|
29
|
+
break; // Para no primeiro que encontrar
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.warn(`[johankit] Erro ao ler ${filename}, tentando próximo...`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Set para garantir que não existam duplicatas nos padrões de ignore
|
|
37
|
+
const ignoreSet = new Set([...DEFAULT_IGNORE, ...(loadedConfig.ignore || [])]);
|
|
38
|
+
return {
|
|
39
|
+
ignore: Array.from(ignoreSet),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -9,20 +9,17 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
function applyDiff(basePath, patches) {
|
|
11
11
|
for (const patch of patches) {
|
|
12
|
+
if (!patch.path)
|
|
13
|
+
continue;
|
|
12
14
|
const fullPath = path_1.default.join(basePath, patch.path);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
fs_1.default.unlinkSync(fullPath);
|
|
17
|
-
}
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
case "create":
|
|
21
|
-
case "modify": {
|
|
22
|
-
fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
|
|
23
|
-
fs_1.default.writeFileSync(fullPath, patch.content ?? "", "utf8");
|
|
24
|
-
break;
|
|
15
|
+
if (patch.content === null || patch.content === undefined) {
|
|
16
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
17
|
+
fs_1.default.unlinkSync(fullPath);
|
|
25
18
|
}
|
|
26
19
|
}
|
|
20
|
+
else {
|
|
21
|
+
fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
|
|
22
|
+
fs_1.default.writeFileSync(fullPath, patch.content, "utf8");
|
|
23
|
+
}
|
|
27
24
|
}
|
|
28
25
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scanDir = scanDir;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
// Cache para evitar re-leitura de configurações durante o mesmo processo
|
|
11
|
+
let cachedConfig = null;
|
|
12
|
+
let cachedIgnoreList = null;
|
|
13
|
+
function scanDir(basePath, options = {}) {
|
|
14
|
+
const result = [];
|
|
15
|
+
const base = path_1.default.resolve(basePath);
|
|
16
|
+
const exts = options.extensions?.map(e => e.startsWith('.') ? e : `.${e}`);
|
|
17
|
+
if (!cachedConfig) {
|
|
18
|
+
cachedConfig = (0, config_1.loadConfig)(base);
|
|
19
|
+
const ignorePatterns = new Set(cachedConfig.ignore);
|
|
20
|
+
const gitignorePath = path_1.default.join(base, '.gitignore');
|
|
21
|
+
if (fs_1.default.existsSync(gitignorePath)) {
|
|
22
|
+
try {
|
|
23
|
+
fs_1.default.readFileSync(gitignorePath, 'utf8')
|
|
24
|
+
.split('\n')
|
|
25
|
+
.forEach(line => {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
28
|
+
ignorePatterns.add(trimmed.replace(/^\//, '').replace(/\/$/, ''));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
}
|
|
34
|
+
cachedIgnoreList = Array.from(ignorePatterns);
|
|
35
|
+
}
|
|
36
|
+
const finalIgnoreList = cachedIgnoreList;
|
|
37
|
+
function loop(currentPath) {
|
|
38
|
+
const entries = fs_1.default.readdirSync(currentPath, { withFileTypes: true });
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const name = entry.name;
|
|
41
|
+
// Otimização: Verificação rápida de binários e pastas ocultas comuns
|
|
42
|
+
if (name.startsWith('.') && name !== '.gitignore')
|
|
43
|
+
continue;
|
|
44
|
+
if (name.match(/\.(png|jpg|jpeg|gif|pdf|zip|exe|dll|so|db|map|lock)$/i))
|
|
45
|
+
continue;
|
|
46
|
+
const fullPath = path_1.default.join(currentPath, name);
|
|
47
|
+
const relPath = path_1.default.relative(base, fullPath).replace(/\\/g, '/');
|
|
48
|
+
const shouldIgnore = finalIgnoreList.some(p => relPath === p || relPath.startsWith(p + '/'));
|
|
49
|
+
if (shouldIgnore)
|
|
50
|
+
continue;
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
loop(fullPath);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
if (exts && !exts.includes(path_1.default.extname(name)))
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
const stats = fs_1.default.statSync(fullPath);
|
|
59
|
+
// Ignora arquivos maiores que 200KB para manter performance do clipboard
|
|
60
|
+
if (stats.size > 200 * 1024)
|
|
61
|
+
continue;
|
|
62
|
+
result.push({
|
|
63
|
+
path: relPath,
|
|
64
|
+
content: fs_1.default.readFileSync(fullPath, 'utf8')
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
loop(base);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/schema.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.validatePatches = validatePatches;
|
|
5
|
+
function validatePatches(json) {
|
|
6
|
+
if (!Array.isArray(json)) {
|
|
7
|
+
throw new Error("Input must be a valid JSON array");
|
|
8
|
+
}
|
|
9
|
+
// Validação permissiva: ou tem path (arquivo) ou tem type console + command
|
|
10
|
+
return json.map((item, index) => {
|
|
11
|
+
const isFile = typeof item.path === 'string';
|
|
12
|
+
const isCommand = item.type === 'console' && typeof item.command === 'string';
|
|
13
|
+
if (!isFile && !isCommand) {
|
|
14
|
+
throw new Error(`Item at index ${index} is invalid. Must have 'path' or 'type: console'`);
|
|
15
|
+
}
|
|
16
|
+
return item;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validatePatches = validatePatches;
|
|
4
|
+
// src/core/validation.ts
|
|
5
|
+
const schema_1 = require("./schema");
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use validatePatches from core/schema instead.
|
|
8
|
+
*/
|
|
9
|
+
function validatePatches(json) {
|
|
10
|
+
return (0, schema_1.validatePatches)(json);
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeFiles = writeFiles;
|
|
7
|
+
// src/core/write.ts
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use applyDiff from core/diff for more flexibility (supports deletes and console commands).
|
|
12
|
+
*/
|
|
13
|
+
function writeFiles(basePath, files, commit = true) {
|
|
14
|
+
// if (commit && files.length > 0) {
|
|
15
|
+
// ensureGitCommit("johankit: before write");
|
|
16
|
+
// }
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
if (!file.path)
|
|
19
|
+
continue;
|
|
20
|
+
const fullPath = path_1.default.join(basePath, file.path);
|
|
21
|
+
fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
|
|
22
|
+
fs_1.default.writeFileSync(fullPath, file.content || "", "utf8");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const copy_1 = require("./cli/commands/copy");
|
|
6
|
+
const paste_1 = require("./cli/commands/paste");
|
|
7
|
+
const prompt_1 = require("./cli/commands/prompt");
|
|
8
|
+
const sync_1 = require("./cli/commands/sync");
|
|
9
|
+
const program = new commander_1.Command();
|
|
10
|
+
program
|
|
11
|
+
.name('johankit')
|
|
12
|
+
.description('Developer-friendly CLI for codebase snapshots and AI vibe-coding')
|
|
13
|
+
.version('0.0.3');
|
|
14
|
+
program
|
|
15
|
+
.command('copy [dir] [exts]')
|
|
16
|
+
.action((dir = '.', exts) => (0, copy_1.copy)(dir, exts?.split(',')));
|
|
17
|
+
program
|
|
18
|
+
.command('paste [dir]')
|
|
19
|
+
.option('--run', 'execute console commands')
|
|
20
|
+
.option('-y', 'auto accept commands without confirmation')
|
|
21
|
+
.option('--dry-run', 'list changes without applying them')
|
|
22
|
+
.option('--diff', 'show diff and ask for confirmation for each file')
|
|
23
|
+
.action((dir = '.', opts) => (0, paste_1.paste)(dir, !!opts.run, !!opts.dryRun, !!opts.diff));
|
|
24
|
+
program
|
|
25
|
+
.command('prompt [dir] <request...>')
|
|
26
|
+
.action((dir = '.', request) => (0, prompt_1.prompt)(dir, request.join(' ')));
|
|
27
|
+
program
|
|
28
|
+
.command('sync [dir]')
|
|
29
|
+
.option('--run', 'execute console commands')
|
|
30
|
+
.option('-y', 'auto accept commands without confirmation')
|
|
31
|
+
.option('--dry-run', 'list changes without applying them')
|
|
32
|
+
.option('--diff', 'show diff and ask for confirmation for each file')
|
|
33
|
+
.option('--watch', 'continuously watch clipboard for new patches')
|
|
34
|
+
.option('--auto', 'alias for --watch with auto-accept')
|
|
35
|
+
.action((dir = '.', opts) => {
|
|
36
|
+
const isAuto = !!opts.auto;
|
|
37
|
+
(0, sync_1.sync)(dir, isAuto || !!opts.run, !!opts.dryRun, !!opts.diff, isAuto || !!opts.watch);
|
|
38
|
+
});
|
|
39
|
+
program.parse();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cleanCodeBlock_1 = __importDefault(require("../utils/cleanCodeBlock"));
|
|
7
|
+
describe('cleanCodeBlock', () => {
|
|
8
|
+
it('should extract JSON from markdown blocks', () => {
|
|
9
|
+
const input = 'Check this: ```json [{"path": "test"}] ``` and some text';
|
|
10
|
+
const { cleaned } = (0, cleanCodeBlock_1.default)(input);
|
|
11
|
+
expect(cleaned).toBe('[{"path": "test"}]');
|
|
12
|
+
});
|
|
13
|
+
it('should handle raw JSON arrays', () => {
|
|
14
|
+
const input = '[{"path": "test"}]';
|
|
15
|
+
const { cleaned } = (0, cleanCodeBlock_1.default)(input);
|
|
16
|
+
expect(cleaned).toBe('[{"path": "test"}]');
|
|
17
|
+
});
|
|
18
|
+
it('should remove invisible characters', () => {
|
|
19
|
+
const input = '\uFEFF[{"path": "test"}]';
|
|
20
|
+
const { cleaned } = (0, cleanCodeBlock_1.default)(input);
|
|
21
|
+
expect(cleaned).toBe('[{"path": "test"}]');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const scan_1 = require("../core/scan");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
describe('scanDir', () => {
|
|
10
|
+
const testDir = path_1.default.join(__dirname, 'test-tmp');
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
if (fs_1.default.existsSync(testDir))
|
|
13
|
+
fs_1.default.rmSync(testDir, { recursive: true });
|
|
14
|
+
fs_1.default.mkdirSync(testDir);
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (fs_1.default.existsSync(testDir))
|
|
18
|
+
fs_1.default.rmSync(testDir, { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
it('should scan files in a directory', () => {
|
|
21
|
+
fs_1.default.writeFileSync(path_1.default.join(testDir, 'test.ts'), 'console.log("hello")');
|
|
22
|
+
const results = (0, scan_1.scanDir)(testDir);
|
|
23
|
+
expect(results.length).toBe(1);
|
|
24
|
+
expect(results[0].path).toBe('test.ts');
|
|
25
|
+
expect(results[0].content).toBe('console.log("hello")');
|
|
26
|
+
});
|
|
27
|
+
it('should respect ignore patterns from config', () => {
|
|
28
|
+
fs_1.default.mkdirSync(path_1.default.join(testDir, 'node_modules'));
|
|
29
|
+
fs_1.default.writeFileSync(path_1.default.join(testDir, 'node_modules/ignore.ts'), 'ignore');
|
|
30
|
+
fs_1.default.writeFileSync(path_1.default.join(testDir, 'keep.ts'), 'keep');
|
|
31
|
+
const results = (0, scan_1.scanDir)(testDir);
|
|
32
|
+
expect(results.find(r => r.path.includes('node_modules'))).toBeUndefined();
|
|
33
|
+
expect(results.find(r => r.path === 'keep.ts')).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schema_1 = require("../core/schema");
|
|
4
|
+
describe('validatePatches', () => {
|
|
5
|
+
it('should validate correct file patches', () => {
|
|
6
|
+
const input = [{ path: 'src/index.ts', content: 'test' }];
|
|
7
|
+
const output = (0, schema_1.validatePatches)(input);
|
|
8
|
+
expect(output).toEqual(input);
|
|
9
|
+
});
|
|
10
|
+
it('should validate correct console patches', () => {
|
|
11
|
+
const input = [{ type: 'console', command: 'npm install' }];
|
|
12
|
+
const output = (0, schema_1.validatePatches)(input);
|
|
13
|
+
expect(output).toEqual(input);
|
|
14
|
+
});
|
|
15
|
+
it('should throw error for invalid items', () => {
|
|
16
|
+
const input = [{ invalid: 'item' }];
|
|
17
|
+
expect(() => (0, schema_1.validatePatches)(input)).toThrow();
|
|
18
|
+
});
|
|
19
|
+
it('should throw error if input is not an array', () => {
|
|
20
|
+
expect(() => (0, schema_1.validatePatches)({})).toThrow();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = cleanCodeBlock;
|
|
4
|
+
// src/utils/cleanCodeBlock.ts
|
|
5
|
+
function cleanCodeBlock(content) {
|
|
6
|
+
// Regex robusta para capturar o primeiro array JSON ou bloco de código em string suja
|
|
7
|
+
const jsonRegex = /```json\s*([\s\S]*?)\s*```|(\[\s*{[\s\S]*}\s*\])/;
|
|
8
|
+
const match = content.match(jsonRegex);
|
|
9
|
+
let cleaned = match ? (match[1] || match[2]) : content;
|
|
10
|
+
cleaned = cleaned.replace(/^\uFEFF/, '');
|
|
11
|
+
cleaned = cleaned.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
12
|
+
return { cleaned: cleaned.trim() };
|
|
13
|
+
}
|
package/dist/types.js
CHANGED
package/johankit.yml
ADDED
package/package.json
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "johankit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Developer-friendly CLI for codebase snapshots and AI vibe-coding.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"johankit": "./dist/index.js"
|
|
7
|
+
"johankit": "./dist/src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "jest",
|
|
11
11
|
"build": "tsc",
|
|
12
|
-
"start": "ts-node src/index.ts"
|
|
12
|
+
"start": "ts-node src/index.ts",
|
|
13
|
+
"prepare": "npm run build"
|
|
13
14
|
},
|
|
14
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cli",
|
|
17
|
+
"ai",
|
|
18
|
+
"snapshot",
|
|
19
|
+
"vibe-coding",
|
|
20
|
+
"productivity"
|
|
21
|
+
],
|
|
15
22
|
"author": "",
|
|
16
23
|
"license": "ISC",
|
|
24
|
+
"type": "commonjs",
|
|
17
25
|
"devDependencies": {
|
|
18
26
|
"@types/jest": "^29.5.3",
|
|
19
27
|
"@types/js-yaml": "^4.0.9",
|
|
20
|
-
"@types/node": "^
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
21
29
|
"jest": "^29.5.0",
|
|
22
30
|
"ts-jest": "^29.1.0",
|
|
23
31
|
"ts-node": "^10.9.2",
|
|
24
|
-
"typescript": "^5.
|
|
32
|
+
"typescript": "^5.0.0"
|
|
25
33
|
},
|
|
26
34
|
"dependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
35
|
+
"@types/diff": "^7.0.2",
|
|
36
|
+
"clipboardy": "^4.0.0",
|
|
37
|
+
"colors": "^1.4.0",
|
|
38
|
+
"commander": "^11.0.0",
|
|
39
|
+
"diff": "^8.0.2",
|
|
29
40
|
"js-yaml": "^4.1.1",
|
|
30
|
-
"ts-morph": "^27.0.2",
|
|
31
41
|
"vm2": "^3.10.0"
|
|
32
42
|
}
|
|
33
43
|
}
|