Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Manual workflow dispatch via GitHub Actions (`release.yml`). **Critical rules:**
## Commands

```bash
bun test # run tests (258+)
bun test # run tests (354+)
bun run check # biome lint + format
bun run packages/cli/bin/kib.ts # run CLI locally
```
Expand All @@ -49,3 +49,29 @@ bun run packages/cli/bin/kib.ts # run CLI locally
- CLI lazy-imports from core to keep cold starts fast
- LLM providers: Anthropic, OpenAI, Ollama (auto-detected from env vars)
- Credentials stored at `~/.config/kib/credentials`, loaded on CLI startup

## Skill Ecosystem (v0.8.0)

### Built-in Skills (10)
`summarize`, `flashcards`, `connections`, `find-contradictions`, `weekly-digest`, `export-slides`, `timeline`, `compare`, `explain`, `suggest-tags`

### Skill Management CLI
```bash
kib skill list # list all available skills
kib skill run <name> # run a skill
kib skill install github:user/repo # install from GitHub
kib skill install <npm-package> # install from npm
kib skill uninstall <name> # remove an installed skill
kib skill create <name> # scaffold a new skill
kib skill publish <name> # validate for publishing
kib skill installed # list installed skills
```

### Skill Architecture
- Skills defined as `SkillDefinition` objects with `run(ctx: SkillContext)` method
- `SkillContext` provides: vault read/write, LLM (complete/stream), search, logger, args, `invoke()` for skill-to-skill calls
- Skills live in: `packages/core/src/skills/builtins.ts` (built-in) or `.kb/skills/` (installed)
- Directory-based skills use `skill.json` for metadata + entry point
- Dependency resolution with circular dependency detection
- Hooks system: skills auto-run after compile/ingest/lint via `hooks` field or `[skills.hooks]` in config.toml
- Config: `[skills]` section in `config.toml` for hooks and per-skill settings
32 changes: 16 additions & 16 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,26 +134,26 @@ What's built, what's next, and what's deferred.
## v0.8.0 — Skill Ecosystem

### Remote Skill Registry
- [ ] `kib skill install github:user/skill-name` — install from GitHub repo
- [ ] `kib skill install <npm-package>` — install from npm
- [ ] Skill dependency resolution (skills can depend on other skills)
- [ ] `kib skill create <name>` — scaffold a new skill from template
- [ ] `kib skill publish` — publish to registry
- [x] `kib skill install github:user/skill-name` — install from GitHub repo
- [x] `kib skill install <npm-package>` — install from npm
- [x] Skill dependency resolution (skills can depend on other skills)
- [x] `kib skill create <name>` — scaffold a new skill from template
- [x] `kib skill publish` — publish to registry

### Additional Built-in Skills
- [ ] `find-contradictions` — detect contradictory claims across articles
- [ ] `weekly-digest` — generate a weekly summary of new additions
- [ ] `export-slides` — generate Marp slide deck from articles
- [ ] `timeline` — generate chronological timeline from articles
- [ ] `compare` — compare two articles/topics side by side
- [ ] `explain` — explain a topic at a specified reading level
- [ ] `suggest-tags` — auto-tag articles based on content analysis
- [x] `find-contradictions` — detect contradictory claims across articles
- [x] `weekly-digest` — generate a weekly summary of new additions
- [x] `export-slides` — generate Marp slide deck from articles
- [x] `timeline` — generate chronological timeline from articles
- [x] `compare` — compare two articles/topics side by side
- [x] `explain` — explain a topic at a specified reading level
- [x] `suggest-tags` — auto-tag articles based on content analysis

### Skill API Enhancements
- [ ] Skill-to-skill invocation (one skill can call another)
- [ ] Skill hooks: run automatically after compile, ingest, or lint
- [ ] Skill configuration in `config.toml`
- [ ] Skill output to specific wiki category
- [x] Skill-to-skill invocation (one skill can call another)
- [x] Skill hooks: run automatically after compile, ingest, or lint
- [x] Skill configuration in `config.toml`
- [x] Skill output to specific wiki category

---

Expand Down
150 changes: 148 additions & 2 deletions packages/cli/src/commands/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export async function skill(subcommand: string, name?: string, opts?: SkillOpts)
throw err;
}

const { loadSkills, findSkill, runSkill } = await import("@kibhq/core");
const {
loadSkills,
findSkill,
runSkill,
installSkill,
uninstallSkill,
createSkill,
publishSkill,
listInstalledSkills,
} = await import("@kibhq/core");

