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
package/README.md CHANGED
@@ -2,109 +2,77 @@
2
2
 
3
3
  **JohanKit** is a developer-friendly CLI designed to supercharge your **vibe-coding** flow. It helps you capture, restore, and manipulate snapshots of your codebase, making it effortless to experiment, refactor, and collaborate—without locking you into a specific framework or workflow.
4
4
 
5
- Think of it as your personal **code snapshot toolkit**: lightweight, fast, and agnostic.
6
-
7
- ---
8
-
9
- ## Why JohanKit?
10
-
11
- JohanKit is a “kit for developers,” crafted to streamline coding sessions, prototype features quickly, and integrate seamlessly with AI-assisted refactoring or review tools.
5
+ Think of it as your personal **code snapshot toolkit**: lightweight, fast, and platform-agnostic.
12
6
 
13
7
  ---
14
8
 
15
9
  ## Features
16
10
 
17
- ### copy
18
-
19
- Take a snapshot of files in a directory and copy it to your clipboard as JSON.
20
-
11
+ ### 📸 copy
12
+ Take a snapshot of files in a directory and copy it to your clipboard as a JSON array.
21
13
  ```bash
22
14
  johankit copy <dir> [exts]
23
- ````
24
-
15
+ ```
25
16
  * `dir`: Directory to scan (default: current)
26
17
  * `exts`: Comma-separated list of extensions (e.g., `ts,js`)
27
18
 
28
- ---
29
-
30
- ### paste
31
-
32
- Restore files from a JSON snapshot stored in your clipboard.
33
-
19
+ ### 📋 paste
20
+ Restore files or apply patches from a JSON snapshot stored in your clipboard.
34
21
  ```bash
35
- johankit paste <dir>
22
+ johankit paste [dir] [--run] [--diff] [--dry-run]
36
23
  ```
24
+ * `--run`: Execute console commands included in the patch.
25
+ * `--diff`: Interactive mode. Shows a line-by-line diff and asks for confirmation before applying changes to each file.
26
+ * `--dry-run`: Preview changes without modifying any files.
27
+ * `-y`: Auto-accept all prompts.
37
28
 
38
- * `dir`: Directory where files will be created (default: current)
39
-
40
- ---
41
-
42
- ### prompt
43
-
44
- Generate a ready-to-use AI prompt including a snapshot of your codebase.
45
-
29
+ ### 🤖 prompt
30
+ Generate a ready-to-use AI prompt containing your codebase snapshot and your instructions.
46
31
  ```bash
47
32
  johankit prompt <dir> "<user request>"
48
33
  ```
34
+ * `dir`: Directory to include in the context.
35
+ * `<user request>`: Instructions for the AI to perform on the codebase.
49
36
 
50
- * `dir`: Directory to scan (default: current)
51
- * `<user request>`: Instruction for AI to apply on the snapshot
52
-
53
- ---
54
-
55
- ### sync
56
-
57
- Apply JSON patches to your codebase and update the clipboard with the new snapshot.
58
-
37
+ ### 🔄 sync
38
+ Two-way synchronization: copies the current snapshot, waits for your AI-generated patch input via terminal (Ctrl+D to finish), applies it, and copies the updated snapshot back to the clipboard.
59
39
  ```bash
60
- johankit sync <dir>
40
+ johankit sync [dir] [--run] [--dry-run]
61
41
  ```
62
42
 
63
- * `dir`: Target directory (default: current)
64
-
65
43
  ---
66
44
 
67
45
  ## Example Workflow
68
46
 
69
- ```bash
70
- # Copy snapshot of your current src
71
- johankit copy src
72
-
73
- # Ask AI to refactor your codebase
74
- johankit prompt src "Convert all callbacks to async/await"
75
-
76
- # Apply AI-suggested changes and update snapshot
77
- johankit sync src
78
- ```
79
-
80
- With just a few commands, JohanKit lets you **capture ideas, experiment safely, and sync changes instantly**.
47
+ 1. **Prepare Context**: `johankit prompt src "Add input validation to all API routes"`
48
+ 2. **Get AI Response**: Copy the JSON patch array generated by your favorite LLM.
49
+ 3. **Review & Apply**: `johankit paste --diff`
81
50
 
