knowy-cli 0.1.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.
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Tool registry — defines how to detect and handshake with each AI/spec tool.
3
+ *
4
+ * Each entry:
5
+ * id — unique key
6
+ * name — human-readable label
7
+ * detect — array of { type: 'file'|'dir', path } checks (OR logic)
8
+ * targets — files to inject the Knowy reference into
9
+ * readsAgents — true if the tool also reads AGENTS.md
10
+ * category — 'ai' | 'spec' | 'standard'
11
+ */
12
+ export const TOOL_REGISTRY = [
13
+ // ── Cross-tool standard ──────────────────────────────────────────
14
+ {
15
+ id: 'agents-md',
16
+ name: 'AGENTS.md (Cross-tool standard)',
17
+ detect: [{ type: 'file', path: 'AGENTS.md' }],
18
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
19
+ readsAgents: true,
20
+ category: 'standard',
21
+ alwaysAvailable: true,
22
+ },
23
+
24
+ // ── AI coding tools ──────────────────────────────────────────────
25
+ {
26
+ id: 'claude-code',
27
+ name: 'Claude Code',
28
+ detect: [{ type: 'file', path: 'CLAUDE.md' }],
29
+ targets: [{ file: 'CLAUDE.md', format: 'markdown' }],
30
+ readsAgents: false,
31
+ category: 'ai',
32
+ },
33
+ {
34
+ id: 'cursor',
35
+ name: 'Cursor',
36
+ detect: [{ type: 'dir', path: '.cursor' }],
37
+ targets: [{ file: '.cursor/rules/knowy.mdc', format: 'mdc' }],
38
+ readsAgents: true,
39
+ category: 'ai',
40
+ },
41
+ {
42
+ id: 'windsurf',
43
+ name: 'Windsurf',
44
+ detect: [
45
+ { type: 'file', path: '.windsurfrules' },
46
+ { type: 'dir', path: '.windsurf' },
47
+ ],
48
+ targets: [{ file: '.windsurf/rules/knowy.md', format: 'markdown' }],
49
+ readsAgents: true,
50
+ category: 'ai',
51
+ },
52
+ {
53
+ id: 'copilot',
54
+ name: 'GitHub Copilot',
55
+ detect: [
56
+ { type: 'file', path: '.github/copilot-instructions.md' },
57
+ { type: 'dir', path: '.github/instructions' },
58
+ ],
59
+ targets: [{ file: '.github/copilot-instructions.md', format: 'markdown' }],
60
+ readsAgents: true,
61
+ category: 'ai',
62
+ },
63
+ {
64
+ id: 'codex',
65
+ name: 'Codex CLI (OpenAI)',
66
+ detect: [],
67
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
68
+ readsAgents: true,
69
+ category: 'ai',
70
+ },
71
+ {
72
+ id: 'gemini',
73
+ name: 'Gemini CLI',
74
+ detect: [{ type: 'file', path: 'GEMINI.md' }],
75
+ targets: [{ file: 'GEMINI.md', format: 'markdown' }],
76
+ readsAgents: false,
77
+ category: 'ai',
78
+ },
79
+ {
80
+ id: 'kiro',
81
+ name: 'Kiro',
82
+ detect: [{ type: 'dir', path: '.kiro' }],
83
+ targets: [{ file: '.kiro/steering/knowy.md', format: 'markdown' }],
84
+ readsAgents: false,
85
+ category: 'ai',
86
+ },
87
+ {
88
+ id: 'amazon-q',
89
+ name: 'Amazon Q Developer',
90
+ detect: [{ type: 'dir', path: '.amazonq' }],
91
+ targets: [{ file: '.amazonq/rules/knowy.md', format: 'markdown' }],
92
+ readsAgents: false,
93
+ category: 'ai',
94
+ },
95
+ {
96
+ id: 'cline',
97
+ name: 'Cline',
98
+ detect: [
99
+ { type: 'file', path: '.clinerules' },
100
+ { type: 'dir', path: '.clinerules' },
101
+ ],
102
+ targets: [{ file: '.clinerules/knowy.md', format: 'markdown' }],
103
+ readsAgents: false,
104
+ category: 'ai',
105
+ },
106
+ {
107
+ id: 'roo-code',
108
+ name: 'Roo Code',
109
+ detect: [{ type: 'dir', path: '.roo' }],
110
+ targets: [{ file: '.roo/rules/knowy.md', format: 'markdown' }],
111
+ readsAgents: true,
112
+ category: 'ai',
113
+ },
114
+ {
115
+ id: 'kilo-code',
116
+ name: 'Kilo Code',
117
+ detect: [{ type: 'dir', path: '.kilocode' }],
118
+ targets: [{ file: '.kilocode/rules/knowy.md', format: 'markdown' }],
119
+ readsAgents: true,
120
+ category: 'ai',
121
+ },
122
+ {
123
+ id: 'aider',
124
+ name: 'Aider',
125
+ detect: [{ type: 'file', path: '.aider.conf.yml' }],
126
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
127
+ readsAgents: true,
128
+ category: 'ai',
129
+ },
130
+ {
131
+ id: 'continue',
132
+ name: 'Continue.dev',
133
+ detect: [{ type: 'dir', path: '.continue' }],
134
+ targets: [{ file: '.continue/rules/knowy.md', format: 'markdown' }],
135
+ readsAgents: false,
136
+ category: 'ai',
137
+ },
138
+ {
139
+ id: 'augment',
140
+ name: 'Augment Code',
141
+ detect: [
142
+ { type: 'dir', path: '.augment' },
143
+ { type: 'file', path: '.augment-guidelines' },
144
+ ],
145
+ targets: [{ file: '.augment/rules/knowy.md', format: 'markdown' }],
146
+ readsAgents: true,
147
+ category: 'ai',
148
+ },
149
+ {
150
+ id: 'amp',
151
+ name: 'Amp (Sourcegraph)',
152
+ detect: [],
153
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
154
+ readsAgents: true,
155
+ category: 'ai',
156
+ },
157
+ {
158
+ id: 'devin',
159
+ name: 'Devin',
160
+ detect: [],
161
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
162
+ readsAgents: true,
163
+ category: 'ai',
164
+ },
165
+ {
166
+ id: 'warp',
167
+ name: 'Warp',
168
+ detect: [{ type: 'file', path: 'WARP.md' }],
169
+ targets: [{ file: 'WARP.md', format: 'markdown' }],
170
+ readsAgents: true,
171
+ category: 'ai',
172
+ },
173
+ {
174
+ id: 'zed',
175
+ name: 'Zed',
176
+ detect: [{ type: 'file', path: '.rules' }],
177
+ targets: [{ file: '.rules', format: 'markdown' }],
178
+ readsAgents: true,
179
+ category: 'ai',
180
+ },
181
+ {
182
+ id: 'opencode',
183
+ name: 'OpenCode',
184
+ detect: [
185
+ { type: 'file', path: 'opencode.json' },
186
+ { type: 'file', path: 'opencode.jsonc' },
187
+ ],
188
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
189
+ readsAgents: true,
190
+ category: 'ai',
191
+ },
192
+ {
193
+ id: 'qodo',
194
+ name: 'Qodo',
195
+ detect: [],
196
+ targets: [{ file: 'AGENTS.md', format: 'markdown' }],
197
+ readsAgents: true,
198
+ category: 'ai',
199
+ },
200
+ {
201
+ id: 'jetbrains',
202
+ name: 'JetBrains AI Assistant',
203
+ detect: [{ type: 'dir', path: '.aiassistant' }],
204
+ targets: [{ file: '.aiassistant/rules/knowy.md', format: 'markdown' }],
205
+ readsAgents: false,
206
+ category: 'ai',
207
+ },
208
+ {
209
+ id: 'tabnine',
210
+ name: 'Tabnine',
211
+ detect: [
212
+ { type: 'dir', path: '.tabnine' },
213
+ { type: 'file', path: '.tabnine' },
214
+ ],
215
+ targets: [{ file: '.tabnine/guidelines/knowy.md', format: 'markdown' }],
216
+ readsAgents: false,
217
+ category: 'ai',
218
+ },
219
+ {
220
+ id: 'replit',
221
+ name: 'Replit Agent',
222
+ detect: [{ type: 'file', path: 'replit.md' }],
223
+ targets: [{ file: 'replit.md', format: 'markdown' }],
224
+ readsAgents: false,
225
+ category: 'ai',
226
+ },
227
+ {
228
+ id: 'bolt',
229
+ name: 'Bolt.new',
230
+ detect: [{ type: 'dir', path: '.bolt' }],
231
+ targets: [{ file: '.bolt/prompt', format: 'markdown' }],
232
+ readsAgents: false,
233
+ category: 'ai',
234
+ },
235
+
236
+ // ── Spec tools ───────────────────────────────────────────────────
237
+ {
238
+ id: 'speckit',
239
+ name: 'Speckit',
240
+ detect: [{ type: 'dir', path: '.specify' }],
241
+ targets: [{ file: '.specify/memory/constitution.md', format: 'markdown' }],
242
+ readsAgents: false,
243
+ category: 'spec',
244
+ },
245
+ {
246
+ id: 'openspec',
247
+ name: 'OpenSpec',
248
+ detect: [{ type: 'dir', path: 'openspec' }],
249
+ targets: [{ file: 'openspec/project.md', format: 'markdown' }],
250
+ readsAgents: false,
251
+ category: 'spec',
252
+ },
253
+ {
254
+ id: 'kiro-specs',
255
+ name: 'Kiro Specs',
256
+ detect: [{ type: 'dir', path: '.kiro/specs' }],
257
+ targets: [{ file: '.kiro/steering/knowy.md', format: 'markdown' }],
258
+ readsAgents: false,
259
+ category: 'spec',
260
+ },
261
+ ];
262
+
263
+ /** Get tools that can be detected (have detect rules) */
264
+ export function getDetectableTools() {
265
+ return TOOL_REGISTRY.filter(t => t.detect.length > 0);
266
+ }
267
+
268
+ /** Get all selectable tools (for the interactive menu) */
269
+ export function getSelectableTools() {
270
+ return TOOL_REGISTRY.filter(t => t.category !== 'standard' || t.alwaysAvailable);
271
+ }
272
+
273
+ /** Find a tool by id */
274
+ export function getToolById(id) {
275
+ return TOOL_REGISTRY.find(t => t.id === id);
276
+ }
package/src/cli.js ADDED
@@ -0,0 +1,58 @@
1
+ import { VERSION } from './constants.js';
2
+ import { resolveLanguage, detectLanguage, normalizeLanguage, t } from './i18n.js';
3
+
4
+ function buildHelp(lang) {
5
+ return `
6
+ knowy v${VERSION} — ${t(lang, 'cli.help.tagline')}
7
+
8
+ ${t(lang, 'cli.help.usage')}:
9
+ knowy <command>
10
+
11
+ ${t(lang, 'cli.help.commands')}:
12
+ init ${t(lang, 'cli.help.init')}
13
+ update ${t(lang, 'cli.help.update')}
14
+ setup-mcp ${t(lang, 'cli.help.setupMcp')}
15
+
16
+ ${t(lang, 'cli.help.options')}:
17
+ --help, -h ${t(lang, 'cli.help.help')}
18
+ --version, -v ${t(lang, 'cli.help.version')}
19
+ `.trim();
20
+ }
21
+
22
+ export async function run(args) {
23
+ const cmd = args[0];
24
+ const lang = await resolveLanguage(process.cwd()).catch(() => normalizeLanguage(detectLanguage()));
25
+
26
+ if (!cmd || cmd === '--help' || cmd === '-h') {
27
+ console.log(buildHelp(lang));
28
+ return;
29
+ }
30
+
31
+ if (cmd === '--version' || cmd === '-v') {
32
+ console.log(VERSION);
33
+ return;
34
+ }
35
+
36
+ if (cmd === 'init') {
37
+ const { init } = await import('./commands/init.js');
38
+ await init(process.cwd());
39
+ return;
40
+ }
41
+
42
+ if (cmd === 'update') {
43
+ const { update } = await import('./commands/update.js');
44
+ await update(process.cwd());
45
+ return;
46
+ }
47
+
48
+ if (cmd === 'setup-mcp') {
49
+ const { setupMcp } = await import('./commands/setup-mcp.js');
50
+ await setupMcp();
51
+ return;
52
+ }
53
+
54
+ const unknownMsg = t(lang, 'cli.unknownCommand');
55
+ console.error(typeof unknownMsg === 'function' ? unknownMsg(cmd) : `Unknown command: ${cmd}`);
56
+ console.log(buildHelp(lang));
57
+ process.exitCode = 1;
58
+ }
@@ -0,0 +1,130 @@
1
+ import { readFile, writeFile, access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { KNOWLEDGE_DIR, KNOWY_CONFIG, VERSION } from '../constants.js';
4
+ import { scaffoldKnowledge } from '../scaffold.js';
5
+ import { installTemplates } from '../templates.js';
6
+ import { installSkills } from '../skills.js';
7
+ import { detectTools } from '../adapters/detect.js';
8
+ import { getToolById, TOOL_REGISTRY } from '../adapters/registry.js';
9
+ import { injectHandshake } from '../adapters/handshake.js';
10
+ import { confirm, multiSelect, select } from '../ui.js';
11
+ import { detectLanguage, normalizeLanguage, t } from '../i18n.js';
12
+
13
+ async function exists(p) {
14
+ try { await access(p); return true; } catch { return false; }
15
+ }
16
+
17
+ const LANGUAGE_CHOICES = [
18
+ { id: 'en', name: 'English' },
19
+ { id: 'zh-TW', name: '繁體中文' },
20
+ ];
21
+
22
+ export async function init(projectRoot) {
23
+ // Detect language
24
+ const detectedLang = normalizeLanguage(detectLanguage());
25
+ let lang = detectedLang;
26
+
27
+ console.log(`\n🧠 knowy v${VERSION}\n`);
28
+
29
+ // 1. Language selection
30
+ const msg = t(lang, 'cli.init.langDetected');
31
+ console.log(typeof msg === 'function' ? msg(lang) : msg);
32
+
33
+ const langChoices = LANGUAGE_CHOICES.map(c => ({
34
+ ...c,
35
+ checked: c.id === detectedLang,
36
+ }));
37
+ lang = await select(t(lang, 'cli.init.selectLanguage'), langChoices);
38
+
39
+ // 2. Check if .knowledge/ already exists
40
+ const knowledgeExists = await exists(join(projectRoot, KNOWLEDGE_DIR));
41
+ if (knowledgeExists) {
42
+ console.log(t(lang, 'cli.init.exists'));
43
+ const proceed = await confirm(t(lang, 'cli.init.continue'));
44
+ if (!proceed) {
45
+ console.log(t(lang, 'cli.init.aborted'));
46
+ return;
47
+ }
48
+ }
49
+
50
+ // 3. Scaffold .knowledge/
51
+ console.log('');
52
+ const scaffoldReport = await scaffoldKnowledge(projectRoot, lang);
53
+ for (const f of scaffoldReport.created) console.log(t(lang, 'cli.init.created')(f));
54
+ for (const f of scaffoldReport.skipped) console.log(t(lang, 'cli.init.skipped')(f));
55
+
56
+ // 4. Install templates
57
+ const templates = await installTemplates(projectRoot, lang);
58
+ console.log(t(lang, 'cli.init.templates')(templates.length));
59
+
60
+ // 5. Detect tools
61
+ const { detected } = await detectTools(projectRoot);
62
+ if (detected.length > 0) {
63
+ const names = detected.map(id => getToolById(id)?.name).filter(Boolean);
64
+ console.log(`\n${t(lang, 'cli.init.detected')(names.join(', '))}`);
65
+ }
66
+
67
+ // 6. Build selection choices
68
+ const aiTools = TOOL_REGISTRY.filter(t => t.category === 'ai');
69
+ const specTools = TOOL_REGISTRY.filter(t => t.category === 'spec');
70
+ const standardTools = TOOL_REGISTRY.filter(t => t.category === 'standard');
71
+
72
+ const choices = [
73
+ ...standardTools.map(t => ({
74
+ id: t.id,
75
+ name: t.name,
76
+ checked: true,
77
+ })),
78
+ ...aiTools.map(t => ({
79
+ id: t.id,
80
+ name: t.name,
81
+ checked: detected.includes(t.id),
82
+ })),
83
+ ...specTools.map(t => ({
84
+ id: t.id,
85
+ name: `${t.name} (spec tool)`,
86
+ checked: detected.includes(t.id),
87
+ })),
88
+ ];
89
+
90
+ const selectedIds = await multiSelect(t(lang, 'cli.init.selectTools'), choices, lang);
91
+
92
+ // 7. Handshake
93
+ const handshaked = [];
94
+ const writtenFiles = new Set();
95
+
96
+ for (const id of selectedIds) {
97
+ const tool = getToolById(id);
98
+ if (!tool) continue;
99
+
100
+ for (const target of tool.targets) {
101
+ if (writtenFiles.has(target.file)) continue;
102
+ const result = await injectHandshake(projectRoot, target);
103
+ writtenFiles.add(target.file);
104
+ const msgKey = `cli.init.handshake.${result.action}`;
105
+ console.log(t(lang, msgKey)(result.file, tool.name));
106
+ }
107
+ }
108
+
109
+ // 8. Install skills
110
+ const skills = await installSkills(projectRoot);
111
+ console.log(`\n${t(lang, 'cli.init.skills')(skills.length)}`);
112
+
113
+ // 9. Update .knowy.json
114
+ const configPath = join(projectRoot, KNOWY_CONFIG);
115
+ let config;
116
+ try {
117
+ config = JSON.parse(await readFile(configPath, 'utf-8'));
118
+ } catch {
119
+ config = { version: VERSION, createdAt: new Date().toISOString() };
120
+ }
121
+ config.version = VERSION;
122
+ config.language = lang;
123
+ config.tools = selectedIds;
124
+ config.updatedAt = new Date().toISOString();
125
+ await writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
126
+
127
+ // 10. Summary
128
+ console.log(`\n${t(lang, 'cli.init.done')}\n`);
129
+ console.log(`${t(lang, 'cli.init.nextStep')}\n`);
130
+ }
@@ -0,0 +1,87 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { VERSION } from '../constants.js';
5
+ import { multiSelect } from '../ui.js';
6
+ import { resolveLanguage, t } from '../i18n.js';
7
+
8
+ const MCP_TARGETS = [
9
+ {
10
+ id: 'claude-code',
11
+ name: 'Claude Code (project settings)',
12
+ configPath: (projectRoot) => join(projectRoot, '.claude', 'settings.local.json'),
13
+ ensureDir: (projectRoot) => join(projectRoot, '.claude'),
14
+ scope: 'project',
15
+ },
16
+ {
17
+ id: 'claude-desktop',
18
+ name: 'Claude Desktop',
19
+ configPath: () => join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
20
+ ensureDir: () => join(homedir(), 'Library', 'Application Support', 'Claude'),
21
+ scope: 'global',
22
+ },
23
+ {
24
+ id: 'cursor',
25
+ name: 'Cursor (project settings)',
26
+ configPath: (projectRoot) => join(projectRoot, '.cursor', 'mcp.json'),
27
+ ensureDir: (projectRoot) => join(projectRoot, '.cursor'),
28
+ scope: 'project',
29
+ },
30
+ ];
31
+
32
+ const MCP_ENTRY = {
33
+ command: 'npx',
34
+ args: ['-y', 'knowy-cli', '--', 'knowy-mcp'],
35
+ };
36
+
37
+ async function readJsonSafe(path) {
38
+ try {
39
+ return JSON.parse(await readFile(path, 'utf-8'));
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ export async function setupMcp(projectRoot = process.cwd()) {
46
+ const lang = await resolveLanguage(projectRoot);
47
+
48
+ console.log(`\n🧠 knowy v${VERSION} — ${t(lang, 'cli.mcp.title')}\n`);
49
+
50
+ const choices = MCP_TARGETS.map(t => ({
51
+ id: t.id,
52
+ name: `${t.name} (${t.scope})`,
53
+ checked: false,
54
+ }));
55
+
56
+ const selectedIds = await multiSelect(t(lang, 'cli.mcp.selectTools'), choices, lang);
57
+
58
+ if (selectedIds.length === 0) {
59
+ console.log(`\n${t(lang, 'cli.mcp.noSelection')}`);
60
+ return;
61
+ }
62
+
63
+ for (const id of selectedIds) {
64
+ const target = MCP_TARGETS.find(t => t.id === id);
65
+ if (!target) continue;
66
+
67
+ const configPath = target.configPath(projectRoot);
68
+ const dir = target.ensureDir(projectRoot);
69
+ await mkdir(dir, { recursive: true });
70
+
71
+ let config = await readJsonSafe(configPath) || {};
72
+
73
+ if (!config.mcpServers) config.mcpServers = {};
74
+
75
+ if (config.mcpServers.knowy) {
76
+ console.log(t(lang, 'cli.mcp.alreadyConfigured')(target.name));
77
+ continue;
78
+ }
79
+
80
+ config.mcpServers.knowy = MCP_ENTRY;
81
+ await writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
82
+ console.log(t(lang, 'cli.mcp.added')(target.name));
83
+ }
84
+
85
+ console.log(`\n${t(lang, 'cli.mcp.done')}`);
86
+ console.log(`${t(lang, 'cli.mcp.restart')}\n`);
87
+ }
@@ -0,0 +1,90 @@
1
+ import { readFile, writeFile, access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { KNOWY_CONFIG, VERSION } from '../constants.js';
4
+ import { installTemplates } from '../templates.js';
5
+ import { installSkills } from '../skills.js';
6
+ import { detectTools } from '../adapters/detect.js';
7
+ import { getToolById } from '../adapters/registry.js';
8
+ import { injectHandshake } from '../adapters/handshake.js';
9
+ import { confirm } from '../ui.js';
10
+ import { resolveLanguage, t } from '../i18n.js';
11
+
12
+ async function exists(p) {
13
+ try { await access(p); return true; } catch { return false; }
14
+ }
15
+
16
+ export async function update(projectRoot) {
17
+ const lang = await resolveLanguage(projectRoot);
18
+
19
+ console.log(`\n🧠 knowy v${VERSION} — ${t(lang, 'cli.update.title')}\n`);
20
+
21
+ // 1. Check .knowy.json exists
22
+ const configPath = join(projectRoot, KNOWY_CONFIG);
23
+ if (!await exists(configPath)) {
24
+ console.log(t(lang, 'cli.update.noConfig'));
25
+ return;
26
+ }
27
+
28
+ let config;
29
+ try {
30
+ config = JSON.parse(await readFile(configPath, 'utf-8'));
31
+ } catch {
32
+ console.log(t(lang, 'cli.update.badConfig'));
33
+ return;
34
+ }
35
+
36
+ const configLang = config.language || lang;
37
+
38
+ // 2. Update templates
39
+ const templates = await installTemplates(projectRoot, configLang);
40
+ console.log(t(lang, 'cli.update.templates')(templates.length));
41
+
42
+ // 3. Update skills
43
+ const skills = await installSkills(projectRoot);
44
+ console.log(t(lang, 'cli.update.skills')(skills.length));
45
+
46
+ // 4. Re-detect tools
47
+ const { detected } = await detectTools(projectRoot);
48
+ const existingTools = new Set(config.tools || []);
49
+ const newTools = detected.filter(id => !existingTools.has(id));
50
+
51
+ // 5. Handshake new tools
52
+ if (newTools.length > 0) {
53
+ const names = newTools.map(id => getToolById(id)?.name).filter(Boolean);
54
+ console.log(`\n${t(lang, 'cli.update.newTools')(names.join(', '))}`);
55
+ const add = await confirm(t(lang, 'cli.update.addTools'));
56
+ if (add) {
57
+ for (const id of newTools) {
58
+ const tool = getToolById(id);
59
+ if (!tool) continue;
60
+ for (const target of tool.targets) {
61
+ const result = await injectHandshake(projectRoot, target);
62
+ const verb = result.action === 'created' ? 'Created' : 'Updated';
63
+ console.log(` ✓ ${verb} ${result.file}`);
64
+ }
65
+ existingTools.add(id);
66
+ }
67
+ }
68
+ }
69
+
70
+ // 6. Refresh existing handshakes
71
+ const writtenFiles = new Set();
72
+ for (const id of existingTools) {
73
+ const tool = getToolById(id);
74
+ if (!tool) continue;
75
+ for (const target of tool.targets) {
76
+ if (writtenFiles.has(target.file)) continue;
77
+ await injectHandshake(projectRoot, target);
78
+ writtenFiles.add(target.file);
79
+ }
80
+ }
81
+ console.log(t(lang, 'cli.update.refreshed')(writtenFiles.size));
82
+
83
+ // 7. Update .knowy.json
84
+ config.version = VERSION;
85
+ config.tools = [...existingTools];
86
+ config.updatedAt = new Date().toISOString();
87
+ await writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
88
+
89
+ console.log(`\n${t(lang, 'cli.update.done')}\n`);
90
+ }
@@ -0,0 +1,23 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
7
+
8
+ export const VERSION = pkg.version;
9
+ export const PACKAGE_ROOT = join(__dirname, '..');
10
+
11
+ export const KNOWLEDGE_DIR = '.knowledge';
12
+ export const KNOWY_CONFIG = '.knowy.json';
13
+ export const TEMPLATES_DIR = '.templates';
14
+
15
+ export const CORE_FILES = ['principles.md', 'vision.md', 'experience.md'];
16
+ export const SUBDIRS = ['research', 'design', 'history', TEMPLATES_DIR];
17
+
18
+ export const SKILLS_SOURCE = join(PACKAGE_ROOT, 'skills');
19
+ export const SKILLS_TARGET = '.claude/skills/knowy';
20
+ export const SKILL_NAMES = ['knowy-init', 'knowy-update', 'knowy-judge', 'knowy-next'];
21
+
22
+ export const MARKER_START = '<!-- Knowy: Project Knowledge -->';
23
+ export const MARKER_END = '<!-- /Knowy -->';