@netnodeag/kraftwerk 0.35.0 → 0.37.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.
Files changed (57) hide show
  1. package/README.md +65 -20
  2. package/dist/cli/doctor.js +18 -1
  3. package/dist/cli/init.js +19 -12
  4. package/dist/cli/kraftwerk.js +3 -0
  5. package/dist/cli/projects.d.ts +14 -0
  6. package/dist/cli/projects.js +1 -1
  7. package/dist/cli/repos.d.ts +2 -0
  8. package/dist/cli/repos.js +115 -0
  9. package/dist/cli/routines.d.ts +10 -0
  10. package/dist/cli/routines.js +10 -10
  11. package/dist/config.d.ts +29 -2
  12. package/dist/config.js +56 -2
  13. package/dist/inspector/{team.d.ts → agents.d.ts} +21 -12
  14. package/dist/inspector/{team.js → agents.js} +34 -19
  15. package/dist/inspector/chat/backend.d.ts +1 -1
  16. package/dist/inspector/chat/sessions.js +72 -45
  17. package/dist/inspector/chat/store.js +13 -1
  18. package/dist/inspector/chat/types.d.ts +2 -2
  19. package/dist/inspector/git.d.ts +14 -0
  20. package/dist/inspector/git.js +11 -9
  21. package/dist/inspector/instances.d.ts +12 -0
  22. package/dist/inspector/instances.js +28 -0
  23. package/dist/inspector/repos.d.ts +118 -0
  24. package/dist/inspector/repos.js +249 -0
  25. package/dist/inspector/routines.d.ts +4 -4
  26. package/dist/inspector/routines.js +11 -11
  27. package/dist/inspector/search.d.ts +25 -0
  28. package/dist/inspector/search.js +31 -0
  29. package/dist/inspector/server.js +77 -30
  30. package/dist/inspector/settings.d.ts +7 -1
  31. package/dist/inspector/settings.js +32 -1
  32. package/dist/inspector/skills.d.ts +6 -6
  33. package/dist/inspector/skills.js +8 -8
  34. package/inspector/dist/assets/{editor-BABv2LmA.js → editor-vM6JSj5h.js} +3 -3
  35. package/inspector/dist/assets/{index-CCACy6uF.js → index-BAAjszPI.js} +1 -1
  36. package/inspector/dist/assets/{index-DuwmzZAr.js → index-BKcOp35r.js} +1 -1
  37. package/inspector/dist/assets/index-Ba8VU6Wz.js +217 -0
  38. package/inspector/dist/assets/{index-Ca9Qs5LZ.js → index-Bb08wK-l.js} +1 -1
  39. package/inspector/dist/assets/{index-ByWpqzAI.js → index-BrCwDkJZ.js} +1 -1
  40. package/inspector/dist/assets/{index-hkVov8T4.js → index-Bs1U0rWW.js} +1 -1
  41. package/inspector/dist/assets/{index-CdwTmXTV.js → index-C1UXlW32.js} +1 -1
  42. package/inspector/dist/assets/{index-HBdsdJhD.js → index-CQ2XKqmD.js} +1 -1
  43. package/inspector/dist/assets/{index-DCR7u4Xa.js → index-CReepirH.js} +1 -1
  44. package/inspector/dist/assets/{index-CzPQygR7.js → index-CUGfxoYt.js} +1 -1
  45. package/inspector/dist/assets/{index-sLxulPOm.js → index-CaKmxcD6.js} +1 -1
  46. package/inspector/dist/assets/{index-Bmm4zJNU.js → index-CtMGH62C.js} +1 -1
  47. package/inspector/dist/assets/{index-BXspF2KR.css → index-D4_mLTSH.css} +1 -1
  48. package/inspector/dist/assets/{index-BbTer62d.js → index-D_11IZBV.js} +1 -1
  49. package/inspector/dist/assets/{index-wkYwz0Tl.js → index-DfZT0IoM.js} +1 -1
  50. package/inspector/dist/assets/{index-Gx93brFq.js → index-DykDNrYm.js} +1 -1
  51. package/inspector/dist/assets/{index-DuPp3MUc.js → index-MWv5H1oP.js} +1 -1
  52. package/inspector/dist/assets/{index-CjlJeW9z.js → index-oIQqfbwm.js} +1 -1
  53. package/inspector/dist/assets/{index-DbWUNbt-.js → index-yGZsomcy.js} +1 -1
  54. package/inspector/dist/index.html +2 -2
  55. package/package.json +1 -1
  56. package/schema/kraftwerk.schema.json +15 -0
  57. package/inspector/dist/assets/index-uEig4--d.js +0 -217