82
51
  ---
83
52
 
84
53
  ## Configuration
85
54
 
86
- You can configure defaults with a `johankit.yaml` file in the project root:
55
+ Customize ignored patterns by creating a `johankit.yaml` or `johankit.yml` in your project root:
87
56
 
88
57
  ```yaml
89
58
  ignore:
90
59
  - dist
91
60
  - node_modules
92
61
  - .git
62
+ - custom-build-folder
93
63
  ```
94
64
 
95
- This ensures you never accidentally snapshot unnecessary files.
65
+ JohanKit also automatically respects patterns found in your `.gitignore` file.
96
66
 
97
67
  ---
98
68
 
99
69
  ## Requirements
100
70
 
101
71
  * Node.js >= 14
102
- * Clipboard-compatible OS (`xclip`, `pbcopy`, or `clip` on Windows)
103
-
104
- ---
72
+ * Clipboard-compatible OS (`pbcopy`, `xclip`, or Windows `clip`)
105
73
 
106
74
  ## Installation
107
75
 
108
76
  ```bash
109
77
  npm install -g johankit
110
- ```
78
+ ```
@@ -1,84 +1,63 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sync = void 0;
6
+ exports.sync = sync;
4
7
  // src/cli/commands/sync.ts
5
8
  const scan_1 = require("../../core/scan");
9
+ const schema_1 = require("../../core/schema");
6
10
  const diff_1 = require("../../core/diff");
7
11
  const clipboard_1 = require("../../core/clipboard");
8
- const validation_1 = require("../../core/validation");
9
- const write_1 = require("../../core/write");
10
- async function sync(dir, diff = false) {
12
+ const child_process_1 = require("child_process");
13
+ const cleanCodeBlock_1 = __importDefault(require("../../utils/cleanCodeBlock"));
14
+ const readline_1 = __importDefault(require("readline"));
15
+ async function confirm(msg) {
16
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
17
+ return new Promise(resolve => {
18
+ rl.question(`${msg} (y/N): `, (ans) => {
19
+ rl.close();
20
+ resolve(ans.toLowerCase() === 'y');
21
+ });
22
+ });
23
+ }
24
+ async function sync(dir, runAll = false) {
25
+ const autoAccept = process.argv.includes("-y");
11
26
  try {
12
27
  const snapshotBefore = (0, scan_1.scanDir)(dir);
13
28
  const template = `
14
- You are an AI software engineer.
15
-
16
- You will receive a JSON array representing a snapshot of a codebase.
17
- Each item has the following structure:
18
-
19
- \`\`\`json
20
- {
21
- "path": "relative/path/to/file.ext",
22
- "content": "full file content"
23
- }
24
- \`\`\`
25
-
26
- ---
27
-
28
- SNAPSHOT
29
+ // ... (template mantido) ...
29
30
  ${JSON.stringify(snapshotBefore, null, 2)}
30
-
31
- ---
32
-
33
- YOUR TASK
34
- Propose changes according to the user request.
35
-
36
- Return ONLY a JSON array of ${diff ? 'DiffPatch' : 'FileSnapshot'}.
37
-
38
- PATCH FORMAT (STRICT)
39
- {
40
- \"path\": \"relative/path/to/file.ext\",
41
- \"content\": \"FULL updated file content (omit for delete)\"
42
- }
43
-
44
- IMPORTANT RULES
45
- - Do NOT return explanations
46
- - Do NOT return markdown
47
- - Return ONLY valid JSON
48
-
49
- USER REQUEST
50
- <Replace this with the user request>
51
31
  `;
