@senior-skills/registry 0.0.0-stage → 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.
@@ -0,0 +1,24 @@
1
+ import { type Clock, type CommandRunner, type FileSystem, type Hasher, type HttpClient, type Logger, type PackageExtractor, type RegistryConfig, type RegistryFactory, type SkillRegistry } from '@senior-skills/core';
2
+ export interface DefaultRegistryFactoryOptions {
3
+ readonly fs: FileSystem;
4
+ readonly hasher: Hasher;
5
+ readonly http: HttpClient;
6
+ readonly commands: CommandRunner;
7
+ readonly clock: Clock;
8
+ readonly logger: Logger;
9
+ readonly extractor: PackageExtractor;
10
+ readonly cacheDir: string;
11
+ readonly ttlSeconds?: number;
12
+ }
13
+ /**
14
+ * Builds a driver for a configured registry.
15
+ *
16
+ * Exists so the CLI's composition root can stay free of registry-kind imports and so adding a
17
+ * fourth kind is a change here rather than a change in every command.
18
+ */
19
+ export declare class DefaultRegistryFactory implements RegistryFactory {
20
+ private readonly options;
21
+ constructor(options: DefaultRegistryFactoryOptions);
22
+ create(config: RegistryConfig): SkillRegistry;
23
+ }
24
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;gBAE5C,OAAO,EAAE,6BAA6B;IAIlD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa;CAmD9C"}
@@ -0,0 +1,69 @@
1
+ import { AgentSkillsError, ErrorCode, } from '@senior-skills/core';
2
+ import { GitRegistry } from "./git-registry.js";
3
+ import { HttpRegistry } from "./http-registry.js";
4
+ import { LocalRegistry } from "./local-registry.js";
5
+ /**
6
+ * Builds a driver for a configured registry.
7
+ *
8
+ * Exists so the CLI's composition root can stay free of registry-kind imports and so adding a
9
+ * fourth kind is a change here rather than a change in every command.
10
+ */
11
+ export class DefaultRegistryFactory {
12
+ options;
13
+ constructor(options) {
14
+ this.options = options;
15
+ }
16
+ create(config) {
17
+ const { fs, hasher, http, commands, clock, logger, extractor, cacheDir } = this.options;
18
+ const ttlSeconds = this.options.ttlSeconds ?? 3600;
19
+ switch (config.kind) {
20
+ case 'local':
21
+ return new LocalRegistry({
22
+ name: config.name,
23
+ root: toLocalPath(fs, config.url),
24
+ fs,
25
+ hasher,
26
+ trusted: config.trusted,
27
+ });
28
+ case 'git':
29
+ return new GitRegistry({
30
+ name: config.name,
31
+ url: config.url,
32
+ ...(config.ref === undefined ? {} : { ref: config.ref }),
33
+ cacheDir,
34
+ fs,
35
+ hasher,
36
+ commands,
37
+ clock,
38
+ logger,
39
+ trusted: config.trusted,
40
+ ttlSeconds,
41
+ });
42
+ case 'http':
43
+ return new HttpRegistry({
44
+ name: config.name,
45
+ url: config.url,
46
+ http,
47
+ fs,
48
+ hasher,
49
+ clock,
50
+ extractor,
51
+ cacheDir,
52
+ trusted: config.trusted,
53
+ ttlSeconds,
54
+ });
55
+ default:
56
+ throw new AgentSkillsError(ErrorCode.USAGE, `Unknown registry kind "${String(config.kind)}" for "${config.name}"`, { hints: ['Supported kinds: local, git, http'], data: { config } });
57
+ }
58
+ }
59
+ }
60
+ function toLocalPath(fs, url) {
61
+ if (!url.startsWith('file://'))
62
+ return fs.resolve(url);
63
+ // `file:///C:/x` and `file:///home/x` both need the leading slash handling that
64
+ // `new URL().pathname` gets wrong on Windows.
65
+ const parsed = new URL(url);
66
+ const decoded = decodeURIComponent(parsed.pathname);
67
+ return fs.resolve(/^\/[a-zA-Z]:/.test(decoded) ? decoded.slice(1) : decoded);
68
+ }
69
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,GAWV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAcpD;;;;;GAKG;AACH,MAAM,OAAO,sBAAsB;IAChB,OAAO,CAAgC;IAExD,YAAY,OAAsC;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,MAAsB;QAC3B,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACxF,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;QAEnD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,OAAO;gBACV,OAAO,IAAI,aAAa,CAAC;oBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;oBACjC,EAAE;oBACF,MAAM;oBACN,OAAO,EAAE,MAAM,CAAC,OAAO;iBACxB,CAAC,CAAC;YAEL,KAAK,KAAK;gBACR,OAAO,IAAI,WAAW,CAAC;oBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;oBACxD,QAAQ;oBACR,EAAE;oBACF,MAAM;oBACN,QAAQ;oBACR,KAAK;oBACL,MAAM;oBACN,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,UAAU;iBACX,CAAC,CAAC;YAEL,KAAK,MAAM;gBACT,OAAO,IAAI,YAAY,CAAC;oBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,IAAI;oBACJ,EAAE;oBACF,MAAM;oBACN,KAAK;oBACL,SAAS;oBACT,QAAQ;oBACR,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,UAAU;iBACX,CAAC,CAAC;YAEL;gBACE,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,KAAK,EACf,0BAA0B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,IAAI,GAAG,EACrE,EAAE,KAAK,EAAE,CAAC,mCAAmC,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CACnE,CAAC;QACN,CAAC;IACH,CAAC;CACF;AAED,SAAS,WAAW,CAAC,EAAc,EAAE,GAAW;IAC9C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEvD,gFAAgF;IAChF,8CAA8C;IAC9C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACpD,OAAO,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type FederatedRegistry, type FetchedPackage, type IndexVersionEntry, type RefreshOptions, type RegistryKind, type SearchQuery, type SemanticVersion, type SkillManifest, type SkillRegistry, type SkillSummary } from '@senior-skills/core';
2
+ /**
3
+ * An ordered set of registries presented as one.
4
+ *
5
+ * Precedence is **name-scoped**: the first registry publishing *any* version of a name owns
6
+ * that name outright, and a later registry cannot inject a higher version of it. That is the
7
+ * npm `.npmrc` model rather than "highest version anywhere wins", and the difference is a
8
+ * security property — it is what closes the dependency-confusion hole that has repeatedly
9
+ * been used against npm and PyPI.
10
+ *
11
+ * A caller that genuinely wants a specific source qualifies the reference
12
+ * (`company:java-performance`), which bypasses precedence entirely.
13
+ */
14
+ export declare class RegistryFederation implements FederatedRegistry {
15
+ readonly name = "<federation>";
16
+ readonly kind: RegistryKind;
17
+ readonly trusted: boolean;
18
+ private readonly registries;
19
+ private readonly owners;
20
+ constructor(registries: readonly SkillRegistry[]);
21
+ members(): readonly SkillRegistry[];
22
+ named(name: string): SkillRegistry | undefined;
23
+ ownerOf(name: string): Promise<string | undefined>;
24
+ refresh(options?: RefreshOptions): Promise<void>;
25
+ /**
26
+ * Aggregates across every registry, labelling shadowed duplicates rather than dropping them.
27
+ * A user searching for a name their company registry overrides should be able to see that.
28
+ */
29
+ search(query: SearchQuery): Promise<readonly SkillSummary[]>;
30
+ has(name: string): Promise<boolean>;
31
+ versions(name: string): Promise<readonly IndexVersionEntry[]>;
32
+ manifest(name: string, version: SemanticVersion): Promise<SkillManifest>;
33
+ fetch(name: string, version: SemanticVersion): Promise<FetchedPackage>;
34
+ private require;
35
+ }
36
+ //# sourceMappingURL=federation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAmB,YAAW,iBAAiB;IAC1D,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAW;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAEpD,UAAU,EAAE,SAAS,aAAa,EAAE;IAKhD,OAAO,IAAI,SAAS,aAAa,EAAE;IAInC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAkBlD,OAAO,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBtD;;;OAGG;IACG,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAoB5D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAInC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAK7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;IAKxE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;YAK9D,OAAO;CAwBtB"}
@@ -0,0 +1,121 @@
1
+ import { AgentSkillsError, ErrorCode, } from '@senior-skills/core';
2
+ /**
3
+ * An ordered set of registries presented as one.
4
+ *
5
+ * Precedence is **name-scoped**: the first registry publishing *any* version of a name owns
6
+ * that name outright, and a later registry cannot inject a higher version of it. That is the
7
+ * npm `.npmrc` model rather than "highest version anywhere wins", and the difference is a
8
+ * security property — it is what closes the dependency-confusion hole that has repeatedly
9
+ * been used against npm and PyPI.
10
+ *
11
+ * A caller that genuinely wants a specific source qualifies the reference
12
+ * (`company:java-performance`), which bypasses precedence entirely.
13
+ */
14
+ export class RegistryFederation {
15
+ name = '<federation>';
16
+ kind = 'local';
17
+ trusted;
18
+ registries;
19
+ owners = new Map();
20
+ constructor(registries) {
21
+ this.registries = registries;
22
+ this.trusted = registries.every((registry) => registry.trusted);
23
+ }
24
+ members() {
25
+ return this.registries;
26
+ }
27
+ named(name) {
28
+ return this.registries.find((registry) => registry.name === name);
29
+ }
30
+ async ownerOf(name) {
31
+ if (this.owners.has(name))
32
+ return this.owners.get(name);
33
+ for (const registry of this.registries) {
34
+ // One unreachable registry must not hide a name a lower-precedence one can serve, but it
35
+ // also must not silently promote the next registry to owner — so failures are skipped
36
+ // here and surfaced by `doctor`, which is the command whose job that is.
37
+ const has = await registry.has(name).catch(() => false);
38
+ if (has) {
39
+ this.owners.set(name, registry.name);
40
+ return registry.name;
41
+ }
42
+ }
43
+ this.owners.set(name, undefined);
44
+ return undefined;
45
+ }
46
+ async refresh(options) {
47
+ this.owners.clear();
48
+ const failures = [];
49
+ for (const registry of this.registries) {
50
+ try {
51
+ await registry.refresh(options);
52
+ }
53
+ catch (error) {
54
+ failures.push(error instanceof AgentSkillsError
55
+ ? error
56
+ : new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, String(error)));
57
+ }
58
+ }
59
+ // Every registry failed: this is not a partial outage, it is "no registries work".
60
+ if (failures.length > 0 && failures.length === this.registries.length) {
61
+ throw new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, 'No registry could be reached', {
62
+ details: failures.map((failure) => ` ${failure.message}`),
63
+ hints: ['senior-skills doctor to diagnose', 'senior-skills registry list'],
64
+ });
65
+ }
66
+ }
67
+ /**
68
+ * Aggregates across every registry, labelling shadowed duplicates rather than dropping them.
69
+ * A user searching for a name their company registry overrides should be able to see that.
70
+ */
71
+ async search(query) {
72
+ const seen = new Map();
73
+ const results = [];
74
+ for (const registry of this.registries) {
75
+ const found = await registry.search(query).catch(() => []);
76
+ for (const summary of found) {
77
+ const owner = seen.get(summary.name);
78
+ if (owner === undefined) {
79
+ seen.set(summary.name, registry.name);
80
+ results.push(summary);
81
+ }
82
+ else {
83
+ results.push({ ...summary, shadowedBy: owner });
84
+ }
85
+ }
86
+ }
87
+ return query.limit === undefined ? results : results.slice(0, query.limit);
88
+ }
89
+ async has(name) {
90
+ return (await this.ownerOf(name)) !== undefined;
91
+ }
92
+ async versions(name) {
93
+ const registry = await this.require(name);
94
+ return registry.versions(name);
95
+ }
96
+ async manifest(name, version) {
97
+ const registry = await this.require(name);
98
+ return registry.manifest(name, version);
99
+ }
100
+ async fetch(name, version) {
101
+ const registry = await this.require(name);
102
+ return registry.fetch(name, version);
103
+ }
104
+ async require(name) {
105
+ const owner = await this.ownerOf(name);
106
+ const registry = owner === undefined ? undefined : this.named(owner);
107
+ if (registry === undefined) {
108
+ throw new AgentSkillsError(ErrorCode.SKILL_NOT_FOUND, `Skill "${name}" was not found in any configured registry`, {
109
+ details: [
110
+ '',
111
+ 'Searched, in precedence order:',
112
+ ...this.registries.map((member, index) => ` ${index + 1}. ${member.name} (${member.kind})`),
113
+ ],
114
+ hints: [`senior-skills search ${name}`, 'senior-skills registry list'],
115
+ data: { name, registries: this.registries.map((member) => member.name) },
116
+ });
117
+ }
118
+ return registry;
119
+ }
120
+ }
121
+ //# sourceMappingURL=federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.js","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { type Clock, type CommandRunner, type FileSystem, type Hasher, type Logger, type RegistryKind } from '@senior-skills/core';
2
+ import { LocalRegistry } from './local-registry.ts';
3
+ export interface GitRegistryOptions {
4
+ readonly name: string;
5
+ /** Clone URL, optionally with a `#ref` suffix. */
6
+ readonly url: string;
7
+ readonly ref?: string;
8
+ readonly cacheDir: string;
9
+ readonly fs: FileSystem;
10
+ readonly hasher: Hasher;
11
+ readonly commands: CommandRunner;
12
+ readonly clock: Clock;
13
+ readonly logger: Logger;
14
+ readonly trusted?: boolean;
15
+ readonly ttlSeconds?: number;
16
+ }
17
+ /**
18
+ * A registry backed by a git repository.
19
+ *
20
+ * This is the v1 default because it requires no infrastructure: a public repository with
21
+ * `registry/skills.yaml` and a `skills/` directory *is* a registry, and its write path is a
22
+ * pull request — which is a better review surface for content that becomes agent instructions
23
+ * than an upload API would be.
24
+ *
25
+ * The clone is shallow (`--depth 1`), stored under the CLI cache, and refreshed on a TTL.
26
+ * Once it is on disk the behaviour is exactly {@link LocalRegistry}'s, which is why this class
27
+ * only has to deal with fetching.
28
+ */
29
+ export declare class GitRegistry extends LocalRegistry {
30
+ readonly kind: RegistryKind;
31
+ private readonly url;
32
+ private readonly ref;
33
+ private readonly cloneDir;
34
+ private readonly commands;
35
+ private readonly clock;
36
+ private readonly logger;
37
+ private readonly ttlSeconds;
38
+ private refreshed;
39
+ constructor(options: GitRegistryOptions);
40
+ refresh(): Promise<void>;
41
+ protected ensureAvailable(): Promise<string>;
42
+ protected resolvedLocation(directory: string): string;
43
+ private sync;
44
+ private isStale;
45
+ private assertGitAvailable;
46
+ private git;
47
+ }
48
+ /** `https://host/repo.git#main` → `{ url, ref }`. An explicit `ref` option wins. */
49
+ export declare function splitRef(url: string, ref?: string): {
50
+ url: string;
51
+ ref: string | undefined;
52
+ };
53
+ /**
54
+ * Cache directory name for a clone. Readable prefix plus a hash, so a user can tell which
55
+ * cache belongs to which registry without the path depending on unsanitised URL characters.
56
+ */
57
+ export declare function cacheKey(url: string, ref: string | undefined): string;
58
+ //# sourceMappingURL=git-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-registry.d.ts","sourceRoot":"","sources":["../src/git-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAID;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,aAAa;IAC5C,SAAkB,IAAI,EAAE,YAAY,CAAS;IAE7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,kBAAkB;IAsBxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;cAMd,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;cAKxC,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;YAKhD,IAAI;YAgDJ,OAAO;YAYP,kBAAkB;YAgBlB,GAAG;CAwBlB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAK5F;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAcrE"}
@@ -0,0 +1,162 @@
1
+ import { AgentSkillsError, ErrorCode, } from '@senior-skills/core';
2
+ import { LocalRegistry } from "./local-registry.js";
3
+ const CACHE_MARKER = '.senior-skills-fetched-at';
4
+ /**
5
+ * A registry backed by a git repository.
6
+ *
7
+ * This is the v1 default because it requires no infrastructure: a public repository with
8
+ * `registry/skills.yaml` and a `skills/` directory *is* a registry, and its write path is a
9
+ * pull request — which is a better review surface for content that becomes agent instructions
10
+ * than an upload API would be.
11
+ *
12
+ * The clone is shallow (`--depth 1`), stored under the CLI cache, and refreshed on a TTL.
13
+ * Once it is on disk the behaviour is exactly {@link LocalRegistry}'s, which is why this class
14
+ * only has to deal with fetching.
15
+ */
16
+ export class GitRegistry extends LocalRegistry {
17
+ kind = 'git';
18
+ url;
19
+ ref;
20
+ cloneDir;
21
+ commands;
22
+ clock;
23
+ logger;
24
+ ttlSeconds;
25
+ refreshed = false;
26
+ constructor(options) {
27
+ const { url, ref } = splitRef(options.url, options.ref);
28
+ const cloneDir = options.fs.join(options.cacheDir, 'git', cacheKey(url, ref));
29
+ super({
30
+ name: options.name,
31
+ root: cloneDir,
32
+ fs: options.fs,
33
+ hasher: options.hasher,
34
+ trusted: options.trusted ?? true,
35
+ kind: 'git',
36
+ });
37
+ this.url = url;
38
+ this.ref = ref;
39
+ this.cloneDir = cloneDir;
40
+ this.commands = options.commands;
41
+ this.clock = options.clock;
42
+ this.logger = options.logger;
43
+ this.ttlSeconds = options.ttlSeconds ?? 3600;
44
+ }
45
+ async refresh() {
46
+ this.refreshed = false;
47
+ await this.sync(true);
48
+ await super.refresh();
49
+ }
50
+ async ensureAvailable() {
51
+ await this.sync(false);
52
+ return this.cloneDir;
53
+ }
54
+ resolvedLocation(directory) {
55
+ const relative = this.fs.relative(this.cloneDir, directory).replace(/\\/g, '/');
56
+ return `${this.url}${this.ref === undefined ? '' : `#${this.ref}`}:${relative}`;
57
+ }
58
+ async sync(force) {
59
+ if (this.refreshed && !force)
60
+ return;
61
+ const cloned = await this.fs.exists(this.fs.join(this.cloneDir, '.git'));
62
+ if (cloned && !force && !(await this.isStale())) {
63
+ this.refreshed = true;
64
+ return;
65
+ }
66
+ await this.assertGitAvailable();
67
+ if (!cloned) {
68
+ // A stale partial clone from an interrupted run would make `git fetch` fail confusingly.
69
+ await this.fs.remove(this.cloneDir).catch(() => undefined);
70
+ await this.fs.mkdirp(this.fs.dirname(this.cloneDir));
71
+ this.logger.debug(`Cloning registry "${this.name}" from ${this.url}`);
72
+ await this.git([
73
+ 'clone',
74
+ '--depth',
75
+ '1',
76
+ ...(this.ref === undefined ? [] : ['--branch', this.ref]),
77
+ '--',
78
+ this.url,
79
+ this.cloneDir,
80
+ ], this.fs.dirname(this.cloneDir));
81
+ }
82
+ else {
83
+ this.logger.debug(`Refreshing registry "${this.name}"`);
84
+ await this.git(['fetch', '--depth', '1', 'origin', ...(this.ref === undefined ? [] : [this.ref])], this.cloneDir);
85
+ await this.git(['reset', '--hard', this.ref === undefined ? 'FETCH_HEAD' : `origin/${this.ref}`], this.cloneDir);
86
+ }
87
+ await this.fs.writeFile(this.fs.join(this.cloneDir, CACHE_MARKER), this.clock.now().toISOString());
88
+ this.refreshed = true;
89
+ }
90
+ async isStale() {
91
+ const marker = this.fs.join(this.cloneDir, CACHE_MARKER);
92
+ if (!(await this.fs.exists(marker)))
93
+ return true;
94
+ try {
95
+ const fetchedAt = Date.parse(await this.fs.readTextFile(marker));
96
+ if (Number.isNaN(fetchedAt))
97
+ return true;
98
+ return this.clock.now().getTime() - fetchedAt > this.ttlSeconds * 1000;
99
+ }
100
+ catch {
101
+ return true;
102
+ }
103
+ }
104
+ async assertGitAvailable() {
105
+ if ((await this.commands.which('git')) !== undefined)
106
+ return;
107
+ throw new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, `Registry "${this.name}" needs git`, {
108
+ details: [`git was not found on PATH, and "${this.name}" is a git registry.`],
109
+ hints: [
110
+ 'Install git: https://git-scm.com/downloads',
111
+ 'Or configure an http registry instead: senior-skills registry add <name> <https url>',
112
+ ],
113
+ data: { registry: this.name, url: this.url },
114
+ });
115
+ }
116
+ async git(args, cwd) {
117
+ // core.longpaths: on Windows, git refuses paths past MAX_PATH (260) by default, and a
118
+ // registry checkout under ~/.senior-skills/cache plus a skill's reference path can exceed
119
+ // it — the clone then half-succeeds and every later sync fails. The setting is read only
120
+ // by Git for Windows; elsewhere it is inert.
121
+ const result = await this.commands.run('git', ['-c', 'core.longpaths=true', ...args], {
122
+ cwd,
123
+ timeoutMs: 120_000,
124
+ });
125
+ if (result.code === 0)
126
+ return;
127
+ throw new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, `Could not reach registry "${this.name}"`, {
128
+ details: [`git ${args.join(' ')}`, '', result.stderr.trim() || result.stdout.trim()],
129
+ hints: [
130
+ 'Check the URL and your network access',
131
+ 'For a private repository, make sure your git credentials are configured',
132
+ ],
133
+ data: { registry: this.name, url: this.url, exitCode: result.code },
134
+ });
135
+ }
136
+ }
137
+ /** `https://host/repo.git#main` → `{ url, ref }`. An explicit `ref` option wins. */
138
+ export function splitRef(url, ref) {
139
+ const hash = url.lastIndexOf('#');
140
+ if (hash === -1)
141
+ return { url, ref };
142
+ const fromUrl = url.slice(hash + 1);
143
+ return { url: url.slice(0, hash), ref: ref ?? (fromUrl === '' ? undefined : fromUrl) };
144
+ }
145
+ /**
146
+ * Cache directory name for a clone. Readable prefix plus a hash, so a user can tell which
147
+ * cache belongs to which registry without the path depending on unsanitised URL characters.
148
+ */
149
+ export function cacheKey(url, ref) {
150
+ const slug = url
151
+ .replace(/^https?:\/\//, '')
152
+ .replace(/\.git$/, '')
153
+ .replace(/[^a-zA-Z0-9]+/g, '-')
154
+ .replace(/^-|-$/g, '')
155
+ .slice(0, 48);
156
+ let hash = 5381;
157
+ for (const character of `${url}#${ref ?? ''}`) {
158
+ hash = ((hash << 5) + hash + character.charCodeAt(0)) >>> 0;
159
+ }
160
+ return `${slug}-${hash.toString(36)}`;
161
+ }
162
+ //# sourceMappingURL=git-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-registry.js","sourceRoot":"","sources":["../src/git-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,GAOV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiBpD,MAAM,YAAY,GAAG,2BAA2B,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAY,SAAQ,aAAa;IAC1B,IAAI,GAAiB,KAAK,CAAC;IAE5B,GAAG,CAAS;IACZ,GAAG,CAAqB;IACxB,QAAQ,CAAS;IACjB,QAAQ,CAAgB;IACxB,KAAK,CAAQ;IACb,MAAM,CAAS;IACf,UAAU,CAAS;IAC5B,SAAS,GAAG,KAAK,CAAC;IAE1B,YAAY,OAA2B;QACrC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAE9E,KAAK,CAAC;YACJ,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,QAAQ;YACd,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;YAChC,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;IAC/C,CAAC;IAEQ,KAAK,CAAC,OAAO;QACpB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEkB,KAAK,CAAC,eAAe;QACtC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAEkB,gBAAgB,CAAC,SAAiB;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChF,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;IAClF,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,KAAc;QAC/B,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK;YAAE,OAAO;QAErC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAEzE,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,OAAO;QACT,CAAC;QAED,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAEhC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,yFAAyF;YACzF,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC3D,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACtE,MAAM,IAAI,CAAC,GAAG,CACZ;gBACE,OAAO;gBACP,SAAS;gBACT,GAAG;gBACH,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzD,IAAI;gBACJ,IAAI,CAAC,GAAG;gBACR,IAAI,CAAC,QAAQ;aACd,EACD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAC/B,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YACxD,MAAM,IAAI,CAAC,GAAG,CACZ,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAClF,IAAI,CAAC,QAAQ,CACd,CAAC;YACF,MAAM,IAAI,CAAC,GAAG,CACZ,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,EACjF,IAAI,CAAC,QAAQ,CACd,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CACrB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EACzC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAC/B,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YACjE,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;gBAAE,OAAO,IAAI,CAAC;YACzC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,kBAAkB;QAC9B,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO;QAC7D,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,oBAAoB,EAC9B,aAAa,IAAI,CAAC,IAAI,aAAa,EACnC;YACE,OAAO,EAAE,CAAC,mCAAmC,IAAI,CAAC,IAAI,sBAAsB,CAAC;YAC7E,KAAK,EAAE;gBACL,4CAA4C;gBAC5C,sFAAsF;aACvF;YACD,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;SAC7C,CACF,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,GAAG,CAAC,IAAuB,EAAE,GAAW;QACpD,sFAAsF;QACtF,0FAA0F;QAC1F,yFAAyF;QACzF,6CAA6C;QAC7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC,EAAE;YACpF,GAAG;YACH,SAAS,EAAE,OAAO;SACnB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAE9B,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,oBAAoB,EAC9B,6BAA6B,IAAI,CAAC,IAAI,GAAG,EACzC;YACE,OAAO,EAAE,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACpF,KAAK,EAAE;gBACL,uCAAuC;gBACvC,yEAAyE;aAC1E;YACD,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;SACpE,CACF,CAAC;IACJ,CAAC;CACF;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,GAAY;IAChD,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;AACzF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,GAAuB;IAC3D,MAAM,IAAI,GAAG,GAAG;SACb,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;SAC9B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEhB,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,KAAK,MAAM,SAAS,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC;QAC9C,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { type Clock, type FetchedPackage, type FileSystem, type Hasher, type HttpClient, type IndexVersionEntry, type PackageExtractor, type RegistryKind, type SearchQuery, type SemanticVersion, type SkillManifest, type SkillRegistry, type SkillSummary } from '@senior-skills/core';
2
+ export interface HttpRegistryOptions {
3
+ readonly name: string;
4
+ /** URL of the index document, e.g. `https://skills.example.com/index.json`. */
5
+ readonly url: string;
6
+ readonly http: HttpClient;
7
+ readonly fs: FileSystem;
8
+ readonly hasher: Hasher;
9
+ readonly clock: Clock;
10
+ readonly extractor: PackageExtractor;
11
+ readonly cacheDir: string;
12
+ readonly trusted?: boolean;
13
+ readonly ttlSeconds?: number;
14
+ }
15
+ /**
16
+ * A registry served over HTTPS: an index document plus `.tar.gz` payloads.
17
+ *
18
+ * Exists so a static bucket — or a future hosted service — can speak the identical protocol
19
+ * with no client change. Transport security is enforced by the HTTP client (plaintext is
20
+ * refused outside loopback), and payload safety by the shared {@link SafeExtractor}, so this
21
+ * class is only responsible for caching and for turning HTTP status codes into good errors.
22
+ */
23
+ export declare class HttpRegistry implements SkillRegistry {
24
+ readonly name: string;
25
+ readonly kind: RegistryKind;
26
+ readonly trusted: boolean;
27
+ private readonly url;
28
+ private readonly http;
29
+ private readonly fs;
30
+ private readonly hasher;
31
+ private readonly clock;
32
+ private readonly extractor;
33
+ private readonly cacheDir;
34
+ private readonly ttlSeconds;
35
+ private index;
36
+ private fetchedAt;
37
+ constructor(options: HttpRegistryOptions);
38
+ refresh(): Promise<void>;
39
+ search(query: SearchQuery): Promise<readonly SkillSummary[]>;
40
+ has(name: string): Promise<boolean>;
41
+ versions(name: string): Promise<readonly IndexVersionEntry[]>;
42
+ manifest(name: string, version: SemanticVersion): Promise<SkillManifest>;
43
+ fetch(name: string, version: SemanticVersion): Promise<FetchedPackage>;
44
+ private download;
45
+ private loadIndex;
46
+ private cachePath;
47
+ private writeCache;
48
+ private readCache;
49
+ }
50
+ //# sourceMappingURL=http-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-registry.d.ts","sourceRoot":"","sources":["../src/http-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAErB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,qBAAa,YAAa,YAAW,aAAa;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAU;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,SAAS,CAAK;gBAEV,OAAO,EAAE,mBAAmB;IAalC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAMxB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAU5D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAInC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAK7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;IAIxE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;YA6C9D,QAAQ;YAoBR,SAAS;IA6BvB,OAAO,CAAC,SAAS;YAIH,UAAU;YAIV,SAAS;CAKxB"}
@@ -0,0 +1,137 @@
1
+ import { AgentSkillsError, ErrorCode, computePackageIntegrity, parseRegistryIndex, } from '@senior-skills/core';
2
+ import { matches, toSummaries } from "./search.js";
3
+ /**
4
+ * A registry served over HTTPS: an index document plus `.tar.gz` payloads.
5
+ *
6
+ * Exists so a static bucket — or a future hosted service — can speak the identical protocol
7
+ * with no client change. Transport security is enforced by the HTTP client (plaintext is
8
+ * refused outside loopback), and payload safety by the shared {@link SafeExtractor}, so this
9
+ * class is only responsible for caching and for turning HTTP status codes into good errors.
10
+ */
11
+ export class HttpRegistry {
12
+ name;
13
+ kind = 'http';
14
+ trusted;
15
+ url;
16
+ http;
17
+ fs;
18
+ hasher;
19
+ clock;
20
+ extractor;
21
+ cacheDir;
22
+ ttlSeconds;
23
+ index;
24
+ fetchedAt = 0;
25
+ constructor(options) {
26
+ this.name = options.name;
27
+ this.url = options.url;
28
+ this.http = options.http;
29
+ this.fs = options.fs;
30
+ this.hasher = options.hasher;
31
+ this.clock = options.clock;
32
+ this.extractor = options.extractor;
33
+ this.cacheDir = options.cacheDir;
34
+ this.trusted = options.trusted ?? true;
35
+ this.ttlSeconds = options.ttlSeconds ?? 3600;
36
+ }
37
+ async refresh() {
38
+ this.index = undefined;
39
+ this.fetchedAt = 0;
40
+ await this.loadIndex();
41
+ }
42
+ async search(query) {
43
+ const index = await this.loadIndex();
44
+ return toSummaries(index.skills.filter((skill) => matches(skill, query.text)), this.name, query.limit, query.text);
45
+ }
46
+ async has(name) {
47
+ return (await this.loadIndex()).skills.some((skill) => skill.name === name);
48
+ }
49
+ async versions(name) {
50
+ const index = await this.loadIndex();
51
+ return index.skills.find((skill) => skill.name === name)?.versions ?? [];
52
+ }
53
+ async manifest(name, version) {
54
+ return (await this.fetch(name, version)).pkg.manifest;
55
+ }
56
+ async fetch(name, version) {
57
+ const entry = (await this.versions(name)).find((candidate) => candidate.version === version);
58
+ if (entry === undefined) {
59
+ throw new AgentSkillsError(ErrorCode.VERSION_NOT_FOUND, `${name}@${version} is not published in "${this.name}"`, { data: { name, version, registry: this.name } });
60
+ }
61
+ if (entry.tarball === undefined) {
62
+ throw new AgentSkillsError(ErrorCode.REGISTRY_INVALID_INDEX, `${name}@${version} in "${this.name}" has no "tarball"`, {
63
+ details: ['An http registry must serve packages as .tar.gz URLs.'],
64
+ data: { name, version, registry: this.name },
65
+ });
66
+ }
67
+ const tarballUrl = new URL(entry.tarball, this.url).toString();
68
+ const bytes = await this.download(tarballUrl, `${name}@${version}`);
69
+ const pkg = await this.extractor.extract(bytes, tarballUrl);
70
+ if (pkg.manifest.name !== name || pkg.manifest.version !== version) {
71
+ // The index said one thing and the payload another. Treat it as an attack, not a typo.
72
+ throw new AgentSkillsError(ErrorCode.INTEGRITY_MISMATCH, `Registry "${this.name}" served ${pkg.manifest.name}@${pkg.manifest.version} for ${name}@${version}`, {
73
+ hints: ['Do not install this. Report it to the registry maintainers.'],
74
+ data: { registry: this.name, requested: `${name}@${version}` },
75
+ });
76
+ }
77
+ return {
78
+ pkg,
79
+ registry: this.name,
80
+ resolved: tarballUrl,
81
+ integrity: computePackageIntegrity(pkg, this.hasher),
82
+ };
83
+ }
84
+ async download(url, label) {
85
+ const response = await this.http.get(url, { timeoutMs: 60_000 });
86
+ if (response.status === 404) {
87
+ throw new AgentSkillsError(ErrorCode.SKILL_NOT_FOUND, `${label} was not found at ${url}`, {
88
+ hints: ['The registry index may be out of date; try `senior-skills doctor`'],
89
+ data: { url, registry: this.name },
90
+ });
91
+ }
92
+ if (response.status >= 400) {
93
+ throw new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, `Registry "${this.name}" returned HTTP ${response.status} for ${label}`, { data: { url, status: response.status, registry: this.name } });
94
+ }
95
+ return response.body;
96
+ }
97
+ async loadIndex() {
98
+ const now = this.clock.now().getTime();
99
+ if (this.index !== undefined && now - this.fetchedAt < this.ttlSeconds * 1000)
100
+ return this.index;
101
+ let text;
102
+ try {
103
+ const response = await this.http.get(this.url, { timeoutMs: 30_000 });
104
+ if (response.status >= 400) {
105
+ throw new AgentSkillsError(ErrorCode.REGISTRY_UNAVAILABLE, `Registry "${this.name}" returned HTTP ${response.status}`, { details: [this.url], data: { url: this.url, status: response.status } });
106
+ }
107
+ text = new TextDecoder().decode(response.body);
108
+ // Cache on disk so `search` and `info` keep working when the network is unavailable.
109
+ await this.writeCache(text).catch(() => undefined);
110
+ }
111
+ catch (error) {
112
+ const cached = await this.readCache();
113
+ if (cached === undefined)
114
+ throw error;
115
+ text = cached;
116
+ }
117
+ this.index = parseRegistryIndex(text, { source: this.url, fallbackName: this.name });
118
+ this.fetchedAt = now;
119
+ return this.index;
120
+ }
121
+ cachePath() {
122
+ return this.fs.join(this.cacheDir, 'http', `${sanitise(this.name)}.index`);
123
+ }
124
+ async writeCache(text) {
125
+ await this.fs.writeFile(this.cachePath(), text);
126
+ }
127
+ async readCache() {
128
+ const path = this.cachePath();
129
+ if (!(await this.fs.exists(path)))
130
+ return undefined;
131
+ return this.fs.readTextFile(path);
132
+ }
133
+ }
134
+ function sanitise(name) {
135
+ return name.replace(/[^a-zA-Z0-9._-]+/g, '-');
136
+ }
137
+ //# sourceMappingURL=http-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-registry.js","sourceRoot":"","sources":["../src/http-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@senior-skills/registry` — where skills come from.
3
+ *
4
+ * Three interchangeable drivers behind one interface, plus the federation that gives an
5
+ * ordered list of them precedence semantics. Nothing here knows about agents or installation.
6
+ */
7
+ export { DefaultRegistryFactory, type DefaultRegistryFactoryOptions } from './factory.ts';
8
+ export { RegistryFederation } from './federation.ts';
9
+ export { GitRegistry, cacheKey, splitRef, type GitRegistryOptions } from './git-registry.ts';
10
+ export { HttpRegistry, type HttpRegistryOptions } from './http-registry.ts';
11
+ export { INDEX_RELATIVE_PATH, LocalRegistry, type LocalRegistryOptions } from './local-registry.ts';
12
+ export { matches, score, toSummaries } from './search.ts';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@senior-skills/registry` — where skills come from.
3
+ *
4
+ * Three interchangeable drivers behind one interface, plus the federation that gives an
5
+ * ordered list of them precedence semantics. Nothing here knows about agents or installation.
6
+ */
7
+ export { DefaultRegistryFactory } from "./factory.js";
8
+ export { RegistryFederation } from "./federation.js";
9
+ export { GitRegistry, cacheKey, splitRef } from "./git-registry.js";
10
+ export { HttpRegistry } from "./http-registry.js";
11
+ export { INDEX_RELATIVE_PATH, LocalRegistry } from "./local-registry.js";
12
+ export { matches, score, toSummaries } from "./search.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,sBAAsB,EAAsC,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAA2B,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type FetchedPackage, type FileSystem, type Hasher, type IndexVersionEntry, type RegistryKind, type SearchQuery, type SemanticVersion, type SkillManifest, type SkillRegistry, type SkillSummary } from '@senior-skills/core';
2
+ export declare const INDEX_RELATIVE_PATH = "registry/skills.yaml";
3
+ export interface LocalRegistryOptions {
4
+ readonly name: string;
5
+ /** Absolute path of the registry checkout root (the directory holding `registry/`). */
6
+ readonly root: string;
7
+ readonly fs: FileSystem;
8
+ readonly hasher: Hasher;
9
+ readonly trusted?: boolean;
10
+ readonly kind?: RegistryKind;
11
+ }
12
+ /**
13
+ * A registry backed by a directory on disk.
14
+ *
15
+ * Used directly (`senior-skills registry add local ./my-skills`), as the test double for the
16
+ * whole registry protocol, and as the second half of {@link GitRegistry}, which clones into a
17
+ * cache directory and then reads it exactly like this.
18
+ */
19
+ export declare class LocalRegistry implements SkillRegistry {
20
+ readonly name: string;
21
+ readonly kind: RegistryKind;
22
+ readonly trusted: boolean;
23
+ protected readonly fs: FileSystem;
24
+ protected readonly hasher: Hasher;
25
+ private readonly root;
26
+ private index;
27
+ constructor(options: LocalRegistryOptions);
28
+ /** Overridden by GitRegistry to refresh the clone before the index is read. */
29
+ protected ensureAvailable(): Promise<string>;
30
+ refresh(): Promise<void>;
31
+ search(query: SearchQuery): Promise<readonly SkillSummary[]>;
32
+ has(name: string): Promise<boolean>;
33
+ versions(name: string): Promise<readonly IndexVersionEntry[]>;
34
+ manifest(name: string, version: SemanticVersion): Promise<SkillManifest>;
35
+ fetch(name: string, version: SemanticVersion): Promise<FetchedPackage>;
36
+ protected resolvedLocation(directory: string): string;
37
+ private loadPackage;
38
+ private loadIndex;
39
+ }
40
+ //# sourceMappingURL=local-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-registry.d.ts","sourceRoot":"","sources":["../src/local-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,iBAAiB,EAEtB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAG7B,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,aAAa;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAE1B,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,KAAK,CAA4B;gBAE7B,OAAO,EAAE,oBAAoB;IASzC,+EAA+E;cAC/D,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAM5D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAK7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;IAIxE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5E,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;YAIvC,WAAW;YA8BX,SAAS;CAwBxB"}
@@ -0,0 +1,101 @@
1
+ import { AgentSkillsError, ErrorCode, computePackageIntegrity, loadPackageFromDirectory, parseRegistryIndex, } from '@senior-skills/core';
2
+ import { matches, toSummaries } from "./search.js";
3
+ export const INDEX_RELATIVE_PATH = 'registry/skills.yaml';
4
+ /**
5
+ * A registry backed by a directory on disk.
6
+ *
7
+ * Used directly (`senior-skills registry add local ./my-skills`), as the test double for the
8
+ * whole registry protocol, and as the second half of {@link GitRegistry}, which clones into a
9
+ * cache directory and then reads it exactly like this.
10
+ */
11
+ export class LocalRegistry {
12
+ name;
13
+ kind;
14
+ trusted;
15
+ fs;
16
+ hasher;
17
+ root;
18
+ index;
19
+ constructor(options) {
20
+ this.name = options.name;
21
+ this.kind = options.kind ?? 'local';
22
+ this.trusted = options.trusted ?? true;
23
+ this.fs = options.fs;
24
+ this.hasher = options.hasher;
25
+ this.root = options.root;
26
+ }
27
+ /** Overridden by GitRegistry to refresh the clone before the index is read. */
28
+ async ensureAvailable() {
29
+ return this.root;
30
+ }
31
+ async refresh() {
32
+ this.index = undefined;
33
+ await this.loadIndex();
34
+ }
35
+ async search(query) {
36
+ const index = await this.loadIndex();
37
+ const found = index.skills.filter((skill) => matches(skill, query.text));
38
+ return toSummaries(found, this.name, query.limit);
39
+ }
40
+ async has(name) {
41
+ const index = await this.loadIndex();
42
+ return index.skills.some((skill) => skill.name === name);
43
+ }
44
+ async versions(name) {
45
+ const index = await this.loadIndex();
46
+ return index.skills.find((skill) => skill.name === name)?.versions ?? [];
47
+ }
48
+ async manifest(name, version) {
49
+ return (await this.loadPackage(name, version)).pkg.manifest;
50
+ }
51
+ async fetch(name, version) {
52
+ const { pkg, directory } = await this.loadPackage(name, version);
53
+ return {
54
+ pkg,
55
+ registry: this.name,
56
+ resolved: this.resolvedLocation(directory),
57
+ integrity: computePackageIntegrity(pkg, this.hasher),
58
+ };
59
+ }
60
+ resolvedLocation(directory) {
61
+ return `file://${directory.replace(/\\/g, '/')}`;
62
+ }
63
+ async loadPackage(name, version) {
64
+ const root = await this.ensureAvailable();
65
+ const entry = (await this.versions(name)).find((candidate) => candidate.version === version);
66
+ if (entry === undefined) {
67
+ throw new AgentSkillsError(ErrorCode.VERSION_NOT_FOUND, `${name}@${version} is not published in "${this.name}"`, {
68
+ hints: [`senior-skills info ${name} to see published versions`],
69
+ data: { name, version, registry: this.name },
70
+ });
71
+ }
72
+ if (entry.path === undefined) {
73
+ throw new AgentSkillsError(ErrorCode.REGISTRY_INVALID_INDEX, `${name}@${version} in "${this.name}" has no "path"`, {
74
+ details: ['A local or git registry serves packages from a directory, not a tarball.'],
75
+ data: { name, version, registry: this.name },
76
+ });
77
+ }
78
+ const directory = this.fs.join(root, ...entry.path.split('/'));
79
+ const loaded = await loadPackageFromDirectory(this.fs, directory);
80
+ return { pkg: loaded.pkg, directory };
81
+ }
82
+ async loadIndex() {
83
+ if (this.index !== undefined)
84
+ return this.index;
85
+ const root = await this.ensureAvailable();
86
+ const path = this.fs.join(root, ...INDEX_RELATIVE_PATH.split('/'));
87
+ if (!(await this.fs.exists(path))) {
88
+ throw new AgentSkillsError(ErrorCode.REGISTRY_INVALID_INDEX, `Registry "${this.name}" has no ${INDEX_RELATIVE_PATH}`, {
89
+ details: [`Looked in: ${root}`],
90
+ hints: ['A registry is a directory containing registry/skills.yaml and skills/'],
91
+ data: { registry: this.name, root },
92
+ });
93
+ }
94
+ this.index = parseRegistryIndex(await this.fs.readTextFile(path), {
95
+ source: path,
96
+ fallbackName: this.name,
97
+ });
98
+ return this.index;
99
+ }
100
+ }
101
+ //# sourceMappingURL=local-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-registry.js","sourceRoot":"","sources":["../src/local-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { IndexSkillEntry, SkillSummary } from '@senior-skills/core';
2
+ /**
3
+ * Search ranking, shared by every registry implementation so results are ordered the same way
4
+ * whatever the backend. Intentionally simple: substring scoring over name, keywords and
5
+ * description, which is the right amount of machinery for an index that fits in memory.
6
+ */
7
+ export declare function matches(skill: IndexSkillEntry, rawQuery: string): boolean;
8
+ export declare function score(skill: IndexSkillEntry, rawQuery: string): number;
9
+ export declare function toSummaries(skills: readonly IndexSkillEntry[], registry: string, limit?: number, query?: string): readonly SkillSummary[];
10
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYtE;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,SAAK,GACT,SAAS,YAAY,EAAE,CAezB"}
package/dist/search.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Search ranking, shared by every registry implementation so results are ordered the same way
3
+ * whatever the backend. Intentionally simple: substring scoring over name, keywords and
4
+ * description, which is the right amount of machinery for an index that fits in memory.
5
+ */
6
+ export function matches(skill, rawQuery) {
7
+ const query = rawQuery.trim().toLowerCase();
8
+ if (query === '')
9
+ return true;
10
+ const terms = query.split(/\s+/).filter(Boolean);
11
+ const haystack = `${skill.name} ${skill.keywords.join(' ')} ${skill.description}`.toLowerCase();
12
+ return terms.every((term) => haystack.includes(term));
13
+ }
14
+ export function score(skill, rawQuery) {
15
+ const query = rawQuery.trim().toLowerCase();
16
+ if (query === '')
17
+ return 0;
18
+ const name = skill.name.toLowerCase();
19
+ if (name === query)
20
+ return 100;
21
+ if (name.startsWith(query))
22
+ return 80;
23
+ if (name.includes(query))
24
+ return 60;
25
+ if (skill.keywords.some((keyword) => keyword.toLowerCase() === query))
26
+ return 50;
27
+ if (skill.keywords.some((keyword) => keyword.toLowerCase().includes(query)))
28
+ return 30;
29
+ if (skill.description.toLowerCase().includes(query))
30
+ return 10;
31
+ return 0;
32
+ }
33
+ export function toSummaries(skills, registry, limit, query = '') {
34
+ const ranked = [...skills].sort((a, b) => {
35
+ const difference = score(b, query) - score(a, query);
36
+ return difference !== 0 ? difference : a.name.localeCompare(b.name);
37
+ });
38
+ const summaries = ranked.map((skill) => ({
39
+ name: skill.name,
40
+ description: skill.description,
41
+ keywords: skill.keywords,
42
+ latest: skill.latest,
43
+ registry,
44
+ }));
45
+ return limit === undefined ? summaries : summaries.slice(0, limit);
46
+ }
47
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAsB,EAAE,QAAgB;IAC9D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,WAAW,EAAE,CAAC;IAChG,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAsB,EAAE,QAAgB;IAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IAE3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,GAAG,CAAC;IAC/B,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjF,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACvF,IAAI,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,MAAkC,EAClC,QAAgB,EAChB,KAAc,EACd,KAAK,GAAG,EAAE;IAEV,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAgB,EAAE,CAAC,CAAC;QACrD,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ;KACT,CAAC,CAAC,CAAC;IAEJ,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@senior-skills/registry",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Local, Git and HTTP skill registries with precedence-aware federation",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/robsonkades/senior-skills.git",
9
+ "directory": "packages/registry"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "engines": {
24
+ "node": ">=22.18.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@senior-skills/core": "1.0.0"
31
+ }
32
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.