lingot 1.0.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/README.md +59 -0
- package/bin/lingot.js +85 -0
- package/lib/config.js +5 -0
- package/lib/init.js +104 -0
- package/lib/inspect.js +55 -0
- package/lib/install.js +114 -0
- package/lib/list.js +32 -0
- package/lib/serve.js +256 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# lingot
|
|
2
|
+
|
|
3
|
+
The standard library for AI agents. Intelligence blocks that make Cursor, Windsurf, and Claude Code measurably better at writing code.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install a block
|
|
9
|
+
npx lingot add supabase-auth
|
|
10
|
+
|
|
11
|
+
# Detect your stack and see recommendations
|
|
12
|
+
npx lingot init
|
|
13
|
+
|
|
14
|
+
# Install multiple blocks
|
|
15
|
+
npx lingot add drizzle-orm stripe-billing tailwind-v4
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What Are Intelligence Blocks?
|
|
19
|
+
|
|
20
|
+
Each block contains 4 files of curated, token-optimized context:
|
|
21
|
+
|
|
22
|
+
- **knowledge.md** — Dense domain knowledge, mental models, architecture patterns
|
|
23
|
+
- **rules.xml** — ALWAYS/NEVER heuristic rules that prevent hallucinations
|
|
24
|
+
- **examples.yaml** — Few-shot input/output examples for common tasks
|
|
25
|
+
- **manifest.json** — Metadata, version, scope coverage
|
|
26
|
+
|
|
27
|
+
Load blocks into your AI agent's context to get measurably better output. Our Supabase Auth block improved LLM accuracy from 35.3% to 100% on domain-specific tasks.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
| Command | Description |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| `lingot add <name>` | Install a block from the registry |
|
|
34
|
+
| `lingot init` | Scan package.json and suggest relevant blocks |
|
|
35
|
+
| `lingot list` | List installed blocks |
|
|
36
|
+
| `lingot inspect <name>` | Show block details and token counts |
|
|
37
|
+
| `lingot serve` | Start local MCP server for installed blocks |
|
|
38
|
+
|
|
39
|
+
## 40+ Blocks Available
|
|
40
|
+
|
|
41
|
+
Auth, frontend, backend, database, AI SDKs, payments, testing, DevOps, and more.
|
|
42
|
+
|
|
43
|
+
Browse all blocks at [lingot.sh](https://lingot.sh).
|
|
44
|
+
|
|
45
|
+
## MCP Integration
|
|
46
|
+
|
|
47
|
+
Start the local MCP server to expose installed blocks to any MCP-compatible agent:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
lingot serve
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This provides two tools:
|
|
54
|
+
- `search_packages` — Find relevant blocks by topic or domain
|
|
55
|
+
- `get_package_context` — Load block content into agent context
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/bin/lingot.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { install } from '../lib/install.js';
|
|
4
|
+
import { init } from '../lib/init.js';
|
|
5
|
+
import { list } from '../lib/list.js';
|
|
6
|
+
import { serve } from '../lib/serve.js';
|
|
7
|
+
import { inspect } from '../lib/inspect.js';
|
|
8
|
+
import { PACKAGES_DIR } from '../lib/config.js';
|
|
9
|
+
|
|
10
|
+
const [,, command, ...args] = process.argv;
|
|
11
|
+
|
|
12
|
+
const HELP = `
|
|
13
|
+
lingot — The standard library for AI agents.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
lingot add <name> Install an intelligence block from the registry
|
|
17
|
+
lingot init Detect your stack and suggest relevant blocks
|
|
18
|
+
lingot list List installed blocks
|
|
19
|
+
lingot inspect <name> Show block details and token counts
|
|
20
|
+
lingot serve Start local MCP server for installed blocks
|
|
21
|
+
lingot help Show this help
|
|
22
|
+
|
|
23
|
+
Blocks are installed to: ${PACKAGES_DIR}
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
npx lingot add supabase-auth
|
|
27
|
+
npx lingot init
|
|
28
|
+
npx lingot add drizzle-orm stripe-billing tailwind-v4
|
|
29
|
+
|
|
30
|
+
Registry: https://lingot.sh
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
switch (command) {
|
|
35
|
+
case 'add':
|
|
36
|
+
case 'install':
|
|
37
|
+
case 'i':
|
|
38
|
+
if (!args[0]) {
|
|
39
|
+
console.error('Usage: lingot add <block-name> [block-name...]');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
for (const name of args) {
|
|
43
|
+
await install(name);
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
case 'init':
|
|
48
|
+
await init();
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'list':
|
|
52
|
+
case 'ls':
|
|
53
|
+
await list();
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'inspect':
|
|
57
|
+
if (!args[0]) {
|
|
58
|
+
console.error('Usage: lingot inspect <block-name>');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
await inspect(args[0]);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'serve':
|
|
65
|
+
await serve();
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'help':
|
|
69
|
+
case '--help':
|
|
70
|
+
case '-h':
|
|
71
|
+
case undefined:
|
|
72
|
+
console.log(HELP);
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
default:
|
|
76
|
+
console.error(`Unknown command: ${command}`);
|
|
77
|
+
console.log(HELP);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main().catch(err => {
|
|
83
|
+
console.error(err.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
package/lib/config.js
ADDED
package/lib/init.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { REGISTRY_URL } from './config.js';
|
|
4
|
+
|
|
5
|
+
// Maps package.json dependency names to lingot block slugs
|
|
6
|
+
const DEPENDENCY_MAP = {
|
|
7
|
+
'@supabase/supabase-js': 'supabase-auth',
|
|
8
|
+
'@supabase/ssr': 'supabase-auth',
|
|
9
|
+
'next': 'nextjs-app-router',
|
|
10
|
+
'typescript': 'typescript-strict',
|
|
11
|
+
'tailwindcss': 'tailwind-patterns',
|
|
12
|
+
'@tailwindcss/vite': 'tailwind-v4',
|
|
13
|
+
'drizzle-orm': 'drizzle-orm',
|
|
14
|
+
'drizzle-kit': 'drizzle-orm',
|
|
15
|
+
'zod': 'zod',
|
|
16
|
+
'stripe': 'stripe-billing',
|
|
17
|
+
'@aws-sdk/client-s3': 'aws-s3-v3',
|
|
18
|
+
'resend': 'resend',
|
|
19
|
+
'@tanstack/react-query': 'tanstack-query',
|
|
20
|
+
'@prisma/client': 'prisma',
|
|
21
|
+
'prisma': 'prisma',
|
|
22
|
+
'openai': 'openai',
|
|
23
|
+
'@anthropic-ai/sdk': 'anthropic',
|
|
24
|
+
'react-email': 'react-email',
|
|
25
|
+
'@react-email/components': 'react-email',
|
|
26
|
+
'ioredis': 'redis',
|
|
27
|
+
'@upstash/redis': 'redis',
|
|
28
|
+
'vitest': 'vitest',
|
|
29
|
+
'framer-motion': 'framer-motion',
|
|
30
|
+
'motion': 'framer-motion',
|
|
31
|
+
'next-auth': 'next-auth',
|
|
32
|
+
'@trpc/server': 'trpc',
|
|
33
|
+
'@trpc/client': 'trpc',
|
|
34
|
+
'react-hook-form': 'react-hook-form',
|
|
35
|
+
'@clerk/nextjs': 'clerk',
|
|
36
|
+
'mongoose': 'mongoose',
|
|
37
|
+
'zustand': 'zustand',
|
|
38
|
+
'uploadthing': 'uploadthing',
|
|
39
|
+
'@uploadthing/react': 'uploadthing',
|
|
40
|
+
'lucia': 'lucia-auth',
|
|
41
|
+
'arctic': 'lucia-auth',
|
|
42
|
+
'swr': 'swr',
|
|
43
|
+
'eslint': 'eslint',
|
|
44
|
+
'@playwright/test': 'playwright',
|
|
45
|
+
'next-intl': 'next-intl',
|
|
46
|
+
'convex': 'convex',
|
|
47
|
+
'turbo': 'turborepo',
|
|
48
|
+
'nuxt': 'nuxt',
|
|
49
|
+
'cypress': 'cypress',
|
|
50
|
+
'prettier': 'prettier',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export async function init() {
|
|
54
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
55
|
+
|
|
56
|
+
if (!existsSync(pkgPath)) {
|
|
57
|
+
console.error('No package.json found in current directory.');
|
|
58
|
+
console.error('Run this command from your project root.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
63
|
+
const allDeps = {
|
|
64
|
+
...pkg.dependencies,
|
|
65
|
+
...pkg.devDependencies,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const detected = new Set();
|
|
69
|
+
for (const dep of Object.keys(allDeps)) {
|
|
70
|
+
if (DEPENDENCY_MAP[dep]) {
|
|
71
|
+
detected.add(DEPENDENCY_MAP[dep]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (detected.size === 0) {
|
|
76
|
+
console.log('\nNo matching blocks found for your dependencies.');
|
|
77
|
+
console.log('Browse all 40+ blocks at https://lingot.sh');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const blocks = [...detected].sort();
|
|
82
|
+
|
|
83
|
+
console.log(`\nDetected ${blocks.length} relevant intelligence blocks:\n`);
|
|
84
|
+
|
|
85
|
+
for (const slug of blocks) {
|
|
86
|
+
console.log(` lingot add ${slug}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`\nInstall all at once:`);
|
|
90
|
+
console.log(` lingot add ${blocks.join(' ')}`);
|
|
91
|
+
|
|
92
|
+
// SaaS stack upsell — if user has auth + payments + db detected
|
|
93
|
+
const hasSaasStack = detected.has('supabase-auth') || detected.has('next-auth') || detected.has('clerk');
|
|
94
|
+
const hasPayments = detected.has('stripe-billing');
|
|
95
|
+
const hasDb = detected.has('drizzle-orm') || detected.has('prisma') || detected.has('mongoose');
|
|
96
|
+
|
|
97
|
+
if (hasSaasStack && (hasPayments || hasDb)) {
|
|
98
|
+
console.log(`\n \u{1F4A1} SaaS stack detected. Give your AI the integration patterns`);
|
|
99
|
+
console.log(` to wire auth, payments, and database together securely:`);
|
|
100
|
+
console.log(` npx lingot add saas-blueprint (https://lingot.sh/blueprint)`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
package/lib/inspect.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { PACKAGES_DIR } from './config.js';
|
|
4
|
+
|
|
5
|
+
function fmt(num) {
|
|
6
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function inspect(name) {
|
|
10
|
+
const pkgDir = join(PACKAGES_DIR, name);
|
|
11
|
+
const manifestPath = join(pkgDir, 'manifest.json');
|
|
12
|
+
|
|
13
|
+
if (!existsSync(manifestPath)) {
|
|
14
|
+
throw new Error(`Package not found: ${name}\nRun 'lingot list' to see installed packages.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
18
|
+
|
|
19
|
+
console.log(`\n${manifest.name}@${manifest.version}`);
|
|
20
|
+
console.log(`${'─'.repeat(50)}`);
|
|
21
|
+
console.log(`Description: ${manifest.description}`);
|
|
22
|
+
console.log(`Domain: ${manifest.domain}`);
|
|
23
|
+
console.log(`Category: ${manifest.category}`);
|
|
24
|
+
console.log(`Requires: ${manifest.requires?.join(', ') || 'none'}`);
|
|
25
|
+
console.log(`Keywords: ${manifest.keywords?.join(', ')}`);
|
|
26
|
+
console.log(`Author: ${manifest.author}`);
|
|
27
|
+
console.log(`License: ${manifest.license}`);
|
|
28
|
+
console.log();
|
|
29
|
+
|
|
30
|
+
if (manifest.quality) {
|
|
31
|
+
const q = manifest.quality;
|
|
32
|
+
const compression = (q.source_tokens / manifest.tokens.total).toFixed(1);
|
|
33
|
+
const coveragePct = Math.round((q.scope.covered_items / q.scope.total_items) * 100);
|
|
34
|
+
|
|
35
|
+
console.log(`Quality:`);
|
|
36
|
+
console.log(` Compression: ${compression}x (${fmt(q.source_tokens)} source tokens -> ${fmt(manifest.tokens.total)} block tokens)`);
|
|
37
|
+
console.log(` Coverage: ${coveragePct}% of ${q.scope.type} (${q.scope.covered_items}/${q.scope.total_items} items)`);
|
|
38
|
+
console.log(` Scope: ${q.scope.description}`);
|
|
39
|
+
console.log(` Verified: ${q.verified} (${q.timestamp})`);
|
|
40
|
+
console.log(` Status: ${q.maintenance}`);
|
|
41
|
+
console.log();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`Tokens:`);
|
|
45
|
+
console.log(` knowledge: ${fmt(manifest.tokens.knowledge)}`);
|
|
46
|
+
console.log(` rules: ${fmt(manifest.tokens.rules)}`);
|
|
47
|
+
console.log(` examples: ${fmt(manifest.tokens.examples)}`);
|
|
48
|
+
console.log(` total: ${fmt(manifest.tokens.total)}`);
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(`Sources:`);
|
|
51
|
+
for (const src of manifest.sources || []) {
|
|
52
|
+
console.log(` ${src.title}: ${src.url}`);
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
}
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { PACKAGES_DIR, REGISTRY_URL } from './config.js';
|
|
5
|
+
|
|
6
|
+
function computeIntegrity(filePath) {
|
|
7
|
+
const content = readFileSync(filePath);
|
|
8
|
+
const hash = createHash('sha256').update(content).digest('hex');
|
|
9
|
+
return `sha256-${hash}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function updateLockfile(manifest) {
|
|
13
|
+
const lockPath = join(process.cwd(), 'aipkg.lock');
|
|
14
|
+
let lock = { lockfileVersion: 1, packages: {} };
|
|
15
|
+
|
|
16
|
+
if (existsSync(lockPath)) {
|
|
17
|
+
lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
lock.packages[manifest.name] = {
|
|
21
|
+
version: manifest.version,
|
|
22
|
+
integrity: computeIntegrity(join(PACKAGES_DIR, manifest.name, 'knowledge.md')),
|
|
23
|
+
tokens: manifest.tokens?.total || 0,
|
|
24
|
+
requires: manifest.requires || [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
28
|
+
return lockPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isLocalPath(arg) {
|
|
32
|
+
return arg.startsWith('.') || arg.startsWith('/') || arg.startsWith('~');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fetchFile(url) {
|
|
36
|
+
const res = await fetch(url);
|
|
37
|
+
if (!res.ok) throw new Error(`Failed to download ${url}: ${res.status}`);
|
|
38
|
+
return res.text();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function installRemote(packageName) {
|
|
42
|
+
// Fetch metadata from registry
|
|
43
|
+
console.log(`Resolving ${packageName}...`);
|
|
44
|
+
const metaRes = await fetch(`${REGISTRY_URL}/packages/${packageName}`);
|
|
45
|
+
if (!metaRes.ok) {
|
|
46
|
+
throw new Error(`Package not found: ${packageName}`);
|
|
47
|
+
}
|
|
48
|
+
const meta = await metaRes.json();
|
|
49
|
+
|
|
50
|
+
const destDir = join(PACKAGES_DIR, packageName);
|
|
51
|
+
mkdirSync(destDir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
// Download all 4 files in parallel
|
|
54
|
+
console.log(`Downloading ${meta.name}@${meta.version}...`);
|
|
55
|
+
const files = ['manifest', 'knowledge', 'rules', 'examples'];
|
|
56
|
+
const filenames = {
|
|
57
|
+
manifest: 'manifest.json',
|
|
58
|
+
knowledge: 'knowledge.md',
|
|
59
|
+
rules: 'rules.xml',
|
|
60
|
+
examples: 'examples.yaml',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const downloads = await Promise.all(
|
|
64
|
+
files.map(async (key) => {
|
|
65
|
+
const content = await fetchFile(meta.files[key]);
|
|
66
|
+
return { key, content };
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
for (const { key, content } of downloads) {
|
|
71
|
+
writeFileSync(join(destDir, filenames[key]), content);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const manifest = JSON.parse(readFileSync(join(destDir, 'manifest.json'), 'utf-8'));
|
|
75
|
+
const lockPath = updateLockfile(manifest);
|
|
76
|
+
|
|
77
|
+
console.log(`✓ Installed ${manifest.name}@${manifest.version} (${manifest.tokens.total} tokens)`);
|
|
78
|
+
console.log(` → ${destDir}`);
|
|
79
|
+
console.log(` → ${lockPath}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function installLocal(sourcePath) {
|
|
83
|
+
const manifestPath = join(sourcePath, 'manifest.json');
|
|
84
|
+
if (!existsSync(manifestPath)) {
|
|
85
|
+
throw new Error(`No manifest.json found in ${sourcePath}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
89
|
+
|
|
90
|
+
const required = ['knowledge.md', 'rules.xml', 'examples.yaml'];
|
|
91
|
+
for (const file of required) {
|
|
92
|
+
if (!existsSync(join(sourcePath, file))) {
|
|
93
|
+
throw new Error(`Missing required file: ${file}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const destDir = join(PACKAGES_DIR, manifest.name);
|
|
98
|
+
mkdirSync(destDir, { recursive: true });
|
|
99
|
+
cpSync(sourcePath, destDir, { recursive: true });
|
|
100
|
+
|
|
101
|
+
const lockPath = updateLockfile(manifest);
|
|
102
|
+
|
|
103
|
+
console.log(`✓ Installed ${manifest.name}@${manifest.version} (${manifest.tokens.total} tokens)`);
|
|
104
|
+
console.log(` → ${destDir}`);
|
|
105
|
+
console.log(` → ${lockPath}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function install(arg) {
|
|
109
|
+
if (isLocalPath(arg)) {
|
|
110
|
+
await installLocal(arg);
|
|
111
|
+
} else {
|
|
112
|
+
await installRemote(arg);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/lib/list.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { PACKAGES_DIR } from './config.js';
|
|
4
|
+
|
|
5
|
+
export async function list() {
|
|
6
|
+
if (!existsSync(PACKAGES_DIR)) {
|
|
7
|
+
console.log('No packages installed.');
|
|
8
|
+
console.log(`Install path: ${PACKAGES_DIR}`);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const dirs = readdirSync(PACKAGES_DIR, { withFileTypes: true })
|
|
13
|
+
.filter(d => d.isDirectory());
|
|
14
|
+
|
|
15
|
+
if (dirs.length === 0) {
|
|
16
|
+
console.log('No packages installed.');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`\nInstalled packages (${PACKAGES_DIR}):\n`);
|
|
21
|
+
|
|
22
|
+
for (const dir of dirs) {
|
|
23
|
+
const manifestPath = join(PACKAGES_DIR, dir.name, 'manifest.json');
|
|
24
|
+
if (!existsSync(manifestPath)) continue;
|
|
25
|
+
|
|
26
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
27
|
+
const tokens = manifest.tokens?.total || '?';
|
|
28
|
+
console.log(` ${manifest.name}@${manifest.version} (${tokens} tokens)`);
|
|
29
|
+
console.log(` ${manifest.description}`);
|
|
30
|
+
console.log();
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/serve.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import {
|
|
4
|
+
ListResourcesRequestSchema,
|
|
5
|
+
ReadResourceRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { PACKAGES_DIR } from './config.js';
|
|
12
|
+
|
|
13
|
+
function loadPackages() {
|
|
14
|
+
if (!existsSync(PACKAGES_DIR)) return [];
|
|
15
|
+
|
|
16
|
+
return readdirSync(PACKAGES_DIR, { withFileTypes: true })
|
|
17
|
+
.filter(d => d.isDirectory())
|
|
18
|
+
.map(d => {
|
|
19
|
+
const dir = join(PACKAGES_DIR, d.name);
|
|
20
|
+
const manifestPath = join(dir, 'manifest.json');
|
|
21
|
+
if (!existsSync(manifestPath)) return null;
|
|
22
|
+
|
|
23
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
24
|
+
return { dir, manifest };
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readFile(pkgDir, filename) {
|
|
30
|
+
const filepath = join(pkgDir, filename);
|
|
31
|
+
return existsSync(filepath) ? readFileSync(filepath, 'utf-8') : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildCombined(pkgDir) {
|
|
35
|
+
const knowledge = readFile(pkgDir, 'knowledge.md');
|
|
36
|
+
const rules = readFile(pkgDir, 'rules.xml');
|
|
37
|
+
const examples = readFile(pkgDir, 'examples.yaml');
|
|
38
|
+
|
|
39
|
+
return [
|
|
40
|
+
knowledge,
|
|
41
|
+
'',
|
|
42
|
+
rules,
|
|
43
|
+
'',
|
|
44
|
+
'# Examples',
|
|
45
|
+
'```yaml',
|
|
46
|
+
examples,
|
|
47
|
+
'```',
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function serve() {
|
|
52
|
+
const server = new Server(
|
|
53
|
+
{ name: 'lingot-registry', version: '1.0.0' },
|
|
54
|
+
{ capabilities: { resources: {}, tools: {} } }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// List all resources from installed packages
|
|
58
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
59
|
+
const packages = loadPackages();
|
|
60
|
+
const resources = [];
|
|
61
|
+
|
|
62
|
+
for (const pkg of packages) {
|
|
63
|
+
const name = pkg.manifest.name;
|
|
64
|
+
|
|
65
|
+
// Combined resource
|
|
66
|
+
resources.push({
|
|
67
|
+
uri: `aipkg://local/${name}/combined`,
|
|
68
|
+
name: `${name} (full context)`,
|
|
69
|
+
description: pkg.manifest.description,
|
|
70
|
+
mimeType: 'text/markdown',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Granular resources
|
|
74
|
+
resources.push({
|
|
75
|
+
uri: `aipkg://local/${name}/knowledge`,
|
|
76
|
+
name: `${name}/knowledge`,
|
|
77
|
+
description: `Domain knowledge for ${name}`,
|
|
78
|
+
mimeType: 'text/markdown',
|
|
79
|
+
});
|
|
80
|
+
resources.push({
|
|
81
|
+
uri: `aipkg://local/${name}/rules`,
|
|
82
|
+
name: `${name}/rules`,
|
|
83
|
+
description: `Constraints and heuristics for ${name}`,
|
|
84
|
+
mimeType: 'application/xml',
|
|
85
|
+
});
|
|
86
|
+
resources.push({
|
|
87
|
+
uri: `aipkg://local/${name}/examples`,
|
|
88
|
+
name: `${name}/examples`,
|
|
89
|
+
description: `Few-shot examples for ${name}`,
|
|
90
|
+
mimeType: 'text/yaml',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { resources };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Read a specific resource
|
|
98
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
99
|
+
const uri = request.params.uri;
|
|
100
|
+
const match = uri.match(/^aipkg:\/\/local\/([^/]+)\/(.+)$/);
|
|
101
|
+
if (!match) {
|
|
102
|
+
throw new Error(`Invalid resource URI: ${uri}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const [, pkgName, part] = match;
|
|
106
|
+
const pkgDir = join(PACKAGES_DIR, pkgName);
|
|
107
|
+
|
|
108
|
+
if (!existsSync(pkgDir)) {
|
|
109
|
+
throw new Error(`Package not found: ${pkgName}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let content;
|
|
113
|
+
let mimeType = 'text/plain';
|
|
114
|
+
|
|
115
|
+
switch (part) {
|
|
116
|
+
case 'combined':
|
|
117
|
+
content = buildCombined(pkgDir);
|
|
118
|
+
mimeType = 'text/markdown';
|
|
119
|
+
break;
|
|
120
|
+
case 'knowledge':
|
|
121
|
+
content = readFile(pkgDir, 'knowledge.md');
|
|
122
|
+
mimeType = 'text/markdown';
|
|
123
|
+
break;
|
|
124
|
+
case 'rules':
|
|
125
|
+
content = readFile(pkgDir, 'rules.xml');
|
|
126
|
+
mimeType = 'application/xml';
|
|
127
|
+
break;
|
|
128
|
+
case 'examples':
|
|
129
|
+
content = readFile(pkgDir, 'examples.yaml');
|
|
130
|
+
mimeType = 'text/yaml';
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(`Unknown resource part: ${part}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
contents: [{ uri, mimeType, text: content }],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// List available tools
|
|
142
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
143
|
+
return {
|
|
144
|
+
tools: [
|
|
145
|
+
{
|
|
146
|
+
name: 'search_packages',
|
|
147
|
+
description: 'Search locally installed Lingot intelligence blocks by topic, domain, or category. Returns verified, version-locked context packages with token counts. Use when you need authoritative reference material for a specific technology or domain.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
query: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'Search term to match against package names, descriptions, and keywords',
|
|
154
|
+
},
|
|
155
|
+
domain: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'Filter by domain (e.g., payments, infrastructure)',
|
|
158
|
+
},
|
|
159
|
+
category: {
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: 'Filter by category (e.g., developer, architect)',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'get_package_context',
|
|
168
|
+
description: 'Retrieve the full verified context of an Ingot intelligence block. Returns curated domain knowledge, strict heuristic constraints, and schema-validated code examples. Use to ensure syntax compliance with the latest API patterns.',
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
name: {
|
|
173
|
+
type: 'string',
|
|
174
|
+
description: 'The package name (e.g., stripe-webhooks)',
|
|
175
|
+
},
|
|
176
|
+
part: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
enum: ['combined', 'knowledge', 'rules', 'examples'],
|
|
179
|
+
description: 'Which part to retrieve. Defaults to combined.',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
required: ['name'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Handle tool calls
|
|
190
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
191
|
+
const { name, arguments: args } = request.params;
|
|
192
|
+
|
|
193
|
+
if (name === 'search_packages') {
|
|
194
|
+
const packages = loadPackages();
|
|
195
|
+
const query = (args.query || '').toLowerCase();
|
|
196
|
+
const domain = args.domain?.toLowerCase();
|
|
197
|
+
const category = args.category?.toLowerCase();
|
|
198
|
+
|
|
199
|
+
const results = packages.filter(pkg => {
|
|
200
|
+
const m = pkg.manifest;
|
|
201
|
+
const matchesQuery = !query ||
|
|
202
|
+
m.name.includes(query) ||
|
|
203
|
+
m.description.toLowerCase().includes(query) ||
|
|
204
|
+
m.keywords?.some(k => k.toLowerCase().includes(query));
|
|
205
|
+
const matchesDomain = !domain || m.domain?.toLowerCase() === domain;
|
|
206
|
+
const matchesCategory = !category || m.category?.toLowerCase() === category;
|
|
207
|
+
return matchesQuery && matchesDomain && matchesCategory;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const text = results.length === 0
|
|
211
|
+
? 'No packages found.'
|
|
212
|
+
: results.map(pkg => {
|
|
213
|
+
const m = pkg.manifest;
|
|
214
|
+
return `${m.name}@${m.version} (${m.tokens.total} tokens)\n ${m.description}\n domain: ${m.domain} | category: ${m.category}\n keywords: ${m.keywords?.join(', ')}`;
|
|
215
|
+
}).join('\n\n');
|
|
216
|
+
|
|
217
|
+
return { content: [{ type: 'text', text }] };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (name === 'get_package_context') {
|
|
221
|
+
const pkgName = args.name;
|
|
222
|
+
const part = args.part || 'combined';
|
|
223
|
+
const pkgDir = join(PACKAGES_DIR, pkgName);
|
|
224
|
+
|
|
225
|
+
if (!existsSync(pkgDir)) {
|
|
226
|
+
return { content: [{ type: 'text', text: `Package not found: ${pkgName}` }] };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let content;
|
|
230
|
+
switch (part) {
|
|
231
|
+
case 'combined':
|
|
232
|
+
content = buildCombined(pkgDir);
|
|
233
|
+
break;
|
|
234
|
+
case 'knowledge':
|
|
235
|
+
content = readFile(pkgDir, 'knowledge.md');
|
|
236
|
+
break;
|
|
237
|
+
case 'rules':
|
|
238
|
+
content = readFile(pkgDir, 'rules.xml');
|
|
239
|
+
break;
|
|
240
|
+
case 'examples':
|
|
241
|
+
content = readFile(pkgDir, 'examples.yaml');
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
content = buildCombined(pkgDir);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { content: [{ type: 'text', text: content }] };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const transport = new StdioServerTransport();
|
|
254
|
+
await server.connect(transport);
|
|
255
|
+
// Server is now running on stdio
|
|
256
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lingot",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The standard library for AI agents. Intelligence blocks that make Cursor, Windsurf, and Claude Code measurably better.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lingot": "./bin/lingot.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/lingot.js list"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["ai", "agents", "context", "mcp", "cursor", "windsurf", "claude", "intelligence-blocks", "llm"],
|
|
13
|
+
"author": "Lingot <hello@lingot.sh>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/lingot-sh/lingot"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://lingot.sh",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|