draftgo-cli 4.0.1 → 4.0.22
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 +87 -11
- package/package.json +9 -4
- package/resources/custom-service-sdk/ai.go +520 -0
- package/resources/custom-service-sdk/ai_test.go +156 -0
- package/resources/custom-service-sdk/billing.go +596 -0
- package/resources/custom-service-sdk/billing_test.go +150 -0
- package/resources/custom-service-sdk/go.mod +3 -0
- package/resources/custom-service-sdk/manifest.json +72 -0
- package/resources/custom-service-sdk/platform.go +360 -0
- package/resources/custom-service-sdk/platform_logger_test.go +24 -0
- package/resources/custom-service-sdk/registration_test.go +39 -0
- package/resources/custom-service-sdk/resources.go +246 -0
- package/resources/custom-service-sdk/resources_billing_test.go +115 -0
- package/resources/custom-service-sdk/resources_files_test.go +57 -0
- package/resources/custom-service-sdk/resources_scope_test.go +87 -0
- package/resources/custom-service-sdk/sdk.go +208 -0
- package/resources/skill/SKILL.md +36 -88
- package/resources/skill/init/SKILL.md +4 -4
- package/resources/skill/manifest.json +5 -1
- package/resources/skill/references/aihub.md +25 -2
- package/resources/skill/references/app-api.md +56 -6
- package/resources/skill/references/architecture.md +2 -2
- package/resources/skill/references/chat-sdk.md +4 -2
- package/resources/skill/references/checkout.md +17 -3
- package/resources/skill/references/custom-services.md +111 -46
- package/resources/skill/references/data.md +19 -4
- package/resources/skill/references/delivery.md +33 -0
- package/resources/skill/references/diagnostics.md +51 -0
- package/resources/skill/references/frontend.md +34 -46
- package/resources/skill/references/mcp.md +33 -5
- package/resources/skill/references/methods.md +189 -0
- package/resources/skill/references/modules.md +36 -8
- package/resources/skill/references/runtime.md +23 -1
- package/src/cli.js +24 -0
- package/src/commandRegistry.js +9 -1
- package/src/commands/api.js +21 -10
- package/src/commands/apiKey.js +34 -0
- package/src/commands/capabilities.js +93 -0
- package/src/commands/checkout.js +1 -1
- package/src/commands/commit.js +1 -1
- package/src/commands/components.js +550 -0
- package/src/commands/conflict.js +1 -1
- package/src/commands/connect.js +18 -8
- package/src/commands/customService.js +20 -4
- package/src/commands/dataRange.js +33 -0
- package/src/commands/delete.js +12 -1
- package/src/commands/diff.js +18 -2
- package/src/commands/grant.js +29 -0
- package/src/commands/group.js +38 -0
- package/src/commands/help.js +64 -20
- package/src/commands/init.js +3 -3
- package/src/commands/map.js +145 -17
- package/src/commands/mcp.js +2 -2
- package/src/commands/reconcile.js +1 -1
- package/src/commands/role.js +32 -0
- package/src/commands/space.js +41 -0
- package/src/commands/status.js +110 -7
- package/src/commands/update.js +23 -11
- package/src/commands/verify.js +75 -0
- package/src/commands/worklog.js +6 -2
- package/src/consoleEncoding.js +34 -0
- package/src/contractCompatibility.js +57 -0
- package/src/customServices.js +138 -18
- package/src/diffReport.js +106 -0
- package/src/index.js +2 -0
- package/src/localRuntime/compose.js +14 -17
- package/src/localRuntime/index.js +22 -23
- package/src/localRuntime/services.js +27 -36
- package/src/mcp/client.js +11 -2
- package/src/mcp/protocol.js +2 -2
- package/src/mcp/tools.js +14 -1
- package/src/platforms.js +9 -0
- package/src/projectConfig.js +6 -4
- package/src/releaseInstall.js +105 -0
- package/src/updateCheck.js +48 -28
- package/src/worklog.js +2 -1
- package/src/worktree/backend.js +1 -1
- package/src/worktree/index.js +7 -2
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const AdmZip = require('adm-zip');
|
|
7
|
+
const parse5 = require('parse5');
|
|
8
|
+
const log = require('../logger');
|
|
9
|
+
const { loadProjectConfig } = require('../projectConfig');
|
|
10
|
+
const { loadManifest, getEntry, absolutePath } = require('../worktree/manifest');
|
|
11
|
+
|
|
12
|
+
async function request(config, method, route, body) {
|
|
13
|
+
const multipart = typeof FormData !== 'undefined' && body instanceof FormData;
|
|
14
|
+
const response = await fetch(`${config.server}/api/${route.replace(/^\//, '')}`, {
|
|
15
|
+
method,
|
|
16
|
+
headers: { Authorization: `Bearer ${config.token}`, ...(multipart ? {} : { 'Content-Type': 'application/json' }) },
|
|
17
|
+
body: body === undefined ? undefined : (multipart ? body : JSON.stringify(body)),
|
|
18
|
+
});
|
|
19
|
+
const payload = await response.json().catch(() => ({}));
|
|
20
|
+
if (!response.ok) throw new Error(payload.message || payload.error?.message || `DraftGo API ${response.status}`);
|
|
21
|
+
return payload.data ?? payload;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function componentName(item) {
|
|
25
|
+
return item.full_name || `${item.library_slug}/${item.slug}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function findComponent(config, name) {
|
|
29
|
+
if (!name) throw new Error('Component name must be <library/component>.');
|
|
30
|
+
const [library, slug] = String(name).split('/');
|
|
31
|
+
if (!library || !slug) throw new Error('Component name must be <library/component>.');
|
|
32
|
+
const result = await request(config, 'GET', `components?library=${encodeURIComponent(library)}&q=${encodeURIComponent(slug)}&page_size=100`);
|
|
33
|
+
const item = (result.items || []).find(value => componentName(value) === name || (value.library_slug === library && value.slug === slug));
|
|
34
|
+
if (!item) throw new Error(`Component not found: ${name}`);
|
|
35
|
+
return request(config, 'GET', `components/${item.id}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readJSON(projectDir, file) {
|
|
39
|
+
if (!file) return {};
|
|
40
|
+
return JSON.parse(fs.readFileSync(path.resolve(projectDir, String(file)), 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function csv(value) {
|
|
44
|
+
return value ? String(value).split(',').map(item => item.trim()).filter(Boolean) : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeDefinition(definition = {}) {
|
|
48
|
+
return {
|
|
49
|
+
html: definition.html || '', css: definition.css || '', js: definition.js || '',
|
|
50
|
+
props: definition.props || [], slots: definition.slots || [], css_variables: definition.css_variables || [],
|
|
51
|
+
events: definition.events || [], dependencies: definition.dependencies || [], examples: definition.examples || [],
|
|
52
|
+
usage: definition.usage || '', root_tag: definition.root_tag || '', meta: definition.meta || {},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function definitionHash(definition) {
|
|
57
|
+
return crypto.createHash('sha256').update(JSON.stringify(normalizeDefinition(definition))).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function componentDir(projectDir, name) {
|
|
61
|
+
const [library, slug] = String(name).split('/');
|
|
62
|
+
if (!library || !slug || !/^[a-z0-9-]+$/.test(library) || !/^[a-z0-9-]+$/.test(slug)) {
|
|
63
|
+
throw new Error('Component name must be a safe <library/component> slug.');
|
|
64
|
+
}
|
|
65
|
+
return path.join(projectDir, '.draftgo', 'worktree', 'components', library, slug);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function contractFrom(definition = {}) {
|
|
69
|
+
const { props, slots, css_variables, events, dependencies, examples, usage, root_tag, meta } = normalizeDefinition(definition);
|
|
70
|
+
return { props, slots, css_variables, events, dependencies, examples, usage, root_tag, meta };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function localDefinition(directory) {
|
|
74
|
+
const contract = JSON.parse(fs.readFileSync(path.join(directory, 'contract.json'), 'utf8'));
|
|
75
|
+
return normalizeDefinition({
|
|
76
|
+
html: fs.readFileSync(path.join(directory, 'component.html'), 'utf8'),
|
|
77
|
+
css: fs.readFileSync(path.join(directory, 'component.css'), 'utf8'),
|
|
78
|
+
js: fs.readFileSync(path.join(directory, 'component.js'), 'utf8'),
|
|
79
|
+
...contract,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function writeComponentFiles(directory, item) {
|
|
84
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
85
|
+
fs.writeFileSync(path.join(directory, 'component.html'), item.draft.html || '', 'utf8');
|
|
86
|
+
fs.writeFileSync(path.join(directory, 'component.css'), item.draft.css || '', 'utf8');
|
|
87
|
+
fs.writeFileSync(path.join(directory, 'component.js'), item.draft.js || '', 'utf8');
|
|
88
|
+
fs.writeFileSync(path.join(directory, 'contract.json'), `${JSON.stringify(contractFrom(item.draft), null, 2)}\n`, 'utf8');
|
|
89
|
+
const metadata = {
|
|
90
|
+
id: item.id, library_id: item.library_id, library_slug: item.library_slug,
|
|
91
|
+
name: item.name, slug: item.slug, category: item.category || '', description: item.description || '',
|
|
92
|
+
tags: item.tags || [], status: item.status || 'active', published_revision: item.published_revision || 0,
|
|
93
|
+
remote_draft_hash: definitionHash(item.draft), updated_at: item.updated_at,
|
|
94
|
+
};
|
|
95
|
+
fs.writeFileSync(path.join(directory, 'component.json'), `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
|
|
96
|
+
fs.writeFileSync(path.join(directory, '.base.json'), `${JSON.stringify(item, null, 2)}\n`, 'utf8');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function output(value, flags, title) {
|
|
100
|
+
if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
|
|
101
|
+
else if (Array.isArray(value)) { log.title(title); value.forEach(item => log.plain(` ${item.name || item.slug || item}`)); }
|
|
102
|
+
else console.log(JSON.stringify(value, null, 2));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function search(projectDir, query, flags) {
|
|
106
|
+
if (!query) throw new Error('Usage: draftgo components search <query>');
|
|
107
|
+
const config = loadProjectConfig(projectDir);
|
|
108
|
+
const result = await request(config, 'GET', `components?q=${encodeURIComponent(query)}&page_size=100`);
|
|
109
|
+
output(result.items || result, flags, `DraftGo components: ${query}`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function show(projectDir, name, flags) {
|
|
114
|
+
if (!name) throw new Error('Usage: draftgo components show <library/component>');
|
|
115
|
+
const config = loadProjectConfig(projectDir);
|
|
116
|
+
const slug = String(name).split('/').pop();
|
|
117
|
+
const result = await request(config, 'GET', `components?q=${encodeURIComponent(slug)}&page_size=100`);
|
|
118
|
+
const item = (result.items || []).find(component => `${component.library_slug}/${component.slug}` === name || component.slug === slug);
|
|
119
|
+
if (!item) throw new Error(`Component not found: ${name}`);
|
|
120
|
+
output(item, flags, `DraftGo component ${name}`);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function list(projectDir, flags) {
|
|
125
|
+
const config = loadProjectConfig(projectDir);
|
|
126
|
+
const query = new URLSearchParams({ page_size: '100' });
|
|
127
|
+
if (flags.library) query.set('library', String(flags.library));
|
|
128
|
+
if (flags.status) query.set('status', String(flags.status));
|
|
129
|
+
const result = await request(config, 'GET', `components?${query}`);
|
|
130
|
+
output(result.items || result, flags, 'DraftGo components');
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function libraries(projectDir, positional, flags) {
|
|
135
|
+
const action = String(positional[0] || 'list').toLowerCase();
|
|
136
|
+
const config = loadProjectConfig(projectDir);
|
|
137
|
+
const items = async () => (await request(config, 'GET', 'component-libraries')).items || [];
|
|
138
|
+
const find = async value => {
|
|
139
|
+
const item = (await items()).find(candidate => String(candidate.id) === String(value) || candidate.slug === value);
|
|
140
|
+
if (!item) throw new Error(`Component library not found: ${value}`);
|
|
141
|
+
return item;
|
|
142
|
+
};
|
|
143
|
+
if (action === 'list') { output(await items(), flags, 'DraftGo component libraries'); return 0; }
|
|
144
|
+
if (action === 'show') { output(await find(positional[1]), flags, `Component library ${positional[1]}`); return 0; }
|
|
145
|
+
if (action === 'delete') {
|
|
146
|
+
const item = await find(positional[1]);
|
|
147
|
+
output(await request(config, 'DELETE', `component-libraries/${item.id}`), flags, 'Component library deleted');
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
if (action === 'create' || action === 'update') {
|
|
151
|
+
const current = action === 'update' ? await find(positional[1]) : {};
|
|
152
|
+
const input = { ...current, ...readJSON(projectDir, flags.file) };
|
|
153
|
+
for (const [flag, key] of [['name', 'name'], ['slug', 'slug'], ['description', 'description'], ['source-type', 'source_type'], ['status', 'status']]) {
|
|
154
|
+
if (flags[flag] !== undefined) input[key] = flags[flag];
|
|
155
|
+
}
|
|
156
|
+
const result = await request(config, action === 'create' ? 'POST' : 'PUT',
|
|
157
|
+
action === 'create' ? 'component-libraries' : `component-libraries/${current.id}`, input);
|
|
158
|
+
output(result, flags, `Component library ${action}d`);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
throw new Error('Usage: draftgo components libraries list|show|create|update|delete');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function create(projectDir, flags) {
|
|
165
|
+
const config = loadProjectConfig(projectDir);
|
|
166
|
+
const library = (await request(config, 'GET', 'component-libraries')).items
|
|
167
|
+
.find(item => String(item.id) === String(flags.library) || item.slug === flags.library);
|
|
168
|
+
if (!library) throw new Error('Usage: draftgo components create --library <id|slug> --name <name> --slug <slug> [--file input.json]');
|
|
169
|
+
const fileInput = readJSON(projectDir, flags.file);
|
|
170
|
+
const input = {
|
|
171
|
+
...fileInput, library_id: library.id,
|
|
172
|
+
name: flags.name || fileInput.name, slug: flags.slug || fileInput.slug,
|
|
173
|
+
category: flags.category ?? fileInput.category ?? '', description: flags.description ?? fileInput.description ?? '',
|
|
174
|
+
tags: csv(flags.tags) || fileInput.tags || [], status: flags.status || fileInput.status || 'active',
|
|
175
|
+
draft: fileInput.draft || { html: '<div></div>', root_tag: 'div' },
|
|
176
|
+
};
|
|
177
|
+
const item = await request(config, 'POST', 'components', input);
|
|
178
|
+
output(item, flags, 'Component created');
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function remove(projectDir, name, flags) {
|
|
183
|
+
const config = loadProjectConfig(projectDir);
|
|
184
|
+
const item = await findComponent(config, name);
|
|
185
|
+
const confirm = flags.yes || flags.y ? '?confirm=1' : '';
|
|
186
|
+
output(await request(config, 'DELETE', `components/${item.id}${confirm}`), flags, 'Component deleted');
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function copy(projectDir, source, flags) {
|
|
191
|
+
const config = loadProjectConfig(projectDir);
|
|
192
|
+
const item = await findComponent(config, source);
|
|
193
|
+
const librariesResult = await request(config, 'GET', 'component-libraries');
|
|
194
|
+
const library = librariesResult.items.find(value => value.slug === (flags.library || item.library_slug));
|
|
195
|
+
if (!library || !flags.slug) throw new Error('Usage: draftgo components copy <library/component> --slug <new-slug> [--library slug] [--name name]');
|
|
196
|
+
const created = await request(config, 'POST', 'components', {
|
|
197
|
+
library_id: library.id, name: flags.name || `${item.name} Copy`, slug: flags.slug,
|
|
198
|
+
category: item.category, description: item.description, tags: item.tags, draft: item.draft, status: item.status,
|
|
199
|
+
});
|
|
200
|
+
output(created, flags, 'Component copied');
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function checkout(projectDir, name, flags) {
|
|
205
|
+
const config = loadProjectConfig(projectDir);
|
|
206
|
+
const item = await findComponent(config, name);
|
|
207
|
+
const directory = componentDir(projectDir, componentName(item));
|
|
208
|
+
if (fs.existsSync(path.join(directory, '.base.json')) && !flags.force) {
|
|
209
|
+
const base = JSON.parse(fs.readFileSync(path.join(directory, '.base.json'), 'utf8'));
|
|
210
|
+
if (definitionHash(localDefinition(directory)) !== definitionHash(base.draft)) {
|
|
211
|
+
throw new Error(`Local changes present for ${name}; commit them or use --force.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
writeComponentFiles(directory, item);
|
|
215
|
+
output({ component: componentName(item), directory: path.relative(projectDir, directory).replace(/\\/g, '/') }, flags, 'Component checked out');
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function localState(projectDir, name) {
|
|
220
|
+
const directory = componentDir(projectDir, name);
|
|
221
|
+
if (!fs.existsSync(path.join(directory, '.base.json'))) throw new Error(`${name} is not checked out.`);
|
|
222
|
+
return {
|
|
223
|
+
directory,
|
|
224
|
+
base: JSON.parse(fs.readFileSync(path.join(directory, '.base.json'), 'utf8')),
|
|
225
|
+
metadata: JSON.parse(fs.readFileSync(path.join(directory, 'component.json'), 'utf8')),
|
|
226
|
+
definition: localDefinition(directory),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function diff(projectDir, name, flags) {
|
|
231
|
+
const config = loadProjectConfig(projectDir);
|
|
232
|
+
const state = localState(projectDir, name);
|
|
233
|
+
const remote = await findComponent(config, name);
|
|
234
|
+
const sections = ['html', 'css', 'js', 'props', 'slots', 'css_variables', 'events', 'dependencies', 'examples', 'usage', 'root_tag', 'meta'];
|
|
235
|
+
const changed = (left, right) => {
|
|
236
|
+
const normalizedLeft = normalizeDefinition(left);
|
|
237
|
+
const normalizedRight = normalizeDefinition(right);
|
|
238
|
+
return sections.filter(key => JSON.stringify(normalizedLeft[key]) !== JSON.stringify(normalizedRight[key]));
|
|
239
|
+
};
|
|
240
|
+
const result = {
|
|
241
|
+
component: name, local_vs_base: changed(state.definition, state.base.draft),
|
|
242
|
+
local_vs_remote_draft: changed(state.definition, remote.draft),
|
|
243
|
+
local_vs_published: changed(state.definition, remote.published),
|
|
244
|
+
base_remote_changed: definitionHash(state.base.draft) !== definitionHash(remote.draft),
|
|
245
|
+
published_revision: remote.published_revision || 0,
|
|
246
|
+
};
|
|
247
|
+
output(result, flags, `Component diff ${name}`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function verifyDefinition(definition) {
|
|
252
|
+
const errors = [];
|
|
253
|
+
const fragment = parse5.parseFragment(definition.html || '');
|
|
254
|
+
const roots = fragment.childNodes.filter(node => node.nodeName !== '#text' || String(node.value || '').trim());
|
|
255
|
+
const elements = roots.filter(node => node.nodeName !== '#text' && node.nodeName !== '#comment');
|
|
256
|
+
if (elements.length !== 1 || roots.some(node => node.nodeName === '#text')) errors.push('HTML must contain exactly one root element.');
|
|
257
|
+
const rootTag = elements[0]?.tagName || elements[0]?.nodeName;
|
|
258
|
+
if (definition.root_tag && definition.root_tag.toLowerCase() !== rootTag) errors.push('root_tag does not match the HTML root.');
|
|
259
|
+
const propNames = new Set();
|
|
260
|
+
for (const prop of definition.props || []) {
|
|
261
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(prop.name || '') || propNames.has(prop.name)) errors.push(`Invalid or duplicate prop: ${prop.name || '(empty)'}`);
|
|
262
|
+
if (!['string', 'boolean', 'number'].includes(prop.type || 'string')) errors.push(`Invalid prop type: ${prop.type}`);
|
|
263
|
+
propNames.add(prop.name);
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(definition.dependencies || [])) errors.push('dependencies must be an array.');
|
|
266
|
+
for (const example of definition.examples || []) {
|
|
267
|
+
if (!example.name || !example.html) errors.push('Each example requires name and html.');
|
|
268
|
+
}
|
|
269
|
+
return errors;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function verify(projectDir, name, flags) {
|
|
273
|
+
const config = loadProjectConfig(projectDir);
|
|
274
|
+
const state = localState(projectDir, name);
|
|
275
|
+
const errors = verifyDefinition(state.definition);
|
|
276
|
+
for (const dependency of state.definition.dependencies || []) {
|
|
277
|
+
try { await findComponent(config, dependency); } catch { errors.push(`Dependency not found: ${dependency}`); }
|
|
278
|
+
}
|
|
279
|
+
const result = { component: name, valid: errors.length === 0, errors };
|
|
280
|
+
output(result, flags, `Component verify ${name}`);
|
|
281
|
+
if (errors.length) throw new Error(`Component verification failed with ${errors.length} error(s).`);
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function commit(projectDir, name, flags) {
|
|
286
|
+
const config = loadProjectConfig(projectDir);
|
|
287
|
+
const state = localState(projectDir, name);
|
|
288
|
+
const errors = verifyDefinition(state.definition);
|
|
289
|
+
for (const dependency of state.definition.dependencies || []) {
|
|
290
|
+
try { await findComponent(config, dependency); } catch { errors.push(`Dependency not found: ${dependency}`); }
|
|
291
|
+
}
|
|
292
|
+
if (errors.length) throw new Error(`Component verification failed: ${errors.join(' ')}`);
|
|
293
|
+
const remote = await findComponent(config, name);
|
|
294
|
+
if (definitionHash(remote.draft) !== definitionHash(state.base.draft)) {
|
|
295
|
+
throw new Error(`Remote draft changed after checkout for ${name}; run components checkout after preserving local changes.`);
|
|
296
|
+
}
|
|
297
|
+
if (definitionHash(state.definition) === definitionHash(remote.draft)
|
|
298
|
+
&& ['name', 'slug', 'category', 'description', 'tags', 'status'].every(key => JSON.stringify(state.metadata[key] ?? null) === JSON.stringify(remote[key] ?? null))) {
|
|
299
|
+
writeComponentFiles(state.directory, remote);
|
|
300
|
+
output({ component: name, status: 'unchanged' }, flags, 'Component draft unchanged');
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
const metadata = state.metadata;
|
|
304
|
+
const result = await request(config, 'PUT', `components/${remote.id}`, {
|
|
305
|
+
library_id: remote.library_id, name: metadata.name, slug: metadata.slug, category: metadata.category || '',
|
|
306
|
+
description: metadata.description || '', tags: metadata.tags || [], status: metadata.status || 'active', draft: state.definition,
|
|
307
|
+
});
|
|
308
|
+
writeComponentFiles(state.directory, result);
|
|
309
|
+
output(result, flags, 'Component draft committed');
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function publish(projectDir, name, flags) {
|
|
314
|
+
const config = loadProjectConfig(projectDir);
|
|
315
|
+
const remote = await findComponent(config, name);
|
|
316
|
+
const state = localState(projectDir, name);
|
|
317
|
+
if (definitionHash(state.definition) !== definitionHash(remote.draft)) {
|
|
318
|
+
throw new Error('Local component differs from the remote draft; commit before publish.');
|
|
319
|
+
}
|
|
320
|
+
const result = await request(config, 'POST', `components/${remote.id}/publish`, {});
|
|
321
|
+
writeComponentFiles(state.directory, result);
|
|
322
|
+
output(result, flags, 'Component published');
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function importZIP(projectDir, file, flags) {
|
|
327
|
+
if (!file) throw new Error('Usage: draftgo components import <zip-file> [--dry-run]');
|
|
328
|
+
const config = loadProjectConfig(projectDir);
|
|
329
|
+
const absolute = path.resolve(projectDir, file);
|
|
330
|
+
const form = new FormData();
|
|
331
|
+
form.append('file', new Blob([fs.readFileSync(absolute)]), path.basename(absolute));
|
|
332
|
+
const result = await request(config, 'POST', `component-libraries/import${flags['dry-run'] ? '?dry_run=1' : ''}`, form);
|
|
333
|
+
output(result, flags, flags['dry-run'] ? 'Component import preview' : 'Components imported');
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function exportZIP(projectDir, librarySlug, flags) {
|
|
338
|
+
if (!librarySlug) throw new Error('Usage: draftgo components export <library> --file <zip-file>');
|
|
339
|
+
const config = loadProjectConfig(projectDir);
|
|
340
|
+
const librariesResult = await request(config, 'GET', 'component-libraries');
|
|
341
|
+
const library = librariesResult.items.find(item => item.slug === librarySlug || String(item.id) === String(librarySlug));
|
|
342
|
+
if (!library) throw new Error(`Component library not found: ${librarySlug}`);
|
|
343
|
+
const components = [];
|
|
344
|
+
for (let page = 1; ; page += 1) {
|
|
345
|
+
const result = await request(config, 'GET', `components?library=${encodeURIComponent(library.slug)}&page=${page}&page_size=100`);
|
|
346
|
+
components.push(...(result.items || []));
|
|
347
|
+
if ((result.items || []).length < 100) break;
|
|
348
|
+
}
|
|
349
|
+
const zip = new AdmZip();
|
|
350
|
+
const manifest = {
|
|
351
|
+
library: { name: library.name, slug: library.slug, description: library.description, source_type: 'imported', common_css: 'library/common.css', common_js: 'library/common.js', assets: [], status: library.status },
|
|
352
|
+
components: [],
|
|
353
|
+
};
|
|
354
|
+
zip.addFile('library/common.css', Buffer.from(library.common_css || '', 'utf8'));
|
|
355
|
+
zip.addFile('library/common.js', Buffer.from(library.common_js || '', 'utf8'));
|
|
356
|
+
for (const [index, asset] of (library.assets || []).entries()) {
|
|
357
|
+
const url = new URL(String(asset), `${config.server}/`);
|
|
358
|
+
const response = await fetch(url, { headers: url.origin === new URL(config.server).origin ? { Authorization: `Bearer ${config.token}` } : {} });
|
|
359
|
+
if (!response.ok) throw new Error(`Unable to export component asset ${asset}: HTTP ${response.status}`);
|
|
360
|
+
const base = path.posix.basename(url.pathname) || `asset-${index + 1}`;
|
|
361
|
+
const archivePath = `library/assets/${index + 1}-${base.replace(/[^A-Za-z0-9._-]/g, '_')}`;
|
|
362
|
+
zip.addFile(archivePath, Buffer.from(await response.arrayBuffer()));
|
|
363
|
+
manifest.library.assets.push(archivePath);
|
|
364
|
+
}
|
|
365
|
+
for (const summary of components) {
|
|
366
|
+
const item = await request(config, 'GET', `components/${summary.id}`);
|
|
367
|
+
const definition = `components/${item.slug}.json`;
|
|
368
|
+
manifest.components.push({ name: item.name, slug: item.slug, category: item.category, description: item.description, tags: item.tags, definition });
|
|
369
|
+
zip.addFile(definition, Buffer.from(`${JSON.stringify(item.draft, null, 2)}\n`, 'utf8'));
|
|
370
|
+
}
|
|
371
|
+
zip.addFile('draftgo-components.json', Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf8'));
|
|
372
|
+
const destination = path.resolve(projectDir, flags.file || `${library.slug}-components.zip`);
|
|
373
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
374
|
+
zip.writeZip(destination);
|
|
375
|
+
output({ library: library.slug, components: manifest.components.length, file: path.relative(projectDir, destination).replace(/\\/g, '/') }, flags, 'Component library exported');
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function walk(node, visit) {
|
|
380
|
+
if (!node) return null;
|
|
381
|
+
if (visit(node)) return node;
|
|
382
|
+
for (const child of node.childNodes || []) { const found = walk(child, visit); if (found) return found; }
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function cloneNode(node, parentNode = null) {
|
|
387
|
+
const copy = { ...node };
|
|
388
|
+
if (node.attrs) copy.attrs = node.attrs.map(attr => ({ ...attr }));
|
|
389
|
+
if (node.childNodes) {
|
|
390
|
+
copy.childNodes = node.childNodes.map(child => cloneNode(child, copy));
|
|
391
|
+
}
|
|
392
|
+
if (parentNode) copy.parentNode = parentNode;
|
|
393
|
+
return copy;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function escapeScript(value) {
|
|
397
|
+
return String(value || '').replace(/<\/script/gi, '<\\/script').replace(/<!--/g, '<\\!--');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function fillSlots(root, target) {
|
|
401
|
+
const supplied = new Map();
|
|
402
|
+
for (const child of target.childNodes || []) {
|
|
403
|
+
const marker = child.attrs?.find(attr => attr.name === 'data-dg-slot');
|
|
404
|
+
const slot = marker?.value || 'default';
|
|
405
|
+
if (!supplied.has(slot)) supplied.set(slot, []);
|
|
406
|
+
if (marker) supplied.get(slot).push(...(child.childNodes || []).map(node => cloneNode(node)));
|
|
407
|
+
else supplied.get(slot).push(cloneNode(child));
|
|
408
|
+
}
|
|
409
|
+
walk(root, node => {
|
|
410
|
+
const marker = node.attrs?.find(attr => attr.name === 'data-dg-slot-target');
|
|
411
|
+
if (!marker) return false;
|
|
412
|
+
const name = marker.value || 'default';
|
|
413
|
+
const nodes = supplied.get(name) || [];
|
|
414
|
+
if (nodes.length) {
|
|
415
|
+
node.childNodes = nodes.map(child => {
|
|
416
|
+
const copy = cloneNode(child, node);
|
|
417
|
+
if (copy.attrs) copy.attrs = copy.attrs.filter(attr => attr.name !== 'data-dg-slot');
|
|
418
|
+
return copy;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
node.attrs = node.attrs.filter(attr => attr.name !== 'data-dg-slot-target');
|
|
422
|
+
return false;
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function expandHtml(source, instance, component, runtimeCatalog = null) {
|
|
427
|
+
const document = parse5.parse(source, { sourceCodeLocationInfo: true });
|
|
428
|
+
const target = walk(document, node => node.attrs?.some(attr => attr.name === 'data-dg-instance' && attr.value === instance));
|
|
429
|
+
if (!target) throw new Error(`data-dg-instance not found: ${instance}`);
|
|
430
|
+
const definition = component.published || component.draft;
|
|
431
|
+
const template = parse5.parseFragment(definition?.html || '');
|
|
432
|
+
const root = template.childNodes.find(node => node.nodeName !== '#text');
|
|
433
|
+
if (!root) throw new Error('Component definition has no root HTML element.');
|
|
434
|
+
if (target.nodeName !== root.nodeName) throw new Error(`root tag must be <${root.nodeName}>`);
|
|
435
|
+
const originalAttrs = new Map((target.attrs || []).map(attr => [attr.name, attr.value]));
|
|
436
|
+
const props = {};
|
|
437
|
+
const slots = {};
|
|
438
|
+
for (const child of target.childNodes || []) {
|
|
439
|
+
const marker = child.attrs?.find(attr => attr.name === 'data-dg-slot');
|
|
440
|
+
const slot = marker?.value || 'default';
|
|
441
|
+
const content = marker ? (child.childNodes || []).map(node => parse5.serialize(node)).join('') : parse5.serialize(child);
|
|
442
|
+
slots[slot] = (slots[slot] || '') + content;
|
|
443
|
+
}
|
|
444
|
+
for (const prop of definition.props || []) {
|
|
445
|
+
const raw = originalAttrs.get(`data-dg-prop-${prop.name}`);
|
|
446
|
+
if (raw === undefined) props[prop.name] = prop.default;
|
|
447
|
+
else if (prop.type === 'boolean') props[prop.name] = raw === '' || raw === 'true' || raw === '1';
|
|
448
|
+
else if (prop.type === 'number') props[prop.name] = Number.isFinite(Number(raw)) ? Number(raw) : prop.default;
|
|
449
|
+
else props[prop.name] = raw;
|
|
450
|
+
}
|
|
451
|
+
fillSlots(root, target);
|
|
452
|
+
const replacement = root;
|
|
453
|
+
replacement.attrs = replacement.attrs || [];
|
|
454
|
+
const attrMap = new Map(replacement.attrs.map(attr => [attr.name, attr.value]));
|
|
455
|
+
for (const [name, value] of originalAttrs) {
|
|
456
|
+
if (name === 'class') continue;
|
|
457
|
+
if (name === 'data-dg-use' || name.startsWith('data-dg-prop-')) continue;
|
|
458
|
+
if (name === 'data-dg-slot') continue;
|
|
459
|
+
// Page-owned attributes win over template defaults, just like the live
|
|
460
|
+
// browser runtime. This preserves ids, names, type, aria and business
|
|
461
|
+
// data attributes when an instance is expanded.
|
|
462
|
+
attrMap.set(name, value);
|
|
463
|
+
}
|
|
464
|
+
attrMap.set('data-dg-expanded-instance', instance);
|
|
465
|
+
attrMap.set('class', [...new Set(`${attrMap.get('class') || ''} ${originalAttrs.get('class') || ''}`.split(/\s+/).filter(Boolean))].join(' '));
|
|
466
|
+
replacement.attrs = [...attrMap].map(([name, value]) => ({ name, value }));
|
|
467
|
+
target.nodeName = replacement.nodeName;
|
|
468
|
+
target.tagName = replacement.tagName;
|
|
469
|
+
target.attrs = replacement.attrs || [];
|
|
470
|
+
target.childNodes = replacement.childNodes || [];
|
|
471
|
+
target.childNodes.forEach(node => { node.parentNode = target; });
|
|
472
|
+
const css = (definition?.css || '').replaceAll(`[data-dg-use="${component.library_slug}/${component.slug}"]`, `[data-dg-expanded-instance="${instance}"]`).replaceAll(`[data-dg-use='${component.library_slug}/${component.slug}']`, `[data-dg-expanded-instance="${instance}"]`);
|
|
473
|
+
const js = definition?.js || '';
|
|
474
|
+
const libraries = runtimeCatalog?.libraries || [];
|
|
475
|
+
const libraryCSS = libraries.map(library => library.common_css || '').filter(Boolean).join('\n');
|
|
476
|
+
const dependencyCSS = (runtimeCatalog?.components || [])
|
|
477
|
+
.filter(item => item.name !== (component.full_name || `${component.library_slug}/${component.slug}`))
|
|
478
|
+
.map(item => item.definition?.css || '')
|
|
479
|
+
.filter(Boolean)
|
|
480
|
+
.join('\n');
|
|
481
|
+
const assets = libraries.flatMap(library => library.assets || []).filter(Boolean);
|
|
482
|
+
const assetLinks = assets.map(value => {
|
|
483
|
+
const url = String(value).replace(/&/g, '&').replace(/"/g, '"');
|
|
484
|
+
if (/\.css(?:[?#]|$)/i.test(url)) return `<link rel="stylesheet" href="${url}" data-dg-expanded-asset>`;
|
|
485
|
+
if (/\.(?:m?js|cjs)(?:[?#]|$)/i.test(url)) return `<script src="${url}" data-dg-expanded-asset></script>`;
|
|
486
|
+
return `<link rel="preload" href="${url}" as="fetch" data-dg-expanded-asset>`;
|
|
487
|
+
}).join('');
|
|
488
|
+
const libraryJS = libraries.map(library => library.common_js || '').filter(Boolean).map((value, index) => `<script data-dg-expanded-library="${index}">${escapeScript(value)}</script>`).join('');
|
|
489
|
+
const combinedCSS = [libraryCSS, dependencyCSS, css].filter(Boolean).join('\n');
|
|
490
|
+
const style = combinedCSS ? `<style data-dg-expanded="${instance}">${combinedCSS}</style>` : '';
|
|
491
|
+
const propsJSON = JSON.stringify(props).replace(/</g, '\\u003c').replace(/<\/script/gi, '<\\/script');
|
|
492
|
+
const slotsJSON = JSON.stringify(slots).replace(/</g, '\\u003c').replace(/<\/script/gi, '<\\/script');
|
|
493
|
+
const slotExpression = `Object.fromEntries(Object.entries(${slotsJSON}).map(([name,html])=>[name,Array.from(document.createRange().createContextualFragment(html).childNodes)]))`;
|
|
494
|
+
const script = js ? `<script data-dg-expanded="${instance}">(()=>{const host=document.querySelector('[data-dg-instance="${instance}"]');const context={props:${propsJSON},slots:${slotExpression},App:window.App,component:${JSON.stringify({ name: component.full_name || `${component.library_slug}/${component.slug}`, revision: component.published_revision || 0 })}};${escapeScript(js)};if(typeof mount==='function')mount(host,context)})()</script>` : '';
|
|
495
|
+
const htmlSource = parse5.serialize(document);
|
|
496
|
+
return htmlSource.replace(/<\/head>/i, `${assetLinks}${style}</head>`).replace(/<\/body>/i, `${libraryJS}${script}</body>`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function expand(projectDir, flags) {
|
|
500
|
+
const pageID = flags.page;
|
|
501
|
+
const instance = flags.instance;
|
|
502
|
+
if (!pageID || !instance) throw new Error('Usage: draftgo components expand --page <id> --instance <data-dg-instance>');
|
|
503
|
+
const config = loadProjectConfig(projectDir);
|
|
504
|
+
const manifest = loadManifest(projectDir);
|
|
505
|
+
const entry = getEntry(manifest, 'pages', pageID);
|
|
506
|
+
if (!entry) throw new Error(`Page ${pageID} is not checked out; run draftgo checkout pages ${pageID} first.`);
|
|
507
|
+
const file = absolutePath(projectDir, entry.local_path);
|
|
508
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
509
|
+
const parsed = parse5.parse(source);
|
|
510
|
+
const target = walk(parsed, node => node.attrs?.some(attr => attr.name === 'data-dg-instance' && attr.value === instance));
|
|
511
|
+
if (!target) throw new Error(`data-dg-instance not found: ${instance}`);
|
|
512
|
+
const use = target.attrs.find(attr => attr.name === 'data-dg-use')?.value;
|
|
513
|
+
if (!use) throw new Error('The selected instance does not have data-dg-use.');
|
|
514
|
+
const slug = use.split('/').pop();
|
|
515
|
+
const result = await request(config, 'GET', `components?q=${encodeURIComponent(slug)}&page_size=100`);
|
|
516
|
+
const component = (result.items || []).find(item => item.full_name === use || `${item.library_slug}/${item.slug}` === use || item.slug === slug);
|
|
517
|
+
if (!component) throw new Error(`Component not found: ${use}`);
|
|
518
|
+
let runtimeCatalog = null;
|
|
519
|
+
try {
|
|
520
|
+
runtimeCatalog = await request(config, 'POST', 'component-runtime/resolve', { components: [use] });
|
|
521
|
+
} catch {
|
|
522
|
+
// Older servers may not expose runtime resolve yet; the component can
|
|
523
|
+
// still be expanded with its own definition as a compatibility fallback.
|
|
524
|
+
}
|
|
525
|
+
fs.writeFileSync(file, expandHtml(source, instance, component, runtimeCatalog), 'utf8');
|
|
526
|
+
output({ page: String(pageID), instance, component: use, file: path.relative(projectDir, file).replace(/\\/g, '/') }, flags, 'Component expanded');
|
|
527
|
+
return 0;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
module.exports = async function components(projectDir, positional = [], flags = {}) {
|
|
531
|
+
const action = String(positional[0] || 'search').toLowerCase();
|
|
532
|
+
if (action === 'libraries') return libraries(projectDir, positional.slice(1), flags);
|
|
533
|
+
if (action === 'list') return list(projectDir, flags);
|
|
534
|
+
if (action === 'search') return search(projectDir, positional.slice(1).join(' ').trim(), flags);
|
|
535
|
+
if (action === 'show') return show(projectDir, positional[1], flags);
|
|
536
|
+
if (action === 'create') return create(projectDir, flags);
|
|
537
|
+
if (action === 'delete') return remove(projectDir, positional[1], flags);
|
|
538
|
+
if (action === 'copy') return copy(projectDir, positional[1], flags);
|
|
539
|
+
if (action === 'checkout') return checkout(projectDir, positional[1], flags);
|
|
540
|
+
if (action === 'diff') return diff(projectDir, positional[1], flags);
|
|
541
|
+
if (action === 'verify') return verify(projectDir, positional[1], flags);
|
|
542
|
+
if (action === 'commit') return commit(projectDir, positional[1], flags);
|
|
543
|
+
if (action === 'publish') return publish(projectDir, positional[1], flags);
|
|
544
|
+
if (action === 'import') return importZIP(projectDir, positional[1], flags);
|
|
545
|
+
if (action === 'export') return exportZIP(projectDir, positional[1], flags);
|
|
546
|
+
if (action === 'expand') return expand(projectDir, flags);
|
|
547
|
+
throw new Error('Usage: draftgo components libraries|list|search|show|create|delete|copy|checkout|diff|verify|commit|publish|import|export|expand');
|
|
548
|
+
};
|
|
549
|
+
module.exports.expandHtml = expandHtml;
|
|
550
|
+
module.exports.verifyDefinition = verifyDefinition;
|
package/src/commands/conflict.js
CHANGED
|
@@ -15,7 +15,7 @@ function printRecord(record) {
|
|
|
15
15
|
async function conflict(projectDir, positional, flags = {}) {
|
|
16
16
|
const [action, resourceType, resourceId] = positional;
|
|
17
17
|
if (!['show', 'resolve'].includes(action) || !resourceType || !resourceId) {
|
|
18
|
-
log.err('Usage: draftgo conflict <show|resolve> <pages|nav|docs> <id>');
|
|
18
|
+
log.err('Usage: draftgo conflict <show|resolve> <pages|nav|docs|custom-services> <id>');
|
|
19
19
|
return 1;
|
|
20
20
|
}
|
|
21
21
|
const custom = require('./customService').isServiceType(resourceType);
|
package/src/commands/connect.js
CHANGED
|
@@ -19,11 +19,11 @@ async function promptServer(defaultValue) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async function
|
|
22
|
+
async function promptAPIKey() {
|
|
23
23
|
while (true) {
|
|
24
|
-
const value = String(await askPassword('DraftGo
|
|
24
|
+
const value = String(await askPassword('DraftGo user API Key') || '').trim();
|
|
25
25
|
if (value) return value;
|
|
26
|
-
log.dim(' A
|
|
26
|
+
log.dim(' A DraftGo API Key is required.');
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -42,31 +42,41 @@ async function connect(projectDir, positional, flags = {}) {
|
|
|
42
42
|
mcp_url: endpointConnection && endpointConnection.mcp_url || '',
|
|
43
43
|
};
|
|
44
44
|
const server = connection.server;
|
|
45
|
-
const
|
|
45
|
+
const suppliedAPIKey = flags['api-key'] || flags.token;
|
|
46
|
+
const token = suppliedAPIKey ? String(suppliedAPIKey).trim() : await promptAPIKey();
|
|
46
47
|
if (!token) {
|
|
47
|
-
log.err('A non-empty
|
|
48
|
+
log.err('A non-empty DraftGo API Key is required.');
|
|
48
49
|
return 1;
|
|
49
50
|
}
|
|
50
51
|
const timeoutMs = parseTimeout(flags.timeout);
|
|
52
|
+
if ((flags['scope-type'] === 'space') !== Boolean(flags['space-id'])) {
|
|
53
|
+
throw new Error('--scope-type space and --space-id must be provided together.');
|
|
54
|
+
}
|
|
51
55
|
|
|
52
|
-
log.step('Validating
|
|
56
|
+
log.step('Validating DraftGo API Key and MCP capabilities...');
|
|
53
57
|
try {
|
|
54
58
|
const diagnostic = await testConnection({ ...connection, server, token }, {
|
|
55
59
|
timeoutMs,
|
|
60
|
+
scopeType: flags['scope-type'],
|
|
61
|
+
spaceId: flags['space-id'] && Number(flags['space-id']),
|
|
56
62
|
});
|
|
57
63
|
log.ok(`MCP ready (${diagnostic.tools.length} tools; tested ${diagnostic.testedCalls.join(', ')}).`);
|
|
58
64
|
} catch (error) {
|
|
59
65
|
const message = redactText(error && error.message ? error.message : error, [token]);
|
|
60
66
|
if (!flags['allow-offline']) {
|
|
61
67
|
log.err(message);
|
|
62
|
-
log.dim(' The project config was not changed. Run `draftgo mcp test` after checking the server and
|
|
68
|
+
log.dim(' The project config was not changed. Run `draftgo mcp test` after checking the server and API Key.');
|
|
63
69
|
return 1;
|
|
64
70
|
}
|
|
65
71
|
log.warn(`MCP validation unavailable: ${message}`);
|
|
66
72
|
log.warn('Continuing only because --allow-offline was explicitly supplied.');
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
const configFile = writeProjectConfig(projectDir, server, token, {
|
|
75
|
+
const configFile = writeProjectConfig(projectDir, server, token, {
|
|
76
|
+
mcp_url: connection.mcp_url,
|
|
77
|
+
scope_type: flags['scope-type'],
|
|
78
|
+
space_id: flags['space-id'],
|
|
79
|
+
});
|
|
70
80
|
log.ok(`Project configuration written: ${configFile}`);
|
|
71
81
|
|
|
72
82
|
if (!flags['no-mcp-setup']) {
|