@@ -8,11 +8,13 @@ import { listWorkflows, getWorkflow } from "./workflows.js";
8
8
  import { dockerStatus, triggerRun, stopRun } from "./runner.js";
9
9
  import { cancelChat, createChat, deleteChat, disposeAllBackends, getChat, listChats, postMessage, resolvePermission, subscribeChat, } from "./chat/sessions.js";
10
10
  import { bundleDetail, conceptDetail, createBundle, knowledgeIndex, putConcept, verifyFromUi, } from "./knowledge.js";
11
- import { deleteMember, getMember, listMembers, saveMember, setMemberArchived, teamRoot, } from "./team.js";
11
+ import { deleteAgent, getAgent, listAgents, saveAgent, setAgentArchived, agentsRoot, } from "./agents.js";
12
12
  import { deleteAgentSkill, getSkill, listAgentSkills, listSkills, readSkill, saveAgentSkill, skillsRoot, } from "./skills.js";
13
13
  import { discoverWorkspaces, forgetProject, listWorkspacesDetailed, markProjectStopped, registerInstance, registerProject, startProject, stopProject, tildify, unregisterInstance, } from "./instances.js";
14
14
  import { gitCommit, gitDiff, gitFetch, gitPull, gitPush, gitStatus, startGitSync, } from "./git.js";
15
15
  import { getSettings, saveSettings } from "./settings.js";
16
+ import { addRepo, listRepos, openRepos, removeRepo, updateRepo } from "./repos.js";
17
+ import { searchAgents } from "./search.js";
16
18
  import { deleteRoutine, routineStatuses, runRoutineNow, saveRoutine, startRoutineScheduler, } from "./routines.js";
