@getlore/cli 0.2.0
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/LICENSE +13 -0
- package/README.md +80 -0
- package/dist/cli/colors.d.ts +48 -0
- package/dist/cli/colors.js +48 -0
- package/dist/cli/commands/ask.d.ts +7 -0
- package/dist/cli/commands/ask.js +97 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.js +484 -0
- package/dist/cli/commands/daemon.d.ts +22 -0
- package/dist/cli/commands/daemon.js +244 -0
- package/dist/cli/commands/docs.d.ts +7 -0
- package/dist/cli/commands/docs.js +188 -0
- package/dist/cli/commands/extensions.d.ts +7 -0
- package/dist/cli/commands/extensions.js +204 -0
- package/dist/cli/commands/misc.d.ts +7 -0
- package/dist/cli/commands/misc.js +172 -0
- package/dist/cli/commands/pending.d.ts +7 -0
- package/dist/cli/commands/pending.js +63 -0
- package/dist/cli/commands/projects.d.ts +7 -0
- package/dist/cli/commands/projects.js +136 -0
- package/dist/cli/commands/search.d.ts +7 -0
- package/dist/cli/commands/search.js +102 -0
- package/dist/cli/commands/skills.d.ts +24 -0
- package/dist/cli/commands/skills.js +447 -0
- package/dist/cli/commands/sources.d.ts +7 -0
- package/dist/cli/commands/sources.js +121 -0
- package/dist/cli/commands/sync.d.ts +31 -0
- package/dist/cli/commands/sync.js +768 -0
- package/dist/cli/helpers.d.ts +30 -0
- package/dist/cli/helpers.js +119 -0
- package/dist/core/auth.d.ts +62 -0
- package/dist/core/auth.js +330 -0
- package/dist/core/config.d.ts +41 -0
- package/dist/core/config.js +96 -0
- package/dist/core/data-repo.d.ts +31 -0
- package/dist/core/data-repo.js +146 -0
- package/dist/core/embedder.d.ts +22 -0
- package/dist/core/embedder.js +104 -0
- package/dist/core/git.d.ts +37 -0
- package/dist/core/git.js +140 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +5 -0
- package/dist/core/insight-extractor.d.ts +26 -0
- package/dist/core/insight-extractor.js +114 -0
- package/dist/core/local-search.d.ts +43 -0
- package/dist/core/local-search.js +221 -0
- package/dist/core/themes.d.ts +15 -0
- package/dist/core/themes.js +77 -0
- package/dist/core/types.d.ts +177 -0
- package/dist/core/types.js +9 -0
- package/dist/core/user-settings.d.ts +15 -0
- package/dist/core/user-settings.js +42 -0
- package/dist/core/vector-store-lance.d.ts +98 -0
- package/dist/core/vector-store-lance.js +384 -0
- package/dist/core/vector-store-supabase.d.ts +89 -0
- package/dist/core/vector-store-supabase.js +295 -0
- package/dist/core/vector-store.d.ts +131 -0
- package/dist/core/vector-store.js +503 -0
- package/dist/daemon-runner.d.ts +8 -0
- package/dist/daemon-runner.js +246 -0
- package/dist/extensions/config.d.ts +22 -0
- package/dist/extensions/config.js +102 -0
- package/dist/extensions/proposals.d.ts +30 -0
- package/dist/extensions/proposals.js +178 -0
- package/dist/extensions/registry.d.ts +35 -0
- package/dist/extensions/registry.js +309 -0
- package/dist/extensions/sandbox.d.ts +16 -0
- package/dist/extensions/sandbox.js +17 -0
- package/dist/extensions/types.d.ts +114 -0
- package/dist/extensions/types.js +4 -0
- package/dist/extensions/worker.d.ts +1 -0
- package/dist/extensions/worker.js +49 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +105 -0
- package/dist/mcp/handlers/archive-project.d.ts +51 -0
- package/dist/mcp/handlers/archive-project.js +112 -0
- package/dist/mcp/handlers/get-quotes.d.ts +27 -0
- package/dist/mcp/handlers/get-quotes.js +61 -0
- package/dist/mcp/handlers/get-source.d.ts +9 -0
- package/dist/mcp/handlers/get-source.js +40 -0
- package/dist/mcp/handlers/ingest.d.ts +25 -0
- package/dist/mcp/handlers/ingest.js +305 -0
- package/dist/mcp/handlers/list-projects.d.ts +4 -0
- package/dist/mcp/handlers/list-projects.js +16 -0
- package/dist/mcp/handlers/list-sources.d.ts +11 -0
- package/dist/mcp/handlers/list-sources.js +20 -0
- package/dist/mcp/handlers/research-agent.d.ts +21 -0
- package/dist/mcp/handlers/research-agent.js +369 -0
- package/dist/mcp/handlers/research.d.ts +22 -0
- package/dist/mcp/handlers/research.js +225 -0
- package/dist/mcp/handlers/retain.d.ts +18 -0
- package/dist/mcp/handlers/retain.js +92 -0
- package/dist/mcp/handlers/search.d.ts +52 -0
- package/dist/mcp/handlers/search.js +145 -0
- package/dist/mcp/handlers/sync.d.ts +47 -0
- package/dist/mcp/handlers/sync.js +211 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +268 -0
- package/dist/mcp/tools.d.ts +16 -0
- package/dist/mcp/tools.js +297 -0
- package/dist/sync/config.d.ts +26 -0
- package/dist/sync/config.js +140 -0
- package/dist/sync/discover.d.ts +51 -0
- package/dist/sync/discover.js +190 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.js +11 -0
- package/dist/sync/process.d.ts +50 -0
- package/dist/sync/process.js +285 -0
- package/dist/sync/processors.d.ts +24 -0
- package/dist/sync/processors.js +351 -0
- package/dist/tui/browse-handlers-ask.d.ts +30 -0
- package/dist/tui/browse-handlers-ask.js +372 -0
- package/dist/tui/browse-handlers-autocomplete.d.ts +49 -0
- package/dist/tui/browse-handlers-autocomplete.js +270 -0
- package/dist/tui/browse-handlers-extensions.d.ts +18 -0
- package/dist/tui/browse-handlers-extensions.js +107 -0
- package/dist/tui/browse-handlers-pending.d.ts +22 -0
- package/dist/tui/browse-handlers-pending.js +100 -0
- package/dist/tui/browse-handlers-research.d.ts +32 -0
- package/dist/tui/browse-handlers-research.js +363 -0
- package/dist/tui/browse-handlers-tools.d.ts +42 -0
- package/dist/tui/browse-handlers-tools.js +289 -0
- package/dist/tui/browse-handlers.d.ts +239 -0
- package/dist/tui/browse-handlers.js +1944 -0
- package/dist/tui/browse-render-extensions.d.ts +14 -0
- package/dist/tui/browse-render-extensions.js +114 -0
- package/dist/tui/browse-render-tools.d.ts +18 -0
- package/dist/tui/browse-render-tools.js +259 -0
- package/dist/tui/browse-render.d.ts +51 -0
- package/dist/tui/browse-render.js +599 -0
- package/dist/tui/browse-types.d.ts +142 -0
- package/dist/tui/browse-types.js +70 -0
- package/dist/tui/browse-ui.d.ts +10 -0
- package/dist/tui/browse-ui.js +432 -0
- package/dist/tui/browse.d.ts +17 -0
- package/dist/tui/browse.js +625 -0
- package/dist/tui/markdown.d.ts +22 -0
- package/dist/tui/markdown.js +223 -0
- package/package.json +71 -0
- package/plugins/claude-code/.claude-plugin/plugin.json +10 -0
- package/plugins/claude-code/.mcp.json +6 -0
- package/plugins/claude-code/skills/lore/SKILL.md +63 -0
- package/plugins/codex/SKILL.md +36 -0
- package/plugins/codex/agents/openai.yaml +10 -0
- package/plugins/gemini/GEMINI.md +31 -0
- package/plugins/gemini/gemini-extension.json +11 -0
- package/skills/generic-agent.md +99 -0
- package/skills/openclaw.md +67 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills Command
|
|
3
|
+
*
|
|
4
|
+
* Install Lore agent instruction files into the correct locations
|
|
5
|
+
* for each supported agent platform, using native plugin/extension formats.
|
|
6
|
+
*
|
|
7
|
+
* Each platform gets:
|
|
8
|
+
* 1. MCP server configuration (so Lore auto-starts)
|
|
9
|
+
* 2. Instruction/skill files (so the agent knows how to use Lore)
|
|
10
|
+
*
|
|
11
|
+
* lore skills list — show available skills
|
|
12
|
+
* lore skills install claude-code — install Claude Code plugin (MCP + skill)
|
|
13
|
+
* lore skills install gemini — install Gemini CLI extension (MCP + GEMINI.md)
|
|
14
|
+
* lore skills install codex — install Codex CLI skill (MCP + SKILL.md)
|
|
15
|
+
* lore skills install openclaw — install OpenClaw skill (SKILL.md)
|
|
16
|
+
* lore skills install generic — print generic instructions to stdout
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, cpSync, } from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
import { c } from '../colors.js';
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = path.dirname(__filename);
|
|
24
|
+
// ── Path Resolution ─────────────────────────────────────────────────────
|
|
25
|
+
function packageRoot() {
|
|
26
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
27
|
+
}
|
|
28
|
+
function pluginsDir() {
|
|
29
|
+
const dir = path.join(packageRoot(), 'plugins');
|
|
30
|
+
if (existsSync(dir))
|
|
31
|
+
return dir;
|
|
32
|
+
throw new Error(`Plugins directory not found at ${dir}`);
|
|
33
|
+
}
|
|
34
|
+
function skillsDir() {
|
|
35
|
+
const dir = path.join(packageRoot(), 'skills');
|
|
36
|
+
if (existsSync(dir))
|
|
37
|
+
return dir;
|
|
38
|
+
throw new Error(`Skills directory not found at ${dir}`);
|
|
39
|
+
}
|
|
40
|
+
function homeDir() {
|
|
41
|
+
return process.env.HOME || process.env.USERPROFILE || '~';
|
|
42
|
+
}
|
|
43
|
+
const LORE_MARKER = '# Lore Knowledge Base';
|
|
44
|
+
const AVAILABLE_SKILLS = [
|
|
45
|
+
{
|
|
46
|
+
name: 'claude-code',
|
|
47
|
+
description: 'Claude Code plugin (MCP server + skill)',
|
|
48
|
+
native: '/plugin install — from marketplace or local',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'gemini',
|
|
52
|
+
description: 'Gemini CLI extension (MCP server + context)',
|
|
53
|
+
native: 'gemini extensions install <repo>',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'codex',
|
|
57
|
+
description: 'Codex CLI skill (MCP + SKILL.md)',
|
|
58
|
+
native: '$skill-installer inside Codex',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'openclaw',
|
|
62
|
+
description: 'OpenClaw skill (SKILL.md)',
|
|
63
|
+
native: 'clawhub install lore',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'generic',
|
|
67
|
+
description: 'Platform-agnostic instructions (stdout)',
|
|
68
|
+
native: 'N/A',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Merge Lore MCP server config into a project's .mcp.json.
|
|
74
|
+
* Creates the file if it doesn't exist.
|
|
75
|
+
*/
|
|
76
|
+
function mergeMcpJson(targetPath) {
|
|
77
|
+
const loreServer = {
|
|
78
|
+
command: 'npx',
|
|
79
|
+
args: ['-y', '@getlore/cli', 'mcp'],
|
|
80
|
+
};
|
|
81
|
+
let config = { mcpServers: {} };
|
|
82
|
+
if (existsSync(targetPath)) {
|
|
83
|
+
try {
|
|
84
|
+
config = JSON.parse(readFileSync(targetPath, 'utf-8'));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Corrupted file — overwrite
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const servers = (config.mcpServers || config);
|
|
91
|
+
if (servers.lore) {
|
|
92
|
+
return { added: false, path: targetPath };
|
|
93
|
+
}
|
|
94
|
+
servers.lore = loreServer;
|
|
95
|
+
if (!config.mcpServers) {
|
|
96
|
+
config = { mcpServers: servers };
|
|
97
|
+
}
|
|
98
|
+
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
99
|
+
writeFileSync(targetPath, JSON.stringify(config, null, 2) + '\n');
|
|
100
|
+
return { added: true, path: targetPath };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Append content to a file if the marker isn't already present.
|
|
104
|
+
*/
|
|
105
|
+
function appendIfMissing(filepath, content) {
|
|
106
|
+
mkdirSync(path.dirname(filepath), { recursive: true });
|
|
107
|
+
if (existsSync(filepath)) {
|
|
108
|
+
const existing = readFileSync(filepath, 'utf-8');
|
|
109
|
+
if (existing.includes(LORE_MARKER))
|
|
110
|
+
return 'exists';
|
|
111
|
+
appendFileSync(filepath, '\n\n' + content);
|
|
112
|
+
return 'appended';
|
|
113
|
+
}
|
|
114
|
+
writeFileSync(filepath, content);
|
|
115
|
+
return 'created';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Copy a directory recursively.
|
|
119
|
+
*/
|
|
120
|
+
function copyDir(src, dest) {
|
|
121
|
+
mkdirSync(dest, { recursive: true });
|
|
122
|
+
cpSync(src, dest, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
// ── Installers ──────────────────────────────────────────────────────────
|
|
125
|
+
function installClaudeCode(options) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
const pluginSrc = path.join(pluginsDir(), 'claude-code');
|
|
128
|
+
if (options.global) {
|
|
129
|
+
// Global: install plugin to ~/.lore/plugins/claude-code/ + add MCP to ~/.claude.json
|
|
130
|
+
const dest = path.join(homeDir(), '.lore', 'plugins', 'claude-code');
|
|
131
|
+
copyDir(pluginSrc, dest);
|
|
132
|
+
lines.push(`${c.success('Installed')} plugin to ${c.file(dest)}`);
|
|
133
|
+
// Try to add MCP to user-level config
|
|
134
|
+
const claudeJsonPath = path.join(homeDir(), '.claude.json');
|
|
135
|
+
const mcp = mergeMcpJson(claudeJsonPath);
|
|
136
|
+
if (mcp.added) {
|
|
137
|
+
lines.push(`${c.success('Added')} Lore MCP server to ${c.file(mcp.path)}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
lines.push(`${c.dim('MCP server already configured in')} ${c.file(mcp.path)}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(c.dim('To activate the plugin in Claude Code, run:'));
|
|
144
|
+
lines.push(c.bold(` claude --plugin-dir ${dest}`));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Project-level: add MCP to .mcp.json + install skill to .claude/rules/
|
|
148
|
+
const mcpJsonPath = path.join(process.cwd(), '.mcp.json');
|
|
149
|
+
const mcp = mergeMcpJson(mcpJsonPath);
|
|
150
|
+
if (mcp.added) {
|
|
151
|
+
lines.push(`${c.success('Added')} Lore MCP server to ${c.file(mcp.path)}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
lines.push(`${c.dim('MCP server already configured in')} ${c.file(mcp.path)}`);
|
|
155
|
+
}
|
|
156
|
+
// Install skill as a rule file
|
|
157
|
+
const skillContent = readFileSync(path.join(pluginSrc, 'skills', 'lore', 'SKILL.md'), 'utf-8');
|
|
158
|
+
// Strip YAML frontmatter for rules file
|
|
159
|
+
const body = skillContent.replace(/^---[\s\S]*?---\n*/, '');
|
|
160
|
+
const rulesPath = path.join(process.cwd(), '.claude', 'rules', 'lore.md');
|
|
161
|
+
const result = appendIfMissing(rulesPath, body);
|
|
162
|
+
if (result === 'exists') {
|
|
163
|
+
lines.push(`${c.dim('Instructions already in')} ${c.file(rulesPath)}`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
lines.push(`${c.success(result === 'created' ? 'Created' : 'Appended to')} ${c.file(rulesPath)}`);
|
|
167
|
+
}
|
|
168
|
+
// Also copy the full plugin for reference
|
|
169
|
+
const pluginDest = path.join(homeDir(), '.lore', 'plugins', 'claude-code');
|
|
170
|
+
copyDir(pluginSrc, pluginDest);
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push(c.dim('Claude Code will auto-start the Lore MCP server for this project.'));
|
|
173
|
+
lines.push(c.dim('For the full plugin experience (marketplace distribution):'));
|
|
174
|
+
lines.push(c.bold(` claude --plugin-dir ${pluginDest}`));
|
|
175
|
+
}
|
|
176
|
+
return lines.join('\n');
|
|
177
|
+
}
|
|
178
|
+
function installGemini(options) {
|
|
179
|
+
const lines = [];
|
|
180
|
+
const extensionSrc = path.join(pluginsDir(), 'gemini');
|
|
181
|
+
if (options.global) {
|
|
182
|
+
// Install extension to ~/.lore/plugins/gemini/ and add context to ~/.gemini/GEMINI.md
|
|
183
|
+
const dest = path.join(homeDir(), '.lore', 'plugins', 'gemini');
|
|
184
|
+
copyDir(extensionSrc, dest);
|
|
185
|
+
lines.push(`${c.success('Installed')} extension to ${c.file(dest)}`);
|
|
186
|
+
const geminiMd = readFileSync(path.join(extensionSrc, 'GEMINI.md'), 'utf-8');
|
|
187
|
+
const globalPath = path.join(homeDir(), '.gemini', 'GEMINI.md');
|
|
188
|
+
const result = appendIfMissing(globalPath, geminiMd);
|
|
189
|
+
if (result === 'exists') {
|
|
190
|
+
lines.push(`${c.dim('Instructions already in')} ${c.file(globalPath)}`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
lines.push(`${c.success(result === 'created' ? 'Created' : 'Appended to')} ${c.file(globalPath)}`);
|
|
194
|
+
}
|
|
195
|
+
lines.push('');
|
|
196
|
+
lines.push(c.dim('For native extension install (auto-starts MCP server):'));
|
|
197
|
+
lines.push(c.bold(` gemini extensions install ${dest}`));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Project-level: install extension locally
|
|
201
|
+
const dest = path.join(homeDir(), '.lore', 'plugins', 'gemini');
|
|
202
|
+
copyDir(extensionSrc, dest);
|
|
203
|
+
const geminiMd = readFileSync(path.join(extensionSrc, 'GEMINI.md'), 'utf-8');
|
|
204
|
+
const projectPath = path.join(process.cwd(), 'GEMINI.md');
|
|
205
|
+
const result = appendIfMissing(projectPath, geminiMd);
|
|
206
|
+
if (result === 'exists') {
|
|
207
|
+
lines.push(`${c.dim('Instructions already in')} ${c.file(projectPath)}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
lines.push(`${c.success(result === 'created' ? 'Created' : 'Appended to')} ${c.file(projectPath)}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push('');
|
|
213
|
+
lines.push(c.dim('For native extension install (auto-starts MCP server):'));
|
|
214
|
+
lines.push(c.bold(` gemini extensions install ${dest}`));
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
function installCodex(options) {
|
|
219
|
+
const lines = [];
|
|
220
|
+
const skillSrc = path.join(pluginsDir(), 'codex');
|
|
221
|
+
// Install skill to ~/.codex/skills/lore/ (or ~/.agents/skills/lore/)
|
|
222
|
+
const codexSkillsDir = path.join(homeDir(), '.codex', 'skills', 'lore');
|
|
223
|
+
const agentsSkillsDir = path.join(homeDir(), '.agents', 'skills', 'lore');
|
|
224
|
+
// Prefer ~/.codex/skills/ if ~/.codex/ exists, otherwise ~/.agents/skills/
|
|
225
|
+
const codexDir = path.join(homeDir(), '.codex');
|
|
226
|
+
const targetDir = existsSync(codexDir) ? codexSkillsDir : agentsSkillsDir;
|
|
227
|
+
copyDir(skillSrc, targetDir);
|
|
228
|
+
lines.push(`${c.success('Installed')} skill to ${c.file(targetDir)}`);
|
|
229
|
+
if (options.global) {
|
|
230
|
+
// Add instructions to global AGENTS.md
|
|
231
|
+
const content = readFileSync(path.join(skillSrc, 'SKILL.md'), 'utf-8');
|
|
232
|
+
const body = content.replace(/^---[\s\S]*?---\n*/, '');
|
|
233
|
+
const globalPath = path.join(homeDir(), '.codex', 'AGENTS.md');
|
|
234
|
+
const result = appendIfMissing(globalPath, body);
|
|
235
|
+
if (result === 'exists') {
|
|
236
|
+
lines.push(`${c.dim('Instructions already in')} ${c.file(globalPath)}`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
lines.push(`${c.success(result === 'created' ? 'Created' : 'Appended to')} ${c.file(globalPath)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
// Add instructions to project AGENTS.md
|
|
244
|
+
const content = readFileSync(path.join(skillSrc, 'SKILL.md'), 'utf-8');
|
|
245
|
+
const body = content.replace(/^---[\s\S]*?---\n*/, '');
|
|
246
|
+
const projectPath = path.join(process.cwd(), 'AGENTS.md');
|
|
247
|
+
const result = appendIfMissing(projectPath, body);
|
|
248
|
+
if (result === 'exists') {
|
|
249
|
+
lines.push(`${c.dim('Instructions already in')} ${c.file(projectPath)}`);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
lines.push(`${c.success(result === 'created' ? 'Created' : 'Appended to')} ${c.file(projectPath)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
lines.push('');
|
|
256
|
+
lines.push(c.dim('Restart Codex to pick up the new skill.'));
|
|
257
|
+
lines.push(c.dim('Configure MCP server in Codex:'));
|
|
258
|
+
lines.push(c.bold(' codex mcp add lore -- npx -y @getlore/cli mcp'));
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
}
|
|
261
|
+
function installOpenClaw() {
|
|
262
|
+
const lines = [];
|
|
263
|
+
const content = readFileSync(path.join(skillsDir(), 'openclaw.md'), 'utf-8');
|
|
264
|
+
const skillDir = path.join(homeDir(), '.openclaw', 'skills', 'lore');
|
|
265
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
266
|
+
mkdirSync(skillDir, { recursive: true });
|
|
267
|
+
if (existsSync(skillPath)) {
|
|
268
|
+
const existing = readFileSync(skillPath, 'utf-8');
|
|
269
|
+
if (existing.includes('name: lore')) {
|
|
270
|
+
lines.push(`${c.dim('Already installed at')} ${c.file(skillPath)}`);
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
writeFileSync(skillPath, content);
|
|
275
|
+
lines.push(`${c.success('Created')} ${c.file(skillPath)}`);
|
|
276
|
+
lines.push(c.dim('OpenClaw will auto-discover this skill.'));
|
|
277
|
+
return lines.join('\n');
|
|
278
|
+
}
|
|
279
|
+
function showGeneric() {
|
|
280
|
+
return readFileSync(path.join(skillsDir(), 'generic-agent.md'), 'utf-8');
|
|
281
|
+
}
|
|
282
|
+
// ── Command Registration ────────────────────────────────────────────────
|
|
283
|
+
export function registerSkillsCommand(program) {
|
|
284
|
+
const skillsCmd = program
|
|
285
|
+
.command('skills')
|
|
286
|
+
.description('Install Lore into your AI coding agents');
|
|
287
|
+
// lore skills list
|
|
288
|
+
skillsCmd
|
|
289
|
+
.command('list')
|
|
290
|
+
.description('List available agent integrations')
|
|
291
|
+
.action(() => {
|
|
292
|
+
console.log(`\n${c.title('Available Integrations')}\n`);
|
|
293
|
+
for (const skill of AVAILABLE_SKILLS) {
|
|
294
|
+
console.log(` ${c.bold(skill.name.padEnd(14))} ${c.dim(skill.description)}`);
|
|
295
|
+
}
|
|
296
|
+
console.log(`\n${c.dim('Install with: lore skills install <name>')}`);
|
|
297
|
+
console.log(c.dim('Add --global to install user-wide instead of project-scoped\n'));
|
|
298
|
+
});
|
|
299
|
+
// lore skills install <name>
|
|
300
|
+
skillsCmd
|
|
301
|
+
.command('install <name>')
|
|
302
|
+
.description('Install Lore integration for a specific agent platform')
|
|
303
|
+
.option('-g, --global', 'Install globally instead of to current project')
|
|
304
|
+
.action((name, options) => {
|
|
305
|
+
try {
|
|
306
|
+
let result;
|
|
307
|
+
switch (name) {
|
|
308
|
+
case 'claude-code':
|
|
309
|
+
case 'claude':
|
|
310
|
+
result = installClaudeCode(options);
|
|
311
|
+
break;
|
|
312
|
+
case 'gemini':
|
|
313
|
+
result = installGemini(options);
|
|
314
|
+
break;
|
|
315
|
+
case 'codex':
|
|
316
|
+
result = installCodex(options);
|
|
317
|
+
break;
|
|
318
|
+
case 'openclaw':
|
|
319
|
+
result = installOpenClaw();
|
|
320
|
+
break;
|
|
321
|
+
case 'generic':
|
|
322
|
+
result = showGeneric();
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
console.error(c.error(`Unknown agent: ${name}`));
|
|
326
|
+
console.log(c.dim(`Available: ${AVAILABLE_SKILLS.map((s) => s.name).join(', ')}`));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
console.log(result);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error(c.error(`Failed to install: ${error instanceof Error ? error.message : error}`));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// lore skills show <name>
|
|
337
|
+
skillsCmd
|
|
338
|
+
.command('show <name>')
|
|
339
|
+
.description('Print the contents of an integration\'s instruction file')
|
|
340
|
+
.action((name) => {
|
|
341
|
+
try {
|
|
342
|
+
let content;
|
|
343
|
+
switch (name) {
|
|
344
|
+
case 'claude-code':
|
|
345
|
+
case 'claude':
|
|
346
|
+
content = readFileSync(path.join(pluginsDir(), 'claude-code', 'skills', 'lore', 'SKILL.md'), 'utf-8');
|
|
347
|
+
break;
|
|
348
|
+
case 'gemini':
|
|
349
|
+
content = readFileSync(path.join(pluginsDir(), 'gemini', 'GEMINI.md'), 'utf-8');
|
|
350
|
+
break;
|
|
351
|
+
case 'codex':
|
|
352
|
+
content = readFileSync(path.join(pluginsDir(), 'codex', 'SKILL.md'), 'utf-8');
|
|
353
|
+
break;
|
|
354
|
+
case 'openclaw':
|
|
355
|
+
content = readFileSync(path.join(skillsDir(), 'openclaw.md'), 'utf-8');
|
|
356
|
+
break;
|
|
357
|
+
case 'generic':
|
|
358
|
+
content = readFileSync(path.join(skillsDir(), 'generic-agent.md'), 'utf-8');
|
|
359
|
+
break;
|
|
360
|
+
default:
|
|
361
|
+
console.error(c.error(`Unknown agent: ${name}`));
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
console.log(content);
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
console.error(c.error(`Failed to read: ${error instanceof Error ? error.message : error}`));
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Interactive skill installation — used by the setup wizard.
|
|
374
|
+
* Returns a summary of what was installed.
|
|
375
|
+
*/
|
|
376
|
+
export async function interactiveSkillInstall() {
|
|
377
|
+
const readline = await import('readline');
|
|
378
|
+
const rl = readline.createInterface({
|
|
379
|
+
input: process.stdin,
|
|
380
|
+
output: process.stdout,
|
|
381
|
+
});
|
|
382
|
+
const ask = (question, defaultValue) => new Promise((resolve) => {
|
|
383
|
+
const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
384
|
+
rl.question(display, (answer) => resolve(answer.trim() || defaultValue || ''));
|
|
385
|
+
});
|
|
386
|
+
const installed = [];
|
|
387
|
+
console.log(c.dim('Which AI coding agents do you use? Lore will configure the MCP server'));
|
|
388
|
+
console.log(c.dim('and install instructions so agents know how to use your knowledge base.\n'));
|
|
389
|
+
console.log(` ${c.bold('1.')} Claude Code ${c.dim('— plugin with MCP auto-start')}`);
|
|
390
|
+
console.log(` ${c.bold('2.')} Gemini CLI ${c.dim('— extension with MCP auto-start')}`);
|
|
391
|
+
console.log(` ${c.bold('3.')} Codex CLI ${c.dim('— skill + MCP config')}`);
|
|
392
|
+
console.log(` ${c.bold('4.')} OpenClaw ${c.dim('— SKILL.md auto-discovery')}`);
|
|
393
|
+
console.log(` ${c.bold('5.')} All`);
|
|
394
|
+
console.log(` ${c.bold('6.')} Skip\n`);
|
|
395
|
+
const choice = await ask('Choose (comma-separated for multiple, e.g. 1,2)', '1');
|
|
396
|
+
rl.close();
|
|
397
|
+
const choices = choice.split(',').map((s) => s.trim().toLowerCase());
|
|
398
|
+
if (choices.includes('6') || choices.includes('skip')) {
|
|
399
|
+
return installed;
|
|
400
|
+
}
|
|
401
|
+
const all = choices.includes('5') || choices.includes('all');
|
|
402
|
+
if (all || choices.includes('1') || choices.includes('claude-code') || choices.includes('claude')) {
|
|
403
|
+
try {
|
|
404
|
+
console.log('');
|
|
405
|
+
console.log(c.bold('Claude Code:'));
|
|
406
|
+
console.log(installClaudeCode({ global: false }));
|
|
407
|
+
installed.push('claude-code');
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.log(c.warning(`Claude Code: ${error instanceof Error ? error.message : error}`));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (all || choices.includes('2') || choices.includes('gemini')) {
|
|
414
|
+
try {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(c.bold('Gemini CLI:'));
|
|
417
|
+
console.log(installGemini({ global: false }));
|
|
418
|
+
installed.push('gemini');
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.log(c.warning(`Gemini: ${error instanceof Error ? error.message : error}`));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (all || choices.includes('3') || choices.includes('codex')) {
|
|
425
|
+
try {
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log(c.bold('Codex CLI:'));
|
|
428
|
+
console.log(installCodex({ global: false }));
|
|
429
|
+
installed.push('codex');
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
console.log(c.warning(`Codex: ${error instanceof Error ? error.message : error}`));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (all || choices.includes('4') || choices.includes('openclaw')) {
|
|
436
|
+
try {
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log(c.bold('OpenClaw:'));
|
|
439
|
+
console.log(installOpenClaw());
|
|
440
|
+
installed.push('openclaw');
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
console.log(c.warning(`OpenClaw: ${error instanceof Error ? error.message : error}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return installed;
|
|
447
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sources Command
|
|
3
|
+
*
|
|
4
|
+
* Manage sync source directories.
|
|
5
|
+
*/
|
|
6
|
+
export function registerSourcesCommand(program) {
|
|
7
|
+
const sourcesCmd = program
|
|
8
|
+
.command('sources')
|
|
9
|
+
.description('Manage sync source directories');
|
|
10
|
+
sourcesCmd
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('List configured sync sources')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
const { loadSyncConfig, getConfigPath } = await import('../../sync/config.js');
|
|
15
|
+
console.log(`\nSync Sources`);
|
|
16
|
+
console.log(`============`);
|
|
17
|
+
console.log(`Config: ${getConfigPath()}\n`);
|
|
18
|
+
const config = await loadSyncConfig();
|
|
19
|
+
if (config.sources.length === 0) {
|
|
20
|
+
console.log('No sources configured. Run "lore sources add" to add one.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const source of config.sources) {
|
|
24
|
+
const status = source.enabled ? '✓' : '○';
|
|
25
|
+
console.log(`${status} ${source.name}`);
|
|
26
|
+
console.log(` Path: ${source.path}`);
|
|
27
|
+
console.log(` Glob: ${source.glob}`);
|
|
28
|
+
console.log(` Project: ${source.project}`);
|
|
29
|
+
console.log('');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
sourcesCmd
|
|
33
|
+
.command('add')
|
|
34
|
+
.description('Add a new sync source')
|
|
35
|
+
.option('-n, --name <name>', 'Source name')
|
|
36
|
+
.option('-p, --path <path>', 'Directory path')
|
|
37
|
+
.option('-g, --glob <glob>', 'File glob pattern', '**/*.md')
|
|
38
|
+
.option('--project <project>', 'Default project')
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
const { addSyncSource } = await import('../../sync/config.js');
|
|
41
|
+
const readline = await import('readline');
|
|
42
|
+
const rl = readline.createInterface({
|
|
43
|
+
input: process.stdin,
|
|
44
|
+
output: process.stdout,
|
|
45
|
+
});
|
|
46
|
+
const ask = (question, defaultValue) => new Promise((resolve) => {
|
|
47
|
+
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
48
|
+
rl.question(prompt, (answer) => {
|
|
49
|
+
resolve(answer.trim() || defaultValue || '');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
console.log(`\nAdd Sync Source`);
|
|
53
|
+
console.log(`===============\n`);
|
|
54
|
+
const name = options.name || await ask('Name (e.g., "Granola Meetings")');
|
|
55
|
+
const sourcePath = options.path || await ask('Path (e.g., ~/granola-extractor/output)');
|
|
56
|
+
const glob = options.glob || await ask('Glob pattern', '**/*.md');
|
|
57
|
+
const project = options.project || await ask('Default project');
|
|
58
|
+
rl.close();
|
|
59
|
+
if (!name || !sourcePath || !project) {
|
|
60
|
+
console.log('\nAll fields are required.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
await addSyncSource({
|
|
65
|
+
name,
|
|
66
|
+
path: sourcePath,
|
|
67
|
+
glob,
|
|
68
|
+
project,
|
|
69
|
+
enabled: true,
|
|
70
|
+
});
|
|
71
|
+
console.log(`\n✓ Added source "${name}"`);
|
|
72
|
+
console.log(`\nRun "lore sync" to process files from this source.`);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error(`\nError: ${error}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
sourcesCmd
|
|
80
|
+
.command('enable <name>')
|
|
81
|
+
.description('Enable a sync source')
|
|
82
|
+
.action(async (name) => {
|
|
83
|
+
const { updateSyncSource } = await import('../../sync/config.js');
|
|
84
|
+
try {
|
|
85
|
+
await updateSyncSource(name, { enabled: true });
|
|
86
|
+
console.log(`✓ Enabled "${name}"`);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error(`Error: ${error}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
sourcesCmd
|
|
94
|
+
.command('disable <name>')
|
|
95
|
+
.description('Disable a sync source')
|
|
96
|
+
.action(async (name) => {
|
|
97
|
+
const { updateSyncSource } = await import('../../sync/config.js');
|
|
98
|
+
try {
|
|
99
|
+
await updateSyncSource(name, { enabled: false });
|
|
100
|
+
console.log(`✓ Disabled "${name}"`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(`Error: ${error}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
sourcesCmd
|
|
108
|
+
.command('remove <name>')
|
|
109
|
+
.description('Remove a sync source')
|
|
110
|
+
.action(async (name) => {
|
|
111
|
+
const { removeSyncSource } = await import('../../sync/config.js');
|
|
112
|
+
try {
|
|
113
|
+
await removeSyncSource(name);
|
|
114
|
+
console.log(`✓ Removed "${name}"`);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(`Error: ${error}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Command
|
|
3
|
+
*
|
|
4
|
+
* All sync-related functionality: one-time sync, daemon, watch, sources.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
declare const CONFIG_DIR: string;
|
|
8
|
+
declare const PID_FILE: string;
|
|
9
|
+
declare const STATUS_FILE: string;
|
|
10
|
+
declare const LOG_FILE: string;
|
|
11
|
+
export interface DaemonStatus {
|
|
12
|
+
pid: number;
|
|
13
|
+
started_at: string;
|
|
14
|
+
last_sync?: string;
|
|
15
|
+
last_sync_result?: {
|
|
16
|
+
files_scanned: number;
|
|
17
|
+
files_processed: number;
|
|
18
|
+
errors: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function registerSyncCommand(program: Command, defaultDataDir: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Start the background sync daemon process.
|
|
24
|
+
* Returns { pid } on success, null on failure.
|
|
25
|
+
* If already running, returns the existing PID.
|
|
26
|
+
*/
|
|
27
|
+
export declare function startDaemonProcess(dataDir: string): Promise<{
|
|
28
|
+
pid: number;
|
|
29
|
+
alreadyRunning: boolean;
|
|
30
|
+
} | null>;
|
|
31
|
+
export { CONFIG_DIR, PID_FILE, STATUS_FILE, LOG_FILE };
|