@lumea-labs/skills-polpo 0.1.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 +21 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +11 -0
- package/dist/map-polpo-skill.d.ts +7 -0
- package/dist/map-polpo-skill.js +34 -0
- package/dist/polpo-skills.d.ts +30 -0
- package/dist/polpo-skills.js +43 -0
- package/dist/use-polpo-skills-adapter.d.ts +53 -0
- package/dist/use-polpo-skills-adapter.js +185 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @lumea-labs/skills-polpo
|
|
2
|
+
|
|
3
|
+
Tier 2 wiring of @lumea-labs/skills to the Polpo skills API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lumea-labs/skills-polpo
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { /* … */ } from "@lumea-labs/skills-polpo";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
See the [Lumea Agents repo](https://github.com/Lumea-Technologies/lumea-agents/tree/main/packages/skills-polpo) for the full source and a playground example.
|
|
18
|
+
|
|
19
|
+
## License
|
|
20
|
+
|
|
21
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { UsePolpoSkillsAdapterReturn, usePolpoSkillsAdapter } from './use-polpo-skills-adapter.js';
|
|
2
|
+
export { PolpoSkills, PolpoSkillsProps } from './polpo-skills.js';
|
|
3
|
+
export { mapLoadedSkill, mapPolpoSkill } from './map-polpo-skill.js';
|
|
4
|
+
import '@lumea-labs/skills';
|
|
5
|
+
import 'react';
|
|
6
|
+
import '@polpo-ai/sdk';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
usePolpoSkillsAdapter
|
|
3
|
+
} from "./use-polpo-skills-adapter";
|
|
4
|
+
import { PolpoSkills } from "./polpo-skills";
|
|
5
|
+
import { mapPolpoSkill, mapLoadedSkill } from "./map-polpo-skill";
|
|
6
|
+
export {
|
|
7
|
+
PolpoSkills,
|
|
8
|
+
mapLoadedSkill,
|
|
9
|
+
mapPolpoSkill,
|
|
10
|
+
usePolpoSkillsAdapter
|
|
11
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LoadedSkill, SkillWithAssignment, SkillInfo } from '@polpo-ai/sdk';
|
|
2
|
+
import { Skill } from '@lumea-labs/skills';
|
|
3
|
+
|
|
4
|
+
declare function mapPolpoSkill(s: SkillWithAssignment | SkillInfo): Skill;
|
|
5
|
+
declare function mapLoadedSkill(loaded: LoadedSkill, base?: Skill): Skill;
|
|
6
|
+
|
|
7
|
+
export { mapLoadedSkill, mapPolpoSkill };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function mapScope(source) {
|
|
2
|
+
switch (source) {
|
|
3
|
+
case "project":
|
|
4
|
+
return "project";
|
|
5
|
+
case "global":
|
|
6
|
+
case "home":
|
|
7
|
+
return "personal";
|
|
8
|
+
case "polpo":
|
|
9
|
+
case "claude":
|
|
10
|
+
return "plugin";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function mapPolpoSkill(s) {
|
|
14
|
+
const tags = s.tags ?? [];
|
|
15
|
+
const assignedTo = "assignedTo" in s && Array.isArray(s.assignedTo) ? s.assignedTo : void 0;
|
|
16
|
+
return {
|
|
17
|
+
id: s.name,
|
|
18
|
+
name: s.name,
|
|
19
|
+
description: s.description,
|
|
20
|
+
tags: s.category ? [s.category, ...tags] : tags,
|
|
21
|
+
installPath: s.path,
|
|
22
|
+
installed: true,
|
|
23
|
+
enabled: assignedTo ? assignedTo.length > 0 : void 0,
|
|
24
|
+
scope: mapScope(s.source)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function mapLoadedSkill(loaded, base) {
|
|
28
|
+
const mapped = base ?? mapPolpoSkill(loaded);
|
|
29
|
+
return { ...mapped, readme: loaded.content };
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
mapLoadedSkill,
|
|
33
|
+
mapPolpoSkill
|
|
34
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SkillsLabels, SkillsQuery } from '@lumea-labs/skills';
|
|
4
|
+
|
|
5
|
+
interface PolpoSkillsProps {
|
|
6
|
+
labels?: Partial<SkillsLabels>;
|
|
7
|
+
initialQuery?: SkillsQuery;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
autoLoadInstalled?: boolean;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convenience wrapper: builds a Polpo-wired `SkillsAdapter` and mounts
|
|
14
|
+
* `<SkillsProvider>` with it. Use this when you don't need the adapter
|
|
15
|
+
* outside the tree — otherwise call `usePolpoSkillsAdapter()` and wire
|
|
16
|
+
* `<SkillsProvider>` yourself.
|
|
17
|
+
*
|
|
18
|
+
* <PolpoSkills>
|
|
19
|
+
* <SkillsSearchBar />
|
|
20
|
+
* <SkillsList onOpenSkill={(s) => router.push(`/skills/${s.id}`)} />
|
|
21
|
+
* </PolpoSkills>
|
|
22
|
+
*
|
|
23
|
+
* Internally a `<PolpoSkillsSync>` mounts inside the provider and
|
|
24
|
+
* republishes a refresh whenever Polpo's `useSkills` data changes —
|
|
25
|
+
* the provider only auto-loads once, so without this the cached list
|
|
26
|
+
* stays empty if Polpo's fetch resolves after the first render.
|
|
27
|
+
*/
|
|
28
|
+
declare function PolpoSkills({ labels, initialQuery, autoLoad, autoLoadInstalled, children, }: PolpoSkillsProps): react.JSX.Element;
|
|
29
|
+
|
|
30
|
+
export { PolpoSkills, type PolpoSkillsProps };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import {
|
|
4
|
+
SkillsProvider,
|
|
5
|
+
useSkills as useSkillsPackage
|
|
6
|
+
} from "@lumea-labs/skills";
|
|
7
|
+
import { usePolpoSkillsAdapter } from "./use-polpo-skills-adapter";
|
|
8
|
+
function PolpoSkills({
|
|
9
|
+
labels,
|
|
10
|
+
initialQuery,
|
|
11
|
+
autoLoad,
|
|
12
|
+
autoLoadInstalled,
|
|
13
|
+
children
|
|
14
|
+
}) {
|
|
15
|
+
const { adapter, skills } = usePolpoSkillsAdapter();
|
|
16
|
+
return /* @__PURE__ */ React.createElement(
|
|
17
|
+
SkillsProvider,
|
|
18
|
+
{
|
|
19
|
+
adapter,
|
|
20
|
+
labels,
|
|
21
|
+
initialQuery,
|
|
22
|
+
autoLoad,
|
|
23
|
+
autoLoadInstalled
|
|
24
|
+
},
|
|
25
|
+
/* @__PURE__ */ React.createElement(PolpoSkillsSync, { polpoSkills: skills }),
|
|
26
|
+
children
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function PolpoSkillsSync({ polpoSkills }) {
|
|
30
|
+
const { actions } = useSkillsPackage();
|
|
31
|
+
const key = polpoSkills.map((s) => s.id).join("|");
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const t = setTimeout(() => {
|
|
34
|
+
void actions.refresh();
|
|
35
|
+
void actions.refreshInstalled();
|
|
36
|
+
}, 0);
|
|
37
|
+
return () => clearTimeout(t);
|
|
38
|
+
}, [key]);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
PolpoSkills
|
|
43
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SkillsAdapter, Skill } from '@lumea-labs/skills';
|
|
2
|
+
|
|
3
|
+
interface UsePolpoSkillsAdapterReturn {
|
|
4
|
+
adapter: SkillsAdapter;
|
|
5
|
+
skills: Skill[];
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
refetch: () => Promise<void>;
|
|
9
|
+
/** Pending flags for the currently in-flight mutation, surfaced so
|
|
10
|
+
* consumers can disable buttons without re-deriving them. */
|
|
11
|
+
isCreating: boolean;
|
|
12
|
+
isInstalling: boolean;
|
|
13
|
+
isDeleting: boolean;
|
|
14
|
+
/** Polpo-specific assignment ops — not part of the
|
|
15
|
+
* `@lumea/skills` adapter contract because that package doesn't
|
|
16
|
+
* yet model agent-level assignments. Surface them here so
|
|
17
|
+
* consumers (e.g. an agent detail page) can wire UI on top. */
|
|
18
|
+
assignSkill: (skillName: string, agentName: string) => Promise<void>;
|
|
19
|
+
unassignSkill: (skillName: string, agentName: string) => Promise<void>;
|
|
20
|
+
isAssigning: boolean;
|
|
21
|
+
isUnassigning: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Tier-2 wiring: build a `SkillsAdapter` from `@polpo-ai/react`'s
|
|
25
|
+
* `useSkills` hook + the `PolpoClient` (for content fetching).
|
|
26
|
+
*
|
|
27
|
+
* The mapping is opinionated:
|
|
28
|
+
*
|
|
29
|
+
* - `listSkills` and `listInstalled` both return Polpo's local pool.
|
|
30
|
+
* There is no separate "registry" surface — Polpo doesn't expose
|
|
31
|
+
* one — so the browse view shows installed skills filtered by
|
|
32
|
+
* `query.search` / `query.tags`.
|
|
33
|
+
*
|
|
34
|
+
* - `installSkill(skill)` reads the source from
|
|
35
|
+
* `skill.repositoryUrl ?? skill.repository ?? skill.installPath`
|
|
36
|
+
* and forwards to Polpo's `installSkills(source)`. Skills coming
|
|
37
|
+
* out of `listSkills` set `installPath` so re-installing is a
|
|
38
|
+
* no-op (`force: true` would be the consumer's call).
|
|
39
|
+
*
|
|
40
|
+
* - `getSkill(id)` does a deep fetch via
|
|
41
|
+
* `client.getSkillContent(name)` to populate `readme`.
|
|
42
|
+
*
|
|
43
|
+
* - `createSkill(input)` maps the lumea wizard shape to Polpo's
|
|
44
|
+
* `CreateSkillRequest`. `frontmatter.allowedTools` flows through;
|
|
45
|
+
* `scope` is dropped (Polpo decides where to write).
|
|
46
|
+
*
|
|
47
|
+
* - `toggleSkill` is intentionally not implemented — Polpo's enable
|
|
48
|
+
* state is per-agent assignment, not per-skill. Use the dedicated
|
|
49
|
+
* `assignSkill` / `unassignSkill` returned alongside the adapter.
|
|
50
|
+
*/
|
|
51
|
+
declare function usePolpoSkillsAdapter(): UsePolpoSkillsAdapterReturn;
|
|
52
|
+
|
|
53
|
+
export { type UsePolpoSkillsAdapterReturn, usePolpoSkillsAdapter };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { usePolpo, useSkills } from "@polpo-ai/react";
|
|
4
|
+
import { mapLoadedSkill, mapPolpoSkill } from "./map-polpo-skill";
|
|
5
|
+
function usePolpoSkillsAdapter() {
|
|
6
|
+
const { client } = usePolpo();
|
|
7
|
+
const {
|
|
8
|
+
skills: polpoSkills,
|
|
9
|
+
isLoading,
|
|
10
|
+
error,
|
|
11
|
+
refetch,
|
|
12
|
+
createSkill: polpoCreate,
|
|
13
|
+
isCreating,
|
|
14
|
+
installSkills,
|
|
15
|
+
isInstalling,
|
|
16
|
+
deleteSkill,
|
|
17
|
+
isDeleting,
|
|
18
|
+
assignSkill: polpoAssign,
|
|
19
|
+
unassignSkill: polpoUnassign,
|
|
20
|
+
isAssigning,
|
|
21
|
+
isUnassigning
|
|
22
|
+
} = useSkills();
|
|
23
|
+
const skills = useMemo(
|
|
24
|
+
() => polpoSkills.map(mapPolpoSkill),
|
|
25
|
+
[polpoSkills]
|
|
26
|
+
);
|
|
27
|
+
const matchesQuery = useCallback(
|
|
28
|
+
(skill, q) => {
|
|
29
|
+
if (!q) return true;
|
|
30
|
+
if (q.search) {
|
|
31
|
+
const needle = q.search.toLowerCase();
|
|
32
|
+
const hay = `${skill.name} ${skill.description ?? ""}`.toLowerCase();
|
|
33
|
+
if (!hay.includes(needle)) return false;
|
|
34
|
+
}
|
|
35
|
+
if (q.tags && q.tags.length > 0) {
|
|
36
|
+
const have = new Set((skill.tags ?? []).map((t) => t.toLowerCase()));
|
|
37
|
+
const want = q.tags.map((t) => t.toLowerCase());
|
|
38
|
+
if (!want.every((t) => have.has(t))) return false;
|
|
39
|
+
}
|
|
40
|
+
if (q.installedOnly && !skill.installed) return false;
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
[]
|
|
44
|
+
);
|
|
45
|
+
const sortSkills = useCallback(
|
|
46
|
+
(list, sort) => {
|
|
47
|
+
const out = [...list];
|
|
48
|
+
switch (sort) {
|
|
49
|
+
case "alphabetical":
|
|
50
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
51
|
+
break;
|
|
52
|
+
case "popular":
|
|
53
|
+
case "trending":
|
|
54
|
+
out.sort(
|
|
55
|
+
(a, b) => (b.installs ?? 0) - (a.installs ?? 0) || a.name.localeCompare(b.name)
|
|
56
|
+
);
|
|
57
|
+
break;
|
|
58
|
+
case "recent":
|
|
59
|
+
out.sort(
|
|
60
|
+
(a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
},
|
|
68
|
+
[]
|
|
69
|
+
);
|
|
70
|
+
const listSkills = useCallback(
|
|
71
|
+
async (q) => {
|
|
72
|
+
const filtered = skills.filter((s) => matchesQuery(s, q));
|
|
73
|
+
const sorted = sortSkills(filtered, q?.sort);
|
|
74
|
+
const offset = q?.offset ?? 0;
|
|
75
|
+
const limit = q?.limit ?? sorted.length;
|
|
76
|
+
return sorted.slice(offset, offset + limit);
|
|
77
|
+
},
|
|
78
|
+
[skills, matchesQuery, sortSkills]
|
|
79
|
+
);
|
|
80
|
+
const listInstalled = useCallback(async () => {
|
|
81
|
+
return skills;
|
|
82
|
+
}, [skills]);
|
|
83
|
+
const getSkill = useCallback(
|
|
84
|
+
async (id) => {
|
|
85
|
+
const base = skills.find((s) => s.id === id);
|
|
86
|
+
try {
|
|
87
|
+
const [loaded, listing] = await Promise.all([
|
|
88
|
+
client.getSkillContent(id),
|
|
89
|
+
// The skill folder lives at `installPath` (Polpo's `path`).
|
|
90
|
+
// Listing it gives us the file tree for `<SkillFileTree>`.
|
|
91
|
+
base?.installPath ? client.listFiles(base.installPath).catch(() => null) : Promise.resolve(null)
|
|
92
|
+
]);
|
|
93
|
+
const files = listing?.entries?.map((e) => ({
|
|
94
|
+
path: e.name,
|
|
95
|
+
type: e.type,
|
|
96
|
+
size: e.size
|
|
97
|
+
})) ?? [];
|
|
98
|
+
return { ...mapLoadedSkill(loaded, base), files };
|
|
99
|
+
} catch {
|
|
100
|
+
return base ?? null;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[skills, client]
|
|
104
|
+
);
|
|
105
|
+
const installSkill = useCallback(
|
|
106
|
+
async (skill) => {
|
|
107
|
+
const source = skill.repositoryUrl ?? skill.repository ?? skill.installPath;
|
|
108
|
+
if (!source) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Cannot install skill "${skill.id}" \u2014 no source (repositoryUrl, repository or installPath).`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
await installSkills(source, { skillNames: [skill.name] });
|
|
114
|
+
},
|
|
115
|
+
[installSkills]
|
|
116
|
+
);
|
|
117
|
+
const uninstallSkill = useCallback(
|
|
118
|
+
async (id) => {
|
|
119
|
+
await deleteSkill(id);
|
|
120
|
+
},
|
|
121
|
+
[deleteSkill]
|
|
122
|
+
);
|
|
123
|
+
const createSkill = useCallback(
|
|
124
|
+
async (input) => {
|
|
125
|
+
const created = await polpoCreate({
|
|
126
|
+
name: input.name,
|
|
127
|
+
description: input.description,
|
|
128
|
+
content: input.body,
|
|
129
|
+
allowedTools: input.frontmatter.allowedTools
|
|
130
|
+
});
|
|
131
|
+
const fresh = skills.find((s) => s.name === created.name) ?? {
|
|
132
|
+
id: created.name,
|
|
133
|
+
name: created.name,
|
|
134
|
+
description: input.description,
|
|
135
|
+
installed: true,
|
|
136
|
+
installPath: created.path,
|
|
137
|
+
scope: input.scope,
|
|
138
|
+
tags: [],
|
|
139
|
+
frontmatter: input.frontmatter
|
|
140
|
+
};
|
|
141
|
+
return fresh;
|
|
142
|
+
},
|
|
143
|
+
[polpoCreate, skills]
|
|
144
|
+
);
|
|
145
|
+
const adapter = useMemo(
|
|
146
|
+
() => ({
|
|
147
|
+
listSkills,
|
|
148
|
+
listInstalled,
|
|
149
|
+
getSkill,
|
|
150
|
+
installSkill,
|
|
151
|
+
uninstallSkill,
|
|
152
|
+
createSkill
|
|
153
|
+
}),
|
|
154
|
+
[listSkills, listInstalled, getSkill, installSkill, uninstallSkill, createSkill]
|
|
155
|
+
);
|
|
156
|
+
const assignSkill = useCallback(
|
|
157
|
+
async (skillName, agentName) => {
|
|
158
|
+
await polpoAssign(skillName, agentName);
|
|
159
|
+
},
|
|
160
|
+
[polpoAssign]
|
|
161
|
+
);
|
|
162
|
+
const unassignSkill = useCallback(
|
|
163
|
+
async (skillName, agentName) => {
|
|
164
|
+
await polpoUnassign(skillName, agentName);
|
|
165
|
+
},
|
|
166
|
+
[polpoUnassign]
|
|
167
|
+
);
|
|
168
|
+
return {
|
|
169
|
+
adapter,
|
|
170
|
+
skills,
|
|
171
|
+
isLoading,
|
|
172
|
+
error,
|
|
173
|
+
refetch,
|
|
174
|
+
isCreating,
|
|
175
|
+
isInstalling,
|
|
176
|
+
isDeleting,
|
|
177
|
+
assignSkill,
|
|
178
|
+
unassignSkill,
|
|
179
|
+
isAssigning,
|
|
180
|
+
isUnassigning
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
usePolpoSkillsAdapter
|
|
185
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumea-labs/skills-polpo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tier 2 wiring of @lumea-labs/skills to the Polpo skills API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"react": ">=19",
|
|
11
|
+
"lucide-react": "*",
|
|
12
|
+
"@lumea-labs/skills": "*",
|
|
13
|
+
"@polpo-ai/react": ">=0.6.0",
|
|
14
|
+
"@polpo-ai/sdk": ">=0.6.0"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"build:watch": "tsup --watch",
|
|
31
|
+
"clean": "rm -rf dist"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/Lumea-Technologies/lumea-agents.git",
|
|
39
|
+
"directory": "packages/skills-polpo"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Lumea-Technologies/lumea-agents/tree/main/packages/skills-polpo"
|
|
42
|
+
}
|