johankit 0.1.5 → 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 +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 +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 +9 -12
- 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 -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
|
@@ -2,81 +2,41 @@
|
|
|
2
2
|
import { scanDir } from "../../core/scan";
|
|
3
3
|
import { copyToClipboard } from "../../core/clipboard";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
type: 'FileSnapshot' | 'DiffPatch';
|
|
7
|
-
example: any;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function prompt(dir: string, userPrompt: string, diff = false) {
|
|
5
|
+
export async function prompt(dir: string, userPrompt: string) {
|
|
11
6
|
const snapshot = scanDir(dir);
|
|
12
7
|
|
|
13
|
-
const llmExamples: LLMResponseExample[] = [
|
|
14
|
-
{
|
|
15
|
-
type: 'FileSnapshot',
|
|
16
|
-
example: [
|
|
17
|
-
{
|
|
18
|
-
path: 'src/example.ts',
|
|
19
|
-
content: 'export const x = 42;'
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
type: 'DiffPatch',
|
|
25
|
-
example: [
|
|
26
|
-
{
|
|
27
|
-
type: 'modify',
|
|
28
|
-
path: 'src/example.ts',
|
|
29
|
-
content: 'export const x = 43;'
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
type: 'create',
|
|
33
|
-
path: 'src/newFile.ts',
|
|
34
|
-
content: 'export const newFile = true;'
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
type: 'delete',
|
|
38
|
-
path: 'src/oldFile.ts'
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
];
|
|
43
|
-
|
|
44
8
|
const template = `
|
|
45
|
-
You are an AI software engineer.
|
|
46
|
-
|
|
47
|
-
You will receive a JSON array representing a snapshot of a codebase.
|
|
48
|
-
Each item has the following structure:
|
|
49
|
-
{
|
|
50
|
-
"path": "relative/path/to/file.ext",
|
|
51
|
-
"content": "full file content"
|
|
52
|
-
}
|
|
9
|
+
You are an AI software engineer.
|
|
10
|
+
Your goal is to help the user with their codebase using a specific JSON patch format.
|
|
53
11
|
|
|
12
|
+
Maintain 100% of previous functionality for full compatibility.
|
|
13
|
+
Always submit the complete code; never use comments.
|
|
14
|
+
Use "git add {files} && git commit -m {message}" after each change via the console.
|
|
54
15
|
---
|
|
55
16
|
|
|
56
|
-
|
|
57
|
-
|
|
17
|
+
### CAPABILITIES
|
|
18
|
+
1. **File Updates**: You can create, update, or delete files.
|
|
19
|
+
2. **Console Commands**: You can execute shell commands (e.g., npm install, mkdir, rm, vitest).
|
|
58
20
|
|
|
59
|
-
|
|
21
|
+
### RESPONSE FORMAT
|
|
22
|
+
Return ONLY a JSON array. No conversational text. No explanations.
|
|
23
|
+
Wrap the JSON in a markdown code block: \`\`\`json [your_array] \`\`\`
|
|
60
24
|
|
|
61
|
-
|
|
62
|
-
|
|
25
|
+
### PATCH TYPES
|
|
26
|
+
- **File Patch**: { "path": "src/file.ts", "content": "full code" }
|
|
27
|
+
- **Delete File**: { "path": "src/old-file.ts", "content": null }
|
|
28
|
+
- **Console**: { "type": "console", "command": "npm install lodash" }
|
|
63
29
|
|
|
64
|
-
|
|
30
|
+
### STRATEGY
|
|
31
|
+
If the user request requires a new library, include the "npm install" command in the array before the file updates.
|
|
32
|
+
If the user wants to refactor and ensure it works, you can include a command to run tests.
|
|
65
33
|
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
\"path\": \"relative/path/to/file.ext\",
|
|
69
|
-
\"content\": \"FULL updated file content (omit for delete)\"
|
|
70
|
-
}
|
|
34
|
+
---
|
|
71
35
|
|
|
72
|
-
|
|
73
|
-
${JSON.stringify(
|
|
36
|
+
SNAPSHOT
|
|
37
|
+
${JSON.stringify(snapshot, null, 2)}
|
|
74
38
|
|
|
75
|
-
|
|
76
|
-
- Do NOT return explanations
|
|
77
|
-
- Do NOT return markdown
|
|
78
|
-
- Return ONLY valid JSON inside the \"\`\`\`\"
|
|
79
|
-
- Always return within a Markdown Code Block.
|
|
39
|
+
---
|
|
80
40
|
|
|
81
41
|
USER REQUEST
|
|
82
42
|
${userPrompt}
|
|
@@ -85,7 +45,7 @@ ${userPrompt}
|
|
|
85
45
|
try {
|
|
86
46
|
await copyToClipboard(template.trim());
|
|
87
47
|
process.stdout.write(template.trim());
|
|
88
|
-
process.stdout.write("\n\n✔ Prompt + Snapshot
|
|
48
|
+
process.stdout.write("\n\n✔ Prompt + Snapshot copied to clipboard\n");
|
|
89
49
|
} catch (e) {
|
|
90
50
|
process.stdout.write(template.trim());
|
|
91
51
|
process.stderr.write("\n✖ Failed to copy to clipboard (output only)\n");
|
package/src/cli/commands/sync.ts
CHANGED
|
@@ -1,90 +1,138 @@
|
|
|
1
1
|
// src/cli/commands/sync.ts
|
|
2
2
|
import { scanDir } from "../../core/scan";
|
|
3
|
+
import { validatePatches } from "../../core/schema";
|
|
3
4
|
import { applyDiff } from "../../core/diff";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"content": "full file content"
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import cleanCodeBlock from "../../utils/cleanCodeBlock";
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
import { copyToClipboard, readClipboard } from "../../core/clipboard";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import * as diff from "diff";
|
|
12
|
+
import "colors";
|
|
13
|
+
|
|
14
|
+
async function confirm(msg: string): Promise<boolean> {
|
|
15
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
+
return new Promise(resolve => {
|
|
17
|
+
rl.question(`${msg} (y/N): `, ans => {
|
|
18
|
+
rl.close();
|
|
19
|
+
resolve(ans.toLowerCase() === 'y');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
22
|
}
|
|
23
|
-
\`\`\`
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Return ONLY a JSON array of ${diff ? 'DiffPatch' : 'FileSnapshot'}.
|
|
36
|
-
|
|
37
|
-
PATCH FORMAT (STRICT)
|
|
38
|
-
{
|
|
39
|
-
\"path\": \"relative/path/to/file.ext\",
|
|
40
|
-
\"content\": \"FULL updated file content (omit for delete)\"
|
|
24
|
+
function showDiff(filename: string, oldContent: string, newContent: string) {
|
|
25
|
+
console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
|
|
26
|
+
const patches = diff.diffLines(oldContent, newContent);
|
|
27
|
+
patches.forEach((part: diff.Change) => {
|
|
28
|
+
const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
|
|
29
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
30
|
+
const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
|
|
31
|
+
process.stdout.write((value.split('\n').map((line: string) => line ? `${prefix}${line}` : '').join('\n'))[color as any]);
|
|
32
|
+
});
|
|
33
|
+
console.log('\n-----------------------');
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
async function processInput(input: string, dir: string, runAll: boolean, dryRun: boolean, interactiveDiff: boolean) {
|
|
37
|
+
const autoAccept = process.argv.includes('-y');
|
|
38
|
+
const { cleaned } = cleanCodeBlock(input);
|
|
39
|
+
let patchesData;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
patchesData = JSON.parse(cleaned);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const patches = validatePatches(patchesData);
|
|
48
|
+
if (patches.length === 0) return false;
|
|
49
|
+
|
|
50
|
+
for (const patch of patches) {
|
|
51
|
+
if (patch.type === 'console' && patch.command) {
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
process.stdout.write(`[DRY-RUN] Would execute: ${patch.command}\n`);
|
|
54
|
+
} else if (runAll) {
|
|
55
|
+
const ok = autoAccept || await confirm(`> Execute: ${patch.command}`);
|
|
56
|
+
if (ok) execSync(patch.command, { stdio: 'inherit', cwd: dir });
|
|
57
|
+
}
|
|
58
|
+
} else if (patch.path) {
|
|
59
|
+
const fullPath = path.join(dir, patch.path);
|
|
60
|
+
const exists = fs.existsSync(fullPath);
|
|
61
|
+
const oldContent = exists ? fs.readFileSync(fullPath, 'utf8') : "";
|
|
62
|
+
const newContent = patch.content || "";
|
|
63
|
+
|
|
64
|
+
if (interactiveDiff && patch.content !== null) {
|
|
65
|
+
showDiff(patch.path, oldContent, newContent);
|
|
66
|
+
if (await confirm(`Apply changes to ${patch.path}?`)) {
|
|
67
|
+
applyDiff(dir, [patch]);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`Skipped: ${patch.path}`);
|
|
70
|
+
}
|
|
71
|
+
} else if (dryRun) {
|
|
72
|
+
const action = patch.content === null ? "Delete" : "Write";
|
|
73
|
+
process.stdout.write(`[DRY-RUN] Would ${action}: ${patch.path}\n`);
|
|
74
|
+
} else {
|
|
75
|
+
applyDiff(dir, [patch]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
51
81
|
|
|
52
|
-
|
|
53
|
-
|
|
82
|
+
export async function sync(dir: string, runAll = false, dryRun = false, interactiveDiff = false, watch = false) {
|
|
83
|
+
try {
|
|
84
|
+
const snapshotBefore = scanDir(dir);
|
|
85
|
+
const systemPrompt = `
|
|
86
|
+
YOU ARE AN AI SOFTWARE ENGINEER.
|
|
87
|
+
ALWAYS RESPOND USING THE FOLLOWING JSON PATCH FORMAT ONLY.
|
|
54
88
|
|
|
55
|
-
|
|
89
|
+
FORMAT:
|
|
90
|
+
[{"path": "file.ts", "content": "full code"}, {"type": "console", "command": "npm install"}]
|
|
56
91
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
patches = JSON.parse(input);
|
|
60
|
-
} catch {
|
|
61
|
-
throw new Error("Invalid JSON input");
|
|
62
|
-
}
|
|
92
|
+
SNAPSHOT:
|
|
93
|
+
${JSON.stringify(snapshotBefore, null, 2)}
|
|
63
94
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
95
|
+
PLEASE APPLY THE USER REQUESTS TO THIS SNAPSHOT AND RETURN ONLY THE JSON ARRAY.`;
|
|
96
|
+
|
|
97
|
+
await copyToClipboard(systemPrompt);
|
|
98
|
+
process.stdout.write('√ System Prompt + Snapshot copied to clipboard.\n');
|
|
99
|
+
|
|
100
|
+
if (watch) {
|
|
101
|
+
process.stdout.write('√ Watching clipboard for AI response (Press Ctrl+C to stop)...\n');
|
|
102
|
+
let lastClipboard = await readClipboard();
|
|
103
|
+
|
|
104
|
+
while (true) {
|
|
105
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
106
|
+
const currentClipboard = await readClipboard();
|
|
107
|
+
|
|
108
|
+
if (currentClipboard !== lastClipboard && currentClipboard.trim().length > 0) {
|
|
109
|
+
lastClipboard = currentClipboard;
|
|
110
|
+
const success = await processInput(currentClipboard, dir, runAll, dryRun, interactiveDiff);
|
|
111
|
+
if (success) {
|
|
112
|
+
process.stdout.write('√ Patch applied automatically from clipboard!\n');
|
|
113
|
+
const snapshotAfter = scanDir(dir);
|
|
114
|
+
await copyToClipboard(JSON.stringify(snapshotAfter, null, 2));
|
|
115
|
+
process.stdout.write('√ Updated snapshot copied to clipboard. Ready for next turn.\n');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
67
119
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
120
|
+
process.stdout.write('√ Go to your AI, paste it, copy the result, and come back here.\n');
|
|
121
|
+
await confirm('Press [Enter] when you have the AI response in your clipboard...');
|
|
122
|
+
|
|
123
|
+
const input = await readClipboard();
|
|
124
|
+
if (!input) throw new Error("Clipboard is empty");
|
|
125
|
+
|
|
126
|
+
const success = await processInput(input, dir, runAll, dryRun, interactiveDiff);
|
|
127
|
+
|
|
128
|
+
if (success && !dryRun) {
|
|
129
|
+
const snapshotAfter = scanDir(dir);
|
|
130
|
+
await copyToClipboard(JSON.stringify(snapshotAfter, null, 2));
|
|
131
|
+
process.stdout.write('√ Sync applied! Updated snapshot is now in your clipboard.\n');
|
|
132
|
+
}
|
|
79
133
|
}
|
|
134
|
+
} catch (e: any) {
|
|
135
|
+
process.stderr.write(`× Sync failed: ${e.message}\n`);
|
|
80
136
|
process.exit(1);
|
|
81
137
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function readStdin(): Promise<string> {
|
|
85
|
-
return new Promise(resolve => {
|
|
86
|
-
let data = "";
|
|
87
|
-
process.stdin.on("data", c => (data += c));
|
|
88
|
-
process.stdin.on("end", () => resolve(data));
|
|
89
|
-
});
|
|
90
138
|
}
|
package/src/core/clipboard.ts
CHANGED
|
@@ -1,94 +1,60 @@
|
|
|
1
1
|
// src/core/clipboard.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
|
+
import clipboardy from "clipboardy";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
export async function copyToClipboard(text: string): Promise<void> {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
|
|
8
|
+
const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP;
|
|
5
9
|
|
|
6
|
-
export function copyToClipboard(text: string): Promise<void> {
|
|
7
10
|
return new Promise((resolve, reject) => {
|
|
8
|
-
let command = "
|
|
9
|
-
let args = [
|
|
11
|
+
let command = "";
|
|
12
|
+
let args: string[] = [];
|
|
10
13
|
|
|
11
|
-
if (
|
|
14
|
+
if (isWSL) {
|
|
15
|
+
command = "clip.exe";
|
|
16
|
+
} else if (platform === "darwin") {
|
|
12
17
|
command = "pbcopy";
|
|
13
|
-
|
|
14
|
-
} else if (process.platform === "win32") {
|
|
18
|
+
} else if (platform === "win32") {
|
|
15
19
|
command = "clip";
|
|
16
|
-
args = [];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const child = spawn(command, args, {
|
|
20
|
-
stdio: ["pipe", "ignore", "ignore"]
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
let resolved = false;
|
|
24
|
-
|
|
25
|
-
child.on("error", (err) => {
|
|
26
|
-
memoryClipboard = text;
|
|
27
|
-
resolved = true;
|
|
28
|
-
resolve();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
child.stdin.on("error", (err: any) => {
|
|
32
|
-
if (err.code === "EPIPE") {
|
|
33
|
-
resolved = true;
|
|
34
|
-
resolve();
|
|
35
|
-
} else {
|
|
36
|
-
memoryClipboard = text;
|
|
37
|
-
if (!resolved) resolve();
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
child.stdin.write(text);
|
|
42
|
-
child.stdin.end();
|
|
43
|
-
|
|
44
|
-
child.on("close", () => {
|
|
45
|
-
if (!resolved) resolve();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function readClipboard(): Promise<string> {
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
let command: string;
|
|
53
|
-
let args: string[] = [];
|
|
54
|
-
|
|
55
|
-
if (process.platform === "darwin") {
|
|
56
|
-
command = "pbpaste";
|
|
57
|
-
} else if (process.platform === "win32") {
|
|
58
|
-
command = "powershell";
|
|
59
|
-
args = ["-Command", "Get-Clipboard"];
|
|
60
20
|
} else {
|
|
61
21
|
command = "xclip";
|
|
62
|
-
args = ["-selection", "clipboard"
|
|
22
|
+
args = ["-selection", "clipboard"];
|
|
63
23
|
}
|
|
64
24
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
child.on("error", () => {
|
|
82
|
-
fallback = true;
|
|
83
|
-
resolve(memoryClipboard);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
child.on("close", code => {
|
|
87
|
-
if (code !== 0 || fallback) {
|
|
88
|
-
resolve(memoryClipboard);
|
|
89
|
-
} else {
|
|
90
|
-
resolve(data);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
25
|
+
try {
|
|
26
|
+
const child = spawn(command, args);
|
|
27
|
+
|
|
28
|
+
child.on("error", (err) => reject(err));
|
|
29
|
+
child.on("close", (code) => {
|
|
30
|
+
if (code === 0) resolve();
|
|
31
|
+
else reject(new Error(`Clipboard error: ${code}`));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
child.stdin.write(text);
|
|
35
|
+
child.stdin.end();
|
|
36
|
+
} catch (e) {
|
|
37
|
+
reject(e);
|
|
38
|
+
}
|
|
93
39
|
});
|
|
94
40
|
}
|
|
41
|
+
|
|
42
|
+
export async function readClipboard(): Promise<string> {
|
|
43
|
+
try {
|
|
44
|
+
return await clipboardy.read();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const platform = process.platform;
|
|
48
|
+
let command = platform === "darwin" ? "pbpaste" : platform === "win32" ? "powershell" : "xclip";
|
|
49
|
+
let args = platform === "win32" ? ["-NoProfile", "-Command", "Get-Clipboard"] :
|
|
50
|
+
platform === "linux" ? ["-selection", "clipboard", "-o"] : [];
|
|
51
|
+
|
|
52
|
+
const child = spawn(command, args);
|
|
53
|
+
let output = "";
|
|
54
|
+
|
|
55
|
+
child.stdout.on("data", (d) => (output += d.toString()));
|
|
56
|
+
child.on("close", () => resolve(output.trim()));
|
|
57
|
+
child.on("error", () => resolve(""));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
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
|
+
}
|