@agentxm/extension-sources 0.28.4-bootstrap.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 (101) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +9 -0
  3. package/dist/src/axm-skill-gate.d.ts +64 -0
  4. package/dist/src/axm-skill-gate.js +25 -0
  5. package/dist/src/errors.d.ts +116 -0
  6. package/dist/src/errors.js +98 -0
  7. package/dist/src/failure-category.d.ts +10 -0
  8. package/dist/src/failure-category.js +2 -0
  9. package/dist/src/file-url.d.ts +9 -0
  10. package/dist/src/file-url.js +12 -0
  11. package/dist/src/git/detect.d.ts +7 -0
  12. package/dist/src/git/detect.js +23 -0
  13. package/dist/src/git/operations.d.ts +37 -0
  14. package/dist/src/git/operations.js +101 -0
  15. package/dist/src/glob.d.ts +39 -0
  16. package/dist/src/glob.js +79 -0
  17. package/dist/src/hook-package-discovery.d.ts +19 -0
  18. package/dist/src/hook-package-discovery.js +14 -0
  19. package/dist/src/index.d.ts +30 -0
  20. package/dist/src/index.js +35 -0
  21. package/dist/src/integrity.d.ts +13 -0
  22. package/dist/src/integrity.js +17 -0
  23. package/dist/src/live.d.ts +29 -0
  24. package/dist/src/live.js +131 -0
  25. package/dist/src/manifest-package-discovery.d.ts +28 -0
  26. package/dist/src/manifest-package-discovery.js +96 -0
  27. package/dist/src/package-discovery.d.ts +19 -0
  28. package/dist/src/package-discovery.js +137 -0
  29. package/dist/src/package-sources.d.ts +25 -0
  30. package/dist/src/package-sources.js +102 -0
  31. package/dist/src/providers/azurerepos/index.d.ts +11 -0
  32. package/dist/src/providers/azurerepos/index.js +11 -0
  33. package/dist/src/providers/azurerepos/repo-exists.d.ts +5 -0
  34. package/dist/src/providers/azurerepos/repo-exists.js +21 -0
  35. package/dist/src/providers/azurerepos/scp.d.ts +12 -0
  36. package/dist/src/providers/azurerepos/scp.js +29 -0
  37. package/dist/src/providers/azurerepos/shorthand.d.ts +12 -0
  38. package/dist/src/providers/azurerepos/shorthand.js +45 -0
  39. package/dist/src/providers/azurerepos/url.d.ts +14 -0
  40. package/dist/src/providers/azurerepos/url.js +38 -0
  41. package/dist/src/providers/bitbucket/index.d.ts +11 -0
  42. package/dist/src/providers/bitbucket/index.js +11 -0
  43. package/dist/src/providers/bitbucket/resolve-repo.d.ts +11 -0
  44. package/dist/src/providers/bitbucket/resolve-repo.js +33 -0
  45. package/dist/src/providers/bitbucket/scp.d.ts +11 -0
  46. package/dist/src/providers/bitbucket/scp.js +28 -0
  47. package/dist/src/providers/bitbucket/shorthand.d.ts +10 -0
  48. package/dist/src/providers/bitbucket/shorthand.js +11 -0
  49. package/dist/src/providers/bitbucket/url.d.ts +13 -0
  50. package/dist/src/providers/bitbucket/url.js +42 -0
  51. package/dist/src/providers/convention-discovery.d.ts +11 -0
  52. package/dist/src/providers/convention-discovery.js +473 -0
  53. package/dist/src/providers/git-hosting.d.ts +31 -0
  54. package/dist/src/providers/git-hosting.js +73 -0
  55. package/dist/src/providers/git.d.ts +22 -0
  56. package/dist/src/providers/git.js +46 -0
  57. package/dist/src/providers/github/index.d.ts +11 -0
  58. package/dist/src/providers/github/index.js +11 -0
  59. package/dist/src/providers/github/resolve-repo.d.ts +11 -0
  60. package/dist/src/providers/github/resolve-repo.js +34 -0
  61. package/dist/src/providers/github/scp.d.ts +11 -0
  62. package/dist/src/providers/github/scp.js +28 -0
  63. package/dist/src/providers/github/shorthand.d.ts +10 -0
  64. package/dist/src/providers/github/shorthand.js +11 -0
  65. package/dist/src/providers/github/url.d.ts +13 -0
  66. package/dist/src/providers/github/url.js +42 -0
  67. package/dist/src/providers/gitlab/index.d.ts +11 -0
  68. package/dist/src/providers/gitlab/index.js +11 -0
  69. package/dist/src/providers/gitlab/resolve-repo.d.ts +11 -0
  70. package/dist/src/providers/gitlab/resolve-repo.js +33 -0
  71. package/dist/src/providers/gitlab/scp.d.ts +11 -0
  72. package/dist/src/providers/gitlab/scp.js +28 -0
  73. package/dist/src/providers/gitlab/shorthand.d.ts +10 -0
  74. package/dist/src/providers/gitlab/shorthand.js +11 -0
  75. package/dist/src/providers/gitlab/url.d.ts +13 -0
  76. package/dist/src/providers/gitlab/url.js +42 -0
  77. package/dist/src/providers/local-parser/index.d.ts +8 -0
  78. package/dist/src/providers/local-parser/index.js +8 -0
  79. package/dist/src/providers/local-parser/parser.d.ts +13 -0
  80. package/dist/src/providers/local-parser/parser.js +14 -0
  81. package/dist/src/providers/local.d.ts +23 -0
  82. package/dist/src/providers/local.js +39 -0
  83. package/dist/src/providers/parse-provider-shorthand.d.ts +18 -0
  84. package/dist/src/providers/parse-provider-shorthand.js +74 -0
  85. package/dist/src/providers/registry/host-provider.d.ts +47 -0
  86. package/dist/src/providers/registry/host-provider.js +584 -0
  87. package/dist/src/resolve-identifier.d.ts +73 -0
  88. package/dist/src/resolve-identifier.js +273 -0
  89. package/dist/src/resolve-source-pattern.d.ts +29 -0
  90. package/dist/src/resolve-source-pattern.js +77 -0
  91. package/dist/src/resolve-source.d.ts +79 -0
  92. package/dist/src/resolve-source.js +503 -0
  93. package/dist/src/rule-package-discovery.d.ts +19 -0
  94. package/dist/src/rule-package-discovery.js +14 -0
  95. package/dist/src/service.d.ts +72 -0
  96. package/dist/src/service.js +109 -0
  97. package/dist/src/url-fragment.d.ts +5 -0
  98. package/dist/src/url-fragment.js +19 -0
  99. package/dist/src/workspace-catalog.d.ts +79 -0
  100. package/dist/src/workspace-catalog.js +26 -0
  101. package/package.json +57 -0
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Source resolution: classifies input via parseInputPattern, then routes
3
+ * each pattern type to the appropriate resolution logic.
4
+ *
5
+ * For URL and SCP patterns, resolution iterates configured sources and
6
+ * matches by hostname + provider parse. For other patterns, resolution
7
+ * handles them directly.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ * @packageDocumentation
11
+ */
12
+ import * as Array from "effect/Array";
13
+ import * as Effect from "effect/Effect";
14
+ import * as Match from "effect/Match";
15
+ import * as Option from "effect/Option";
16
+ import * as azurerepos from "./providers/azurerepos/index.js";
17
+ import * as bitbucket from "./providers/bitbucket/index.js";
18
+ import * as github from "./providers/github/index.js";
19
+ import * as gitlab from "./providers/gitlab/index.js";
20
+ import { parseInputPattern } from "@agentxm/extension-model/unstable/sources/parser";
21
+ import { createRegistryClient } from "@agentxm/registry-client";
22
+ import { decodeHandleSync } from "@agentxm/extension-model/unstable/extensions/handle";
23
+ import { decodeExtensionNameSync, extensionTypeSentenceLabels, isExtensionTypePlural, parseRegistrySourcePatternParts, toExtensionType, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions";
24
+ import { SourceHostNotConfigured, SourceNotResolvable, SourceSyntaxInvalid, } from "./errors.js";
25
+ import { WorkspaceCatalog } from "./workspace-catalog.js";
26
+ import { refFromFragment, refFromUrlHash, stripUrlHash } from "./url-fragment.js";
27
+ // -----------------------------------------------------------------------------
28
+ // Constants
29
+ // -----------------------------------------------------------------------------
30
+ const isGenericGitUrl = (url) => url.protocol === "git:" || url.protocol === "ssh:" || url.pathname.endsWith(".git");
31
+ const genericGitSourceFromUrl = (url) => ({
32
+ type: "git",
33
+ url: stripUrlHash(url),
34
+ ref: refFromUrlHash(url),
35
+ });
36
+ const withRefFallback = (params, ref) => {
37
+ if (Option.isNone(ref))
38
+ return params;
39
+ switch (params.type) {
40
+ case "github":
41
+ case "gitlab":
42
+ case "bitbucket":
43
+ case "azurerepos":
44
+ case "git":
45
+ return Option.isSome(params.ref) ? params : { ...params, ref };
46
+ case "registry":
47
+ case "local":
48
+ case "inline":
49
+ case "workspace":
50
+ return params;
51
+ }
52
+ };
53
+ const withCloneUrl = (source, cloneUrl) => {
54
+ if (Option.isNone(cloneUrl))
55
+ return source;
56
+ switch (source.type) {
57
+ case "github":
58
+ case "gitlab":
59
+ case "bitbucket":
60
+ case "azurerepos":
61
+ return { ...source, cloneUrl };
62
+ case "git":
63
+ case "registry":
64
+ case "local":
65
+ case "workspace":
66
+ return source;
67
+ }
68
+ };
69
+ const splitScpPathRef = (scp) => {
70
+ const refIndex = scp.path.lastIndexOf("#");
71
+ if (refIndex < 0) {
72
+ return {
73
+ scp,
74
+ ref: Option.none(),
75
+ cloneUrl: new URL(`ssh://${scp.user}@${scp.host}/${scp.path}`),
76
+ };
77
+ }
78
+ const path = scp.path.slice(0, refIndex);
79
+ const rawRef = scp.path.slice(refIndex + 1);
80
+ const ref = refFromFragment(rawRef);
81
+ return {
82
+ scp: { ...scp, path },
83
+ ref,
84
+ cloneUrl: new URL(`ssh://${scp.user}@${scp.host}/${path}`),
85
+ };
86
+ };
87
+ const firstSuccess = (attempts, onFailure) => Effect.gen(function* () {
88
+ for (const attempt of attempts) {
89
+ const result = yield* Effect.result(attempt);
90
+ if (result._tag === "Success") {
91
+ return result.success;
92
+ }
93
+ }
94
+ return yield* Effect.fail(onFailure());
95
+ });
96
+ // -----------------------------------------------------------------------------
97
+ // Helpers
98
+ // -----------------------------------------------------------------------------
99
+ /** Get configured sources from the workspace catalog, wrapping catalog failures. */
100
+ const getConfiguredSources = (_input) => Effect.gen(function* () {
101
+ const catalog = yield* WorkspaceCatalog;
102
+ return yield* catalog.configuredSources.pipe(Effect.mapError((e) => new SourceNotResolvable({
103
+ category: "validation",
104
+ detail: `Failed to get configured sources: ${e._tag}`,
105
+ })));
106
+ });
107
+ /** Parse shorthand input using the provider for the given source type. */
108
+ const parseShorthandForSource = (shorthand) => {
109
+ const input = `${shorthand.prefix}:${shorthand.remainingInput}`;
110
+ switch (shorthand.prefix) {
111
+ case "github":
112
+ return github.parseShorthand(input);
113
+ case "gitlab":
114
+ return gitlab.parseShorthand(input);
115
+ case "bitbucket":
116
+ return bitbucket.parseShorthand(input);
117
+ case "azurerepos":
118
+ return azurerepos.parseShorthand(input);
119
+ case "registry": {
120
+ const parsed = parseRegistrySourcePatternParts(shorthand.remainingInput);
121
+ return parsed === undefined
122
+ ? Effect.fail(new SourceSyntaxInvalid({
123
+ detail: `Invalid Registry source reference "${input}"`,
124
+ }))
125
+ : Effect.succeed({ type: "registry", owner: Option.some(parsed.owner) });
126
+ }
127
+ default:
128
+ return Effect.fail(new SourceSyntaxInvalid({
129
+ detail: `Source type "${shorthand.prefix}" does not support shorthand syntax`,
130
+ }));
131
+ }
132
+ };
133
+ /**
134
+ * Merge a configured source host with SourceParams to produce a Source.
135
+ * Uses exhaustive type checks on both discriminators and fails on mismatch.
136
+ */
137
+ const configToSource = (config, params, _input) => {
138
+ const mismatch = () => Effect.fail(new SourceHostNotConfigured({
139
+ detail: `Source params type "${params.type}" does not match config type "${config.type}"`,
140
+ }));
141
+ switch (config.type) {
142
+ case "github":
143
+ return params.type === "github"
144
+ ? Effect.succeed({ ...params, name: config.name, url: config.url })
145
+ : mismatch();
146
+ case "gitlab":
147
+ return params.type === "gitlab"
148
+ ? Effect.succeed({ ...params, name: config.name, url: config.url })
149
+ : mismatch();
150
+ case "bitbucket":
151
+ return params.type === "bitbucket"
152
+ ? Effect.succeed({ ...params, name: config.name, url: config.url })
153
+ : mismatch();
154
+ case "azurerepos":
155
+ return params.type === "azurerepos"
156
+ ? Effect.succeed({ ...params, name: config.name, url: config.url })
157
+ : mismatch();
158
+ case "registry":
159
+ return params.type === "registry"
160
+ ? Effect.succeed({ ...params, name: config.name, location: config.location })
161
+ : mismatch();
162
+ }
163
+ };
164
+ // -----------------------------------------------------------------------------
165
+ // URL routing
166
+ // -----------------------------------------------------------------------------
167
+ /**
168
+ * Route a URL input by iterating configured sources and matching by
169
+ * hostname + provider parse. First successful match wins.
170
+ */
171
+ export const routeUrlInput = (url, input) => Effect.gen(function* () {
172
+ // Opaque URLs (empty hostname, e.g. "ghe:owner/repo") may be config-name shorthands
173
+ if (!url.hostname) {
174
+ return yield* routeOpaqueUrl(url, input);
175
+ }
176
+ const sources = yield* getConfiguredSources(input);
177
+ const noMatch = new SourceHostNotConfigured({
178
+ detail: `No configured source matches URL "${url.href}"`,
179
+ });
180
+ const tryParseUrl = (configUrl, config, parse) => configUrl.hostname !== url.hostname
181
+ ? Effect.fail(noMatch)
182
+ : Effect.flatMap(parse(url, configUrl.hostname), (params) => configToSource(config, params, input));
183
+ const tryMatch = Match.type().pipe(Match.when({ type: "github" }, (c) => tryParseUrl(c.url, c, github.parseUrl)), Match.when({ type: "gitlab" }, (c) => tryParseUrl(c.url, c, gitlab.parseUrl)), Match.when({ type: "bitbucket" }, (c) => tryParseUrl(c.url, c, bitbucket.parseUrl)), Match.when({ type: "azurerepos" }, (c) => tryParseUrl(c.url, c, azurerepos.parseUrl)), Match.when({ type: "registry" }, () => Effect.fail(noMatch)), Match.exhaustive);
184
+ const attempts = Array.map(sources, tryMatch);
185
+ if (Array.isReadonlyArrayEmpty(attempts)) {
186
+ if (isGenericGitUrl(url)) {
187
+ return genericGitSourceFromUrl(url);
188
+ }
189
+ return yield* noMatch;
190
+ }
191
+ const matches = [];
192
+ for (const attempt of attempts) {
193
+ const result = yield* Effect.result(attempt);
194
+ if (result._tag === "Success") {
195
+ matches.push(result.success);
196
+ }
197
+ }
198
+ const [match, ...remainingMatches] = matches;
199
+ if (match !== undefined && remainingMatches.length === 0)
200
+ return match;
201
+ if (match !== undefined) {
202
+ return yield* new SourceHostNotConfigured({
203
+ detail: `URL "${url.href}" matches multiple configured sources: ${matches
204
+ .flatMap((source) => ("name" in source ? [source.name] : []))
205
+ .join(", ")}. Select one by source name.`,
206
+ });
207
+ }
208
+ if (isGenericGitUrl(url)) {
209
+ return genericGitSourceFromUrl(url);
210
+ }
211
+ return yield* new SourceHostNotConfigured({
212
+ detail: `No configured source matches URL "${url.href}"`,
213
+ });
214
+ });
215
+ /**
216
+ * Handle opaque URLs (no hostname, e.g. "ghe:owner/repo") by checking if
217
+ * the scheme matches a config name for a git hosting source type.
218
+ */
219
+ const routeOpaqueUrl = (url, input) => Effect.gen(function* () {
220
+ const colonIndex = input.indexOf(":");
221
+ if (colonIndex <= 0) {
222
+ return yield* new SourceSyntaxInvalid({
223
+ detail: "Unable to parse source",
224
+ });
225
+ }
226
+ const prefix = input.slice(0, colonIndex);
227
+ const sources = yield* getConfiguredSources(input);
228
+ // Check if the scheme matches a config name
229
+ const matchedConfig = sources.find((s) => s.name === prefix);
230
+ if (matchedConfig !== undefined) {
231
+ const remainder = input.slice(colonIndex + 1);
232
+ const params = yield* parseShorthandForSource({
233
+ pattern: "shorthand-input",
234
+ prefix: matchedConfig.type,
235
+ remainingInput: remainder,
236
+ });
237
+ return yield* configToSource(matchedConfig, params, input);
238
+ }
239
+ // Not a config name — fail
240
+ return yield* new SourceHostNotConfigured({
241
+ detail: `No configured source matches URL "${url.href}"`,
242
+ });
243
+ });
244
+ // -----------------------------------------------------------------------------
245
+ // SCP routing
246
+ // -----------------------------------------------------------------------------
247
+ /**
248
+ * Route an SCP address by iterating configured sources and matching by
249
+ * hostname + provider parse. First successful match wins.
250
+ */
251
+ export const routeScpInput = (scp, input) => Effect.gen(function* () {
252
+ const sources = yield* getConfiguredSources(input);
253
+ const scpParts = splitScpPathRef(scp);
254
+ const scpInput = `${scpParts.scp.user}@${scpParts.scp.host}:${scpParts.scp.path}`;
255
+ const noMatch = new SourceHostNotConfigured({
256
+ detail: `No configured source matches SCP address "${scpInput}"`,
257
+ });
258
+ const tryParseScp = (scpHostname, config, parse) => scpParts.scp.host !== scpHostname
259
+ ? Effect.fail(noMatch)
260
+ : Effect.flatMap(parse(scpInput, scpParts.scp.host), (params) => Effect.map(configToSource(config, withRefFallback(params, scpParts.ref), input), (source) => withCloneUrl(source, Option.some(scpParts.cloneUrl.href))));
261
+ const tryMatch = Match.type().pipe(Match.when({ type: "github" }, (c) => tryParseScp(c.url.hostname, c, github.parseScp)), Match.when({ type: "gitlab" }, (c) => tryParseScp(c.url.hostname, c, gitlab.parseScp)), Match.when({ type: "bitbucket" }, (c) => tryParseScp(c.url.hostname, c, bitbucket.parseScp)), Match.when({ type: "azurerepos" }, (c) => tryParseScp(`ssh.${c.url.hostname}`, c, azurerepos.parseScp)), Match.when({ type: "registry" }, () => Effect.fail(noMatch)), Match.exhaustive);
262
+ const attempts = Array.map(sources, tryMatch);
263
+ const genericGitSource = genericGitSourceFromUrl(scpParts.cloneUrl);
264
+ const genericGitSourceWithRef = { ...genericGitSource, ref: scpParts.ref };
265
+ if (Array.isReadonlyArrayEmpty(attempts)) {
266
+ return genericGitSourceWithRef;
267
+ }
268
+ const matches = [];
269
+ for (const attempt of attempts) {
270
+ const result = yield* Effect.result(attempt);
271
+ if (result._tag === "Success") {
272
+ matches.push(result.success);
273
+ }
274
+ }
275
+ const [match, ...remainingMatches] = matches;
276
+ if (match !== undefined && remainingMatches.length === 0)
277
+ return match;
278
+ if (match !== undefined) {
279
+ return yield* new SourceHostNotConfigured({
280
+ detail: `SCP address "${scpInput}" matches multiple configured sources: ${matches
281
+ .flatMap((source) => ("name" in source ? [source.name] : []))
282
+ .join(", ")}. Select one by source name.`,
283
+ });
284
+ }
285
+ return genericGitSourceWithRef;
286
+ });
287
+ // -----------------------------------------------------------------------------
288
+ // Shorthand routing
289
+ // -----------------------------------------------------------------------------
290
+ /**
291
+ * Route shorthand input (github:owner/repo, ghe:owner/repo, etc.).
292
+ *
293
+ * Known source-type prefixes dispatch directly to the provider's shorthand
294
+ * parser. Config-name prefixes look up the config and parse using its source
295
+ * type's shorthand parser.
296
+ */
297
+ export const resolveShorthandInputSource = (parseResult) => Effect.gen(function* () {
298
+ const prefix = parseResult.pattern.prefix;
299
+ const input = parseResult.originalInput;
300
+ const sources = yield* getConfiguredSources(input);
301
+ // Prefixes always select an exact configured source name.
302
+ const matchedConfig = sources.find((s) => s.name === prefix);
303
+ if (matchedConfig === undefined) {
304
+ return yield* new SourceHostNotConfigured({
305
+ detail: `No configured source named "${prefix}"`,
306
+ });
307
+ }
308
+ const params = yield* parseShorthandForSource({
309
+ pattern: "shorthand-input",
310
+ prefix: matchedConfig.type,
311
+ remainingInput: parseResult.pattern.remainingInput,
312
+ });
313
+ return yield* configToSource(matchedConfig, params, input);
314
+ });
315
+ // -----------------------------------------------------------------------------
316
+ // Simple pattern routing
317
+ // -----------------------------------------------------------------------------
318
+ /** Route NameInput through the complete desired extension graph. */
319
+ export const routeNameInput = (name, _input, expectedType = "skill") => Effect.gen(function* () {
320
+ const catalog = yield* WorkspaceCatalog;
321
+ const graph = yield* catalog.desiredExtensionGraph;
322
+ if (!graph.complete) {
323
+ return yield* new SourceNotResolvable({
324
+ category: "conflict",
325
+ detail: `Cannot resolve the ${extensionTypeSentenceLabels[expectedType]} while the desired extension graph is incomplete.`,
326
+ recover: "Repair or reinstall the configured packs, then retry.",
327
+ });
328
+ }
329
+ const desired = graph.nodes.find((node) => node.type === expectedType && node.name === name);
330
+ if (desired?.source !== undefined) {
331
+ return yield* resolveSource(desired.source);
332
+ }
333
+ return yield* new SourceNotResolvable({
334
+ category: "validation",
335
+ detail: `Unknown ${extensionTypeSentenceLabels[expectedType]} "${name}".`,
336
+ suggestions: [
337
+ {
338
+ description: `Inspect configured ${extensionTypeSentenceLabels[expectedType]} entries.`,
339
+ cmd: `axm ${toExtensionTypePlural(expectedType)} list`,
340
+ },
341
+ ],
342
+ });
343
+ });
344
+ /** Route RegistryPatternInput: find matching registry config and intersect with params. */
345
+ export const routeRegistryInput = (pattern, _input) => Effect.gen(function* () {
346
+ const catalog = yield* WorkspaceCatalog;
347
+ // Name filtering is handled in the find phase; this routing step only resolves registry host.
348
+ const sources = yield* catalog.configuredSources.pipe(Effect.mapError((e) => new SourceNotResolvable({
349
+ category: "validation",
350
+ detail: `Failed to get source ${pattern.sourceName}: ${e._tag}`,
351
+ })));
352
+ const configured = Option.fromUndefinedOr(sources.find((source) => source.name === pattern.sourceName));
353
+ if (Option.isNone(configured) || configured.value.type !== "registry") {
354
+ return yield* new SourceHostNotConfigured({
355
+ detail: `No Registry source named "${pattern.sourceName}" is configured`,
356
+ });
357
+ }
358
+ return {
359
+ type: "registry",
360
+ name: configured.value.name,
361
+ location: configured.value.location,
362
+ owner: Option.some(pattern.owner),
363
+ };
364
+ });
365
+ /**
366
+ * Route SlashPattern (owner/repo): iterate git-hosting configs that support
367
+ * shorthand, try each provider in config order. First success wins.
368
+ */
369
+ const registryExtensionTypeFromSegment = (segment) => {
370
+ if (!isExtensionTypePlural(segment)) {
371
+ return Option.none();
372
+ }
373
+ return Option.some(toExtensionType(segment));
374
+ };
375
+ export const resolveSlashInputSource = (pattern, input) => Effect.gen(function* () {
376
+ const sources = yield* getConfiguredSources(input);
377
+ const shorthandTypes = ["github", "gitlab", "bitbucket"];
378
+ const shorthandBody = Option.match(pattern.third, {
379
+ onNone: () => `${pattern.first}/${pattern.second}`,
380
+ onSome: (subPath) => `${pattern.first}/${pattern.second}//${subPath}`,
381
+ });
382
+ if (Option.isSome(pattern.third)) {
383
+ const type = registryExtensionTypeFromSegment(pattern.second);
384
+ if (Option.isSome(type)) {
385
+ const catalog = yield* WorkspaceCatalog;
386
+ const owner = pattern.first.startsWith("@") ? decodeHandleSync(pattern.first) : undefined;
387
+ const extensionName = (() => {
388
+ try {
389
+ return decodeExtensionNameSync(pattern.third.value);
390
+ }
391
+ catch {
392
+ return undefined;
393
+ }
394
+ })();
395
+ const registrySources = (yield* catalog.registrySourceHosts.pipe(Effect.mapError((e) => new SourceNotResolvable({
396
+ category: "validation",
397
+ detail: `Failed to get registry sources: ${e._tag}`,
398
+ })))).filter((source) => source.name === "agentxm");
399
+ if (owner !== undefined && extensionName !== undefined) {
400
+ for (const regSource of registrySources) {
401
+ const client = yield* createRegistryClient(regSource.location.href);
402
+ const exists = yield* client
403
+ .extensionExists({ owner, type: type.value, name: extensionName })
404
+ .pipe(Effect.orElseSucceed(() => false));
405
+ if (exists) {
406
+ return {
407
+ type: "registry",
408
+ name: regSource.name,
409
+ location: regSource.location,
410
+ owner: Option.some(owner),
411
+ };
412
+ }
413
+ }
414
+ }
415
+ }
416
+ }
417
+ const attempts = sources.flatMap((config) => {
418
+ const sourceType = shorthandTypes.find((t) => t === config.type);
419
+ if (!sourceType) {
420
+ return [];
421
+ }
422
+ return [
423
+ Effect.flatMap(parseShorthandForSource({
424
+ pattern: "shorthand-input",
425
+ prefix: sourceType,
426
+ remainingInput: shorthandBody,
427
+ }), (params) => configToSource(config, params, input)),
428
+ ];
429
+ });
430
+ if (Array.isReadonlyArrayEmpty(attempts)) {
431
+ return yield* new SourceHostNotConfigured({
432
+ detail: `Ambiguous pattern '${pattern.first}/${pattern.second}' — no git hosting sources configured`,
433
+ });
434
+ }
435
+ return yield* firstSuccess(attempts, () => new SourceHostNotConfigured({
436
+ detail: `Ambiguous pattern '${pattern.first}/${pattern.second}' — use github:${pattern.first}/${pattern.second}, gitlab:${pattern.first}/${pattern.second}, or bitbucket:${pattern.first}/${pattern.second}`,
437
+ }));
438
+ });
439
+ // -----------------------------------------------------------------------------
440
+ // Main resolver
441
+ // -----------------------------------------------------------------------------
442
+ /**
443
+ * Resolve a source string into a fully resolved `Source`.
444
+ *
445
+ * Classifies the input via `parseInputPattern`, then routes each pattern
446
+ * type to the appropriate resolution logic. For URL and SCP patterns,
447
+ * resolution iterates configured sources and matches by hostname + provider
448
+ * parse. For other patterns, resolution handles them directly.
449
+ *
450
+ * @experimental This API is unstable and may change without notice.
451
+ * @param input - The source string to resolve
452
+ * @returns Effect containing a resolved `Source` or a typed resolution failure
453
+ */
454
+ export const resolveSource = (input, options) => Effect.gen(function* () {
455
+ const trimmed = input.trim();
456
+ if (!trimmed) {
457
+ return yield* new SourceSyntaxInvalid({
458
+ detail: "Source string cannot be empty",
459
+ });
460
+ }
461
+ const parseResultOpt = parseInputPattern(trimmed);
462
+ if (Option.isNone(parseResultOpt)) {
463
+ return yield* new SourceSyntaxInvalid({
464
+ detail: "Unable to parse source",
465
+ });
466
+ }
467
+ const parsed = parseResultOpt.value;
468
+ const pattern = parsed.pattern;
469
+ switch (pattern.pattern) {
470
+ case "url-input":
471
+ return yield* routeUrlInput(pattern.url, parsed.originalInput);
472
+ case "git-scp-address":
473
+ return yield* routeScpInput(pattern, parsed.originalInput);
474
+ case "shorthand-input":
475
+ return yield* resolveShorthandInputSource({
476
+ pattern,
477
+ originalInput: parsed.originalInput,
478
+ });
479
+ case "name-input":
480
+ return yield* routeNameInput(pattern.name, parsed.originalInput, options?.expectedType ?? "skill");
481
+ case "file-path-pattern":
482
+ return { type: "local", path: pattern.path };
483
+ case "registry-pattern-input":
484
+ return yield* routeRegistryInput(pattern, parsed.originalInput);
485
+ case "slash-pattern":
486
+ return yield* resolveSlashInputSource(pattern, parsed.originalInput);
487
+ case "glob-input":
488
+ return yield* new SourceSyntaxInvalid({
489
+ detail: `Glob patterns are not supported by resolveSource — use resolveSourcePattern instead`,
490
+ });
491
+ case "workspace-pattern-input":
492
+ return {
493
+ type: "workspace",
494
+ owner: pattern.owner,
495
+ extensionType: pattern.type,
496
+ name: pattern.name,
497
+ };
498
+ }
499
+ return yield* new SourceSyntaxInvalid({
500
+ detail: "Unable to resolve source pattern",
501
+ });
502
+ });
503
+ //# sourceMappingURL=resolve-source.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Rule package discovery for local and git sources.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import type * as Effect from "effect/Effect";
7
+ import type * as FileSystem from "effect/FileSystem";
8
+ import type * as Path from "effect/Path";
9
+ import { type RuleManifest } from "@agentxm/extension-model/unstable/rules/manifest-schema";
10
+ export interface DiscoveredRulePackage {
11
+ readonly type: "rule";
12
+ readonly manifest: RuleManifest;
13
+ readonly location: string;
14
+ }
15
+ export interface RulePackageDiscoveryOptions {
16
+ readonly fullDepth: boolean;
17
+ }
18
+ export declare const rulePackagesInDir: (searchPath: string, options: RulePackageDiscoveryOptions) => Effect.Effect<ReadonlyArray<DiscoveredRulePackage>, never, FileSystem.FileSystem | Path.Path>;
19
+ //# sourceMappingURL=rule-package-discovery.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Rule package discovery for local and git sources.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import { discoverManifestPackagesInDir } from "./manifest-package-discovery.js";
7
+ import { RULE_MANIFEST_FILENAME, RuleManifestSchema, } from "@agentxm/extension-model/unstable/rules/manifest-schema";
8
+ const discoverRulePackagesInDir = discoverManifestPackagesInDir({
9
+ type: "rule",
10
+ manifestFilename: RULE_MANIFEST_FILENAME,
11
+ manifestSchema: RuleManifestSchema,
12
+ });
13
+ export const rulePackagesInDir = (searchPath, options) => discoverRulePackagesInDir(searchPath, options);
14
+ //# sourceMappingURL=rule-package-discovery.js.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * SourceHostProviders Effect service.
3
+ *
4
+ * Provides a unified interface for discovering and fetching extensions
5
+ * across all source types, plus clone URL and source display building.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ * @packageDocumentation
9
+ */
10
+ import * as ServiceMap from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Option from "effect/Option";
13
+ import type * as Scope from "effect/Scope";
14
+ import type { SourceResolutionFailure } from "./errors.js";
15
+ import type { ExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/extension-ref";
16
+ import type { ExtensionFiles, FindOptions, NamedRegistryFindOptions, NamedRegistryResolution } from "@agentxm/extension-model/unstable/sources/source-host-provider";
17
+ import type { RegistrySource, Source } from "@agentxm/extension-model/unstable/sources/types";
18
+ /**
19
+ * Service interface for source host providers.
20
+ *
21
+ * Dependencies (FileSystem, Path, WorkspaceCatalog) are resolved at layer creation —
22
+ * callers only see the service, not its implementation details.
23
+ *
24
+ * @experimental This API is unstable and may change without notice.
25
+ */
26
+ export interface SourceHostProvidersService {
27
+ /** Find extensions matching the given source and search criteria. */
28
+ readonly find: (source: Source, options: FindOptions) => Effect.Effect<ReadonlyArray<ExtensionRef>, SourceResolutionFailure, Scope.Scope>;
29
+ /** Resolve one explicit Registry target with a typed selection outcome. */
30
+ readonly resolveNamedRegistry: (source: RegistrySource, options: NamedRegistryFindOptions) => Effect.Effect<NamedRegistryResolution, SourceResolutionFailure, Scope.Scope>;
31
+ /** Fetch and materialize extension files for a discovered ref. */
32
+ readonly fetch: (ref: ExtensionRef) => Effect.Effect<ExtensionFiles, SourceResolutionFailure, Scope.Scope>;
33
+ /** Build a git clone URL for this source. Returns None for non-git sources. */
34
+ readonly cloneUrl: (source: Source) => Option.Option<string>;
35
+ /** Canonical origin string for display/comparison. */
36
+ readonly origin: (source: Source) => string;
37
+ }
38
+ declare const SourceHostProviders_base: ServiceMap.ServiceClass<SourceHostProviders, "@agentxm/extension-sources/service/SourceHostProviders", SourceHostProvidersService>;
39
+ /**
40
+ * Effect service tag for source host providers.
41
+ *
42
+ * @experimental This API is unstable and may change without notice.
43
+ */
44
+ export declare class SourceHostProviders extends SourceHostProviders_base {
45
+ }
46
+ /**
47
+ * Build a git clone URL from a source.
48
+ * Returns Some for git-based hosting sources, None for others.
49
+ *
50
+ * @internal
51
+ */
52
+ export declare const buildCloneUrlFromSource: (source: Source) => Option.Option<string>;
53
+ /**
54
+ * Get the canonical source string for display/comparison.
55
+ *
56
+ * @internal
57
+ */
58
+ export declare const getOriginFromSource: (source: Source) => string;
59
+ /**
60
+ * Creates a registry meta-provider that delegates find/fetch to the
61
+ * resolved registry host encoded in the provided `RegistrySource`.
62
+ *
63
+ * @internal
64
+ */
65
+ export declare const createRegistryMetaProvider: () => {
66
+ type: "registry";
67
+ find: (source: RegistrySource, options: FindOptions) => Effect.Effect<readonly ExtensionRef[], SourceResolutionFailure, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("./axm-skill-gate.ts").AxmSkillCandidateGate | Scope.Scope | import("effect/unstable/http/HttpClient").HttpClient>;
68
+ resolveNamed: (source: RegistrySource, options: NamedRegistryFindOptions) => Effect.Effect<NamedRegistryResolution, SourceResolutionFailure, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("./axm-skill-gate.ts").AxmSkillCandidateGate | Scope.Scope | import("effect/unstable/http/HttpClient").HttpClient>;
69
+ fetch: (source: RegistrySource, ref: ExtensionRef) => Effect.Effect<ExtensionFiles, SourceResolutionFailure, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("./axm-skill-gate.ts").AxmSkillCandidateGate | Scope.Scope | import("effect/unstable/http/HttpClient").HttpClient>;
70
+ };
71
+ export {};
72
+ //# sourceMappingURL=service.d.ts.map