@onuraslan/sdd 1.0.5 → 1.0.6

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/cli/install.js CHANGED
@@ -17,11 +17,34 @@ function writeMetadata(metadataPath, targetId, plugins, version) {
17
17
  fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
18
18
  }
19
19
 
20
- function writeMcpConfig(target, selectedPlugins) {
20
+ async function writeMcpConfig(target, selectedPlugins, scope = 'project', cwd = process.cwd()) {
21
21
  if (!selectedPlugins.includes('frontend')) {
22
22
  return;
23
23
  }
24
24
 
25
+ if (target.id === 'claude') {
26
+ const { spawnSync } = await import('node:child_process');
27
+ const commandArgs = ['mcp', 'add', '--scope', scope, '--transport', 'stdio', 'playwright', '--', 'npx', '-y', '@playwright/mcp'];
28
+
29
+ const result = process.platform === 'win32'
30
+ ? spawnSync('cmd.exe', ['/d', '/s', '/c', 'claude', ...commandArgs], { cwd, stdio: 'inherit', env: process.env })
31
+ : spawnSync('claude', commandArgs, { cwd, stdio: 'inherit', env: process.env });
32
+
33
+ if (result.error && (result.error.code === 'ENOENT' || result.error.code === 'EINVAL')) {
34
+ return;
35
+ }
36
+
37
+ if (result.error) {
38
+ throw new Error(`Claude MCP setup failed: ${result.error.message}`);
39
+ }
40
+
41
+ if (result.status !== 0) {
42
+ throw new Error(`Claude MCP setup exited with code ${result.status}.`);
43
+ }
44
+
45
+ return;
46
+ }
47
+
25
48
  const { mcpConfigPath, mcpFormat } = target;
26
49
  const playwrightConfig = {
27
50
  command: 'npx',
@@ -69,19 +92,25 @@ function writeMcpConfig(target, selectedPlugins) {
69
92
  return;
70
93
  }
71
94
 
72
- const existing = fs.existsSync(mcpConfigPath)
73
- ? JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'))
74
- : {};
95
+ let existing = {};
96
+ if (fs.existsSync(mcpConfigPath)) {
97
+ try {
98
+ existing = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
99
+ } catch {
100
+ existing = {};
101
+ }
102
+ }
75
103
 
76
104
  const nextConfig = {
77
105
  ...existing,
78
106
  mcpServers: {
79
107
  ...(existing.mcpServers || {}),
80
108
  playwright: playwrightConfig
81
- },
82
- playwright: playwrightConfig
109
+ }
83
110
  };
84
111
 
112
+ delete nextConfig.playwright;
113
+
85
114
  const dir = path.dirname(mcpConfigPath);
86
115
  fs.mkdirSync(dir, { recursive: true });
87
116
  fs.writeFileSync(mcpConfigPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
@@ -120,19 +149,18 @@ export async function installSelectedSkills({
120
149
  const destination = path.join(target.installRoot, skillName);
121
150
 
122
151
  if (fs.existsSync(destination)) {
123
- if (conflictHandler) {
124
- const action = await conflictHandler({ skillName, destination, source, pluginId });
125
- if (action === 'skip') {
126
- continue;
127
- }
128
- if (action === 'abort') {
129
- throw new Error(`Installation aborted while processing Skill: ${skillName}`);
130
- }
131
- if (action === 'overwrite') {
132
- fs.rmSync(destination, { recursive: true, force: true });
133
- }
134
- } else {
135
- throw new Error(`Skill already exists: ${skillName}`);
152
+ const action = conflictHandler
153
+ ? await conflictHandler({ skillName, destination, source, pluginId })
154
+ : 'overwrite';
155
+
156
+ if (action === 'skip') {
157
+ continue;
158
+ }
159
+ if (action === 'abort') {
160
+ throw new Error(`Installation aborted while processing Skill: ${skillName}`);
161
+ }
162
+ if (action === 'overwrite' || action === undefined) {
163
+ fs.rmSync(destination, { recursive: true, force: true });
136
164
  }
137
165
  }
138
166
 
@@ -142,7 +170,7 @@ export async function installSelectedSkills({
142
170
  }
143
171
 
144
172
  writeMetadata(target.metadataPath, targetId, consistentPlugins, version);
145
- writeMcpConfig(target, consistentPlugins);
173
+ await writeMcpConfig(target, consistentPlugins, scope, cwd);
146
174
 
147
175
  return {
148
176
  installedCount,
package/cli/targets.js CHANGED
@@ -57,7 +57,10 @@ export function resolveTargetPath(targetId, scope = 'project', cwd = process.cwd
57
57
  const baseDir = resolveInstallBase(scope, cwd);
58
58
  const installRoot = path.resolve(baseDir, target.skillDirectory);
59
59
  const metadataPath = path.resolve(baseDir, path.dirname(target.skillDirectory), 'sdd.json');
60
- const mcpConfigPath = path.resolve(baseDir, target.mcpConfigPath);
60
+ const effectiveMcpConfigPath = target.id === 'claude' && scope === 'user'
61
+ ? '.claude.json'
62
+ : target.mcpConfigPath;
63
+ const mcpConfigPath = path.resolve(baseDir, effectiveMcpConfigPath);
61
64
 
62
65
  return {
63
66
  ...target,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onuraslan/sdd",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Spec-Driven Development skill package and interactive installer for AI coding agents.",
6
6
  "license": "MIT",