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.
Files changed (59) hide show
  1. package/README.md +26 -58
  2. package/dist/cli/commands/sync.js +38 -59
  3. package/dist/core/validation.js +7 -20
  4. package/dist/core/write.js +10 -9
  5. package/dist/src/cli/commands/copy.js +11 -0
  6. package/dist/src/cli/commands/paste.js +120 -0
  7. package/dist/src/cli/commands/prompt.js +54 -0
  8. package/dist/src/cli/commands/sync.js +173 -0
  9. package/dist/src/core/clipboard.js +64 -0
  10. package/dist/src/core/config.js +39 -0
  11. package/dist/{core → src/core}/diff.js +10 -14
  12. package/dist/{core → src/core}/git.js +1 -2
  13. package/dist/src/core/scan.js +70 -0
  14. package/dist/src/core/schema.js +18 -0
  15. package/dist/src/core/validation.js +11 -0
  16. package/dist/src/core/write.js +24 -0
  17. package/dist/src/index.js +34 -0
  18. package/dist/src/tests/cleanCodeBlock.test.js +23 -0
  19. package/dist/src/tests/scan.test.js +35 -0
  20. package/dist/src/tests/schema.test.js +22 -0
  21. package/dist/src/utils/cleanCodeBlock.js +21 -0
  22. package/dist/types.js +1 -0
  23. package/johankit.yml +6 -0
  24. package/package.json +20 -10
  25. package/src/cli/commands/copy.ts +6 -19
  26. package/src/cli/commands/paste.ts +70 -31
  27. package/src/cli/commands/prompt.ts +24 -64
  28. package/src/cli/commands/sync.ts +121 -73
  29. package/src/core/clipboard.ts +46 -80
  30. package/src/core/config.ts +20 -32
  31. package/src/core/diff.ts +10 -21
  32. package/src/core/scan.ts +43 -40
  33. package/src/core/schema.ts +17 -34
  34. package/src/core/validation.ts +8 -27
  35. package/src/core/write.ts +11 -17
  36. package/src/index.ts +38 -77
  37. package/src/types.ts +4 -50
  38. package/src/utils/cleanCodeBlock.ts +17 -8
  39. package/tsconfig.json +14 -5
  40. package/Readme.md +0 -56
  41. package/dist/cli/commands/copy.js +0 -29
  42. package/dist/cli/commands/paste.js +0 -49
  43. package/dist/cli/commands/prompt.js +0 -89
  44. package/dist/cli/commands/three.js +0 -106
  45. package/dist/cli/commands/tree.js +0 -107
  46. package/dist/core/clipboard.js +0 -89
  47. package/dist/core/config.js +0 -52
  48. package/dist/core/scan.js +0 -67
  49. package/dist/core/schema.js +0 -41
  50. package/dist/index.js +0 -72
  51. package/dist/services/JohankitService.js +0 -59
  52. package/dist/utils/cleanCodeBlock.js +0 -12
  53. package/dist/utils/createAsciiTree.js +0 -46
  54. package/johankit.yaml +0 -2
  55. package/src/cli/commands/tree.ts +0 -119
  56. package/src/services/JohankitService.ts +0 -70
  57. package/src/utils/createAsciiTree.ts +0 -53
  58. package/types.ts +0 -11
  59. /package/{types.js → dist/src/types.js} +0 -0