17
19
  /**
18
20
  * The inspector server: a plain node:http server with no dependencies.
@@ -214,6 +216,7 @@ async function handleApi(req, res, url) {
214
216
  projectRoot: project?.root ?? getProjectRoot(),
215
217
  projectRootLabel: tildify(project?.root ?? getProjectRoot()),
216
218
  git: (project?.config.git && project.config.git.enabled !== false) === true,
219
+ repos: (project?.config.repos && project.config.repos.enabled !== false) === true,
217
220
  switcher,
218
221
  });
219
222
  }
@@ -249,6 +252,50 @@ async function handleApi(req, res, url) {
249
252
  const result = await run();
250
253
  return json(res, result, result.ok ? 200 : 409);
251
254
  }
255
+ // GET /api/repos — every clone under the repos root, read live from git
256
+ if (seg.length === 2 && seg[1] === "repos" && method === "GET") {
257
+ return json(res, await listRepos());
258
+ }
259
+ // POST /api/repos {url, name?, branch?, depth?} — clone into the root
260
+ if (seg.length === 2 && seg[1] === "repos" && method === "POST") {
261
+ if ((await openRepos()).off)
262
+ return json(res, { error: "repositories are off" }, 409);
263
+ try {
264
+ const body = JSON.parse(await readBody(req));
265
+ const str = (v) => (typeof v === "string" ? v : undefined);
266
+ const depth = body.depth === undefined ? undefined : typeof body.depth === "number" ? body.depth : Number.NaN;
267
+ const repo = await addRepo({ url: str(body.url) ?? "", name: str(body.name), branch: str(body.branch), depth });
268
+ return json(res, repo, 201);
269
+ }
270
+ catch (err) {
271
+ return json(res, { error: err.message }, 400);
272
+ }
273
+ }
274
+ // POST /api/repos/<slug>/update — fetch, fast-forward when clean
275
+ if (seg.length === 4 && seg[1] === "repos" && seg[3] === "update" && method === "POST") {
276
+ try {
277
+ const result = await updateRepo(seg[2]);
278
+ return json(res, result, result.ok ? 200 : result.repo || result.off ? 409 : 404);
279
+ }
280
+ catch (err) {
281
+ return json(res, { error: err.message }, 400);
282
+ }
283
+ }
284
+ // DELETE /api/repos/<slug>[?force=1] — remove the clone; 409 while it holds unpushed or uncommitted work
285
+ if (seg.length === 3 && seg[1] === "repos" && method === "DELETE") {
286
+ try {
287
+ const force = ["1", "true"].includes(url.searchParams.get("force") ?? "");
288
+ const result = await removeRepo(seg[2], force);
289
+ return json(res, result, result.ok ? 200 : result.conflict || result.off ? 409 : 404);
290
+ }
291
+ catch (err) {
292
+ return json(res, { error: err.message }, 400);
293
+ }
294
+ }
295
+ // GET /api/search/agents — active agents of every reachable workspace (the ⌘K palette)
296
+ if (seg.length === 3 && seg[1] === "search" && seg[2] === "agents" && method === "GET") {
297
+ return json(res, await searchAgents());
298
+ }
252
299
  // GET /api/projects — every known workspace incl. this one, with state + counts (admin screen)
253
300
  if (seg.length === 2 && seg[1] === "projects" && method === "GET") {
254
301
  return json(res, await listWorkspacesDetailed());
@@ -296,7 +343,7 @@ async function handleApi(req, res, url) {
296
343
  if (seg.length === 2 && seg[1] === "settings" && method === "GET") {
297
344
  return json(res, await getSettings());
298
345
  }
299
- // PUT /api/settings — write the UI-editable subset (name, icon, switcher, git) back
346
+ // PUT /api/settings — write the UI-editable subset (name, icon, switcher, git, repos) back
300
347
  if (seg.length === 2 && seg[1] === "settings" && method === "PUT") {
301
348
  try {
302
349
  const input = JSON.parse(await readBody(req));
@@ -516,33 +563,33 @@ async function handleApi(req, res, url) {
516
563
  const skill = await getSkill(decodeURIComponent(seg[2]));
517
564
  return skill ? json(res, skill) : json(res, { error: "not found" }, 404);
518
565
  }
519
- // GET/POST /api/team — member list / create a member
520
- if (seg.length === 2 && seg[1] === "team") {
566
+ // GET/POST /api/agents — member list / create a member
567
+ if (seg.length === 2 && seg[1] === "agents") {
521
568
  if (method === "GET") {
522
- return json(res, { root: await teamRoot(), members: await listMembers() });
569
+ return json(res, { root: await agentsRoot(), agents: await listAgents() });
523
570
  }
524
571
  if (method === "POST") {
525
572
  try {
526
573
  const body = JSON.parse(await readBody(req));
527
- return json(res, await saveMember({ ...body, slug: undefined }));
574
+ return json(res, await saveAgent({ ...body, slug: undefined }));
528
575
  }
529
576
  catch (err) {
530
577
  return json(res, { error: err.message }, 400);
531
578
  }
532
579
  }
533
580
  }
534
- // POST /api/team/:slug/archive — archive/unarchive a member ({ archived: boolean })
535
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "archive" && method === "POST") {
581
+ // POST /api/agents/:slug/archive — archive/unarchive a member ({ archived: boolean })
582
+ if (seg.length === 4 && seg[1] === "agents" && seg[3] === "archive" && method === "POST") {
536
583
  try {
537
584
  const body = JSON.parse(await readBody(req));
538
- return json(res, await setMemberArchived(decodeURIComponent(seg[2]), body.archived === true));
585
+ return json(res, await setAgentArchived(decodeURIComponent(seg[2]), body.archived === true));
539
586
  }
540
587
  catch (err) {
541
588
  return json(res, { error: err.message }, 400);
542
589
  }
543
590
  }
544
- // GET/POST /api/team/:slug/skills — the agent's own skills / create or update one
545
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "skills") {
591
+ // GET/POST /api/agents/:slug/skills — the agent's own skills / create or update one
592
+ if (seg.length === 4 && seg[1] === "agents" && seg[3] === "skills") {
546
593
  const slug = decodeURIComponent(seg[2]);
547
594
  try {
548
595
  if (method === "GET")
@@ -556,8 +603,8 @@ async function handleApi(req, res, url) {
556
603
  return json(res, { error: err.message }, 400);
557
604
  }
558
605
  }
559
- // GET/DELETE /api/team/:slug/skills/:name — one agent skill with content / delete
560
- if (seg.length === 5 && seg[1] === "team" && seg[3] === "skills") {
606
+ // GET/DELETE /api/agents/:slug/skills/:name — one agent skill with content / delete
607
+ if (seg.length === 5 && seg[1] === "agents" && seg[3] === "skills") {
561
608
  const slug = decodeURIComponent(seg[2]);
562
609
  const name = decodeURIComponent(seg[4]);
563
610
  try {
@@ -576,8 +623,8 @@ async function handleApi(req, res, url) {
576
623
  return json(res, { error: err.message }, 400);
577
624
  }
578
625
  }
579
- // GET/POST /api/team/:slug/routines — list (with run state) / upsert
580
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "routines") {
626
+ // GET/POST /api/agents/:slug/routines — list (with run state) / upsert
627
+ if (seg.length === 4 && seg[1] === "agents" && seg[3] === "routines") {
581
628
  const slug = decodeURIComponent(seg[2]);
582
629
  try {
583
630
  if (method === "GET")
@@ -591,8 +638,8 @@ async function handleApi(req, res, url) {
591
638
  return json(res, { error: err.message }, 400);
592
639
  }
593
640
  }
594
- // DELETE /api/team/:slug/routines/:id | POST /api/team/:slug/routines/:id/run
595
- if (seg.length >= 5 && seg[1] === "team" && seg[3] === "routines") {
641
+ // DELETE /api/agents/:slug/routines/:id | POST /api/agents/:slug/routines/:id/run
642
+ if (seg.length >= 5 && seg[1] === "agents" && seg[3] === "routines") {
596
643
  const slug = decodeURIComponent(seg[2]);
597
644
  const id = decodeURIComponent(seg[4]);
598
645
  try {
@@ -608,20 +655,20 @@ async function handleApi(req, res, url) {
608
655
  return json(res, { error: err.message }, 400);
609
656
  }
610
657
  }
611
- // GET/PUT/DELETE /api/team/:slug
612
- if (seg.length === 3 && seg[1] === "team") {
658
+ // GET/PUT/DELETE /api/agents/:slug
659
+ if (seg.length === 3 && seg[1] === "agents") {
613
660
  const slug = decodeURIComponent(seg[2]);
614
661
  try {
615
662
  if (method === "GET") {
616
- const member = await getMember(slug);
617
- return member ? json(res, member) : json(res, { error: "not found" }, 404);
663
+ const agent = await getAgent(slug);
664
+ return agent ? json(res, agent) : json(res, { error: "not found" }, 404);
618
665
  }
619
666
  if (method === "PUT") {
620
667
  const body = JSON.parse(await readBody(req));
621
- return json(res, await saveMember({ ...body, slug }));
668
+ return json(res, await saveAgent({ ...body, slug }));
622
669
  }
623
670
  if (method === "DELETE") {
624
- await deleteMember(slug);
671
+ await deleteAgent(slug);
625
672
  return json(res, { ok: true });
626
673
  }
627
674
  }
@@ -643,13 +690,13 @@ async function handleApi(req, res, url) {
643
690
  }
644
691
  let agent = body.agent;
645
692
  let scope;
646
- if (body.scope?.kind === "team" && body.scope.member) {
647
- // Team sessions run on the member's configured harness.
648
- const member = await getMember(body.scope.member).catch(() => null);
649
- if (!member)
650
- return json(res, { error: "team agent not found" }, 404);
651
- scope = { kind: "team", member: member.slug };
652
- agent = member.harness;
693
+ if (body.scope?.kind === "agent" && body.scope.slug) {
694
+ // Agent sessions run on the agent's configured harness.
695
+ const def = await getAgent(body.scope.slug).catch(() => null);
696
+ if (!def)
697
+ return json(res, { error: "agent not found" }, 404);
698
+ scope = { kind: "agent", slug: def.slug };
699
+ agent = def.harness;
653
700
  }
654
701
  else if (body.scope?.kind === "run" && body.scope.runId) {
655
702
  scope = { kind: "run", runId: body.scope.runId };
@@ -1,7 +1,7 @@
1
1
  import { type ProjectConfig, type SwitcherEntry } from "../config.js";
2
2
  /**
3
3
  * Workspace settings: read kraftwerk.yml for the UI and write the
4
- * UI-editable subset (name, icon, switcher, git) back. Edits go through the
4
+ * UI-editable subset (name, icon, switcher, git, repos) back. Edits go through the
5
5
  * yaml Document API so comments and every other key survive untouched;
6
6
  * a missing kraftwerk.yml is created at the project root on first save.
7
7
  */
