knowzcode 0.3.7 → 0.5.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +79 -20
- package/bin/knowzcode.mjs +522 -21
- package/commands/connect-mcp.md +25 -0
- package/commands/init.md +177 -0
- package/commands/register.md +24 -0
- package/commands/status.md +35 -6
- package/commands/work.md +8 -6
- package/knowzcode/claude_code_execution.md +11 -0
- package/knowzcode/knowzcode_loop.md +6 -1
- package/knowzcode/mcp_config.md +5 -0
- package/knowzcode/platform_adapters.md +1610 -80
- package/package.json +1 -1
- package/skills/alias-resolver.json +15 -15
- package/skills/architecture-diff.json +12 -12
- package/skills/check-installation-status.json +14 -14
- package/skills/environment-guard.json +12 -12
- package/skills/generate-workgroup-id.json +25 -25
- package/skills/install-knowzcode.json +21 -21
- package/skills/load-core-context.json +18 -18
- package/skills/log-entry-builder.json +15 -15
- package/skills/spec-quality-check.json +14 -14
- package/skills/spec-template.json +15 -15
- package/skills/spec-validator.json +25 -25
- package/skills/tracker-scan.json +12 -12
- package/skills/tracker-update.json +28 -28
- package/skills/validate-installation.json +14 -14
package/bin/knowzcode.mjs
CHANGED
|
@@ -43,7 +43,7 @@ const PLATFORMS = {
|
|
|
43
43
|
},
|
|
44
44
|
codex: {
|
|
45
45
|
name: 'OpenAI Codex',
|
|
46
|
-
detect: (dir) => existsSync(join(dir, 'AGENTS.md')) || existsSync(join(dir, 'AGENTS.override.md')) || existsSync(join(dir, '.codex')),
|
|
46
|
+
detect: (dir) => existsSync(join(dir, 'AGENTS.md')) || existsSync(join(dir, 'AGENTS.override.md')) || existsSync(join(dir, '.codex')) || existsSync(join(dir, '.agents')),
|
|
47
47
|
adapterPath: (dir) => join(dir, 'AGENTS.md'),
|
|
48
48
|
templateHeader: '## OpenAI Codex (AGENTS.md)',
|
|
49
49
|
},
|
|
@@ -63,7 +63,7 @@ const PLATFORMS = {
|
|
|
63
63
|
name: 'GitHub Copilot',
|
|
64
64
|
detect: (dir) => existsSync(join(dir, '.github', 'copilot-instructions.md')) || existsSync(join(dir, '.github')),
|
|
65
65
|
adapterPath: (dir) => join(dir, '.github', 'copilot-instructions.md'),
|
|
66
|
-
templateHeader: '## GitHub Copilot
|
|
66
|
+
templateHeader: '## GitHub Copilot',
|
|
67
67
|
},
|
|
68
68
|
windsurf: {
|
|
69
69
|
name: 'Windsurf',
|
|
@@ -128,6 +128,185 @@ function detectPlatforms(dir) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// ─── Adapter Template Parser ─────────────────────────────────────────────────
|
|
131
|
+
// Returns Map<platformId, { primary: string, files: Map<relativePath, { content, lang }> }>
|
|
132
|
+
|
|
133
|
+
function injectVersion(content) {
|
|
134
|
+
return content.replace(/vX\.Y\.Z/g, `v${VERSION}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractSection(content, headerIdx) {
|
|
138
|
+
const afterHeader = content.slice(headerIdx);
|
|
139
|
+
const nextSection = afterHeader.search(/\r?\n---\r?\n\r?\n## /);
|
|
140
|
+
return nextSection !== -1 ? afterHeader.slice(0, nextSection) : afterHeader;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function extractFence(text, lang, startFrom = 0) {
|
|
144
|
+
const marker = '```' + lang;
|
|
145
|
+
const fenceStart = text.indexOf(marker, startFrom);
|
|
146
|
+
if (fenceStart === -1) return null;
|
|
147
|
+
const contentStart = text.indexOf('\n', fenceStart) + 1;
|
|
148
|
+
// Track nested fences to find the matching closing fence
|
|
149
|
+
let depth = 0;
|
|
150
|
+
let pos = contentStart;
|
|
151
|
+
while (pos < text.length) {
|
|
152
|
+
const nextFence = text.indexOf('\n```', pos);
|
|
153
|
+
if (nextFence === -1) return null;
|
|
154
|
+
const afterBackticks = nextFence + 4;
|
|
155
|
+
const charAfter = afterBackticks < text.length ? text[afterBackticks] : undefined;
|
|
156
|
+
if (charAfter && /\w/.test(charAfter)) {
|
|
157
|
+
// Opening fence (```bash, ```json, etc.)
|
|
158
|
+
depth++;
|
|
159
|
+
} else {
|
|
160
|
+
// Closing fence (``` followed by whitespace/newline/EOF)
|
|
161
|
+
if (depth === 0) {
|
|
162
|
+
return { content: text.slice(contentStart, nextFence), endIdx: afterBackticks };
|
|
163
|
+
}
|
|
164
|
+
depth--;
|
|
165
|
+
}
|
|
166
|
+
pos = afterBackticks;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseCopilotSection(section) {
|
|
172
|
+
const files = new Map();
|
|
173
|
+
|
|
174
|
+
// Section A: copilot-instructions.md (first ```markdown before ### B.)
|
|
175
|
+
const sectionBIdx = section.indexOf('### B.');
|
|
176
|
+
const sectionA = sectionBIdx !== -1 ? section.slice(0, sectionBIdx) : section;
|
|
177
|
+
const primaryFence = extractFence(sectionA, 'markdown');
|
|
178
|
+
if (!primaryFence) return null;
|
|
179
|
+
|
|
180
|
+
// Section B: prompt files (#### kc-*.prompt.md headers)
|
|
181
|
+
const headerRegex = /#### (kc-[\w-]+\.prompt\.md)/g;
|
|
182
|
+
const headers = [];
|
|
183
|
+
let match;
|
|
184
|
+
while ((match = headerRegex.exec(section)) !== null) {
|
|
185
|
+
headers.push({ filename: match[1], index: match.index });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const sectionCIdx = section.indexOf('### C.');
|
|
189
|
+
for (let i = 0; i < headers.length; i++) {
|
|
190
|
+
const start = headers[i].index;
|
|
191
|
+
const end = i + 1 < headers.length
|
|
192
|
+
? headers[i + 1].index
|
|
193
|
+
: (sectionCIdx !== -1 && sectionCIdx > start ? sectionCIdx : section.length);
|
|
194
|
+
const subSection = section.slice(start, end);
|
|
195
|
+
|
|
196
|
+
const fenceOpen = subSection.indexOf('```markdown');
|
|
197
|
+
if (fenceOpen === -1) continue;
|
|
198
|
+
const contentStart = subSection.indexOf('\n', fenceOpen) + 1;
|
|
199
|
+
// Use lastIndexOf to handle prompt files that contain inner code fences
|
|
200
|
+
const lastFenceClose = subSection.lastIndexOf('\n```');
|
|
201
|
+
if (lastFenceClose <= contentStart) continue;
|
|
202
|
+
|
|
203
|
+
files.set(`.github/prompts/${headers[i].filename}`, {
|
|
204
|
+
content: subSection.slice(contentStart, lastFenceClose),
|
|
205
|
+
lang: 'markdown',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Section C: .vscode/mcp.json
|
|
210
|
+
if (sectionCIdx !== -1) {
|
|
211
|
+
const sectionDIdx = section.indexOf('### D.', sectionCIdx);
|
|
212
|
+
const sectionC = section.slice(sectionCIdx, sectionDIdx !== -1 ? sectionDIdx : section.length);
|
|
213
|
+
const jsonFence = extractFence(sectionC, 'json');
|
|
214
|
+
if (jsonFence) {
|
|
215
|
+
files.set('.vscode/mcp.json', { content: jsonFence.content, lang: 'json' });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { primary: primaryFence.content, files };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseGeminiSection(section) {
|
|
223
|
+
const files = new Map();
|
|
224
|
+
|
|
225
|
+
// Extract TOML blocks: ```toml fences with # .gemini/commands/kc/{name}.toml comment
|
|
226
|
+
let searchFrom = 0;
|
|
227
|
+
while (true) {
|
|
228
|
+
const fenceStart = section.indexOf('```toml', searchFrom);
|
|
229
|
+
if (fenceStart === -1) break;
|
|
230
|
+
const contentStart = section.indexOf('\n', fenceStart) + 1;
|
|
231
|
+
const fenceEnd = section.indexOf('\n```', contentStart);
|
|
232
|
+
if (fenceEnd === -1) break;
|
|
233
|
+
const tomlContent = section.slice(contentStart, fenceEnd);
|
|
234
|
+
const pathMatch = tomlContent.match(/^# (\.gemini\/commands\/kc\/[\w-]+\.toml)/);
|
|
235
|
+
if (pathMatch) {
|
|
236
|
+
files.set(pathMatch[1], { content: tomlContent, lang: 'toml' });
|
|
237
|
+
}
|
|
238
|
+
searchFrom = fenceEnd + 4;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Skill files: #### .gemini/skills/kc-{name}/SKILL.md headers
|
|
242
|
+
const skillRegex = /#### (\.gemini\/skills\/kc-[\w-]+\/SKILL\.md)/g;
|
|
243
|
+
const skillHeaders = [];
|
|
244
|
+
let skillMatch;
|
|
245
|
+
while ((skillMatch = skillRegex.exec(section)) !== null) {
|
|
246
|
+
skillHeaders.push({ filepath: skillMatch[1], index: skillMatch.index });
|
|
247
|
+
}
|
|
248
|
+
// Subagent files: #### .gemini/agents/kc-{name}.md headers
|
|
249
|
+
const agentRegex = /#### (\.gemini\/agents\/kc-[\w-]+\.md)/g;
|
|
250
|
+
const agentHeaders = [];
|
|
251
|
+
let agentMatch;
|
|
252
|
+
while ((agentMatch = agentRegex.exec(section)) !== null) {
|
|
253
|
+
agentHeaders.push({ filepath: agentMatch[1], index: agentMatch.index });
|
|
254
|
+
}
|
|
255
|
+
// Combine all subsection headers for boundary detection
|
|
256
|
+
const allSubHeaders = [...skillHeaders, ...agentHeaders].sort((a, b) => a.index - b.index);
|
|
257
|
+
|
|
258
|
+
for (let i = 0; i < allSubHeaders.length; i++) {
|
|
259
|
+
const start = allSubHeaders[i].index;
|
|
260
|
+
const end = i + 1 < allSubHeaders.length ? allSubHeaders[i + 1].index : section.length;
|
|
261
|
+
const subSection = section.slice(start, end);
|
|
262
|
+
const fence = extractFence(subSection, 'markdown');
|
|
263
|
+
if (fence) {
|
|
264
|
+
files.set(allSubHeaders[i].filepath, { content: fence.content, lang: 'markdown' });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Primary: ```markdown fence (GEMINI.md) — extract from content BEFORE first skill/subagent header
|
|
269
|
+
const firstSubHeader = allSubHeaders.length > 0 ? allSubHeaders[0].index : section.length;
|
|
270
|
+
const primarySection = section.slice(0, firstSubHeader);
|
|
271
|
+
const primaryFence = extractFence(primarySection, 'markdown');
|
|
272
|
+
if (!primaryFence) return null;
|
|
273
|
+
|
|
274
|
+
return { primary: primaryFence.content, files };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function parseCodexSection(section) {
|
|
278
|
+
const files = new Map();
|
|
279
|
+
|
|
280
|
+
// Primary: first ```markdown fence (AGENTS.md)
|
|
281
|
+
const primaryFence = extractFence(section, 'markdown');
|
|
282
|
+
if (!primaryFence) return null;
|
|
283
|
+
|
|
284
|
+
// Skill files: #### .agents/skills/kc-{name}/SKILL.md headers
|
|
285
|
+
const headerRegex = /#### (\.agents\/skills\/kc-[\w-]+\/SKILL\.md)/g;
|
|
286
|
+
const headers = [];
|
|
287
|
+
let match;
|
|
288
|
+
while ((match = headerRegex.exec(section)) !== null) {
|
|
289
|
+
headers.push({ filepath: match[1], index: match.index });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i < headers.length; i++) {
|
|
293
|
+
const start = headers[i].index;
|
|
294
|
+
const end = i + 1 < headers.length ? headers[i + 1].index : section.length;
|
|
295
|
+
const subSection = section.slice(start, end);
|
|
296
|
+
const fence = extractFence(subSection, 'markdown');
|
|
297
|
+
if (fence) {
|
|
298
|
+
files.set(headers[i].filepath, { content: fence.content, lang: 'markdown' });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { primary: primaryFence.content, files };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseSimpleSection(section) {
|
|
306
|
+
const primaryFence = extractFence(section, 'markdown');
|
|
307
|
+
if (!primaryFence) return null;
|
|
308
|
+
return { primary: primaryFence.content, files: new Map() };
|
|
309
|
+
}
|
|
131
310
|
|
|
132
311
|
function parseAdapterTemplates() {
|
|
133
312
|
const adaptersPath = join(PKG_ROOT, 'knowzcode', 'platform_adapters.md');
|
|
@@ -145,16 +324,15 @@ function parseAdapterTemplates() {
|
|
|
145
324
|
const headerIdx = content.indexOf(platform.templateHeader);
|
|
146
325
|
if (headerIdx === -1) continue;
|
|
147
326
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
templates.set(id, afterHeader.slice(contentStart, fenceEnd));
|
|
327
|
+
const section = extractSection(content, headerIdx);
|
|
328
|
+
let result;
|
|
329
|
+
switch (id) {
|
|
330
|
+
case 'copilot': result = parseCopilotSection(section); break;
|
|
331
|
+
case 'gemini': result = parseGeminiSection(section); break;
|
|
332
|
+
case 'codex': result = parseCodexSection(section); break;
|
|
333
|
+
default: result = parseSimpleSection(section); break;
|
|
334
|
+
}
|
|
335
|
+
if (result) templates.set(id, result);
|
|
158
336
|
}
|
|
159
337
|
|
|
160
338
|
return templates;
|
|
@@ -233,6 +411,57 @@ function removeMarketplaceConfig(claudeDir) {
|
|
|
233
411
|
}
|
|
234
412
|
}
|
|
235
413
|
|
|
414
|
+
// ─── Gemini MCP Config Helpers ────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
function writeGeminiMcpConfig(settingsPath, apiKey, projectPath, endpoint = 'https://mcp.knowz.io/mcp') {
|
|
417
|
+
ensureDir(dirname(settingsPath));
|
|
418
|
+
let settings = {};
|
|
419
|
+
if (existsSync(settingsPath)) {
|
|
420
|
+
try {
|
|
421
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
422
|
+
} catch {
|
|
423
|
+
settings = {};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
427
|
+
settings.mcpServers.knowz = {
|
|
428
|
+
url: endpoint,
|
|
429
|
+
headers: {
|
|
430
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
431
|
+
'X-Project-Path': projectPath,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function removeGeminiMcpConfig(settingsPath) {
|
|
438
|
+
if (!existsSync(settingsPath)) return false;
|
|
439
|
+
try {
|
|
440
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
441
|
+
if (settings.mcpServers && settings.mcpServers.knowz) {
|
|
442
|
+
delete settings.mcpServers.knowz;
|
|
443
|
+
if (Object.keys(settings.mcpServers).length === 0) {
|
|
444
|
+
delete settings.mcpServers;
|
|
445
|
+
}
|
|
446
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
// Ignore parse errors
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function hasGeminiMcpConfig(settingsPath) {
|
|
456
|
+
if (!existsSync(settingsPath)) return false;
|
|
457
|
+
try {
|
|
458
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
459
|
+
return !!(settings.mcpServers && settings.mcpServers.knowz);
|
|
460
|
+
} catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
236
465
|
// ─── Stale File Cleanup ─────────────────────────────────────────────────────
|
|
237
466
|
|
|
238
467
|
function removeStaleFiles(sourceDir, targetDir) {
|
|
@@ -550,18 +779,76 @@ async function cmdInstall(opts) {
|
|
|
550
779
|
|
|
551
780
|
adapterFiles.push(claudeDir + '/commands/', claudeDir + '/agents/', claudeDir + '/skills/');
|
|
552
781
|
} else {
|
|
553
|
-
// Other platforms: extract template and write adapter
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
782
|
+
// Other platforms: extract template and write adapter + additional files
|
|
783
|
+
const templateSet = templates.get(platformId);
|
|
784
|
+
if (!templateSet) {
|
|
556
785
|
log.warn(`No adapter template found for ${platform.name} — skipping`);
|
|
557
786
|
continue;
|
|
558
787
|
}
|
|
559
788
|
|
|
789
|
+
// Write primary adapter file
|
|
560
790
|
const adapterFile = platform.adapterPath(dir);
|
|
561
791
|
ensureDir(dirname(adapterFile));
|
|
562
|
-
writeFileSync(adapterFile,
|
|
792
|
+
writeFileSync(adapterFile, injectVersion(templateSet.primary));
|
|
563
793
|
adapterFiles.push(adapterFile);
|
|
564
794
|
log.ok(`${platform.name} adapter: ${adapterFile}`);
|
|
795
|
+
|
|
796
|
+
// Write additional files (prompts, TOMLs, skills, subagents)
|
|
797
|
+
for (const [relativePath, { content }] of templateSet.files) {
|
|
798
|
+
let filePath;
|
|
799
|
+
if (platformId === 'codex' && opts.global && relativePath.startsWith('.agents/skills/')) {
|
|
800
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
|
|
801
|
+
filePath = join(homeDir, relativePath);
|
|
802
|
+
} else if (platformId === 'gemini' && opts.global && relativePath.startsWith('.gemini/skills/')) {
|
|
803
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
|
|
804
|
+
filePath = join(homeDir, relativePath);
|
|
805
|
+
} else {
|
|
806
|
+
filePath = join(dir, relativePath);
|
|
807
|
+
}
|
|
808
|
+
ensureDir(dirname(filePath));
|
|
809
|
+
writeFileSync(filePath, injectVersion(content));
|
|
810
|
+
adapterFiles.push(filePath);
|
|
811
|
+
}
|
|
812
|
+
if (templateSet.files.size > 0) {
|
|
813
|
+
log.ok(` + ${templateSet.files.size} additional file(s)`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Clean up legacy .codex/skills/kc/ if present (migrated to .agents/skills/)
|
|
817
|
+
if (platformId === 'codex') {
|
|
818
|
+
const legacySkillDir = join(dir, '.codex', 'skills', 'kc');
|
|
819
|
+
if (existsSync(legacySkillDir)) {
|
|
820
|
+
log.info('Removing legacy .codex/skills/kc/ (migrated to .agents/skills/)');
|
|
821
|
+
rmSync(legacySkillDir, { recursive: true, force: true });
|
|
822
|
+
}
|
|
823
|
+
if (opts.global) {
|
|
824
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
|
|
825
|
+
const legacyGlobal = join(homeDir, '.codex', 'skills', 'kc');
|
|
826
|
+
if (existsSync(legacyGlobal)) {
|
|
827
|
+
log.info('Removing legacy global ~/.codex/skills/kc/');
|
|
828
|
+
rmSync(legacyGlobal, { recursive: true, force: true });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// 3.5. Gemini MCP config offer (when Gemini is selected and not --force)
|
|
836
|
+
if (selectedPlatforms.includes('gemini') && !opts.global && !opts.force) {
|
|
837
|
+
console.log('');
|
|
838
|
+
console.log(`${c.bold}Gemini MCP Configuration${c.reset}`);
|
|
839
|
+
console.log(`MCP enables vector search, vault access, and AI-powered Q&A.`);
|
|
840
|
+
const wantMcp = await promptConfirm('Configure MCP for Gemini CLI? (requires API key)');
|
|
841
|
+
if (wantMcp) {
|
|
842
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
843
|
+
const apiKey = await rl.question('Enter your KnowzCode API key: ');
|
|
844
|
+
rl.close();
|
|
845
|
+
if (apiKey.trim()) {
|
|
846
|
+
const settingsPath = join(dir, '.gemini', 'settings.json');
|
|
847
|
+
writeGeminiMcpConfig(settingsPath, apiKey.trim(), dir);
|
|
848
|
+
log.ok('Gemini MCP configured in .gemini/settings.json');
|
|
849
|
+
} else {
|
|
850
|
+
log.warn('No API key provided — skipping MCP config. Use /kc:connect-mcp later.');
|
|
851
|
+
}
|
|
565
852
|
}
|
|
566
853
|
}
|
|
567
854
|
|
|
@@ -657,6 +944,76 @@ async function cmdUninstall(opts) {
|
|
|
657
944
|
}
|
|
658
945
|
}
|
|
659
946
|
|
|
947
|
+
// Additional platform-specific files/directories
|
|
948
|
+
const copilotPromptsDir = join(dir, '.github', 'prompts');
|
|
949
|
+
if (existsSync(copilotPromptsDir)) {
|
|
950
|
+
for (const f of readdirSync(copilotPromptsDir)) {
|
|
951
|
+
if (f.startsWith('kc-') && f.endsWith('.prompt.md')) {
|
|
952
|
+
components.push({ label: `Copilot prompt: ${f}`, path: join(copilotPromptsDir, f) });
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const vscodeMcp = join(dir, '.vscode', 'mcp.json');
|
|
957
|
+
if (existsSync(vscodeMcp)) {
|
|
958
|
+
components.push({ label: 'VS Code MCP config', path: vscodeMcp });
|
|
959
|
+
}
|
|
960
|
+
const geminiCmdDir = join(dir, '.gemini', 'commands', 'kc');
|
|
961
|
+
if (existsSync(geminiCmdDir)) {
|
|
962
|
+
components.push({ label: 'Gemini commands (kc/)', path: geminiCmdDir });
|
|
963
|
+
}
|
|
964
|
+
// Gemini skills: .gemini/skills/kc-*
|
|
965
|
+
const geminiSkillDir = join(dir, '.gemini', 'skills');
|
|
966
|
+
if (existsSync(geminiSkillDir)) {
|
|
967
|
+
for (const entry of readdirSync(geminiSkillDir)) {
|
|
968
|
+
if (entry.startsWith('kc-')) {
|
|
969
|
+
components.push({ label: `Gemini skill (${entry}/)`, path: join(geminiSkillDir, entry) });
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
const globalGeminiSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.gemini', 'skills');
|
|
974
|
+
if (existsSync(globalGeminiSkillDir)) {
|
|
975
|
+
for (const entry of readdirSync(globalGeminiSkillDir)) {
|
|
976
|
+
if (entry.startsWith('kc-')) {
|
|
977
|
+
components.push({ label: `Gemini skill — global (~/.gemini/skills/${entry}/)`, path: join(globalGeminiSkillDir, entry) });
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
// Gemini subagents: .gemini/agents/kc-*.md
|
|
982
|
+
const geminiAgentDir = join(dir, '.gemini', 'agents');
|
|
983
|
+
if (existsSync(geminiAgentDir)) {
|
|
984
|
+
for (const entry of readdirSync(geminiAgentDir)) {
|
|
985
|
+
if (entry.startsWith('kc-') && entry.endsWith('.md')) {
|
|
986
|
+
components.push({ label: `Gemini subagent (${entry})`, path: join(geminiAgentDir, entry) });
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// New path: .agents/skills/kc-*
|
|
991
|
+
const agentsSkillDir = join(dir, '.agents', 'skills');
|
|
992
|
+
if (existsSync(agentsSkillDir)) {
|
|
993
|
+
for (const entry of readdirSync(agentsSkillDir)) {
|
|
994
|
+
if (entry.startsWith('kc-')) {
|
|
995
|
+
components.push({ label: `Codex skill (${entry}/)`, path: join(agentsSkillDir, entry) });
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const globalAgentsSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.agents', 'skills');
|
|
1000
|
+
if (existsSync(globalAgentsSkillDir)) {
|
|
1001
|
+
for (const entry of readdirSync(globalAgentsSkillDir)) {
|
|
1002
|
+
if (entry.startsWith('kc-')) {
|
|
1003
|
+
components.push({ label: `Codex skill — global (~/.agents/skills/${entry}/)`, path: join(globalAgentsSkillDir, entry) });
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
// Legacy path: .codex/skills/kc (remove on uninstall)
|
|
1008
|
+
const legacyCodexSkillDir = join(dir, '.codex', 'skills', 'kc');
|
|
1009
|
+
if (existsSync(legacyCodexSkillDir)) {
|
|
1010
|
+
components.push({ label: 'Codex skills — legacy (.codex/skills/kc/)', path: legacyCodexSkillDir });
|
|
1011
|
+
}
|
|
1012
|
+
const legacyGlobalCodexSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.codex', 'skills', 'kc');
|
|
1013
|
+
if (existsSync(legacyGlobalCodexSkillDir)) {
|
|
1014
|
+
components.push({ label: 'Codex skills — legacy global (~/.codex/skills/kc/)', path: legacyGlobalCodexSkillDir });
|
|
1015
|
+
}
|
|
1016
|
+
|
|
660
1017
|
if (components.length === 0) {
|
|
661
1018
|
log.info('No KnowzCode installation found.');
|
|
662
1019
|
return;
|
|
@@ -705,6 +1062,17 @@ async function cmdUninstall(opts) {
|
|
|
705
1062
|
// Clean up marketplace config from settings.json
|
|
706
1063
|
removeMarketplaceConfig(claudeDir);
|
|
707
1064
|
|
|
1065
|
+
// Clean up Gemini MCP config (remove only knowz entry, preserve other settings)
|
|
1066
|
+
const geminiSettingsProject = join(dir, '.gemini', 'settings.json');
|
|
1067
|
+
if (removeGeminiMcpConfig(geminiSettingsProject)) {
|
|
1068
|
+
removed.push('Gemini MCP config (.gemini/settings.json)');
|
|
1069
|
+
}
|
|
1070
|
+
const homeDir2 = process.env.HOME || process.env.USERPROFILE || '~';
|
|
1071
|
+
const geminiSettingsUser = join(homeDir2, '.gemini', 'settings.json');
|
|
1072
|
+
if (removeGeminiMcpConfig(geminiSettingsUser)) {
|
|
1073
|
+
removed.push('Gemini MCP config (~/.gemini/settings.json)');
|
|
1074
|
+
}
|
|
1075
|
+
|
|
708
1076
|
console.log('');
|
|
709
1077
|
log.ok('Uninstall complete');
|
|
710
1078
|
console.log(' Removed:');
|
|
@@ -821,11 +1189,144 @@ async function cmdUpgrade(opts) {
|
|
|
821
1189
|
const adapterFile = platform.adapterPath(dir);
|
|
822
1190
|
if (!existsSync(adapterFile)) continue; // Only update existing adapters
|
|
823
1191
|
|
|
824
|
-
const
|
|
825
|
-
if (!
|
|
1192
|
+
const templateSet = templates.get(platformId);
|
|
1193
|
+
if (!templateSet) continue;
|
|
826
1194
|
|
|
827
|
-
|
|
1195
|
+
// Update primary adapter file
|
|
1196
|
+
writeFileSync(adapterFile, injectVersion(templateSet.primary));
|
|
828
1197
|
regenerated.push(platform.name);
|
|
1198
|
+
|
|
1199
|
+
// Regenerate additional files
|
|
1200
|
+
const currentPaths = new Set();
|
|
1201
|
+
for (const [relativePath, { content }] of templateSet.files) {
|
|
1202
|
+
const filePath = join(dir, relativePath);
|
|
1203
|
+
ensureDir(dirname(filePath));
|
|
1204
|
+
writeFileSync(filePath, injectVersion(content));
|
|
1205
|
+
currentPaths.add(relativePath);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Stale file cleanup for platform-owned directories
|
|
1209
|
+
if (platformId === 'copilot') {
|
|
1210
|
+
const promptsDir = join(dir, '.github', 'prompts');
|
|
1211
|
+
if (existsSync(promptsDir)) {
|
|
1212
|
+
for (const f of readdirSync(promptsDir)) {
|
|
1213
|
+
if (f.startsWith('kc-') && f.endsWith('.prompt.md') && !currentPaths.has(`.github/prompts/${f}`)) {
|
|
1214
|
+
log.info(`Removing stale prompt: ${f}`);
|
|
1215
|
+
rmSync(join(promptsDir, f), { force: true });
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
} else if (platformId === 'gemini') {
|
|
1220
|
+
const tomlDir = join(dir, '.gemini', 'commands', 'kc');
|
|
1221
|
+
if (existsSync(tomlDir)) {
|
|
1222
|
+
for (const f of readdirSync(tomlDir)) {
|
|
1223
|
+
if (f.endsWith('.toml') && !currentPaths.has(`.gemini/commands/kc/${f}`)) {
|
|
1224
|
+
log.info(`Removing stale command: ${f}`);
|
|
1225
|
+
rmSync(join(tomlDir, f), { force: true });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
// Stale skill cleanup: .gemini/skills/kc-*
|
|
1230
|
+
const geminiSkillDir = join(dir, '.gemini', 'skills');
|
|
1231
|
+
if (existsSync(geminiSkillDir)) {
|
|
1232
|
+
for (const entry of readdirSync(geminiSkillDir)) {
|
|
1233
|
+
if (entry.startsWith('kc-') && !currentPaths.has(`.gemini/skills/${entry}/SKILL.md`)) {
|
|
1234
|
+
log.info(`Removing stale Gemini skill: ${entry}/`);
|
|
1235
|
+
rmSync(join(geminiSkillDir, entry), { recursive: true, force: true });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
// Stale subagent cleanup: .gemini/agents/kc-*.md
|
|
1240
|
+
const geminiAgentDir = join(dir, '.gemini', 'agents');
|
|
1241
|
+
if (existsSync(geminiAgentDir)) {
|
|
1242
|
+
for (const entry of readdirSync(geminiAgentDir)) {
|
|
1243
|
+
if (entry.startsWith('kc-') && entry.endsWith('.md') && !currentPaths.has(`.gemini/agents/${entry}`)) {
|
|
1244
|
+
log.info(`Removing stale Gemini subagent: ${entry}`);
|
|
1245
|
+
rmSync(join(geminiAgentDir, entry), { force: true });
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
} else if (platformId === 'codex') {
|
|
1250
|
+
const skillDir = join(dir, '.agents', 'skills');
|
|
1251
|
+
if (existsSync(skillDir)) {
|
|
1252
|
+
for (const entry of readdirSync(skillDir)) {
|
|
1253
|
+
if (entry.startsWith('kc-') && !currentPaths.has(`.agents/skills/${entry}/SKILL.md`)) {
|
|
1254
|
+
log.info(`Removing stale skill: ${entry}/`);
|
|
1255
|
+
rmSync(join(skillDir, entry), { recursive: true, force: true });
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// Migration: remove legacy .codex/skills/kc/ if present
|
|
1260
|
+
const legacySkillDir = join(dir, '.codex', 'skills', 'kc');
|
|
1261
|
+
if (existsSync(legacySkillDir)) {
|
|
1262
|
+
log.info('Removing legacy .codex/skills/kc/ (migrated to .agents/skills/)');
|
|
1263
|
+
rmSync(legacySkillDir, { recursive: true, force: true });
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Check for global codex skills
|
|
1269
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
|
|
1270
|
+
const globalAgentsSkillDir = join(homeDir, '.agents', 'skills');
|
|
1271
|
+
if (existsSync(globalAgentsSkillDir)) {
|
|
1272
|
+
const codexTemplateSet = templates.get('codex');
|
|
1273
|
+
if (codexTemplateSet) {
|
|
1274
|
+
const currentPaths = new Set([...codexTemplateSet.files.keys()]);
|
|
1275
|
+
// Stale cleanup
|
|
1276
|
+
for (const entry of readdirSync(globalAgentsSkillDir)) {
|
|
1277
|
+
if (entry.startsWith('kc-') && !currentPaths.has(`.agents/skills/${entry}/SKILL.md`)) {
|
|
1278
|
+
log.info(`Removing stale global skill: ${entry}/`);
|
|
1279
|
+
rmSync(join(globalAgentsSkillDir, entry), { recursive: true, force: true });
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
// Regenerate global skills
|
|
1283
|
+
for (const [relativePath, { content }] of codexTemplateSet.files) {
|
|
1284
|
+
if (relativePath.startsWith('.agents/skills/')) {
|
|
1285
|
+
const filePath = join(homeDir, relativePath);
|
|
1286
|
+
ensureDir(dirname(filePath));
|
|
1287
|
+
writeFileSync(filePath, injectVersion(content));
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
log.info('Updated global Codex skills');
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
// Migration: remove legacy global .codex/skills/kc/ if present
|
|
1294
|
+
const legacyGlobalSkillDir = join(homeDir, '.codex', 'skills', 'kc');
|
|
1295
|
+
if (existsSync(legacyGlobalSkillDir)) {
|
|
1296
|
+
log.info('Removing legacy global ~/.codex/skills/kc/ (migrated to ~/.agents/skills/)');
|
|
1297
|
+
rmSync(legacyGlobalSkillDir, { recursive: true, force: true });
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Check for global Gemini skills
|
|
1301
|
+
const globalGeminiSkillDir = join(homeDir, '.gemini', 'skills');
|
|
1302
|
+
if (existsSync(globalGeminiSkillDir)) {
|
|
1303
|
+
const geminiTemplateSet = templates.get('gemini');
|
|
1304
|
+
if (geminiTemplateSet) {
|
|
1305
|
+
const currentPaths = new Set([...geminiTemplateSet.files.keys()]);
|
|
1306
|
+
// Stale cleanup
|
|
1307
|
+
for (const entry of readdirSync(globalGeminiSkillDir)) {
|
|
1308
|
+
if (entry.startsWith('kc-') && !currentPaths.has(`.gemini/skills/${entry}/SKILL.md`)) {
|
|
1309
|
+
log.info(`Removing stale global Gemini skill: ${entry}/`);
|
|
1310
|
+
rmSync(join(globalGeminiSkillDir, entry), { recursive: true, force: true });
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
// Regenerate global skills
|
|
1314
|
+
for (const [relativePath, { content }] of geminiTemplateSet.files) {
|
|
1315
|
+
if (relativePath.startsWith('.gemini/skills/')) {
|
|
1316
|
+
const filePath = join(homeDir, relativePath);
|
|
1317
|
+
ensureDir(dirname(filePath));
|
|
1318
|
+
writeFileSync(filePath, injectVersion(content));
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
log.info('Updated global Gemini skills');
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// Preserve Gemini MCP config during upgrade (don't overwrite user's API key)
|
|
1326
|
+
const geminiSettingsPath = join(dir, '.gemini', 'settings.json');
|
|
1327
|
+
const geminiMcpPreserved = hasGeminiMcpConfig(geminiSettingsPath);
|
|
1328
|
+
if (geminiMcpPreserved && opts.verbose) {
|
|
1329
|
+
log.info('Preserved: Gemini MCP config (.gemini/settings.json)');
|
|
829
1330
|
}
|
|
830
1331
|
|
|
831
1332
|
// Write new version
|
|
@@ -834,7 +1335,7 @@ async function cmdUpgrade(opts) {
|
|
|
834
1335
|
console.log('');
|
|
835
1336
|
log.ok(`Upgraded to ${VERSION}`);
|
|
836
1337
|
console.log('');
|
|
837
|
-
console.log(` ${c.bold}Preserved:${c.reset} specs/, tracker, log, architecture, project config`);
|
|
1338
|
+
console.log(` ${c.bold}Preserved:${c.reset} specs/, tracker, log, architecture, project config${geminiMcpPreserved ? ', Gemini MCP config' : ''}`);
|
|
838
1339
|
console.log(` ${c.bold}Updated:${c.reset} loop, prompts, adapters, enterprise templates`);
|
|
839
1340
|
if (regenerated.length > 0) {
|
|
840
1341
|
console.log(` ${c.bold}Adapters:${c.reset} ${regenerated.join(', ')}`);
|
|
@@ -859,7 +1360,7 @@ ${c.bold}Options:${c.reset}
|
|
|
859
1360
|
--target <path> Target directory (default: current directory)
|
|
860
1361
|
--platforms <list> Comma-separated: claude,codex,gemini,cursor,copilot,windsurf,all
|
|
861
1362
|
--force Skip confirmation prompts
|
|
862
|
-
--global Install Claude Code
|
|
1363
|
+
--global Install Claude Code to ~/.claude/, Codex skills to ~/.agents/skills/, Gemini skills to ~/.gemini/skills/
|
|
863
1364
|
--agent-teams Enable Agent Teams in .claude/settings.local.json
|
|
864
1365
|
--verbose Show detailed output
|
|
865
1366
|
-h, --help Show this help
|
package/commands/connect-mcp.md
CHANGED
|
@@ -87,6 +87,30 @@ Configure the KnowzCode MCP server using Claude Code's built-in MCP management.
|
|
|
87
87
|
- Parse `--configure-vaults` flag (forces vault prompts)
|
|
88
88
|
- Store parsed values for use in configuration
|
|
89
89
|
|
|
90
|
+
1.5. **Smart Config Discovery (if no API key in arguments)**
|
|
91
|
+
|
|
92
|
+
Before prompting for an API key, check known config sources:
|
|
93
|
+
|
|
94
|
+
a. **Environment variable**: Check `KNOWZ_API_KEY`
|
|
95
|
+
- If set: use as the API key, display "Using API key from KNOWZ_API_KEY (ending ...{last4})"
|
|
96
|
+
|
|
97
|
+
b. **Project config**: Read `knowzcode/mcp_config.md`
|
|
98
|
+
- If `Connected: Yes` and endpoint set: pre-populate endpoint for Step 5
|
|
99
|
+
- If `API Key (last 4)` set: note for confirmation prompt
|
|
100
|
+
|
|
101
|
+
c. **Vault config**: Read `knowzcode/knowzcode_vaults.md`
|
|
102
|
+
- If vaults have non-empty IDs: note for Step 6 (skip vault prompts unless `--configure-vaults`)
|
|
103
|
+
|
|
104
|
+
d. **Cross-platform config files** (check for API key in other platforms):
|
|
105
|
+
- `.gemini/settings.json` → `mcpServers.knowz.headers.Authorization`
|
|
106
|
+
- `~/.gemini/settings.json` → same
|
|
107
|
+
- `.vscode/mcp.json` → `servers.knowz.headers`
|
|
108
|
+
- If found: extract Bearer token, offer to reuse:
|
|
109
|
+
"Found existing API key (ending ...{last4}) in {source}. Use this key? [Yes/No]"
|
|
110
|
+
|
|
111
|
+
If a key was discovered, skip Step 3 (interactive API key prompt).
|
|
112
|
+
If vaults were discovered, skip vault prompts in Step 6.
|
|
113
|
+
|
|
90
114
|
2. **Check for existing configuration**
|
|
91
115
|
- Check if MCP server already configured: `CLAUDECODE= claude mcp get knowz`
|
|
92
116
|
- If already configured, ask if user wants to reconfigure
|
|
@@ -324,6 +348,7 @@ Configure the KnowzCode MCP server using Claude Code's built-in MCP management.
|
|
|
324
348
|
- Set `Connected: Yes`
|
|
325
349
|
- Set `Endpoint: <endpoint-url>`
|
|
326
350
|
- Set `Last Verified: <timestamp>`
|
|
351
|
+
- Set `API Key (last 4): <last 4 characters of the API key>`
|
|
327
352
|
- Set Ecosystem Vault ID and name (if provided or already set)
|
|
328
353
|
- Set Code Vault ID and name (if provided)
|
|
329
354
|
- Set `Auto-configured: No` (to distinguish from /kc:register setup)
|