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
package/src/cli/commands/copy.ts
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
import { scanDir } from "../../core/scan";
|
|
2
2
|
import { copyToClipboard } from "../../core/clipboard";
|
|
3
3
|
|
|
4
|
-
export async function copy(
|
|
5
|
-
|
|
4
|
+
export async function copy(dir: string, extensions?: string[]) {
|
|
5
|
+
const snapshot = scanDir(dir, { extensions });
|
|
6
|
+
const clipboardJSON = JSON.stringify(snapshot);
|
|
7
|
+
await copyToClipboard(clipboardJSON);
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
for (const p of paths) {
|
|
10
|
-
const scanned = scanDir(p);
|
|
11
|
-
snapshot.push(...scanned);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const clipboardJSON = JSON.stringify(snapshot, null, 2);
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
await copyToClipboard(clipboardJSON);
|
|
18
|
-
} catch (err) {
|
|
19
|
-
console.warn("Clipboard unavailable:", err);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return clipboardJSON;
|
|
23
|
-
}
|
|
9
|
+
process.stdout.write(`✔ Snapshot de ${dir} copiado (${(clipboardJSON.length / 1024).toFixed(2)} KB)\n`);
|
|
10
|
+
}
|
|
@@ -1,43 +1,91 @@
|
|
|
1
1
|
// src/cli/commands/paste.ts
|
|
2
|
-
import {
|
|
2
|
+
import { applyDiff } from "../../core/diff";
|
|
3
3
|
import { readClipboard } from "../../core/clipboard";
|
|
4
|
+
import { validatePatches } from "../../core/schema";
|
|
4
5
|
import cleanCodeBlock from "../../utils/cleanCodeBlock";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import * as diff from "diff";
|
|
11
|
+
import "colors";
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
async function confirm(msg: string): Promise<boolean> {
|
|
14
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
return new Promise(resolve => {
|
|
16
|
+
rl.question(`${msg} (y/N): `, (ans) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(ans.toLowerCase() === 'y');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
function showDiff(filename: string, oldContent: string, newContent: string) {
|
|
24
|
+
console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
|
|
25
|
+
// Otimização: Usar diffLines apenas se o conteúdo for diferente para evitar processamento inútil
|
|
26
|
+
if (oldContent === newContent) {
|
|
27
|
+
console.log("No changes detected.".gray);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const patches = diff.diffLines(oldContent, newContent);
|
|
31
|
+
patches.forEach((part) => {
|
|
32
|
+
const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
|
|
33
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
34
|
+
const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
|
|
35
|
+
process.stdout.write((value.split('\n').map(line => line ? `${prefix}${line}` : '').join('\n'))[color as any]);
|
|
36
|
+
});
|
|
37
|
+
console.log('\n-----------------------');
|
|
38
|
+
}
|
|
15
39
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
export async function paste(dir: string, runAll = false, dryRun = false, interactiveDiff = false) {
|
|
41
|
+
const autoAccept = process.argv.includes("-y");
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const content = await readClipboard();
|
|
45
|
+
if (!content) throw new Error("Clipboard empty");
|
|
46
|
+
|
|
47
|
+
const { cleaned } = cleanCodeBlock(content);
|
|
48
|
+
const items = validatePatches(JSON.parse(cleaned));
|
|
23
49
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
if (dryRun) process.stdout.write("--- DRY RUN MODE ---\n");
|
|
51
|
+
|
|
52
|
+
// Otimização: Agrupar patches de arquivos para aplicar de uma vez se não for interativo
|
|
53
|
+
const filePatches = [];
|
|
54
|
+
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
if (item.type === 'console' && item.command) {
|
|
57
|
+
if (dryRun) {
|
|
58
|
+
process.stdout.write(`[DRY-RUN] Would execute: ${item.command}\n`);
|
|
59
|
+
} else if (runAll) {
|
|
60
|
+
if (autoAccept || await confirm(`> Execute: ${item.command}`)) {
|
|
61
|
+
execSync(item.command, { stdio: 'inherit', cwd: dir });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else if (item.path) {
|
|
65
|
+
if (interactiveDiff && item.content !== null) {
|
|
66
|
+
const fullPath = path.join(dir, item.path);
|
|
67
|
+
const oldContent = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : "";
|
|
68
|
+
showDiff(item.path, oldContent, item.content || "");
|
|
69
|
+
if (await confirm(`Apply changes to ${item.path}?`)) {
|
|
70
|
+
applyDiff(dir, [item]);
|
|
71
|
+
} else {
|
|
72
|
+
console.log(`Skipped: ${item.path}`);
|
|
73
|
+
}
|
|
74
|
+
} else if (dryRun) {
|
|
75
|
+
process.stdout.write(`[DRY-RUN] Would ${item.content === null ? 'Delete' : 'Write'}: ${item.path}\n`);
|
|
31
76
|
} else {
|
|
32
|
-
|
|
77
|
+
filePatches.push(item);
|
|
33
78
|
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
34
81
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
process.stderr.write("✖ Paste failed\n");
|
|
38
|
-
if (error instanceof Error) {
|
|
39
|
-
process.stderr.write(`${error.message}\n`);
|
|
40
|
-
}
|
|
41
|
-
process.exit(1);
|
|
82
|
+
if (filePatches.length > 0) {
|
|
83
|
+
applyDiff(dir, filePatches);
|
|
42
84
|
}
|
|
85
|
+
|
|
86
|
+
process.stdout.write("✔ Operation completed\n");
|
|
87
|
+
} catch (error: any) {
|
|
88
|
+
process.stderr.write(`✘ Failed: ${error.message}\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
43
91
|
}
|
|
@@ -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,131 @@
|
|
|
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
|
-
|
|
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
|
+
const INTERNAL_MARKER = "__JOHANKIT_INTERNAL__";
|
|
15
|
+
|
|
16
|
+
async function confirm(msg: string): Promise<boolean> {
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
return new Promise(resolve => {
|
|
19
|
+
rl.question(`${msg} (y/N): `, ans => {
|
|
20
|
+
rl.close();
|
|
21
|
+
resolve(ans.toLowerCase() === 'y');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
22
24
|
}
|
|
23
|
-
\`\`\`
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
SNAPSHOT
|
|
28
|
-
${JSON.stringify(snapshotBefore, null, 2)}
|
|
29
|
-
|
|
30
|
-
---
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
function showDiff(filename: string, oldContent: string, newContent: string) {
|
|
27
|
+
console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
|
|
28
|
+
const patches = diff.diffLines(oldContent, newContent);
|
|
29
|
+
patches.forEach((part) => {
|
|
30
|
+
const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
|
|
31
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
32
|
+
const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
|
|
33
|
+
process.stdout.write((value.split('\n').map(line => line ? `${prefix}${line}` : '').join('\n'))[color as any]);
|
|
34
|
+
});
|
|
35
|
+
console.log('\n-----------------------');
|
|
36
|
+
}
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
async function processInput(input: string, dir: string, runAll: boolean, dryRun: boolean, interactiveDiff: boolean) {
|
|
39
|
+
const autoAccept = process.argv.includes('-y');
|
|
40
|
+
const { cleaned } = cleanCodeBlock(input);
|
|
41
|
+
let patchesData;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
patchesData = JSON.parse(cleaned);
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
const patches = validatePatches(patchesData);
|
|
50
|
+
if (patches.length === 0) return false;
|
|
51
|
+
|
|
52
|
+
for (const patch of patches) {
|
|
53
|
+
if (patch.type === 'console' && patch.command) {
|
|
54
|
+
if (dryRun) {
|
|
55
|
+
process.stdout.write(`[DRY-RUN] Would execute: ${patch.command}\n`);
|
|
56
|
+
} else if (runAll) {
|
|
57
|
+
const ok = autoAccept || await confirm(`> Execute: ${patch.command}`);
|
|
58
|
+
if (ok) execSync(patch.command, { stdio: 'inherit', cwd: dir });
|
|
59
|
+
}
|
|
60
|
+
} else if (patch.path) {
|
|
61
|
+
const fullPath = path.join(dir, patch.path);
|
|
62
|
+
const exists = fs.existsSync(fullPath);
|
|
63
|
+
const oldContent = exists ? fs.readFileSync(fullPath, 'utf8') : "";
|
|
64
|
+
const newContent = patch.content || "";
|
|
65
|
+
|
|
66
|
+
if (interactiveDiff && patch.content !== null) {
|
|
67
|
+
showDiff(patch.path, oldContent, newContent);
|
|
68
|
+
if (await confirm(`Apply changes to ${patch.path}?`)) {
|
|
69
|
+
applyDiff(dir, [patch]);
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`Skipped: ${patch.path}`);
|
|
72
|
+
}
|
|
73
|
+
} else if (dryRun) {
|
|
74
|
+
const action = patch.content === null ? "Delete" : "Write";
|
|
75
|
+
process.stdout.write(`[DRY-RUN] Would ${action}: ${patch.path}\n`);
|
|
76
|
+
} else {
|
|
77
|
+
applyDiff(dir, [patch]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
41
82
|
}
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
84
|
+
export async function sync(dir: string, runAll = false, dryRun = false, interactiveDiff = false, watch = false) {
|
|
85
|
+
try {
|
|
86
|
+
const snapshotBefore = scanDir(dir);
|
|
87
|
+
const systemPrompt = `YOU ARE AN AI SOFTWARE ENGINEER.\nALWAYS RESPOND USING ONLY A JSON PATCH ARRAY.\n\nSNAPSHOT:\n${JSON.stringify(snapshotBefore, null, 2)}`;
|
|
47
88
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`;
|
|
89
|
+
await copyToClipboard(INTERNAL_MARKER + systemPrompt);
|
|
90
|
+
process.stdout.write("[johankit] System Prompt + Snapshot copied to clipboard\n");
|
|
51
91
|
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
if (watch) {
|
|
93
|
+
process.stdout.write("[johankit] Watching clipboard for AI response (Ctrl+C to stop)\n");
|
|
94
|
+
let lastClipboard = await readClipboard();
|
|
54
95
|
|
|
55
|
-
|
|
96
|
+
while (true) {
|
|
97
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
98
|
+
const currentClipboard = await readClipboard();
|
|
56
99
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
patches = JSON.parse(input);
|
|
60
|
-
} catch {
|
|
61
|
-
throw new Error("Invalid JSON input");
|
|
62
|
-
}
|
|
100
|
+
if (!currentClipboard || currentClipboard === lastClipboard) continue;
|
|
101
|
+
lastClipboard = currentClipboard;
|
|
63
102
|
|
|
64
|
-
|
|
65
|
-
const validated = validatePatches(patches);
|
|
66
|
-
applyDiff(dir, validated);
|
|
67
|
-
} else {
|
|
68
|
-
writeFiles(dir, patches as any, true);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const snapshotAfter = scanDir(dir);
|
|
72
|
-
await copyToClipboard(JSON.stringify(snapshotAfter, null, 2));
|
|
103
|
+
if (currentClipboard.startsWith(INTERNAL_MARKER)) continue;
|
|
73
104
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
105
|
+
const success = await processInput(currentClipboard, dir, runAll, dryRun, interactiveDiff);
|
|
106
|
+
if (success) {
|
|
107
|
+
process.stdout.write("[johankit] Patch applied from clipboard\n");
|
|
108
|
+
const snapshotAfter = scanDir(dir);
|
|
109
|
+
await copyToClipboard(INTERNAL_MARKER + JSON.stringify(snapshotAfter, null, 2));
|
|
110
|
+
process.stdout.write("[johankit] Updated snapshot copied to clipboard\n");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
process.stdout.write("[johankit] Paste AI response into clipboard and press Enter\n");
|
|
115
|
+
await confirm('');
|
|
116
|
+
|
|
117
|
+
const input = await readClipboard();
|
|
118
|
+
if (!input) throw new Error("Clipboard is empty");
|
|
119
|
+
|
|
120
|
+
const success = await processInput(input, dir, runAll, dryRun, interactiveDiff);
|
|
121
|
+
if (success && !dryRun) {
|
|
122
|
+
const snapshotAfter = scanDir(dir);
|
|
123
|
+
await copyToClipboard(INTERNAL_MARKER + JSON.stringify(snapshotAfter, null, 2));
|
|
124
|
+
process.stdout.write("[johankit] Sync applied and snapshot copied to clipboard\n");
|
|
125
|
+
}
|
|
79
126
|
}
|
|
127
|
+
} catch (e: any) {
|
|
128
|
+
process.stderr.write(`[johankit] Sync failed: ${e.message}\n`);
|
|
80
129
|
process.exit(1);
|
|
81
130
|
}
|
|
82
131
|
}
|
|
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
|
-
}
|
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
|
+
}
|