@clossys/launcher 0.1.2 → 0.1.5

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 (55) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +29 -6
  3. package/dist/check-cli.d.ts.map +1 -1
  4. package/dist/check-cli.js +4 -0
  5. package/dist/check-cli.js.map +1 -1
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +8 -5
  9. package/dist/cli.js.map +1 -1
  10. package/dist/core.d.ts +14 -4
  11. package/dist/core.d.ts.map +1 -1
  12. package/dist/core.js +185 -14
  13. package/dist/core.js.map +1 -1
  14. package/dist/host.d.ts.map +1 -1
  15. package/dist/host.js +16 -1
  16. package/dist/host.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/skills.d.ts +20 -0
  22. package/dist/skills.d.ts.map +1 -0
  23. package/dist/skills.js +106 -0
  24. package/dist/skills.js.map +1 -0
  25. package/dist/types.d.ts +21 -0
  26. package/dist/types.d.ts.map +1 -1
  27. package/package.json +3 -2
  28. package/skeleton/README.md +11 -21
  29. package/skill-catalogue/advisor/SKILL.md +50 -0
  30. package/skill-catalogue/architect/SKILL.md +42 -0
  31. package/skill-catalogue/bouncer/SKILL.md +43 -0
  32. package/skill-catalogue/builder/SKILL.md +42 -0
  33. package/skill-catalogue/butler/SKILL.md +43 -0
  34. package/skill-catalogue/controller/SKILL.md +42 -0
  35. package/skill-catalogue/designer/SKILL.md +50 -0
  36. package/skill-catalogue/giver/SKILL.md +43 -0
  37. package/skill-catalogue/influencer/SKILL.md +43 -0
  38. package/skill-catalogue/inspector/SKILL.md +42 -0
  39. package/skill-catalogue/integrator/SKILL.md +42 -0
  40. package/skill-catalogue/keeper/SKILL.md +43 -0
  41. package/skill-catalogue/launcher/SKILL.md +53 -0
  42. package/skill-catalogue/locksmith/SKILL.md +42 -0
  43. package/skill-catalogue/messenger/SKILL.md +43 -0
  44. package/skill-catalogue/observer/SKILL.md +42 -0
  45. package/skill-catalogue/publisher/SKILL.md +50 -0
  46. package/skill-catalogue/starter/SKILL.md +48 -0
  47. package/skill-catalogue/strategist/SKILL.md +51 -0
  48. package/skill-catalogue/writer/SKILL.md +51 -0
  49. package/src/check-cli.ts +4 -0
  50. package/src/cli.ts +8 -5
  51. package/src/core.ts +231 -14
  52. package/src/host.ts +14 -1
  53. package/src/index.ts +3 -0
  54. package/src/skills.ts +120 -0
  55. package/src/types.ts +16 -0
