@adityaaria/spark 6.0.15 → 6.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adityaaria/spark",
3
- "version": "6.0.15",
3
+ "version": "6.0.16",
4
4
  "description": "SPARK skills and runtime bootstrap for coding agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/cli/index.js CHANGED
@@ -1,26 +1,20 @@
1
- import { parseArgs } from './parse-args.js';
2
1
  import { printHelp, printLine } from './output.js';
3
2
  import { runInstall } from './install.js';
4
3
 
5
- export async function run(argv, env) {
6
- const options = parseArgs(argv);
7
-
8
- if (options.help || options.command === 'help') {
4
+ export async function run(argv = [], env = process.env) {
5
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h' || argv[0] === 'help') {
9
6
  printHelp();
10
7
  return;
11
8
  }
12
9
 
13
- if (!options.command) {
14
- printHelp();
15
- return;
16
- }
10
+ const [command, ...args] = argv;
17
11
 
18
- if (options.command === 'install') {
19
- await runInstall(options, env);
12
+ if (command === 'install') {
13
+ await runInstall(args, env);
20
14
  return;
21
15
  }
22
16
 
23
- throw new Error(`Unknown command: ${options.command}`);
17
+ throw new Error(`Unknown command: ${command}`);
24
18
  }
25
19
 
26
20
  export { printLine };
@@ -1,168 +1,40 @@
1
- import {
2
- bullet,
3
- commandText,
4
- labelValue,
5
- pathText,
6
- printCommandHeader,
7
- printLine,
8
- printSection,
9
- printSummary,
10
- statusText,
11
- step,
12
- } from './output.js';
13
- import { askQuestion } from './prompt.js';
14
- import { chooseHarness } from '../installer/detect.js';
15
- import { InstallerError } from '../installer/errors.js';
16
- import fs from 'node:fs';
17
-
18
- export async function runInstall(options, env) {
19
- printCommandHeader('Install');
20
- const selection = await chooseHarness({
21
- forcedHarness: normalizeHarness(options.harness),
22
- env,
23
- fs,
24
- prompt: async (question) => askQuestion(question),
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export function runInstall(args = [], env = process.env) {
8
+ return new Promise((resolve, reject) => {
9
+ const scriptPath = path.resolve(__dirname, '../../bin/spark-install.sh');
10
+ const child = spawn('bash', [scriptPath, ...args], {
11
+ stdio: 'inherit',
12
+ env,
13
+ });
14
+
15
+ let settled = false;
16
+
17
+ child.on('error', (err) => {
18
+ if (settled) return;
19
+ settled = true;
20
+ if (err.code === 'ENOENT') {
21
+ process.stderr.write(
22
+ 'Error: "bash" command not found.\n\n' +
23
+ 'SPARK native installer requires bash to run.\n' +
24
+ 'If you are on Windows, please run this command inside WSL, Git Bash, or check README.md for manual installation instructions.\n'
25
+ );
26
+ process.exitCode = 1;
27
+ resolve();
28
+ return;
29
+ }
30
+ reject(err);
31
+ });
32
+
33
+ child.on('close', (code) => {
34
+ if (settled) return;
35
+ settled = true;
36
+ process.exitCode = code ?? 1;
37
+ resolve();
38
+ });
25
39
  });
26
- const { adapter, source } = selection;
27
- const plan = adapter.planInstall();
28
-
29
- if (options.dryRun) {
30
- const installPreview = await adapter.install({ dryRun: true, cwd: process.cwd(), env });
31
- printSection('Preview');
32
- printLine(labelValue('Mode', 'dry-run'));
33
- printLine(labelValue('Harness', `${adapter.label} (${adapter.id})`));
34
- printLine(labelValue('Selection', source));
35
- printLine(labelValue('Bootstrap', plan.bootstrap));
36
- printPlanDetails(installPreview.plan, installPreview.metadata ?? {});
37
- printSummary('Nothing changed', [bullet('No filesystem changes were made.')], 'info');
38
- return;
39
- }
40
-
41
- if (typeof adapter.install !== 'function') {
42
- throw new InstallerError(`Install flow for ${adapter.label} is not implemented yet.`);
43
- }
44
-
45
- if (!plan.automated) {
46
- printSection('Install');
47
- printLine(labelValue('Harness', adapter.label));
48
- printLine(statusText(`Interactive install is required for ${adapter.label}.`, 'warning'));
49
- printPlanDetails(plan, {});
50
- printSummary(
51
- 'Next step',
52
- [bullet(`Complete the steps above inside ${adapter.label} and then start a fresh session.`)],
53
- 'warning'
54
- );
55
- return;
56
- }
57
-
58
- const installResult = await adapter.install({ options, env });
59
-
60
- if (typeof adapter.verify === 'function') {
61
- await adapter.verify({ options, env });
62
- }
63
- const resolvedPlan = installResult?.plan ?? plan;
64
- const metadata = installResult?.metadata ?? {};
65
-
66
- printSection('Install');
67
- printLine(labelValue('Harness', adapter.label));
68
- printLine(statusText(resolvedPlan.successMessage ?? plan.successMessage, 'success'));
69
- printPlanDetails(resolvedPlan, metadata);
70
- printSummary(
71
- 'Ready',
72
- buildReadyLines(adapter, resolvedPlan, metadata),
73
- 'success'
74
- );
75
- }
76
-
77
- function normalizeHarness(harness) {
78
- if (!harness) return null;
79
- return String(harness).trim().toLowerCase();
80
- }
81
-
82
- function printPlanDetails(plan, metadata) {
83
- if (plan.automated && plan.commands?.length) {
84
- printLine('');
85
- printSection('Commands');
86
- for (const [index, command] of plan.commands.entries()) {
87
- printLine(
88
- step(
89
- index + 1,
90
- plan.commands.length,
91
- commandText(formatCommand(command, metadata))
92
- )
93
- );
94
- }
95
- }
96
-
97
- if (plan.automated && plan.automatedSteps?.length) {
98
- printLine('');
99
- printSection('Steps');
100
- for (const [index, automatedStep] of plan.automatedSteps.entries()) {
101
- printLine(step(index + 1, plan.automatedSteps.length, interpolatePlanText(automatedStep, metadata)));
102
- }
103
- }
104
-
105
- if (!plan.automated && plan.manualSteps?.length) {
106
- printLine('');
107
- printSection('Steps');
108
- for (const [index, manualStep] of plan.manualSteps.entries()) {
109
- printLine(step(index + 1, plan.manualSteps.length, manualStep));
110
- }
111
- }
112
-
113
- printLine('');
114
- printSection('Notes');
115
- printLine(labelValue('Install hint', plan.installHint));
116
- printLine(labelValue('Verify hint', plan.verifyHint));
117
- if (metadata.relativeTargetRoot) {
118
- printLine(labelValue('Bundle path', pathText(metadata.relativeTargetRoot)));
119
- }
120
- if (metadata.relativeMarketplaceRoot) {
121
- printLine(labelValue('Marketplace path', pathText(metadata.relativeMarketplaceRoot)));
122
- }
123
- }
124
-
125
- function formatCommand(command, metadata = {}) {
126
- return [command.file, ...(command.args ?? [])]
127
- .map((part) => interpolatePlanText(part, metadata))
128
- .join(' ');
129
- }
130
-
131
- function interpolatePlanText(text, metadata) {
132
- return String(text)
133
- .replaceAll('{targetRoot}', metadata.targetRoot ?? '')
134
- .replaceAll('{relativeTargetRoot}', metadata.relativeTargetRoot ?? '')
135
- .replaceAll('{marketplaceRoot}', metadata.marketplaceRoot ?? '')
136
- .replaceAll('{relativeMarketplaceRoot}', metadata.relativeMarketplaceRoot ?? '')
137
- .replaceAll('{marketplaceName}', metadata.marketplaceName ?? '');
138
- }
139
-
140
- function buildReadyLines(adapter, plan, metadata) {
141
- const lines = [];
142
-
143
- if (metadata.relativeTargetRoot && adapter.id === 'vscode') {
144
- lines.push(bullet(`Local VS Code bundle prepared at ${pathText(metadata.relativeTargetRoot)}.`));
145
- } else if (metadata.relativeTargetRoot) {
146
- lines.push(bullet(`Plugin bundle staged at ${pathText(metadata.relativeTargetRoot)}.`));
147
- }
148
-
149
- if (metadata.relativeMarketplaceRoot) {
150
- lines.push(bullet(`Local marketplace staged at ${pathText(metadata.relativeMarketplaceRoot)}.`));
151
- }
152
-
153
- if (adapter.id === 'codex') {
154
- lines.push(bullet('Start a fresh Codex session to confirm using-spark loads before coding.'));
155
- return lines;
156
- }
157
-
158
- if (adapter.id === 'vscode') {
159
- if (metadata.relativeSettingsPath) {
160
- lines.push(bullet(`VS Code plugin registration was written to ${pathText(metadata.relativeSettingsPath)}.`));
161
- }
162
- lines.push(bullet('Open a fresh VS Code agent session to confirm using-spark loads before coding.'));
163
- return lines;
164
- }
165
-
166
- lines.push(bullet('Start a fresh session in the selected harness to confirm using-spark loads before coding.'));
167
- return lines;
168
40
  }
package/src/cli/output.js CHANGED
@@ -20,11 +20,14 @@ export function printLine(text = '') {
20
20
 
21
21
  export function printHelp() {
22
22
  printCommandHeader('Install');
23
- printLine(labelValue('Usage', 'npx @adityaaria/spark install [--harness <name>] [--dry-run] [--yes] [--verbose]'));
23
+ printLine(labelValue('Usage', 'npx @adityaaria/spark install [options]'));
24
24
  printLine('');
25
- printMuted('Without --harness, the installer will ask which AI assistance to target.');
25
+ printMuted('Wraps the native SPARK installer (bin/spark-install.sh).');
26
26
  printLine('');
27
- printLine(labelValue('Supported', 'Claude Code, Codex CLI, VS Code, Cursor, Copilot CLI, OpenCode, Gemini CLI, Pi, Antigravity'));
27
+ printLine(labelValue('Options', '-g, --global Install to global agent config (~/.agent/skills/)'));
28
+ printLine(labelValue(' ', '--force Re-install even if already installed'));
29
+ printLine(labelValue(' ', '--dry-run Show what would be done without making changes'));
30
+ printLine(labelValue(' ', '-h, --help Show this help message'));
28
31
  }
29
32
 
30
33
  export function printCommandHeader(title) {
@@ -1,46 +0,0 @@
1
- export function parseArgs(argv) {
2
- const args = [...argv];
3
- const options = {
4
- command: null,
5
- help: false,
6
- dryRun: false,
7
- yes: false,
8
- verbose: false,
9
- harness: null,
10
- };
11
-
12
- while (args.length) {
13
- const arg = args.shift();
14
- if (arg === '--help' || arg === '-h') {
15
- options.help = true;
16
- continue;
17
- }
18
- if (arg === '--dry-run') {
19
- options.dryRun = true;
20
- continue;
21
- }
22
- if (arg === '--yes' || arg === '-y') {
23
- options.yes = true;
24
- continue;
25
- }
26
- if (arg === '--verbose' || arg === '-v') {
27
- options.verbose = true;
28
- continue;
29
- }
30
- if (arg === '--harness') {
31
- options.harness = args.shift() ?? null;
32
- continue;
33
- }
34
- if (arg.startsWith('--harness=')) {
35
- options.harness = arg.slice('--harness='.length) || null;
36
- continue;
37
- }
38
- if (!options.command) {
39
- options.command = arg;
40
- continue;
41
- }
42
- throw new Error(`Unknown argument: ${arg}`);
43
- }
44
-
45
- return options;
46
- }
package/src/cli/prompt.js DELETED
@@ -1,11 +0,0 @@
1
- import readline from 'node:readline/promises';
2
- import { formatPromptPrefix } from './output.js';
3
-
4
- export async function askQuestion(question, { input = process.stdin, output = process.stdout } = {}) {
5
- const rl = readline.createInterface({ input, output });
6
- try {
7
- return await rl.question(`${question}\n${formatPromptPrefix()}`);
8
- } finally {
9
- rl.close();
10
- }
11
- }
@@ -1,68 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- const CLAUDE_MARKETPLACE_DIR = path.join('.spark', 'claude-marketplace');
5
- const CLAUDE_PLUGIN_DIR = path.join(CLAUDE_MARKETPLACE_DIR, 'plugins', 'spark');
6
- const COPY_PATHS = [
7
- '.claude-plugin',
8
- 'assets',
9
- path.join('hooks', 'hooks.json'),
10
- path.join('hooks', 'run-hook.cmd'),
11
- path.join('hooks', 'session-start'),
12
- 'skills',
13
- ];
14
-
15
- export function stageClaudePlugin({ cwd = process.cwd(), packageRoot, dryRun = false }) {
16
- const marketplaceRoot = path.join(cwd, CLAUDE_MARKETPLACE_DIR);
17
- const targetRoot = path.join(cwd, CLAUDE_PLUGIN_DIR);
18
-
19
- if (!dryRun) {
20
- fs.mkdirSync(targetRoot, { recursive: true });
21
-
22
- for (const relativePath of COPY_PATHS) {
23
- const sourcePath = path.join(packageRoot, relativePath);
24
- const targetPath = path.join(targetRoot, relativePath);
25
- const stat = fs.statSync(sourcePath);
26
-
27
- if (stat.isDirectory()) {
28
- fs.cpSync(sourcePath, targetPath, { recursive: true });
29
- continue;
30
- }
31
-
32
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
33
- fs.copyFileSync(sourcePath, targetPath);
34
- fs.chmodSync(targetPath, stat.mode);
35
- }
36
-
37
- writeMarketplaceManifest(marketplaceRoot);
38
- }
39
-
40
- return {
41
- targetRoot,
42
- relativeTargetRoot: CLAUDE_PLUGIN_DIR,
43
- marketplaceRoot,
44
- relativeMarketplaceRoot: CLAUDE_MARKETPLACE_DIR,
45
- marketplaceName: 'spark-local',
46
- };
47
- }
48
-
49
- function writeMarketplaceManifest(marketplaceRoot) {
50
- const manifestPath = path.join(marketplaceRoot, 'marketplace.json');
51
- const manifest = {
52
- name: 'spark-local',
53
- plugins: [
54
- {
55
- name: 'spark',
56
- description: 'SPARK local marketplace for Claude Code',
57
- version: 'local',
58
- source: './plugins/spark',
59
- author: {
60
- name: 'SPARK',
61
- },
62
- },
63
- ],
64
- };
65
-
66
- fs.mkdirSync(marketplaceRoot, { recursive: true });
67
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
68
- }
@@ -1,77 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- const CODEX_MARKETPLACE_DIR = path.join('.spark', 'codex-marketplace');
5
- const CODEX_PLUGIN_DIR = path.join(CODEX_MARKETPLACE_DIR, 'plugins', 'spark');
6
- const COPY_PATHS = [
7
- '.codex-plugin',
8
- 'assets',
9
- path.join('hooks', 'hooks-codex.json'),
10
- path.join('hooks', 'run-hook.cmd'),
11
- path.join('hooks', 'session-start-codex'),
12
- 'skills',
13
- ];
14
-
15
- export function stageCodexPlugin({ cwd = process.cwd(), packageRoot, dryRun = false }) {
16
- const marketplaceRoot = path.join(cwd, CODEX_MARKETPLACE_DIR);
17
- const targetRoot = path.join(cwd, CODEX_PLUGIN_DIR);
18
-
19
- if (!dryRun) {
20
- fs.mkdirSync(targetRoot, { recursive: true });
21
- }
22
-
23
- if (!dryRun) {
24
- for (const relativePath of COPY_PATHS) {
25
- const sourcePath = path.join(packageRoot, relativePath);
26
- const targetPath = path.join(targetRoot, relativePath);
27
- const stat = fs.statSync(sourcePath);
28
-
29
- if (stat.isDirectory()) {
30
- fs.cpSync(sourcePath, targetPath, { recursive: true });
31
- continue;
32
- }
33
-
34
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
35
- fs.copyFileSync(sourcePath, targetPath);
36
- fs.chmodSync(targetPath, stat.mode);
37
- }
38
-
39
- writeMarketplaceManifest(marketplaceRoot);
40
- }
41
-
42
- return {
43
- targetRoot,
44
- relativeTargetRoot: CODEX_PLUGIN_DIR,
45
- marketplaceRoot,
46
- relativeMarketplaceRoot: `./${CODEX_MARKETPLACE_DIR}`,
47
- marketplaceName: 'spark-local',
48
- };
49
- }
50
-
51
- function writeMarketplaceManifest(marketplaceRoot) {
52
- const manifestPath = path.join(marketplaceRoot, '.agents', 'plugins', 'marketplace.json');
53
- const manifest = {
54
- name: 'spark-local',
55
- interface: {
56
- displayName: 'SPARK',
57
- shortDescription: 'Local SPARK marketplace for Codex CLI',
58
- },
59
- plugins: [
60
- {
61
- name: 'spark',
62
- source: {
63
- source: 'local',
64
- path: './plugins/spark',
65
- },
66
- policy: {
67
- installation: 'AVAILABLE',
68
- authentication: 'ON_INSTALL',
69
- },
70
- category: 'Productivity',
71
- },
72
- ],
73
- };
74
-
75
- fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
76
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
77
- }
@@ -1,158 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { InstallerError } from '../errors.js';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
-
6
- export function createAdapter({
7
- id,
8
- label,
9
- kind,
10
- bootstrap,
11
- installHint,
12
- verifyHint,
13
- successMessage = `Installed SPARK for ${label}.`,
14
- command = null,
15
- commands = [],
16
- customInstall = null,
17
- manualSteps = [],
18
- automatedSteps = [],
19
- envKeys = [],
20
- binaryNames = [],
21
- configPaths = [],
22
- }) {
23
- const commandList = normalizeCommandList(commands, command);
24
- const automated = commandList.length > 0 || typeof customInstall === 'function';
25
-
26
- return {
27
- id,
28
- label,
29
- envKeys,
30
- binaryNames,
31
- configPaths,
32
- command: commandList[0] ?? null,
33
- commands: commandList,
34
- manualSteps,
35
- planInstall() {
36
- return {
37
- kind,
38
- bootstrap,
39
- installHint,
40
- verifyHint,
41
- successMessage,
42
- command: commandList[0] ?? null,
43
- commands: commandList,
44
- manualSteps,
45
- automatedSteps,
46
- automated,
47
- };
48
- },
49
- async install({ runner = spawnSync, dryRun = false, cwd = process.cwd(), env = process.env, fs = null } = {}) {
50
- if (!automated) {
51
- throw new InstallerError(`${label} install is not fully automatable yet.`);
52
- }
53
-
54
- const fsImpl = fs ?? nativeFs;
55
- if (!dryRun) {
56
- assertBinaryAvailability({ label, binaryNames, env, fsImpl });
57
- }
58
-
59
- let metadata = {};
60
-
61
- if (typeof customInstall === 'function') {
62
- metadata = (await customInstall({ cwd, env, fs, dryRun })) ?? {};
63
- }
64
-
65
- if (dryRun) {
66
- return { plan: this.planInstall(), metadata };
67
- }
68
-
69
- for (const entry of commandList) {
70
- const result = runner(entry.file, interpolateArgs(entry.args ?? [], metadata), {
71
- cwd: entry.cwd ?? cwd,
72
- env: entry.env ?? env,
73
- encoding: 'utf8',
74
- });
75
-
76
- if (result.error) {
77
- throw new InstallerError(`${label} install failed: ${result.error.message}`);
78
- }
79
-
80
- if (result.status !== 0) {
81
- throw new InstallerError(
82
- `${label} install failed: ${result.stderr || result.stdout || 'unknown error'}`
83
- );
84
- }
85
- }
86
-
87
- return { plan: this.planInstall(), metadata };
88
- },
89
- async verify() {
90
- return verifyHint;
91
- },
92
- };
93
- }
94
-
95
- const nativeFs = fs;
96
-
97
- function normalizeCommandList(commands, command) {
98
- if (Array.isArray(commands) && commands.length > 0) {
99
- return commands;
100
- }
101
-
102
- if (command) {
103
- return [command];
104
- }
105
-
106
- return [];
107
- }
108
-
109
- function assertBinaryAvailability({ label, binaryNames, env, fsImpl }) {
110
- if (!binaryNames || binaryNames.length === 0) {
111
- return;
112
- }
113
-
114
- for (const binaryName of binaryNames) {
115
- if (commandExists(binaryName, env, fsImpl)) {
116
- return;
117
- }
118
- }
119
-
120
- throw new InstallerError(`${label} is not installed or not on PATH. Install ${label} first, then rerun SPARK install.`);
121
- }
122
-
123
- function commandExists(commandName, env, fsImpl) {
124
- const paths = String(env.PATH ?? '').split(path.delimiter).filter(Boolean);
125
- const exts = process.platform === 'win32'
126
- ? String(env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
127
- : [''];
128
-
129
- for (const dir of paths) {
130
- for (const ext of exts) {
131
- const candidate = path.join(dir, `${commandName}${ext}`);
132
- try {
133
- fsImpl.accessSync(candidate, fs.constants.X_OK);
134
- return true;
135
- } catch {
136
- try {
137
- fsImpl.accessSync(candidate, fs.constants.F_OK);
138
- return true;
139
- } catch {
140
- continue;
141
- }
142
- }
143
- }
144
- }
145
-
146
- return false;
147
- }
148
-
149
- function interpolateArgs(args, metadata) {
150
- return args.map((arg) =>
151
- String(arg)
152
- .replaceAll('{targetRoot}', metadata.targetRoot ?? '')
153
- .replaceAll('{relativeTargetRoot}', metadata.relativeTargetRoot ?? '')
154
- .replaceAll('{marketplaceRoot}', metadata.marketplaceRoot ?? '')
155
- .replaceAll('{relativeMarketplaceRoot}', metadata.relativeMarketplaceRoot ?? '')
156
- .replaceAll('{marketplaceName}', metadata.marketplaceName ?? '')
157
- );
158
- }
@@ -1,42 +0,0 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
-
5
- const CURSOR_PLUGIN_DIR = path.join('.cursor', 'plugins', 'spark');
6
- const COPY_PATHS = [
7
- '.cursor-plugin',
8
- 'assets',
9
- path.join('hooks', 'hooks-cursor.json'),
10
- path.join('hooks', 'run-hook.cmd'),
11
- path.join('hooks', 'session-start'),
12
- 'skills',
13
- ];
14
-
15
- export function installCursorPlugin({ packageRoot, env = process.env, dryRun = false }) {
16
- const homeDir = env.HOME || env.USERPROFILE || os.homedir();
17
- const targetRoot = path.join(homeDir, CURSOR_PLUGIN_DIR);
18
-
19
- if (!dryRun) {
20
- fs.mkdirSync(targetRoot, { recursive: true });
21
-
22
- for (const relativePath of COPY_PATHS) {
23
- const sourcePath = path.join(packageRoot, relativePath);
24
- const targetPath = path.join(targetRoot, relativePath);
25
- const stat = fs.statSync(sourcePath);
26
-
27
- if (stat.isDirectory()) {
28
- fs.cpSync(sourcePath, targetPath, { recursive: true });
29
- continue;
30
- }
31
-
32
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
33
- fs.copyFileSync(sourcePath, targetPath);
34
- fs.chmodSync(targetPath, stat.mode);
35
- }
36
- }
37
-
38
- return {
39
- targetRoot,
40
- relativeTargetRoot: `~/${CURSOR_PLUGIN_DIR}`,
41
- };
42
- }