switch (subcommand) {
case "list": {
Expand Down Expand Up @@ -113,9 +122,146 @@ export async function skill(subcommand: string, name?: string, opts?: SkillOpts)
break;
}

case "install": {
if (!name) {
log.error(
"Source required. Usage: kib skill install github:user/repo or kib skill install <npm-package>",
);
process.exit(1);
}

const spinner = opts?.json ? null : createSpinner(`Installing skill from ${name}...`);
spinner?.start();

try {
const result = await installSkill(root, name);

if (opts?.json) {
console.log(JSON.stringify(result, null, 2));
break;
}

spinner?.succeed(`Installed "${result.name}" v${result.version} from ${result.source}`);
log.dim(` Path: ${result.path}`);
log.blank();
} catch (err) {
spinner?.fail("Install failed");
log.error((err as Error).message);
process.exit(1);
}
break;
}

case "uninstall":
case "remove": {
if (!name) {
log.error("Skill name required. Usage: kib skill uninstall <name>");
process.exit(1);
}

const spinner = opts?.json ? null : createSpinner(`Uninstalling "${name}"...`);
spinner?.start();

try {
await uninstallSkill(root, name);

if (opts?.json) {
console.log(JSON.stringify({ uninstalled: name }, null, 2));
break;
}

spinner?.succeed(`Uninstalled "${name}"`);
log.blank();
} catch (err) {
spinner?.fail("Uninstall failed");
log.error((err as Error).message);
process.exit(1);
}
break;
}

case "create": {
if (!name) {
log.error("Skill name required. Usage: kib skill create <name>");
process.exit(1);
}

const spinner = opts?.json ? null : createSpinner(`Creating skill "${name}"...`);
spinner?.start();

try {
const path = await createSkill(root, name);

if (opts?.json) {
console.log(JSON.stringify({ name, path }, null, 2));
break;
}

spinner?.succeed(`Created skill "${name}"`);
log.dim(` Path: ${path}`);
log.dim(" Edit index.ts to implement your skill logic.");
log.blank();
} catch (err) {
spinner?.fail("Create failed");
log.error((err as Error).message);
process.exit(1);
}
break;
}

case "publish": {
if (!name) {
log.error("Skill name required. Usage: kib skill publish <name>");
process.exit(1);
}

const spinner = opts?.json ? null : createSpinner(`Validating skill "${name}"...`);
spinner?.start();

try {
const path = await publishSkill(root, name);

if (opts?.json) {
console.log(JSON.stringify({ name, path, valid: true }, null, 2));
break;
}

spinner?.succeed(`Skill "${name}" is valid and ready to publish`);
log.dim(` Path: ${path}`);
log.dim(" To publish to npm: cd <path> && npm publish");
log.blank();
} catch (err) {
spinner?.fail("Publish validation failed");
log.error((err as Error).message);
process.exit(1);
}
break;
}

case "installed": {
const installed = await listInstalledSkills(root);

if (opts?.json) {
console.log(JSON.stringify(installed, null, 2));
break;
}

if (installed.length === 0) {
log.info("No installed skills. Use kib skill install to add some.");
break;
}

log.header("installed skills");
for (const s of installed) {
console.log(` ${s.name.padEnd(20)} ${s.path}`);
}
log.blank();
break;
}

default:
log.error(`Unknown subcommand: ${subcommand}`);
log.dim("Available: list, run");
log.dim("Available: list, run, install, uninstall, create, publish, installed");
process.exit(1);
}
}
17 changes: 16 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,23 @@ export * from "./schemas.js";
export { highlightSnippet, parseQuery, SearchIndex } from "./search/engine.js";
export { HybridSearch } from "./search/hybrid.js";
export { VectorIndex } from "./search/vector.js";
export { getBuiltinSkills } from "./skills/builtins.js";
export { getHookedSkills, runSkillHooks } from "./skills/hooks.js";
export { findSkill, loadSkills } from "./skills/loader.js";
export {
createSkill,
installSkill,
listInstalledSkills,
publishSkill,
resolveSkillDependencies,
uninstallSkill,
} from "./skills/registry.js";
export { runSkill } from "./skills/runner.js";
export { SkillDefinitionSchema } from "./skills/schema.js";
export {
SkillConfigSchema,
SkillDefinitionSchema,
SkillHookSchema,
SkillPackageSchema,
} from "./skills/schema.js";
export * from "./types.js";
export * from "./vault.js";
12 changes: 12 additions & 0 deletions packages/core/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ export const VaultConfigSchema = z.object({
ttl_hours: z.number().int().positive().default(DEFAULTS.cacheTtlHours),
max_size_mb: z.number().positive().default(DEFAULTS.cacheMaxSizeMb),
}),
skills: z
.object({
hooks: z
.object({
"post-compile": z.array(z.string()).default([]),
"post-ingest": z.array(z.string()).default([]),
"post-lint": z.array(z.string()).default([]),
})
.default({}),
config: z.record(z.string(), z.record(z.string(), z.unknown())).default({}),
})
.default({}),
});

// ─── Article Frontmatter ─────────────────────────────────────────
Expand Down
Loading
Loading