@@ -0,0 +1,173 @@
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
+ async function confirm(msg) {
53
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
54
+ return new Promise(resolve => {
55
+ rl.question(`${msg} (y/N): `, ans => {
56
+ rl.close();
57
+ resolve(ans.toLowerCase() === 'y');
58
+ });
59
+ });
60
+ }
61
+ function showDiff(filename, oldContent, newContent) {
62
+ console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
63
+ const patches = diff.diffLines(oldContent, newContent);
64
+ patches.forEach((part) => {
65
+ const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
66
+ const prefix = part.added ? '+' : part.removed ? '-' : ' ';
67
+ const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
68
+ process.stdout.write((value.split('\n').map((line) => line ? `${prefix}${line}` : '').join('\n'))[color]);
69
+ });
70
+ console.log('\n-----------------------');
71
+ }
72
+ async function processInput(input, dir, runAll, dryRun, interactiveDiff) {
73
+ const autoAccept = process.argv.includes('-y');
74
+ const { cleaned } = (0, cleanCodeBlock_1.default)(input);
75
+ let patchesData;
76
+ try {
77
+ patchesData = JSON.parse(cleaned);
78
+ }
79
+ catch (e) {
80
+ return false;
81
+ }
82
+ const patches = (0, schema_1.validatePatches)(patchesData);
83
+ if (patches.length === 0)
84
+ return false;
85
+ for (const patch of patches) {
86
+ if (patch.type === 'console' && patch.command) {
87
+ if (dryRun) {
88
+ process.stdout.write(`[DRY-RUN] Would execute: ${patch.command}\n`);
89
+ }
90
+ else if (runAll) {
91
+ const ok = autoAccept || await confirm(`> Execute: ${patch.command}`);
92
+ if (ok)
93
+ (0, child_process_1.execSync)(patch.command, { stdio: 'inherit', cwd: dir });
94
+ }
95
+ }
96
+ else if (patch.path) {
97
+ const fullPath = path_1.default.join(dir, patch.path);
98
+ const exists = fs_1.default.existsSync(fullPath);
99
+ const oldContent = exists ? fs_1.default.readFileSync(fullPath, 'utf8') : "";
100
+ const newContent = patch.content || "";
101
+ if (interactiveDiff && patch.content !== null) {
102
+ showDiff(patch.path, oldContent, newContent);
103
+ if (await confirm(`Apply changes to ${patch.path}?`)) {
104
+ (0, diff_1.applyDiff)(dir, [patch]);
105
+ }
106
+ else {
107
+ console.log(`Skipped: ${patch.path}`);
108
+ }
109
+ }
110
+ else if (dryRun) {
111
+ const action = patch.content === null ? "Delete" : "Write";
112
+ process.stdout.write(`[DRY-RUN] Would ${action}: ${patch.path}\n`);
113
+ }
114
+ else {
115
+ (0, diff_1.applyDiff)(dir, [patch]);
116
+ }
117
+ }
118
+ }
119
+ return true;
120
+ }
121
+ async function sync(dir, runAll = false, dryRun = false, interactiveDiff = false, watch = false) {
122
+ try {
123
+ const snapshotBefore = (0, scan_1.scanDir)(dir);
124
+ const systemPrompt = `
125
+ YOU ARE AN AI SOFTWARE ENGINEER.
126
+ ALWAYS RESPOND USING THE FOLLOWING JSON PATCH FORMAT ONLY.
127
+
128
+ FORMAT:
129
+ [{"path": "file.ts", "content": "full code"}, {"type": "console", "command": "npm install"}]
130
+
131
+ SNAPSHOT:
132
+ ${JSON.stringify(snapshotBefore, null, 2)}
133
+
134
+ PLEASE APPLY THE USER REQUESTS TO THIS SNAPSHOT AND RETURN ONLY THE JSON ARRAY.`;
135
+ await (0, clipboard_1.copyToClipboard)(systemPrompt);
136
+ process.stdout.write('√ System Prompt + Snapshot copied to clipboard.\n');
137
+ if (watch) {
138
+ process.stdout.write('√ Watching clipboard for AI response (Press Ctrl+C to stop)...\n');
139
+ let lastClipboard = await (0, clipboard_1.readClipboard)();
140
+ while (true) {
141
+ await new Promise(r => setTimeout(r, 1000));
142
+ const currentClipboard = await (0, clipboard_1.readClipboard)();
143
+ if (currentClipboard !== lastClipboard && currentClipboard.trim().length > 0) {
144
+ lastClipboard = currentClipboard;
145
+ const success = await processInput(currentClipboard, dir, runAll, dryRun, interactiveDiff);
146
+ if (success) {
147
+ process.stdout.write('√ Patch applied automatically from clipboard!\n');
148
+ const snapshotAfter = (0, scan_1.scanDir)(dir);
149
+ await (0, clipboard_1.copyToClipboard)(JSON.stringify(snapshotAfter, null, 2));
150
+ process.stdout.write('√ Updated snapshot copied to clipboard. Ready for next turn.\n');
151
+ }
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ process.stdout.write('√ Go to your AI, paste it, copy the result, and come back here.\n');
157
+ await confirm('Press [Enter] when you have the AI response in your clipboard...');
158
+ const input = await (0, clipboard_1.readClipboard)();
159
+ if (!input)
160
+ throw new Error("Clipboard is empty");
161
+ const success = await processInput(input, dir, runAll, dryRun, interactiveDiff);
162
+ if (success && !dryRun) {
163
+ const snapshotAfter = (0, scan_1.scanDir)(dir);
164
+ await (0, clipboard_1.copyToClipboard)(JSON.stringify(snapshotAfter, null, 2));
165
+ process.stdout.write('√ Sync applied! Updated snapshot is now in your clipboard.\n');
166
+ }
167
+ }
168
+ }
169
+ catch (e) {
170
+ process.stderr.write(`× Sync failed: ${e.message}\n`);
171
+ process.exit(1);
172
+ }
173
+ }
@@ -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,39 @@
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
+ const configFilenames = ["johankit.yaml", "johankit.yml"];
21
+ let loadedConfig = {};
22
+ for (const filename of configFilenames) {
23
+ const configPath = path_1.default.join(basePath, filename);
24
+ if ((0, fs_1.existsSync)(configPath)) {
25
+ try {
26
+ const content = (0, fs_1.readFileSync)(configPath, "utf8");
27
+ loadedConfig = (0, js_yaml_1.load)(content) || {};
28
+ break;
29
+ }
30
+ catch (error) {
31
+ console.warn(`[johankit] Erro ao ler ${filename}, tentando próximo...`);
32
+ }
33
+ }
34
+ }
35
+ const ignoreSet = new Set([...DEFAULT_IGNORE, ...(loadedConfig.ignore || [])]);
36
+ return {
37
+ ignore: Array.from(ignoreSet),
38
+ };
39
+ }
@@ -3,27 +3,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.applyDiff = void 0;
6
+ exports.applyDiff = applyDiff;
7
7
  // src/core/diff.ts
8
8
  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
- switch (patch.type) {
14
- case "delete": {
15
- if (fs_1.default.existsSync(fullPath)) {
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
  }
29
- exports.applyDiff = applyDiff;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ensureGitCommit = void 0;
6
+ exports.ensureGitCommit = ensureGitCommit;
7
7
  // src/core/git.ts
8
8
  const child_process_1 = require("child_process");
9
9
  const crypto_1 = __importDefault(require("crypto"));
@@ -40,4 +40,3 @@ function ensureGitCommit(message) {
40
40
  // noop: no git or nothing to commit
41
41
  }
42
42
  }
43
- exports.ensureGitCommit = ensureGitCommit;
@@ -0,0 +1,70 @@
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
+ function scanDir(basePath, options = {}) {
11
+ const result = [];
12
+ const base = path_1.default.resolve(basePath);
13
+ const exts = options.extensions?.map(e => e.startsWith('.') ? e : `.${e}`);
14
+ const config = (0, config_1.loadConfig)(base);
15
+ const ignorePatterns = new Set(config.ignore);
16
+ // Lê .gitignore e adiciona ao Set
17
+ const gitignorePath = path_1.default.join(base, '.gitignore');
18
+ if (fs_1.default.existsSync(gitignorePath)) {
19
+ try {
20
+ fs_1.default.readFileSync(gitignorePath, 'utf8')
21
+ .split('\n')
22
+ .forEach(line => {
23
+ const trimmed = line.trim();
24
+ if (trimmed && !trimmed.startsWith('#')) {
25
+ // Normaliza o caminho do gitignore para o padrão do scanner
26
+ ignorePatterns.add(trimmed.replace(/^\//, '').replace(/\/$/, ''));
27
+ }
28
+ });
29
+ }
30
+ catch { }
31
+ }
32
+ // PERFORMANCE: Converte o Set em Array UMA VEZ antes de iniciar o loop
33
+ const finalIgnoreList = Array.from(ignorePatterns);
34
+ function loop(currentPath) {
35
+ const entries = fs_1.default.readdirSync(currentPath, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ const isBinary = entry.name.match(/\.(png|jpg|jpeg|gif|pdf|zip|exe|dll|so|db)$/i);
38
+ if (isBinary)
39
+ continue;
40
+ const fullPath = path_1.default.join(currentPath, entry.name);
41
+ const relPath = path_1.default.relative(base, fullPath).replace(/\\/g, '/');
42
+ // PERFORMANCE: O some() agora opera sobre um array fixo, sem Array.from() interno
43
+ const shouldIgnore = finalIgnoreList.some(p => relPath === p || relPath.startsWith(p + '/'));
44
+ if (shouldIgnore)
45
+ continue;
46
+ if (entry.isDirectory()) {
47
+ loop(fullPath);
48
+ }
49
+ else {
50
+ if (exts && !exts.includes(path_1.default.extname(entry.name)))
51
+ continue;
52
+ try {
53
+ // PERFORMANCE: Pular arquivos muito grandes evita travar o clipboard
54
+ const stats = fs_1.default.statSync(fullPath);
55
+ if (stats.size > 1024 * 300)
56
+ continue; // Reduzi para 300KB para ser mais seguro
57
+ result.push({
58
+ path: relPath,
59
+ content: fs_1.default.readFileSync(fullPath, 'utf8')
60
+ });
61
+ }
62
+ catch (e) {
63
+ continue;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ loop(base);
69
+ return result;
70
+ }
@@ -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,34 @@
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
+ .action((dir = '.', opts) => (0, sync_1.sync)(dir, !!opts.run, !!opts.dryRun, !!opts.diff));
34
+ 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,21 @@
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 dentro ou fora de blocos de código markdown
7
+ const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/;
8
+ const match = content.match(codeBlockRegex);
9
+ let cleaned = match ? match[1] : content;
10
+ // Remove caracteres invisíveis como BOM (Byte Order Mark) e espaços extras
11
+ cleaned = cleaned.trim().replace(/\uFEFF/g, "");
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
+ }
20
+ return { cleaned };
21
+ }
package/dist/types.js CHANGED
@@ -1,2 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ // deleted as redundant to src/types.ts
package/johankit.yml ADDED
@@ -0,0 +1,6 @@
1
+ ignore:
2
+ - node_modules
3
+ - .git
4
+ - dist
5
+ - yarn.lock
6
+ - package-lock.json
package/package.json CHANGED
@@ -1,33 +1,43 @@
1
1
  {
2
2
  "name": "johankit",
3
- "version": "0.1.3",
4
- "description": "",
3
+ "version": "0.4.2",
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",
17
24
  "type": "commonjs",
18
25
  "devDependencies": {
26
+ "@types/diff": "^7.0.2",
19
27
  "@types/jest": "^29.5.3",
20
- "@types/node": "^25.0.1",
28
+ "@types/js-yaml": "^4.0.9",
29
+ "@types/node": "^20.0.0",
21
30
  "jest": "^29.5.0",
22
31
  "ts-jest": "^29.1.0",
23
32
  "ts-node": "^10.9.2",
24
- "typescript": "^5.9.3"
33
+ "typescript": "^5.0.0"
25
34
  },
26
35
  "dependencies": {
27
- "clipboardy": "^5.0.2",
28
- "commander": "^14.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
  }