package/src/skills.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { dirname, join, resolve, sep } from "node:path";
2
+ import type { WorkspaceHost } from "./types.js";
3
+
4
+ export interface SkillCompositionResult {
5
+ readonly composed: readonly string[];
6
+ readonly skipped: readonly { readonly packageDir: string; readonly note: string }[];
7
+ }
8
+
9
+ export interface ComposeSkillsOptions {
10
+ readonly skillCatalogueRoot?: string;
11
+ readonly launcherPackageRoot: string;
12
+ }
13
+
14
+ const DISCOVERY_PREFIXES = [".cursor/skills", ".claude/skills"] as const;
15
+ const AGENTS_SKILLS_REL = join(".agents", "skills");
16
+
17
+ function containedPath(root: string, relativePath: string): string {
18
+ const resolved = resolve(root, relativePath);
19
+ const prefix = root.endsWith(sep) ? root : `${root}${sep}`;
20
+ if (resolved !== root && !resolved.startsWith(prefix)) {
21
+ throw new Error(`refusing to write outside the workspace directory: ${relativePath}`);
22
+ }
23
+ return resolved;
24
+ }
25
+
26
+ function skillSourceCandidates(packageDir: string, options: ComposeSkillsOptions): string[] {
27
+ const paths: string[] = [];
28
+ if (options.skillCatalogueRoot) {
29
+ paths.push(join(options.skillCatalogueRoot, packageDir, "SKILL.md"));
30
+ }
31
+ paths.push(join(options.launcherPackageRoot, "skill-catalogue", packageDir, "SKILL.md"));
32
+ paths.push(join(options.launcherPackageRoot, "..", packageDir, "skill", "SKILL.md"));
33
+ return paths;
34
+ }
35
+
36
+ function readSkillBody(host: WorkspaceHost, packageDir: string, options: ComposeSkillsOptions): string | null {
37
+ for (const path of skillSourceCandidates(packageDir, options)) {
38
+ const body = host.readText(path);
39
+ if (body !== null) return body;
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /** Package directory names that have a resolvable skill source in the catalogue or monorepo. */
45
+ export function listSkillPackageCandidates(host: WorkspaceHost, options: ComposeSkillsOptions): string[] {
46
+ const names = new Set<string>();
47
+ const considerRoot = (root: string | undefined): void => {
48
+ if (root === undefined || !host.isDirectory(root)) return;
49
+ for (const name of host.readDir(root)) {
50
+ if (name.startsWith(".")) continue;
51
+ const skillPath = join(root, name, "SKILL.md");
52
+ if (host.readText(skillPath) !== null) names.add(name);
53
+ }
54
+ };
55
+ considerRoot(options.skillCatalogueRoot);
56
+ considerRoot(join(options.launcherPackageRoot, "skill-catalogue"));
57
+ const packagesSibling = join(options.launcherPackageRoot, "..");
58
+ if (host.isDirectory(packagesSibling)) {
59
+ for (const name of host.readDir(packagesSibling)) {
60
+ if (name.startsWith(".")) continue;
61
+ const skillPath = join(packagesSibling, name, "skill", "SKILL.md");
62
+ if (host.readText(skillPath) !== null) names.add(name);
63
+ }
64
+ }
65
+ return [...names].sort((a, b) => a.localeCompare(b));
66
+ }
67
+
68
+ function writeAgentsSkill(host: WorkspaceHost, directory: string, packageDir: string, body: string): void {
69
+ const relativeDir = join(AGENTS_SKILLS_REL, `clossys-${packageDir}`);
70
+ const skillRel = join(relativeDir, "SKILL.md");
71
+ const target = containedPath(directory, skillRel);
72
+ host.mkdirp(dirname(target));
73
+ host.writeText(target, body.endsWith("\n") ? body : `${body}\n`);
74
+ }
75
+
76
+ function writeDiscoveryLink(host: WorkspaceHost, directory: string, packageDir: string, skillBody: string): void {
77
+ const agentsDirRel = join(AGENTS_SKILLS_REL, `clossys-${packageDir}`);
78
+ const linkTarget = join("..", "..", agentsDirRel);
79
+ for (const prefix of DISCOVERY_PREFIXES) {
80
+ const prefixPath = containedPath(directory, prefix);
81
+ // A directory-symlink onto `.agents/skills` already exposes every composed
82
+ // skill. Writing a nested `.claude/skills/clossys-<pkg>` link follows that
83
+ // symlink and replaces the just-written SKILL.md with a circular link.
84
+ if (host.isSymlink(prefixPath)) continue;
85
+ const linkRel = join(prefix, `clossys-${packageDir}`);
86
+ const linkPath = containedPath(directory, linkRel);
87
+ host.mkdirp(dirname(linkPath));
88
+ try {
89
+ host.symlink(linkTarget, linkPath);
90
+ } catch {
91
+ const fallbackDir = containedPath(directory, join(linkRel, "SKILL.md"));
92
+ host.mkdirp(dirname(fallbackDir));
93
+ host.writeText(fallbackDir, skillBody.endsWith("\n") ? skillBody : `${skillBody}\n`);
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Writes `.agents/skills/clossys-<pkg>/SKILL.md` and host discovery links for every
100
+ * catalogue package. Missing sources are skipped with notes; the hub apply continues.
101
+ */
102
+ export function composeSkills(
103
+ host: WorkspaceHost,
104
+ directory: string,
105
+ options: ComposeSkillsOptions,
106
+ ): SkillCompositionResult {
107
+ const composed: string[] = [];
108
+ const skipped: { packageDir: string; note: string }[] = [];
109
+ for (const packageDir of listSkillPackageCandidates(host, options)) {
110
+ const body = readSkillBody(host, packageDir, options);
111
+ if (body === null) {
112
+ skipped.push({ packageDir, note: "skill source missing at apply time" });
113
+ continue;
114
+ }
115
+ writeAgentsSkill(host, directory, packageDir, body);
116
+ writeDiscoveryLink(host, directory, packageDir, body);
117
+ composed.push(packageDir);
118
+ }
119
+ return { composed, skipped };
120
+ }
package/src/types.ts CHANGED
@@ -16,9 +16,13 @@ export interface WorkspaceHost {
16
16
  now(): string;
17
17
  exists(path: string): boolean;
18
18
  isDirectory(path: string): boolean;
19
+ /** True when path exists and is a symlink (lstat; does not follow). Missing path is false. */
20
+ isSymlink(path: string): boolean;
19
21
  readText(path: string): string | null;
20
22
  writeText(path: string, contents: string): void;
21
23
  mkdirp(path: string): void;
24
+ /** Creates a relative symlink at linkPath pointing at relativeTarget (directory link). */
25
+ symlink(relativeTarget: string, linkPath: string): void;
22
26
  readDir(path: string): string[];
23
27
  run(command: string, args: readonly string[], options?: { cwd?: string }): CommandResult;
24
28
  prompt(message: string, choices: readonly string[]): string | null;
@@ -80,6 +84,18 @@ export interface HubHealthReport {
80
84
  readonly extraClossys: readonly string[];
81
85
  readonly pinFindings: readonly PinFinding[];
82
86
  readonly degraded: boolean;
87
+ readonly skillComposition?: {
88
+ readonly composed: readonly string[];
89
+ readonly skipped: readonly { readonly packageDir: string; readonly note: string }[];
90
+ readonly rosterTargets?: readonly string[];
91
+ readonly rosterSkipped?: readonly { readonly inventoryId: string; readonly note: string }[];
92
+ };
93
+ }
94
+
95
+ /** Optional paths for skill composition during apply. */
96
+ export interface ApplyWorkspaceOptions {
97
+ readonly skillCatalogueRoot?: string;
98
+ readonly launcherPackageRoot?: string;
83
99
  }
84
100
 
85
101
  export interface CwdObservation {