52
32
  await (0, clipboard_1.copyToClipboard)(template.trim());
53
- process.stdout.write("✔ Prompt with snapshot copied to clipboard\n");
33
+ process.stdout.write("✔ Prompt with snapshot copied to clipboard. Paste the response here and press Enter (Ctrl+D to finish):\n");
54
34
  const input = await readStdin();
55
- let patches;
56
- try {
57
- patches = JSON.parse(input);
58
- }
59
- catch {
60
- throw new Error("Invalid JSON input");
61
- }
62
- if (diff) {
63
- const validated = (0, validation_1.validatePatches)(patches);
64
- (0, diff_1.applyDiff)(dir, validated);
65
- }
66
- else {
67
- (0, write_1.writeFiles)(dir, patches, true);
35
+ const { cleaned } = (0, cleanCodeBlock_1.default)(input);
36
+ const patches = (0, schema_1.validatePatches)(JSON.parse(cleaned));
37
+ for (const patch of patches) {
38
+ if (patch.type === 'console' && patch.command) {
39
+ if (runAll) {
40
+ const shouldRun = autoAccept || await confirm(`> Execute: ${patch.command}`);
41
+ if (shouldRun)
42
+ (0, child_process_1.execSync)(patch.command, { stdio: 'inherit', cwd: dir });
43
+ }
44
+ else {
45
+ console.log(`> Skipped command: ${patch.command} (use --run)`);
46
+ }
47
+ }
48
+ else if (patch.path) {
49
+ (0, diff_1.applyDiff)(dir, [patch]);
50
+ }
68
51
  }
69
52
  const snapshotAfter = (0, scan_1.scanDir)(dir);
70
53
  await (0, clipboard_1.copyToClipboard)(JSON.stringify(snapshotAfter, null, 2));
71
54
  process.stdout.write("✔ Sync applied and new snapshot copied to clipboard\n");
72
55
  }
73
56
  catch (error) {
74
- process.stderr.write("✖ Sync failed\n");
75
- if (error instanceof Error) {
76
- process.stderr.write(`${error.message}\n`);
77
- }
57
+ process.stderr.write(`✖ Sync failed: ${error.message}\n`);
78
58
  process.exit(1);
79
59
  }
80
60
  }
