@onuraslan/sdd 1.0.5 → 1.0.7

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,40 @@ 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', 'pipe', 'pipe'], env: process.env })
31
+ : spawnSync('claude', commandArgs, { cwd, stdio: ['inherit', 'pipe', 'pipe'], env: process.env });
32
+
33
+ const combinedOutput = [result.stdout?.toString() || '', result.stderr?.toString() || ''].join('\n');
34
+
35
+ if (result.error && (result.error.code === 'ENOENT' || result.error.code === 'EINVAL')) {
36
+ return;
37
+ }
38
+
39
+ if (result.error) {
40
+ throw new Error(`Claude MCP setup failed: ${result.error.message}`);
41
+ }
42
+
43
+ if (result.status !== 0) {
44
+ const duplicateMatch = /already exists/i.test(combinedOutput);
45
+ if (duplicateMatch) {
46
+ return;
47
+ }
48
+ throw new Error(`Claude MCP setup exited with code ${result.status}.`);
49
+ }
50
+
51
+ return;
52
+ }
53
+
25
54
  const { mcpConfigPath, mcpFormat } = target;
26
55
  const playwrightConfig = {
27
56
  command: 'npx',
@@ -69,19 +98,25 @@ function writeMcpConfig(target, selectedPlugins) {
69
98
  return;
70
99
  }
71
100
 
72
- const existing = fs.existsSync(mcpConfigPath)
73
- ? JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'))
74
- : {};
101
+ let existing = {};
102
+ if (fs.existsSync(mcpConfigPath)) {
103
+ try {
104
+ existing = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
105
+ } catch {
106
+ existing = {};
107
+ }
108
+ }
75
109
 
76
110
  const nextConfig = {
77
111
  ...existing,
78
112
  mcpServers: {
79
113
  ...(existing.mcpServers || {}),
80
114
  playwright: playwrightConfig
81
- },
82
- playwright: playwrightConfig
115
+ }
83
116
  };
84
117
 
118
+ delete nextConfig.playwright;
119
+
85
120
  const dir = path.dirname(mcpConfigPath);
86
121
  fs.mkdirSync(dir, { recursive: true });
87
122
  fs.writeFileSync(mcpConfigPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
@@ -120,19 +155,18 @@ export async function installSelectedSkills({
120
155
  const destination = path.join(target.installRoot, skillName);
121
156
 
122
157
  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}`);
158
+ const action = conflictHandler
159
+ ? await conflictHandler({ skillName, destination, source, pluginId })
160
+ : 'overwrite';
161
+
162
+ if (action === 'skip') {
163
+ continue;
164
+ }
165
+ if (action === 'abort') {
166
+ throw new Error(`Installation aborted while processing Skill: ${skillName}`);
167
+ }
168
+ if (action === 'overwrite' || action === undefined) {
169
+ fs.rmSync(destination, { recursive: true, force: true });
136
170
  }
137
171
  }
138
172
 
@@ -142,7 +176,7 @@ export async function installSelectedSkills({
142
176
  }
143
177
 
144
178
  writeMetadata(target.metadataPath, targetId, consistentPlugins, version);
145
- writeMcpConfig(target, consistentPlugins);
179
+ await writeMcpConfig(target, consistentPlugins, scope, cwd);
146
180
 
147
181
  return {
148
182
  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.7",
4
4
  "type": "module",
5
5
  "description": "Spec-Driven Development skill package and interactive installer for AI coding agents.",
6
6
  "license": "MIT",