@@ -24,6 +24,12 @@ export interface SaveSettingsInput {
24
24
  switcher?: SwitcherEntry[];
25
25
  /** Omitted = untouched. See cleanGit for how the block is written. */
26
26
  git?: GitSettingsInput;
27
+ /** Omitted = untouched. Same rules as git: off with defaults = no block. */
28
+ repos?: ReposSettingsInput;
29
+ }
30
+ export interface ReposSettingsInput {
31
+ enabled: boolean;
32
+ root?: string;
27
33
  }
28
34
  /** The git form as the settings screen posts it. Every field but `enabled` may be blank. */
29
35
  export interface GitSettingsInput {
@@ -1,8 +1,9 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parseDocument } from "yaml";
4
- import { resolveProject } from "../config.js";
4
+ import { ignoreEntryFor, REPOS_DEFAULT_ROOT, resolveProject } from "../config.js";
5
5
  import { getProjectRoot } from "./context.js";
6
+ import { ensureReposRoot } from "./repos.js";
6
7
  const GIT_DEFAULTS = { remote: "origin", interval: 300, autosync: "pull" };
7
8
  export async function getSettings() {
8
9
  const project = await resolveProject(getProjectRoot());
@@ -69,6 +70,25 @@ function cleanGit(value) {
69
70
  return null;
70
71
  return { enabled: false, ...out };
71
72
  }
73
+ /** The repos block to write; null when the feature is off and nothing else is set. */
74
+ function cleanRepos(value, projectRoot) {
75
+ if (typeof value !== "object" || value === null)
76
+ throw new Error("repos must be an object");
77
+ if (typeof value.enabled !== "boolean")
78
+ throw new Error("repos.enabled must be true or false");
79
+ const out = {};
80
+ const root = typeof value.root === "string" ? value.root.trim().replace(/\/+$/, "") : "";
81
+ if (root && !ignoreEntryFor(projectRoot, root)) {
82
+ throw new Error('repos.root must be a directory inside the project (not ".", ".." or an absolute path outside it)');
83
+ }
84
+ if (root && root !== REPOS_DEFAULT_ROOT)
85
+ out.root = root;
86
+ if (value.enabled)
87
+ return out;
88
+ if (Object.keys(out).length === 0)
89
+ return null;
90
+ return { enabled: false, ...out };
91
+ }
72
92
  export async function saveSettings(input) {
73
93
  if (input.name !== undefined && typeof input.name !== "string")
74
94
  throw new Error("name must be a string");
@@ -99,6 +119,17 @@ export async function saveSettings(input) {
99
119
  else
100
120
  doc.set("git", git);
101
121
  }
122
+ if (input.repos !== undefined) {
123
+ const repos = cleanRepos(input.repos, project.root);
124
+ if (repos === null)
125
+ doc.delete("repos");
126
+ else
127
+ doc.set("repos", repos);
128
+ }
102
129
  await fs.writeFile(configPath, doc.toString());
130
+ // Turning repositories on prepares the folder so the first clone and the
131
+ // workspace git both find it in the right state.
132
+ if (input.repos?.enabled)
133
+ await ensureReposRoot().catch(() => { });
103
134
  return getSettings();
104
135
  }
@@ -13,12 +13,12 @@
13
13
  * skill. Skills are surfaced to every chat as context (and expanded on
14
14
  * /<name> invocation), so they work identically on claude, codex, and pi.
15
15
  *
16
- * On top of the shared roots, each team agent can carry private skills in
16
+ * On top of the shared roots, each agent can carry private skills in
17
17
  *
18
18
  * agents/<slug>/skills/<name>/SKILL.md (agent, git-tracked)
19
19
  *
20
20
  * Agent skills are visible only to that agent's sessions, always apply
21
- * (the member's allowlist narrows shared skills only), and shadow
21
+ * (the agent's allowlist narrows shared skills only), and shadow
22
22
  * same-named shared skills.
23
23
  */
24
24
  export interface SkillInfo {
@@ -31,7 +31,7 @@ export interface SkillInfo {
31
31
  dir: string;
32
32
  /** workspace = kraftwerk skills root; project = .claude/skills; user = ~/.claude/skills; agent = agents/<slug>/skills. */
33
33
  source: "workspace" | "project" | "user" | "agent";
34
- /** For source "agent": the owning member's slug. */
34
+ /** For source "agent": the owning agent's slug. */
35
35
  agent?: string;
36
36
  }
37
37
  export interface SkillDetail extends SkillInfo {
@@ -42,15 +42,15 @@ export interface SkillDetail extends SkillInfo {
42
42
  }
43
43
  /** Absolute workspace skills root (kraftwerk.yml `skills`, default skills/). */
44
44
  export declare function skillsRoot(): Promise<string>;
45
- /** The private skills root of one team agent: agents/<slug>/skills. */
45
+ /** The private skills root of one agent: agents/<slug>/skills. */
46
46
  export declare function agentSkillsRoot(slug: string): Promise<string>;
47
47
  /** One agent's private skills (agents/<slug>/skills/<name>/SKILL.md). */
48
48
  export declare function listAgentSkills(slug: string): Promise<SkillInfo[]>;
49
49
  /**
50
- * All discovered skills; agent (when a member is given) shadows workspace
50
+ * All discovered skills; agent (when an agent slug is given) shadows workspace
51
51
  * shadows project shadows user (global).
52
52
  */
53
- export declare function listSkills(member?: string): Promise<SkillInfo[]>;
53
+ export declare function listSkills(agent?: string): Promise<SkillInfo[]>;
54
54
  /** Create or update one agent skill; writes agents/<slug>/skills/<name>/SKILL.md. */
55
55
  export declare function saveAgentSkill(slug: string, name: string, content: string): Promise<SkillInfo & {
56
56
  content: string;
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { parse } from "yaml";
5
5
  import { resolveProject } from "../config.js";
6
6
  import { getProjectRoot } from "./context.js";
7
- import { safeMemberSlug, teamRoot } from "./team.js";
7
+ import { safeAgentSlug, agentsRoot } from "./agents.js";
8
8
  const NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
9
9
  function frontmatter(md) {
10
10
  const m = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(md);
@@ -58,9 +58,9 @@ async function scanRoot(root, source) {
58
58
  }));
59
59
  return skills.filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
60
60
  }
61
- /** The private skills root of one team agent: agents/<slug>/skills. */
61
+ /** The private skills root of one agent: agents/<slug>/skills. */
62
62
  export async function agentSkillsRoot(slug) {
63
- return path.join(await teamRoot(), safeMemberSlug(slug), "skills");
63
+ return path.join(await agentsRoot(), safeAgentSlug(slug), "skills");
64
64
  }
65
65
  /** One agent's private skills (agents/<slug>/skills/<name>/SKILL.md). */
66
66
  export async function listAgentSkills(slug) {
@@ -68,17 +68,17 @@ export async function listAgentSkills(slug) {
68
68
  return skills.map((s) => ({ ...s, agent: slug }));
69
69
  }
70
70
  /**
71
- * All discovered skills; agent (when a member is given) shadows workspace
71
+ * All discovered skills; agent (when an agent slug is given) shadows workspace
72
72
  * shadows project shadows user (global).
73
73
  */
74
- export async function listSkills(member) {
75
- const [agent, workspace, project, user] = await Promise.all([
76
- member ? listAgentSkills(member).catch(() => []) : [],
74
+ export async function listSkills(agent) {
75
+ const [own, workspace, project, user] = await Promise.all([
76
+ agent ? listAgentSkills(agent).catch(() => []) : [],
77
77
  skillsRoot().then((root) => scanRoot(root, "workspace")),
78
78
  scanRoot(path.join(getProjectRoot(), ".claude", "skills"), "project"),
79
79
  scanRoot(path.join(os.homedir(), ".claude", "skills"), "user"),
80
80
  ]);
81
- const out = [...agent];
81
+ const out = [...own];
82
82
  const seen = new Set(out.map((s) => s.name.toLowerCase()));
83
83
  for (const s of [...workspace, ...project, ...user]) {
84
84
  if (seen.has(s.name.toLowerCase()))