81
- exports.sync = sync;
82
61
  function readStdin() {
83
62
  return new Promise(resolve => {
84
63
  let data = "";
@@ -1,24 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validatePatches = void 0;
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
+ */
4
9
  function validatePatches(json) {
5
- if (!Array.isArray(json)) {
6
- throw new Error("Validation Error: Input is not a JSON array.");
7
- }
8
- return json.map((item, index) => {
9
- if (typeof item !== "object" || item === null) {
10
- throw new Error(`Validation Error: Item at index ${index} is not an object.`);
11
- }
12
- if (!["modify", "create", "delete"].includes(item.type)) {
13
- throw new Error(`Validation Error: Invalid type '${item.type}' at index ${index}.`);
14
- }
15
- if (typeof item.path !== "string" || !item.path.trim()) {
16
- throw new Error(`Validation Error: Invalid or missing path at index ${index}.`);
17
- }
18
- if (item.type !== "delete" && typeof item.content !== "string") {
19
- throw new Error(`Validation Error: Missing content for '${item.type}' at index ${index}.`);
20
- }
21
- return item;
22
- });
10
+ return (0, schema_1.validatePatches)(json);
23
11
  }
24
- exports.validatePatches = validatePatches;
@@ -3,22 +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.writeFiles = void 0;
6
+ exports.writeFiles = writeFiles;
7
7
  // src/core/write.ts
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const git_1 = require("./git");
11
+ /**
12
+ * @deprecated Use applyDiff from core/diff for more flexibility (supports deletes and console commands).
13
+ */
11
14
  function writeFiles(basePath, files, commit = true) {
12
- if (commit) {
13
- (0, git_1.ensureGitCommit)("johankit: before paste");
15
+ if (commit && files.length > 0) {
16
+ (0, git_1.ensureGitCommit)("johankit: before write");
14
17
  }
15
18
  for (const file of files) {
19
+ if (!file.path)
20
+ continue;
16
21
  const fullPath = path_1.default.join(basePath, file.path);
17
- const dir = path_1.default.dirname(fullPath);
18
- if (!fs_1.default.existsSync(dir)) {
19
- fs_1.default.mkdirSync(dir, { recursive: true });
20
- }
21
- fs_1.default.writeFileSync(fullPath, file.content, "utf8");
22
+ fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
23
+ fs_1.default.writeFileSync(fullPath, file.content || "", "utf8");
22
24
  }
23
25
  }
24
- exports.writeFiles = writeFiles;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.copy = copy;
4
+ const scan_1 = require("../../core/scan");
5
+ const clipboard_1 = require("../../core/clipboard");
6
+ async function copy(dir, extensions) {
7
+ const snapshot = (0, scan_1.scanDir)(dir, { extensions });
8
+ const clipboardJSON = JSON.stringify(snapshot);
9
+ await (0, clipboard_1.copyToClipboard)(clipboardJSON);
10
+ process.stdout.write(`✔ Snapshot de ${dir} copiado (${(clipboardJSON.length / 1024).toFixed(2)} KB)\n`);
11
+ }
@@ -0,0 +1,120 @@
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.paste = paste;
40
+ // src/cli/commands/paste.ts
41
+ const diff_1 = require("../../core/diff");
42
+ const clipboard_1 = require("../../core/clipboard");
43
+ const schema_1 = require("../../core/schema");
44
+ const cleanCodeBlock_1 = __importDefault(require("../../utils/cleanCodeBlock"));
45
+ const child_process_1 = require("child_process");
46
+ const readline_1 = __importDefault(require("readline"));
47
+ const fs_1 = __importDefault(require("fs"));
48
+ const path_1 = __importDefault(require("path"));
49
+ const diff = __importStar(require("diff"));
50
+ require("colors");
51
+ async function confirm(msg) {
52
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
53
+ return new Promise(resolve => {
54
+ rl.question(`${msg} (y/N): `, (ans) => {
55
+ rl.close();
56
+ resolve(ans.toLowerCase() === 'y');
57
+ });
58
+ });
59
+ }
60
+ function showDiff(filename, oldContent, newContent) {
61
+ console.log(`\n--- DIFF FOR: ${filename.bold} ---`);
62
+ const patches = diff.diffLines(oldContent, newContent);
63
+ patches.forEach((part) => {
64
+ const color = part.added ? 'green' : part.removed ? 'red' : 'gray';
65
+ const prefix = part.added ? '+' : part.removed ? '-' : ' ';
66
+ const value = part.value.endsWith('\n') ? part.value : part.value + '\n';
67
+ process.stdout.write((value.split('\n').map((line) => line ? `${prefix}${line}` : '').join('\n'))[color]);
68
+ });
69
+ console.log('\n-----------------------');
70
+ }
71
+ async function paste(dir, runAll = false, dryRun = false, interactiveDiff = false) {
72
+ const autoAccept = process.argv.includes("-y");
73
+ try {
74
+ const content = await (0, clipboard_1.readClipboard)();
75
+ if (!content)
76
+ throw new Error("Clipboard empty");
77
+ const { cleaned } = (0, cleanCodeBlock_1.default)(content);
78
+ const items = (0, schema_1.validatePatches)(JSON.parse(cleaned));
79
+ if (dryRun)
80
+ process.stdout.write("--- DRY RUN MODE ---\n");
81
+ for (const item of items) {
82
+ if (item.type === 'console' && item.command) {
83
+ if (dryRun) {
84
+ process.stdout.write(`[DRY-RUN] Would execute: ${item.command}\n`);
85
+ }
86
+ else if (runAll) {
87
+ if (autoAccept || await confirm(`> Execute: ${item.command}`)) {
88
+ (0, child_process_1.execSync)(item.command, { stdio: 'inherit', cwd: dir });
89
+ }
90
+ }
91
+ }
92
+ else if (item.path) {
93
+ const fullPath = path_1.default.join(dir, item.path);
94
+ const exists = fs_1.default.existsSync(fullPath);
95
+ const oldContent = exists ? fs_1.default.readFileSync(fullPath, 'utf8') : "";
96
+ const newContent = item.content || "";
97
+ if (interactiveDiff && item.content !== null) {
98
+ showDiff(item.path, oldContent, newContent);
99
+ if (await confirm(`Apply changes to ${item.path}?`)) {
100
+ (0, diff_1.applyDiff)(dir, [item]);
101
+ }
102
+ else {
103
+ console.log(`Skipped: ${item.path}`);
104
+ }
105
+ }
106
+ else if (dryRun) {
107
+ process.stdout.write(`[DRY-RUN] Would ${item.content === null ? 'Delete' : 'Write'}: ${item.path}\n`);
108
+ }
109
+ else {
110
+ (0, diff_1.applyDiff)(dir, [item]);
111
+ }
112
+ }
113
+ }
114
+ process.stdout.write("√ Operation completed\n");
115
+ }
116
+ catch (error) {
117
+ process.stderr.write(`× Failed: ${error.message}\n`);
118
+ process.exit(1);
119
+ }
120
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prompt = prompt;
4
+ // src/cli/commands/prompt.ts
5
+ const scan_1 = require("../../core/scan");
6
+ const clipboard_1 = require("../../core/clipboard");
7
+ async function prompt(dir, userPrompt) {
8
+ const snapshot = (0, scan_1.scanDir)(dir);
9
+ const template = `
10
+ You are an AI software engineer.
11
+ Your goal is to help the user with their codebase using a specific JSON patch format.
12
+
13
+ Maintain 100% of previous functionality for full compatibility.
14
+ Always submit the complete code; never use comments.
15
+ Use "git add {files} && git commit -m {message}" after each change via the console.
16
+ ---
17
+
18
+ ### CAPABILITIES
19
+ 1. **File Updates**: You can create, update, or delete files.
20
+ 2. **Console Commands**: You can execute shell commands (e.g., npm install, mkdir, rm, vitest).
21
+
22
+ ### RESPONSE FORMAT
23
+ Return ONLY a JSON array. No conversational text. No explanations.
24
+ Wrap the JSON in a markdown code block: \`\`\`json [your_array] \`\`\`
25
+
26
+ ### PATCH TYPES
27
+ - **File Patch**: { "path": "src/file.ts", "content": "full code" }
28
+ - **Delete File**: { "path": "src/old-file.ts", "content": null }
29
+ - **Console**: { "type": "console", "command": "npm install lodash" }
30
+
31
+ ### STRATEGY
32
+ If the user request requires a new library, include the "npm install" command in the array before the file updates.
33
+ If the user wants to refactor and ensure it works, you can include a command to run tests.
34
+
35
+ ---
36
+
37
+ SNAPSHOT
38
+ ${JSON.stringify(snapshot, null, 2)}
39
+
40
+ ---
41
+
42
+ USER REQUEST
43
+ ${userPrompt}
44
+ `;
45
+ try {
46
+ await (0, clipboard_1.copyToClipboard)(template.trim());
47
+ process.stdout.write(template.trim());
48
+ process.stdout.write("\n\n✔ Prompt + Snapshot copied to clipboard\n");
49
+ }
50
+ catch (e) {
51
+ process.stdout.write(template.trim());
52
+ process.stderr.write("\n✖ Failed to copy to clipboard (output only)\n");
53
+ }
54
+ }