codeep 2.0.0 → 2.0.1
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/dist/renderer/App.js
CHANGED
|
@@ -82,6 +82,15 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
82
82
|
'profile': 'Save/load settings profiles',
|
|
83
83
|
'tasks': 'Show pending tasks from codeep.dev dashboard',
|
|
84
84
|
'sync': 'Sync learning preferences and profiles to codeep.dev',
|
|
85
|
+
// 2.0 — surfaced for `/` autocomplete; documented in /help too.
|
|
86
|
+
'compact': 'Summarize older messages to free up context',
|
|
87
|
+
'commands': 'List custom slash commands in .codeep/commands/*.md',
|
|
88
|
+
'checkpoint': 'Snapshot the session (conversation + provider/model + git HEAD)',
|
|
89
|
+
'checkpoints': 'List saved checkpoints for this workspace',
|
|
90
|
+
'rewind': 'Restore conversation from a saved checkpoint',
|
|
91
|
+
'hooks': 'List installed lifecycle hooks (.codeep/hooks/<event>.sh)',
|
|
92
|
+
'mcp': 'Manage MCP servers (browse, install, add, remove, resources, prompts)',
|
|
93
|
+
'openrouter': 'Tune OpenRouter routing (preferred / ignore providers, fallbacks, privacy)',
|
|
85
94
|
};
|
|
86
95
|
import { helpCategories, keyboardShortcuts } from './components/Help.js';
|
|
87
96
|
import { handleSettingsKey, SETTINGS } from './components/Settings.js';
|
|
@@ -217,6 +226,10 @@ export class App {
|
|
|
217
226
|
'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
|
|
218
227
|
'context-save', 'context-load', 'context-clear', 'learn',
|
|
219
228
|
'cost', 'tasks', 'account', 'sync',
|
|
229
|
+
// 2.0 — extensions, checkpoints, MCP, custom commands, OpenRouter prefs.
|
|
230
|
+
// Keep in lockstep with COMMAND_DESCRIPTIONS below and helpCategories.
|
|
231
|
+
'compact', 'commands', 'checkpoint', 'checkpoints', 'rewind',
|
|
232
|
+
'hooks', 'mcp', 'openrouter',
|
|
220
233
|
'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
|
|
221
234
|
];
|
|
222
235
|
constructor(options) {
|
|
@@ -1685,6 +1685,296 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
1685
1685
|
ctx.app.addMessage({ role: 'system', content: formatCommandList(loadCustomCommands(ctx.projectPath)) });
|
|
1686
1686
|
break;
|
|
1687
1687
|
}
|
|
1688
|
+
case 'mcp': {
|
|
1689
|
+
// Mirrors the ACP `/mcp` handler in src/acp/commands.ts. In TUI the
|
|
1690
|
+
// session id is the constant `codeep-tui` (the same one main.ts uses
|
|
1691
|
+
// when it spawns project MCP servers in the background) and the
|
|
1692
|
+
// workspace root is ctx.projectPath. Without a project we can still
|
|
1693
|
+
// browse the marketplace, but anything that mutates project config
|
|
1694
|
+
// refuses with a clear message.
|
|
1695
|
+
const sub = args[0]?.toLowerCase();
|
|
1696
|
+
const TUI_SESSION = 'codeep-tui';
|
|
1697
|
+
const projectPath = ctx.projectPath;
|
|
1698
|
+
const requireProject = () => {
|
|
1699
|
+
if (!projectPath) {
|
|
1700
|
+
ctx.app.notify('Open a project (cd into it before running codeep) to add or modify MCP servers.');
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1703
|
+
return true;
|
|
1704
|
+
};
|
|
1705
|
+
const { addProjectMcpServer, removeProjectMcpServer, loadMcpServerConfig } = await import('../utils/mcpConfig.js');
|
|
1706
|
+
const { registerSessionServers } = await import('../utils/mcpRegistry.js');
|
|
1707
|
+
if (sub === 'add') {
|
|
1708
|
+
if (!requireProject())
|
|
1709
|
+
break;
|
|
1710
|
+
const name = args[1];
|
|
1711
|
+
const command = args[2];
|
|
1712
|
+
if (!name || !command) {
|
|
1713
|
+
ctx.app.addMessage({ role: 'system', content: 'Usage: `/mcp add <name> <command> [args...]` — e.g. `/mcp add fs npx @modelcontextprotocol/server-filesystem /path`' });
|
|
1714
|
+
break;
|
|
1715
|
+
}
|
|
1716
|
+
const extraArgs = args.slice(3);
|
|
1717
|
+
addProjectMcpServer(projectPath, { name, command, args: extraArgs });
|
|
1718
|
+
ctx.app.notify(`Saved MCP server ${name} to .codeep/mcp_servers.json. Spawning…`);
|
|
1719
|
+
const merged = loadMcpServerConfig(projectPath);
|
|
1720
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, merged, { workspaceRoot: projectPath });
|
|
1721
|
+
const ok = registered.filter(t => t.serverName === name);
|
|
1722
|
+
const failed = errors.find(e => e.server === name);
|
|
1723
|
+
ctx.app.addMessage({
|
|
1724
|
+
role: 'system',
|
|
1725
|
+
content: failed
|
|
1726
|
+
? `Saved \`${name}\` but spawn failed: \`${failed.error}\``
|
|
1727
|
+
: `Added \`${name}\` (${ok.length} tool${ok.length === 1 ? '' : 's'} available).`,
|
|
1728
|
+
});
|
|
1729
|
+
break;
|
|
1730
|
+
}
|
|
1731
|
+
if (sub === 'remove') {
|
|
1732
|
+
if (!requireProject())
|
|
1733
|
+
break;
|
|
1734
|
+
const name = args[1];
|
|
1735
|
+
if (!name) {
|
|
1736
|
+
ctx.app.addMessage({ role: 'system', content: 'Usage: `/mcp remove <name>`' });
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
const removed = removeProjectMcpServer(projectPath, name);
|
|
1740
|
+
if (!removed) {
|
|
1741
|
+
ctx.app.addMessage({ role: 'system', content: `No project-scoped MCP server named \`${name}\`.` });
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
const merged = loadMcpServerConfig(projectPath);
|
|
1745
|
+
await registerSessionServers(TUI_SESSION, merged, { workspaceRoot: projectPath });
|
|
1746
|
+
ctx.app.addMessage({ role: 'system', content: `Removed \`${name}\` from project config and stopped its process.` });
|
|
1747
|
+
break;
|
|
1748
|
+
}
|
|
1749
|
+
if (sub === 'browse') {
|
|
1750
|
+
const { formatMarketplaceList, findMarketplaceEntry, formatMarketplaceEntry, MCP_MARKETPLACE } = await import('../utils/mcpMarketplace.js');
|
|
1751
|
+
const detail = args[1];
|
|
1752
|
+
if (detail) {
|
|
1753
|
+
const entry = findMarketplaceEntry(detail);
|
|
1754
|
+
if (!entry) {
|
|
1755
|
+
ctx.app.addMessage({ role: 'system', content: `Marketplace id not found: \`${detail}\`. Run \`/mcp browse\` for the list.` });
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
const argHints = entry.argHints?.map(h => `<${h.placeholder ?? 'arg'}>`).join(' ') ?? '';
|
|
1759
|
+
ctx.app.addMessage({ role: 'system', content: formatMarketplaceEntry(entry) + `\n\nInstall with \`/mcp install ${entry.id} ${argHints}\`` });
|
|
1760
|
+
}
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
ctx.app.addMessage({ role: 'system', content: formatMarketplaceList() + `\n\nRun \`/mcp browse <id>\` for details or \`/mcp install <id> [args]\` to install. Total: ${MCP_MARKETPLACE.length}.` });
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
if (sub === 'install') {
|
|
1767
|
+
if (!requireProject())
|
|
1768
|
+
break;
|
|
1769
|
+
const id = args[1];
|
|
1770
|
+
if (!id) {
|
|
1771
|
+
ctx.app.addMessage({ role: 'system', content: 'Usage: `/mcp install <id> [extra args...]` — run `/mcp browse` to see ids.' });
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
const { findMarketplaceEntry } = await import('../utils/mcpMarketplace.js');
|
|
1775
|
+
const entry = findMarketplaceEntry(id);
|
|
1776
|
+
if (!entry) {
|
|
1777
|
+
ctx.app.addMessage({ role: 'system', content: `Marketplace id not found: \`${id}\`. Run \`/mcp browse\` for the list.` });
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1780
|
+
const extraArgs = args.slice(2);
|
|
1781
|
+
const fullArgs = [...(entry.server.args ?? []), ...extraArgs];
|
|
1782
|
+
addProjectMcpServer(projectPath, {
|
|
1783
|
+
name: entry.id,
|
|
1784
|
+
command: entry.server.command,
|
|
1785
|
+
args: fullArgs,
|
|
1786
|
+
env: entry.server.env,
|
|
1787
|
+
url: entry.server.url,
|
|
1788
|
+
headers: entry.server.headers,
|
|
1789
|
+
});
|
|
1790
|
+
ctx.app.notify(`Saved ${entry.id} to project config. Spawning…`);
|
|
1791
|
+
const merged = loadMcpServerConfig(projectPath);
|
|
1792
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, merged, { workspaceRoot: projectPath });
|
|
1793
|
+
const failed = errors.find(e => e.server === entry.id);
|
|
1794
|
+
const lines = [];
|
|
1795
|
+
if (failed) {
|
|
1796
|
+
lines.push(`Saved \`${entry.id}\` but spawn failed: \`${failed.error}\``);
|
|
1797
|
+
}
|
|
1798
|
+
else {
|
|
1799
|
+
const ok = registered.filter(t => t.serverName === entry.id);
|
|
1800
|
+
lines.push(`Installed **${entry.name}** (\`${entry.id}\`) — ${ok.length} tool${ok.length === 1 ? '' : 's'} available.`);
|
|
1801
|
+
}
|
|
1802
|
+
if (entry.envNotes?.length) {
|
|
1803
|
+
lines.push('', '**Environment variables you may need:**');
|
|
1804
|
+
for (const e of entry.envNotes) {
|
|
1805
|
+
const req = e.required ? ' (required)' : '';
|
|
1806
|
+
lines.push(`- \`${e.name}\`${req} — ${e.description}`);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n') });
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
if (sub === 'reload') {
|
|
1813
|
+
if (!requireProject())
|
|
1814
|
+
break;
|
|
1815
|
+
ctx.app.notify('Reloading MCP server config…');
|
|
1816
|
+
const merged = loadMcpServerConfig(projectPath);
|
|
1817
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, merged, { workspaceRoot: projectPath });
|
|
1818
|
+
const lines = [`## MCP reloaded`, '', `**${registered.length}** tool${registered.length === 1 ? '' : 's'} from **${merged.length}** server${merged.length === 1 ? '' : 's'}.`];
|
|
1819
|
+
if (errors.length > 0) {
|
|
1820
|
+
lines.push('', '### Failed servers');
|
|
1821
|
+
for (const e of errors)
|
|
1822
|
+
lines.push(`- **${e.server}** — \`${e.error}\``);
|
|
1823
|
+
}
|
|
1824
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n') });
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
if (sub === 'resources') {
|
|
1828
|
+
const { getSessionResources, awaitSessionReady } = await import('../utils/mcpRegistry.js');
|
|
1829
|
+
await awaitSessionReady(TUI_SESSION);
|
|
1830
|
+
const groups = await getSessionResources(TUI_SESSION);
|
|
1831
|
+
if (groups.length === 0) {
|
|
1832
|
+
ctx.app.addMessage({ role: 'system', content: '_No MCP server in this session exposes resources._' });
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
const lines = ['## MCP resources', ''];
|
|
1836
|
+
for (const g of groups) {
|
|
1837
|
+
lines.push(`**${g.serverName}** — ${g.resources.length} resource${g.resources.length === 1 ? '' : 's'}`);
|
|
1838
|
+
for (const r of g.resources) {
|
|
1839
|
+
const label = r.name ? `${r.name} — ` : '';
|
|
1840
|
+
const mime = r.mimeType ? ` (${r.mimeType})` : '';
|
|
1841
|
+
lines.push(`- ${label}\`${r.uri}\`${mime}${r.description ? ` — ${r.description}` : ''}`);
|
|
1842
|
+
}
|
|
1843
|
+
lines.push('');
|
|
1844
|
+
}
|
|
1845
|
+
lines.push('Read one with `/mcp read <uri>`.');
|
|
1846
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n').trim() });
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
if (sub === 'read') {
|
|
1850
|
+
const uri = args[1];
|
|
1851
|
+
if (!uri) {
|
|
1852
|
+
ctx.app.addMessage({ role: 'system', content: 'Usage: `/mcp read <uri>` — run `/mcp resources` to see available URIs.' });
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
const { readSessionResource } = await import('../utils/mcpRegistry.js');
|
|
1856
|
+
try {
|
|
1857
|
+
const contents = await readSessionResource(TUI_SESSION, uri);
|
|
1858
|
+
if (contents.length === 0) {
|
|
1859
|
+
ctx.app.addMessage({ role: 'system', content: `_No content returned for \`${uri}\`._` });
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
const lines = [`## Resource: \`${uri}\``, ''];
|
|
1863
|
+
for (const c of contents) {
|
|
1864
|
+
if (c.text !== undefined) {
|
|
1865
|
+
const fence = c.mimeType?.includes('json') ? 'json' : c.mimeType?.includes('markdown') ? 'markdown' : '';
|
|
1866
|
+
lines.push('```' + fence);
|
|
1867
|
+
lines.push(c.text);
|
|
1868
|
+
lines.push('```');
|
|
1869
|
+
}
|
|
1870
|
+
else if (c.blob) {
|
|
1871
|
+
lines.push(`_(${c.mimeType ?? 'binary'} blob, ${c.blob.length} base64 chars — not rendered)_`);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n') });
|
|
1875
|
+
}
|
|
1876
|
+
catch (err) {
|
|
1877
|
+
ctx.app.addMessage({ role: 'system', content: `Failed to read \`${uri}\`: ${err.message}` });
|
|
1878
|
+
}
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
if (sub === 'prompts') {
|
|
1882
|
+
const { getSessionPrompts, awaitSessionReady } = await import('../utils/mcpRegistry.js');
|
|
1883
|
+
await awaitSessionReady(TUI_SESSION);
|
|
1884
|
+
const groups = await getSessionPrompts(TUI_SESSION);
|
|
1885
|
+
if (groups.length === 0) {
|
|
1886
|
+
ctx.app.addMessage({ role: 'system', content: '_No MCP server in this session exposes prompt templates._' });
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1889
|
+
const lines = ['## MCP prompt templates', ''];
|
|
1890
|
+
for (const g of groups) {
|
|
1891
|
+
lines.push(`**${g.serverName}** — ${g.prompts.length} prompt${g.prompts.length === 1 ? '' : 's'}`);
|
|
1892
|
+
for (const p of g.prompts) {
|
|
1893
|
+
const argList = p.arguments?.length
|
|
1894
|
+
? ` (${p.arguments.map(a => a.required ? a.name : `[${a.name}]`).join(', ')})`
|
|
1895
|
+
: '';
|
|
1896
|
+
lines.push(`- \`${p.name}\`${argList}${p.description ? ` — ${p.description}` : ''}`);
|
|
1897
|
+
}
|
|
1898
|
+
lines.push('');
|
|
1899
|
+
}
|
|
1900
|
+
lines.push('Materialise one with `/mcp prompt <server> <name> [key=value...]`.');
|
|
1901
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n').trim() });
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
if (sub === 'prompt') {
|
|
1905
|
+
const serverName = args[1];
|
|
1906
|
+
const name = args[2];
|
|
1907
|
+
if (!serverName || !name) {
|
|
1908
|
+
ctx.app.addMessage({ role: 'system', content: 'Usage: `/mcp prompt <server> <name> [key=value ...]`' });
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
const promptArgs = {};
|
|
1912
|
+
for (const tok of args.slice(3)) {
|
|
1913
|
+
const eq = tok.indexOf('=');
|
|
1914
|
+
if (eq > 0)
|
|
1915
|
+
promptArgs[tok.slice(0, eq)] = tok.slice(eq + 1);
|
|
1916
|
+
}
|
|
1917
|
+
const { getSessionPrompt } = await import('../utils/mcpRegistry.js');
|
|
1918
|
+
try {
|
|
1919
|
+
const { description, messages } = await getSessionPrompt(TUI_SESSION, serverName, name, promptArgs);
|
|
1920
|
+
const lines = [`## Prompt \`${serverName}/${name}\``];
|
|
1921
|
+
if (description)
|
|
1922
|
+
lines.push(`_${description}_`);
|
|
1923
|
+
lines.push('');
|
|
1924
|
+
for (const m of messages) {
|
|
1925
|
+
const text = typeof m.content?.text === 'string' ? m.content.text : JSON.stringify(m.content);
|
|
1926
|
+
lines.push(`**${m.role}:** ${text}`);
|
|
1927
|
+
lines.push('');
|
|
1928
|
+
}
|
|
1929
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n').trim() });
|
|
1930
|
+
}
|
|
1931
|
+
catch (err) {
|
|
1932
|
+
ctx.app.addMessage({ role: 'system', content: `Failed to materialise prompt: ${err.message}` });
|
|
1933
|
+
}
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
// Default: list servers + tools for the current session.
|
|
1937
|
+
const { getSessionTools, getSessionRegistrationErrors, awaitSessionReady } = await import('../utils/mcpRegistry.js');
|
|
1938
|
+
await awaitSessionReady(TUI_SESSION);
|
|
1939
|
+
const tools = await getSessionTools(TUI_SESSION);
|
|
1940
|
+
const mcpErrors = getSessionRegistrationErrors(TUI_SESSION);
|
|
1941
|
+
if (tools.length === 0 && mcpErrors.length === 0) {
|
|
1942
|
+
ctx.app.addMessage({
|
|
1943
|
+
role: 'system',
|
|
1944
|
+
content: [
|
|
1945
|
+
'_No MCP servers connected to this session._',
|
|
1946
|
+
'',
|
|
1947
|
+
'Add one with `/mcp add <name> <command> [args...]` — it persists to `.codeep/mcp_servers.json`.',
|
|
1948
|
+
'Or browse the marketplace with `/mcp browse` and install with `/mcp install <id>`.',
|
|
1949
|
+
].join('\n'),
|
|
1950
|
+
});
|
|
1951
|
+
break;
|
|
1952
|
+
}
|
|
1953
|
+
const lines = ['## MCP servers', ''];
|
|
1954
|
+
if (tools.length > 0) {
|
|
1955
|
+
const byServer = new Map();
|
|
1956
|
+
for (const t of tools) {
|
|
1957
|
+
if (!byServer.has(t.serverName))
|
|
1958
|
+
byServer.set(t.serverName, []);
|
|
1959
|
+
byServer.get(t.serverName).push(t);
|
|
1960
|
+
}
|
|
1961
|
+
for (const [serverName, serverTools] of byServer) {
|
|
1962
|
+
lines.push(`**${serverName}** — ${serverTools.length} tool${serverTools.length === 1 ? '' : 's'}`);
|
|
1963
|
+
for (const t of serverTools) {
|
|
1964
|
+
const desc = t.description ? ` — ${t.description}` : '';
|
|
1965
|
+
lines.push(`- \`${t.agentName}\`${desc}`);
|
|
1966
|
+
}
|
|
1967
|
+
lines.push('');
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
if (mcpErrors.length > 0) {
|
|
1971
|
+
lines.push('### Failed servers');
|
|
1972
|
+
for (const e of mcpErrors)
|
|
1973
|
+
lines.push(`- **${e.server}** — \`${e.error}\``);
|
|
1974
|
+
}
|
|
1975
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n').trim() });
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1688
1978
|
default: {
|
|
1689
1979
|
// 1. Try custom user command (project + global Markdown templates).
|
|
1690
1980
|
const { findCustomCommand, expandCommand } = await import('../utils/customCommands.js');
|
|
@@ -29,6 +29,16 @@ export const helpCategories = [
|
|
|
29
29
|
{ key: '/rename <name>', description: 'Rename current session' },
|
|
30
30
|
{ key: '/search <term>', description: 'Search chat history' },
|
|
31
31
|
{ key: '/export [md|json|txt]', description: 'Export chat' },
|
|
32
|
+
{ key: '/compact [keepN]', description: 'AI-summarize older messages to free up context (keeps last N)' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: 'Checkpoints (2.0)',
|
|
37
|
+
items: [
|
|
38
|
+
{ key: '/checkpoint [name]', description: 'Snapshot conversation + provider/model + git HEAD' },
|
|
39
|
+
{ key: '/checkpoints', description: 'List saved checkpoints in this workspace' },
|
|
40
|
+
{ key: '/rewind <id>', description: 'Restore conversation from a checkpoint' },
|
|
41
|
+
{ key: '/checkpoint delete <id>', description: 'Delete a saved checkpoint' },
|
|
32
42
|
],
|
|
33
43
|
},
|
|
34
44
|
{
|
|
@@ -110,6 +120,24 @@ export const helpCategories = [
|
|
|
110
120
|
{ key: '/logout', description: 'Logout from provider' },
|
|
111
121
|
{ key: '/profile save <name>', description: 'Save current provider+model as profile' },
|
|
112
122
|
{ key: '/profile list', description: 'List saved profiles' },
|
|
123
|
+
{ key: '/openrouter', description: 'OpenRouter routing prefs (prefer/ignore providers, fallbacks, privacy)' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
title: 'Extensions & MCP (2.0)',
|
|
128
|
+
items: [
|
|
129
|
+
{ key: '/mcp', description: 'List connected MCP servers + their tools' },
|
|
130
|
+
{ key: '/mcp browse [id]', description: 'Browse marketplace (12 servers) or show one' },
|
|
131
|
+
{ key: '/mcp install <id> [args]', description: 'Install a marketplace server into this project' },
|
|
132
|
+
{ key: '/mcp add <name> <cmd>', description: 'Add a custom MCP server (npx, binary, etc.)' },
|
|
133
|
+
{ key: '/mcp remove <name>', description: 'Remove a project-scoped MCP server' },
|
|
134
|
+
{ key: '/mcp reload', description: 'Re-read .codeep/mcp_servers.json (after manual edit)' },
|
|
135
|
+
{ key: '/mcp resources', description: 'List resources exposed by connected servers' },
|
|
136
|
+
{ key: '/mcp read <uri>', description: 'Read one MCP resource' },
|
|
137
|
+
{ key: '/mcp prompts', description: 'List prompt templates exposed by servers' },
|
|
138
|
+
{ key: '/mcp prompt <server> <name>', description: 'Materialize a prompt with arguments (key=value)' },
|
|
139
|
+
{ key: '/hooks', description: 'List installed lifecycle hooks (.codeep/hooks/<event>.sh)' },
|
|
140
|
+
{ key: '/commands', description: 'List custom slash commands (.codeep/commands/*.md)' },
|
|
113
141
|
],
|
|
114
142
|
},
|
|
115
143
|
{
|
|
@@ -24,10 +24,13 @@ export interface RemoteSkill {
|
|
|
24
24
|
updated_at: string;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Publish a
|
|
28
|
-
*
|
|
29
|
-
* —
|
|
30
|
-
*
|
|
27
|
+
* Publish a skill bundle to codeep.dev. The bundle may be project-scoped
|
|
28
|
+
* (`<workspaceRoot>/.codeep/skills/<slug>/SKILL.md`) or global
|
|
29
|
+
* (`~/.codeep/skills/<slug>/SKILL.md`) — either is publishable. The
|
|
30
|
+
* `--public` flag (translated into `opts.isPublic`) is the user's
|
|
31
|
+
* explicit consent gate; we don't gate further on bundle scope. If both
|
|
32
|
+
* exist with the same slug, the project copy wins (mirrors the rest of
|
|
33
|
+
* the bundle-loading flow).
|
|
31
34
|
*/
|
|
32
35
|
export declare function publishBundle(workspaceRoot: string, slug: string, opts?: {
|
|
33
36
|
isPublic?: boolean;
|
|
@@ -17,18 +17,24 @@ import { getSyncToken } from '../config/index.js';
|
|
|
17
17
|
import { findSkillBundle } from './skillBundles.js';
|
|
18
18
|
const API_BASE = 'https://codeep.dev';
|
|
19
19
|
/**
|
|
20
|
-
* Publish a
|
|
21
|
-
*
|
|
22
|
-
* —
|
|
23
|
-
*
|
|
20
|
+
* Publish a skill bundle to codeep.dev. The bundle may be project-scoped
|
|
21
|
+
* (`<workspaceRoot>/.codeep/skills/<slug>/SKILL.md`) or global
|
|
22
|
+
* (`~/.codeep/skills/<slug>/SKILL.md`) — either is publishable. The
|
|
23
|
+
* `--public` flag (translated into `opts.isPublic`) is the user's
|
|
24
|
+
* explicit consent gate; we don't gate further on bundle scope. If both
|
|
25
|
+
* exist with the same slug, the project copy wins (mirrors the rest of
|
|
26
|
+
* the bundle-loading flow).
|
|
24
27
|
*/
|
|
25
28
|
export async function publishBundle(workspaceRoot, slug, opts = {}) {
|
|
26
29
|
const token = getSyncToken();
|
|
27
30
|
if (!token)
|
|
28
31
|
return { ok: false, error: 'Not linked to codeep.dev — run `codeep account` first.' };
|
|
29
32
|
const bundle = findSkillBundle(slug, workspaceRoot);
|
|
30
|
-
if (!bundle
|
|
31
|
-
return {
|
|
33
|
+
if (!bundle) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: `Skill bundle "${slug}" not found in either \`.codeep/skills/${slug}/\` (project) or \`~/.codeep/skills/${slug}/\` (global). Run \`/skills create-bundle ${slug}\` to scaffold one, or check \`/skills bundles\` to see what's available.`,
|
|
37
|
+
};
|
|
32
38
|
}
|
|
33
39
|
// Build the SKILL.md text we'll publish. We re-serialise from the loaded
|
|
34
40
|
// bundle rather than reading the file again — that way frontmatter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|