@stigmer/plugin-package 3.15.3-dev.20260916211208 → 3.16.1-dev.20260917224426

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 (86) hide show
  1. package/client/archive.d.ts +27 -0
  2. package/client/archive.d.ts.map +1 -0
  3. package/client/archive.js +38 -0
  4. package/client/archive.js.map +1 -0
  5. package/client/ignore/defaults.d.ts +2 -0
  6. package/client/ignore/defaults.d.ts.map +1 -0
  7. package/client/ignore/defaults.js +140 -0
  8. package/client/ignore/defaults.js.map +1 -0
  9. package/client/ignore/match.d.ts +3 -0
  10. package/client/ignore/match.d.ts.map +1 -0
  11. package/client/ignore/match.js +166 -0
  12. package/client/ignore/match.js.map +1 -0
  13. package/client/ignore/matcher.d.ts +50 -0
  14. package/client/ignore/matcher.d.ts.map +1 -0
  15. package/client/ignore/matcher.js +116 -0
  16. package/client/ignore/matcher.js.map +1 -0
  17. package/client/ignore/pattern.d.ts +13 -0
  18. package/client/ignore/pattern.d.ts.map +1 -0
  19. package/client/ignore/pattern.js +118 -0
  20. package/client/ignore/pattern.js.map +1 -0
  21. package/client/refs.d.ts +76 -0
  22. package/client/refs.d.ts.map +1 -0
  23. package/client/refs.js +123 -0
  24. package/client/refs.js.map +1 -0
  25. package/client/select.d.ts +74 -0
  26. package/client/select.d.ts.map +1 -0
  27. package/client/select.js +121 -0
  28. package/client/select.js.map +1 -0
  29. package/client/vocabulary.d.ts +13 -0
  30. package/client/vocabulary.d.ts.map +1 -0
  31. package/client/vocabulary.js +17 -0
  32. package/client/vocabulary.js.map +1 -0
  33. package/client.d.ts +34 -0
  34. package/client.d.ts.map +1 -0
  35. package/client.js +34 -0
  36. package/client.js.map +1 -0
  37. package/files.d.ts +6 -0
  38. package/files.d.ts.map +1 -1
  39. package/files.js +6 -0
  40. package/files.js.map +1 -1
  41. package/index.d.ts +11 -6
  42. package/index.d.ts.map +1 -1
  43. package/index.js +9 -5
  44. package/index.js.map +1 -1
  45. package/marketplace/messages.d.ts +20 -0
  46. package/marketplace/messages.d.ts.map +1 -0
  47. package/marketplace/messages.js +50 -0
  48. package/marketplace/messages.js.map +1 -0
  49. package/marketplace/outcome.d.ts +72 -0
  50. package/marketplace/outcome.d.ts.map +1 -0
  51. package/marketplace/outcome.js +29 -0
  52. package/marketplace/outcome.js.map +1 -0
  53. package/marketplace/read-marketplace.d.ts +34 -0
  54. package/marketplace/read-marketplace.d.ts.map +1 -0
  55. package/marketplace/read-marketplace.js +359 -0
  56. package/marketplace/read-marketplace.js.map +1 -0
  57. package/messages.d.ts +25 -9
  58. package/messages.d.ts.map +1 -1
  59. package/messages.js +24 -8
  60. package/messages.js.map +1 -1
  61. package/outcome.d.ts +13 -6
  62. package/outcome.d.ts.map +1 -1
  63. package/package.json +8 -2
  64. package/src/__test-utils__/read.ts +4 -4
  65. package/src/__tests__/client-ignore.test.ts +133 -0
  66. package/src/__tests__/client-refs.test.ts +101 -0
  67. package/src/__tests__/client-select-archive.test.ts +152 -0
  68. package/src/__tests__/fixtures/cursor-plugins/.cursor-plugin/marketplace.json +412 -0
  69. package/src/__tests__/fixtures/cursor-plugins/NOTICE +5 -0
  70. package/src/__tests__/marketplace.test.ts +373 -0
  71. package/src/client/archive.ts +44 -0
  72. package/src/client/ignore/defaults.ts +155 -0
  73. package/src/client/ignore/match.ts +158 -0
  74. package/src/client/ignore/matcher.ts +156 -0
  75. package/src/client/ignore/pattern.ts +131 -0
  76. package/src/client/refs.ts +170 -0
  77. package/src/client/select.ts +161 -0
  78. package/src/client/vocabulary.ts +19 -0
  79. package/src/client.ts +67 -0
  80. package/src/files.ts +6 -0
  81. package/src/index.ts +21 -5
  82. package/src/marketplace/messages.ts +62 -0
  83. package/src/marketplace/outcome.ts +95 -0
  84. package/src/marketplace/read-marketplace.ts +402 -0
  85. package/src/messages.ts +34 -21
  86. package/src/outcome.ts +14 -6
@@ -0,0 +1,118 @@
1
+ // Faithful port of go-git's plumbing/format/gitignore Pattern (ParsePattern +
2
+ // Match). This is what gives `push` its exact gitignore semantics: domain
3
+ // scoping, directory-only patterns, single-name vs glob matching, and the
4
+ // `**` zero-to-many-directories wildcard.
5
+ import { matchName } from "./match.js";
6
+ export var MatchResult;
7
+ (function (MatchResult) {
8
+ MatchResult[MatchResult["NoMatch"] = 0] = "NoMatch";
9
+ MatchResult[MatchResult["Exclude"] = 1] = "Exclude";
10
+ MatchResult[MatchResult["Include"] = 2] = "Include";
11
+ })(MatchResult || (MatchResult = {}));
12
+ const INCLUSION_PREFIX = "!";
13
+ const ZERO_TO_MANY = "**";
14
+ const DIR_SEP = "/";
15
+ class GitignorePattern {
16
+ inclusion;
17
+ domain;
18
+ parts;
19
+ dirOnly;
20
+ isGlob;
21
+ constructor(domain, parts, inclusion, dirOnly, isGlob) {
22
+ this.domain = domain;
23
+ this.parts = parts;
24
+ this.inclusion = inclusion;
25
+ this.dirOnly = dirOnly;
26
+ this.isGlob = isGlob;
27
+ }
28
+ match(path, isDir) {
29
+ const matchResult = this.inclusion ? MatchResult.Include : MatchResult.Exclude;
30
+ if (path.length <= this.domain.length)
31
+ return MatchResult.NoMatch;
32
+ for (let i = 0; i < this.domain.length; i++) {
33
+ if (path[i] !== this.domain[i])
34
+ return MatchResult.NoMatch;
35
+ }
36
+ const scoped = path.slice(this.domain.length);
37
+ const matched = this.isGlob ? this.globMatch(scoped, isDir) : this.simpleNameMatch(scoped, isDir);
38
+ return matched ? matchResult : MatchResult.NoMatch;
39
+ }
40
+ simpleNameMatch(path, isDir) {
41
+ for (let i = 0; i < path.length; i++) {
42
+ if (!matchName(this.parts[0] ?? "", path[i] ?? ""))
43
+ continue;
44
+ if (this.dirOnly && !isDir && i === path.length - 1)
45
+ return false;
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ globMatch(path, isDir) {
51
+ let matched = false;
52
+ let canTraverse = false;
53
+ let remaining = path;
54
+ for (let i = 0; i < this.parts.length; i++) {
55
+ const part = this.parts[i] ?? "";
56
+ if (part === "") {
57
+ canTraverse = false;
58
+ continue;
59
+ }
60
+ if (part === ZERO_TO_MANY) {
61
+ if (i === this.parts.length - 1)
62
+ break;
63
+ canTraverse = true;
64
+ continue;
65
+ }
66
+ if (part.includes(ZERO_TO_MANY))
67
+ return false;
68
+ if (remaining.length === 0)
69
+ return false;
70
+ if (canTraverse) {
71
+ canTraverse = false;
72
+ for (;;) {
73
+ if (remaining.length === 0) {
74
+ matched = false;
75
+ break;
76
+ }
77
+ const e = remaining[0] ?? "";
78
+ remaining = remaining.slice(1);
79
+ if (matchName(part, e)) {
80
+ matched = true;
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ else {
86
+ if (!matchName(part, remaining[0] ?? ""))
87
+ return false;
88
+ matched = true;
89
+ remaining = remaining.slice(1);
90
+ }
91
+ }
92
+ if (matched && this.dirOnly && !isDir && remaining.length === 0) {
93
+ matched = false;
94
+ }
95
+ return matched;
96
+ }
97
+ }
98
+ /** Parse a gitignore pattern string into a matchable Pattern (go-git parity). */
99
+ export function parsePattern(raw, domain) {
100
+ let p = raw;
101
+ let inclusion = false;
102
+ if (p.startsWith(INCLUSION_PREFIX)) {
103
+ inclusion = true;
104
+ p = p.slice(1);
105
+ }
106
+ if (!p.endsWith("\\ ")) {
107
+ p = p.replace(/ +$/, "");
108
+ }
109
+ let dirOnly = false;
110
+ if (p.endsWith(DIR_SEP)) {
111
+ dirOnly = true;
112
+ p = p.slice(0, -1);
113
+ }
114
+ const isGlob = p.includes(DIR_SEP);
115
+ const parts = p.split(DIR_SEP);
116
+ return new GitignorePattern(domain, parts, inclusion, dirOnly, isGlob);
117
+ }
118
+ //# sourceMappingURL=pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern.js","sourceRoot":"","sources":["../../../src/client/ignore/pattern.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The two grammars a user types at any marketplace surface, and the words
3
+ * every client uses for what they name.
4
+ *
5
+ * `install [marketplace/]name[@version]`: the plugin's name in its
6
+ * marketplace, optionally qualified by the marketplace's name and pinned to
7
+ * the version the entry's manifest must carry. Both names obey the plugin
8
+ * name rule (`isValidPluginName`: lowercase letters, digits, `.` and `-`,
9
+ * never `/` or `@`), which is what makes the grammar unambiguous: the first
10
+ * `/` splits the marketplace off, the first `@` splits the version off, and
11
+ * anything else is not a ref. A path (`./thermos`, `/tmp/x`, `..`) is the
12
+ * one likely mistake and gets its own sentence, so a client can point at
13
+ * the command that installs a folder.
14
+ *
15
+ * A GitHub marketplace source: `owner/repo[@ref]`, or the URL a browser
16
+ * shows for that repository (`https://github.com/owner/repo[/tree/<ref>]`),
17
+ * normalised to one record. Every vendor keeps its marketplace file at the
18
+ * repository root, so a subdirectory grammar is not offered.
19
+ *
20
+ * Refusals are returned, not thrown: the CLI wraps one in its `UsageError`
21
+ * and the console renders it, and the sentence is the same in both. A
22
+ * client adds the second paragraph naming its own command.
23
+ */
24
+ /** The built-in marketplace's name: the prefix in `install stigmer/<plugin>`; reserved in every client. */
25
+ export declare const OFFICIAL_MARKETPLACE_NAME = "stigmer";
26
+ export interface InstallRef {
27
+ /** The marketplace named before the `/`, or `undefined` when the name stands alone. */
28
+ readonly marketplace?: string;
29
+ readonly name: string;
30
+ /** The version named after the `@`, asserted against the entry's manifest. */
31
+ readonly version?: string;
32
+ }
33
+ export type InstallRefOutcome = {
34
+ readonly ok: true;
35
+ readonly ref: InstallRef;
36
+ } | {
37
+ readonly ok: false;
38
+ /** `path` when the text names a folder rather than a plugin; `shape` for every other refusal. */
39
+ readonly kind: "path" | "shape";
40
+ readonly message: string;
41
+ };
42
+ /** The grammar in the user's terms, quoted by every refusal. */
43
+ export declare const INSTALL_REF_SHAPE = "[marketplace/]name[@version], for example 'thermos', 'cursor-plugins/thermos' or 'thermos@1.0.0'";
44
+ export declare function parseInstallRef(text: string): InstallRefOutcome;
45
+ /** The grammar's own text for a ref, the inverse of `parseInstallRef`. */
46
+ export declare function formatInstallRef(ref: InstallRef): string;
47
+ /** Whether `text` reads as a filesystem path on any platform (relative, home, absolute, Windows). */
48
+ export declare function looksLikePath(text: string): boolean;
49
+ /** A public GitHub repository at a ref; the default branch when `ref` is absent. */
50
+ export interface GitHubMarketplaceSource {
51
+ readonly type: "github";
52
+ /** `owner/repo`. */
53
+ readonly repo: string;
54
+ /** Branch, tag or commit. */
55
+ readonly ref?: string;
56
+ }
57
+ export type GitHubSourceOutcome = {
58
+ readonly ok: true;
59
+ readonly source: GitHubMarketplaceSource;
60
+ } | {
61
+ readonly ok: false;
62
+ readonly message: string;
63
+ };
64
+ /** What a GitHub source may be typed as, quoted by every refusal. */
65
+ export declare const GITHUB_SOURCE_SHAPE = "a GitHub repository as 'owner/repo' or 'owner/repo@ref', or that repository's URL (https://github.com/owner/repo)";
66
+ /**
67
+ * `owner/repo[@ref]` or a github.com URL as one source. The URL form is
68
+ * tried first because it contains a `/` too; a text that is neither is
69
+ * refused with the shape.
70
+ */
71
+ export declare function parseGitHubSource(text: string): GitHubSourceOutcome;
72
+ /** One phrase naming a GitHub source, for lists and sentences. */
73
+ export declare function describeGitHubSource(source: GitHubMarketplaceSource): string;
74
+ /** `owner/repo` as GitHub spells it: two segments, no `.git` suffix required or refused. */
75
+ export declare function isOwnerRepo(value: string): boolean;
76
+ //# sourceMappingURL=refs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/client/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,2GAA2G;AAC3G,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GAC/C;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAKN,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,qGACsE,CAAC;AAErG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAsC/D;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAIxD;AAED,qGAAqG;AACrG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWnD;AAED,oFAAoF;AACpF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,oBAAoB;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,sHACqF,CAAC;AAItH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAkBnE;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,MAAM,CAE5E;AAID,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGlD"}
package/client/refs.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The two grammars a user types at any marketplace surface, and the words
3
+ * every client uses for what they name.
4
+ *
5
+ * `install [marketplace/]name[@version]`: the plugin's name in its
6
+ * marketplace, optionally qualified by the marketplace's name and pinned to
7
+ * the version the entry's manifest must carry. Both names obey the plugin
8
+ * name rule (`isValidPluginName`: lowercase letters, digits, `.` and `-`,
9
+ * never `/` or `@`), which is what makes the grammar unambiguous: the first
10
+ * `/` splits the marketplace off, the first `@` splits the version off, and
11
+ * anything else is not a ref. A path (`./thermos`, `/tmp/x`, `..`) is the
12
+ * one likely mistake and gets its own sentence, so a client can point at
13
+ * the command that installs a folder.
14
+ *
15
+ * A GitHub marketplace source: `owner/repo[@ref]`, or the URL a browser
16
+ * shows for that repository (`https://github.com/owner/repo[/tree/<ref>]`),
17
+ * normalised to one record. Every vendor keeps its marketplace file at the
18
+ * repository root, so a subdirectory grammar is not offered.
19
+ *
20
+ * Refusals are returned, not thrown: the CLI wraps one in its `UsageError`
21
+ * and the console renders it, and the sentence is the same in both. A
22
+ * client adds the second paragraph naming its own command.
23
+ */
24
+ import { isValidPluginName } from "../detect.js";
25
+ /** The built-in marketplace's name: the prefix in `install stigmer/<plugin>`; reserved in every client. */
26
+ export const OFFICIAL_MARKETPLACE_NAME = "stigmer";
27
+ /** The characters a `@version` may carry: the plugin's tag pattern, so what a ref pins is a tag the server can hold. */
28
+ const VERSION_PATTERN = /^[a-zA-Z0-9._-]+$/;
29
+ /** The grammar in the user's terms, quoted by every refusal. */
30
+ export const INSTALL_REF_SHAPE = "[marketplace/]name[@version], for example 'thermos', 'cursor-plugins/thermos' or 'thermos@1.0.0'";
31
+ export function parseInstallRef(text) {
32
+ if (looksLikePath(text)) {
33
+ return {
34
+ ok: false,
35
+ kind: "path",
36
+ message: `'${text}' is a path, and 'install' takes a plugin's name in a marketplace`,
37
+ };
38
+ }
39
+ let rest = text;
40
+ let marketplace;
41
+ const slash = rest.indexOf("/");
42
+ if (slash !== -1) {
43
+ marketplace = rest.slice(0, slash);
44
+ rest = rest.slice(slash + 1);
45
+ if (!isValidPluginName(marketplace)) {
46
+ return { ok: false, kind: "shape", message: `'${marketplace}' is not a marketplace name` };
47
+ }
48
+ }
49
+ let version;
50
+ const at = rest.indexOf("@");
51
+ if (at !== -1) {
52
+ version = rest.slice(at + 1);
53
+ rest = rest.slice(0, at);
54
+ if (version === "" || !VERSION_PATTERN.test(version)) {
55
+ return { ok: false, kind: "shape", message: `'${version}' is not a version` };
56
+ }
57
+ }
58
+ if (!isValidPluginName(rest)) {
59
+ return { ok: false, kind: "shape", message: `'${rest}' is not a plugin name` };
60
+ }
61
+ return {
62
+ ok: true,
63
+ ref: {
64
+ ...(marketplace !== undefined && { marketplace }),
65
+ name: rest,
66
+ ...(version !== undefined && { version }),
67
+ },
68
+ };
69
+ }
70
+ /** The grammar's own text for a ref, the inverse of `parseInstallRef`. */
71
+ export function formatInstallRef(ref) {
72
+ const prefix = ref.marketplace === undefined ? "" : `${ref.marketplace}/`;
73
+ const suffix = ref.version === undefined ? "" : `@${ref.version}`;
74
+ return `${prefix}${ref.name}${suffix}`;
75
+ }
76
+ /** Whether `text` reads as a filesystem path on any platform (relative, home, absolute, Windows). */
77
+ export function looksLikePath(text) {
78
+ return (text === "." ||
79
+ text === ".." ||
80
+ text.startsWith("./") ||
81
+ text.startsWith("../") ||
82
+ text.startsWith("~") ||
83
+ text.startsWith("/") ||
84
+ text.includes("\\") ||
85
+ /^[A-Za-z]:/.test(text));
86
+ }
87
+ /** What a GitHub source may be typed as, quoted by every refusal. */
88
+ export const GITHUB_SOURCE_SHAPE = "a GitHub repository as 'owner/repo' or 'owner/repo@ref', or that repository's URL (https://github.com/owner/repo)";
89
+ const GITHUB_URL = /^https?:\/\/(?:www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+?)(?:\.git)?(?:\/tree\/([^\s#?]+))?\/?$/;
90
+ /**
91
+ * `owner/repo[@ref]` or a github.com URL as one source. The URL form is
92
+ * tried first because it contains a `/` too; a text that is neither is
93
+ * refused with the shape.
94
+ */
95
+ export function parseGitHubSource(text) {
96
+ const url = GITHUB_URL.exec(text);
97
+ if (url !== null) {
98
+ const repo = `${url[1] ?? ""}/${url[2] ?? ""}`;
99
+ if (!isOwnerRepo(repo)) {
100
+ return { ok: false, message: `'${text}' does not name a GitHub repository` };
101
+ }
102
+ const ref = url[3];
103
+ return { ok: true, source: { type: "github", repo, ...(ref !== undefined && ref !== "" && { ref }) } };
104
+ }
105
+ const at = text.indexOf("@");
106
+ const repo = at === -1 ? text : text.slice(0, at);
107
+ const ref = at === -1 ? undefined : text.slice(at + 1);
108
+ if (isOwnerRepo(repo) && (ref === undefined || ref !== "")) {
109
+ return { ok: true, source: { type: "github", repo, ...(ref !== undefined && { ref }) } };
110
+ }
111
+ return { ok: false, message: `'${text}' is not a GitHub 'owner/repo'` };
112
+ }
113
+ /** One phrase naming a GitHub source, for lists and sentences. */
114
+ export function describeGitHubSource(source) {
115
+ return source.ref === undefined ? `github.com/${source.repo}` : `github.com/${source.repo}@${source.ref}`;
116
+ }
117
+ const OWNER_REPO_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
118
+ /** `owner/repo` as GitHub spells it: two segments, no `.git` suffix required or refused. */
119
+ export function isOwnerRepo(value) {
120
+ const parts = value.split("/");
121
+ return parts.length === 2 && parts.every((part) => OWNER_REPO_SEGMENT.test(part) && part !== "." && part !== "..");
122
+ }
123
+ //# sourceMappingURL=refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.js","sourceRoot":"","sources":["../../src/client/refs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Which files of a tree a plugin push carries, and in what order.
3
+ *
4
+ * A client that installs a plugin holds a tree: the CLI a directory on disk,
5
+ * the console the file list a marketplace host returned. Both must choose
6
+ * the same files in the same order, because the archive's bytes are the
7
+ * plugin's identity on the server (`status.digest` is the SHA-256 of what
8
+ * it received) and the order of the archive's entries is part of those
9
+ * bytes. This module is that choice, made once.
10
+ *
11
+ * The rule is the CLI's original walk (`stigmer push plugin <dir>`): at each
12
+ * directory the names are sorted by code point, files and directories
13
+ * interleaved by name, and a directory the matcher ignores is skipped whole,
14
+ * so no negation inside it can pull a file back. Symlinks never appear (the
15
+ * CLI walker skips them; a hosted tree has none the client asks for).
16
+ * `compareWalkOrder` states that order for a flat list of paths so a client
17
+ * with no directory to walk arrives at the same sequence; it is NOT the plain
18
+ * code-point order of full paths (`b/x.txt` sorts before `b.txt` here,
19
+ * because the directory `b` sorts before the file `b.txt` at their level).
20
+ * The reader in `read-plugin-package.ts` indexes entries by path and is
21
+ * indifferent to their order; only the archive cares.
22
+ *
23
+ * `.gitignore` and `.stigmerignore` are read from the root of the tree being
24
+ * selected, as the CLI does, never from an ancestor: a marketplace entry is
25
+ * its own plugin root.
26
+ */
27
+ import type { PluginFiles } from "../files.js";
28
+ import { type Matcher } from "./ignore/matcher.js";
29
+ /** The tree a client holds before selection: every file it could offer, with its size. */
30
+ export interface CandidateFile {
31
+ /** Root-relative POSIX path, no leading `./`. */
32
+ readonly path: string;
33
+ readonly size: number;
34
+ }
35
+ /** What the selection included and left out, for the summary line every client prints. */
36
+ export interface SelectionStats {
37
+ filesIncluded: number;
38
+ filesIgnored: number;
39
+ /** Ignored directories that held at least one candidate; a tree lists no empty directories. */
40
+ dirsSkipped: number;
41
+ totalSize: number;
42
+ }
43
+ export interface SelectPluginFilesOptions {
44
+ /** Whether the tree's own root `.gitignore` applies (every push says yes unless asked otherwise). */
45
+ readonly respectGitignore: boolean;
46
+ readonly extraIgnore?: readonly string[];
47
+ readonly extraInclude?: readonly string[];
48
+ }
49
+ export interface PluginSelection {
50
+ readonly files: PluginFiles;
51
+ readonly stats: SelectionStats;
52
+ /** The matcher the selection ran, for diagnostics (`push --dry-run` lists its patterns). */
53
+ readonly matcher: Matcher;
54
+ }
55
+ /** The two files at a tree's root that shape its own selection. */
56
+ export declare const IGNORE_FILE_NAMES: {
57
+ readonly gitignore: ".gitignore";
58
+ readonly stigmerignore: ".stigmerignore";
59
+ };
60
+ /**
61
+ * Select the files a push of `candidates` carries. `read` yields a listed
62
+ * candidate's bytes and is called here only for the two ignore files at the
63
+ * root, so a client may hand over a lazy reader and pay for the rest only
64
+ * when the archive is built.
65
+ */
66
+ export declare function selectPluginFiles(candidates: readonly CandidateFile[], read: (path: string) => Uint8Array, options: SelectPluginFilesOptions): PluginSelection;
67
+ /**
68
+ * The order a sorted directory walk visits files: component by component,
69
+ * each compared by code point. Two paths never share a prefix that is a
70
+ * file in one and a directory in the other, so a shorter path is never a
71
+ * prefix of a longer one at the point they differ.
72
+ */
73
+ export declare function compareWalkOrder(a: string, b: string): number;
74
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/client/select.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAsB,KAAK,OAAO,EAAwD,MAAM,qBAAqB,CAAC;AAE7H,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,qGAAqG;IACrG,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,mEAAmE;AACnE,eAAO,MAAM,iBAAiB;;;CAGpB,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,EAClC,OAAO,EAAE,wBAAwB,GAChC,eAAe,CAwCjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAU7D"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Which files of a tree a plugin push carries, and in what order.
3
+ *
4
+ * A client that installs a plugin holds a tree: the CLI a directory on disk,
5
+ * the console the file list a marketplace host returned. Both must choose
6
+ * the same files in the same order, because the archive's bytes are the
7
+ * plugin's identity on the server (`status.digest` is the SHA-256 of what
8
+ * it received) and the order of the archive's entries is part of those
9
+ * bytes. This module is that choice, made once.
10
+ *
11
+ * The rule is the CLI's original walk (`stigmer push plugin <dir>`): at each
12
+ * directory the names are sorted by code point, files and directories
13
+ * interleaved by name, and a directory the matcher ignores is skipped whole,
14
+ * so no negation inside it can pull a file back. Symlinks never appear (the
15
+ * CLI walker skips them; a hosted tree has none the client asks for).
16
+ * `compareWalkOrder` states that order for a flat list of paths so a client
17
+ * with no directory to walk arrives at the same sequence; it is NOT the plain
18
+ * code-point order of full paths (`b/x.txt` sorts before `b.txt` here,
19
+ * because the directory `b` sorts before the file `b.txt` at their level).
20
+ * The reader in `read-plugin-package.ts` indexes entries by path and is
21
+ * indifferent to their order; only the archive cares.
22
+ *
23
+ * `.gitignore` and `.stigmerignore` are read from the root of the tree being
24
+ * selected, as the CLI does, never from an ancestor: a marketplace entry is
25
+ * its own plugin root.
26
+ */
27
+ import { SOURCE_GITIGNORE, SOURCE_STIGMERIGNORE, buildMatcher } from "./ignore/matcher.js";
28
+ /** The two files at a tree's root that shape its own selection. */
29
+ export const IGNORE_FILE_NAMES = {
30
+ gitignore: SOURCE_GITIGNORE,
31
+ stigmerignore: SOURCE_STIGMERIGNORE,
32
+ };
33
+ /**
34
+ * Select the files a push of `candidates` carries. `read` yields a listed
35
+ * candidate's bytes and is called here only for the two ignore files at the
36
+ * root, so a client may hand over a lazy reader and pay for the rest only
37
+ * when the archive is built.
38
+ */
39
+ export function selectPluginFiles(candidates, read, options) {
40
+ const byPath = new Map(candidates.map((candidate) => [candidate.path, candidate]));
41
+ const sources = {
42
+ includeDefaults: true,
43
+ ...(options.respectGitignore &&
44
+ byPath.has(IGNORE_FILE_NAMES.gitignore) && { gitignore: decode(read(IGNORE_FILE_NAMES.gitignore)) }),
45
+ ...(byPath.has(IGNORE_FILE_NAMES.stigmerignore) && {
46
+ stigmerignore: decode(read(IGNORE_FILE_NAMES.stigmerignore)),
47
+ }),
48
+ ...(options.extraIgnore !== undefined && { extraIgnore: options.extraIgnore }),
49
+ ...(options.extraInclude !== undefined && { extraInclude: options.extraInclude }),
50
+ };
51
+ const matcher = buildMatcher(sources);
52
+ const stats = { filesIncluded: 0, filesIgnored: 0, dirsSkipped: 0, totalSize: 0 };
53
+ const skippedDirs = new Set();
54
+ const entries = [];
55
+ for (const candidate of [...candidates].sort((a, b) => compareWalkOrder(a.path, b.path))) {
56
+ if (underSkippedDir(candidate.path, matcher, skippedDirs, stats))
57
+ continue;
58
+ if (matcher.matchWithReason(candidate.path, false).ignored) {
59
+ stats.filesIgnored++;
60
+ continue;
61
+ }
62
+ entries.push({ path: candidate.path, size: candidate.size });
63
+ stats.filesIncluded++;
64
+ stats.totalSize += candidate.size;
65
+ }
66
+ return {
67
+ files: {
68
+ entries,
69
+ read(path) {
70
+ if (!byPath.has(path))
71
+ throw new Error(`plugin file '${path}' is not listed`);
72
+ return read(path);
73
+ },
74
+ },
75
+ stats,
76
+ matcher,
77
+ };
78
+ }
79
+ /**
80
+ * The order a sorted directory walk visits files: component by component,
81
+ * each compared by code point. Two paths never share a prefix that is a
82
+ * file in one and a directory in the other, so a shorter path is never a
83
+ * prefix of a longer one at the point they differ.
84
+ */
85
+ export function compareWalkOrder(a, b) {
86
+ const as = a.split("/");
87
+ const bs = b.split("/");
88
+ const depth = Math.min(as.length, bs.length);
89
+ for (let i = 0; i < depth; i++) {
90
+ const x = as[i] ?? "";
91
+ const y = bs[i] ?? "";
92
+ if (x !== y)
93
+ return x < y ? -1 : 1;
94
+ }
95
+ return as.length - bs.length;
96
+ }
97
+ /**
98
+ * True when an ancestor directory of `path` is ignored as a directory. The
99
+ * walk checks a directory once and skips its subtree, so each ignored
100
+ * directory counts once whatever it holds.
101
+ */
102
+ function underSkippedDir(path, matcher, skipped, stats) {
103
+ const parts = path.split("/");
104
+ let dir = "";
105
+ for (let i = 0; i < parts.length - 1; i++) {
106
+ dir = dir === "" ? (parts[i] ?? "") : `${dir}/${parts[i] ?? ""}`;
107
+ if (skipped.has(dir))
108
+ return true;
109
+ if (matcher.matchWithReason(dir, true).ignored) {
110
+ skipped.add(dir);
111
+ stats.dirsSkipped++;
112
+ return true;
113
+ }
114
+ }
115
+ return false;
116
+ }
117
+ const decoder = new TextDecoder();
118
+ function decode(bytes) {
119
+ return decoder.decode(bytes);
120
+ }
121
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/client/select.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The words every client prints for the same thing.
3
+ *
4
+ * The CLI's `validate -f`, `push plugin --dry-run` and `install`, and the
5
+ * console's install preview, all describe a package to the user; a
6
+ * platform where the same fact is named differently on two surfaces has
7
+ * two vocabularies. The labels here are the docs' words
8
+ * (`docs/vocabulary.md`), kept once.
9
+ */
10
+ import type { PluginDialect } from "../types.js";
11
+ /** Human labels for the four dialects, in the vocabulary the docs use. */
12
+ export declare const DIALECT_LABELS: Readonly<Record<PluginDialect, string>>;
13
+ //# sourceMappingURL=vocabulary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/client/vocabulary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,0EAA0E;AAC1E,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAKlE,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The words every client prints for the same thing.
3
+ *
4
+ * The CLI's `validate -f`, `push plugin --dry-run` and `install`, and the
5
+ * console's install preview, all describe a package to the user; a
6
+ * platform where the same fact is named differently on two surfaces has
7
+ * two vocabularies. The labels here are the docs' words
8
+ * (`docs/vocabulary.md`), kept once.
9
+ */
10
+ /** Human labels for the four dialects, in the vocabulary the docs use. */
11
+ export const DIALECT_LABELS = {
12
+ "agent-plugins": "Agent Plugins 1.0",
13
+ claude: "Claude Code plugin",
14
+ cursor: "Cursor plugin",
15
+ codex: "Codex plugin",
16
+ };
17
+ //# sourceMappingURL=vocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vocabulary.js","sourceRoot":"","sources":["../../src/client/vocabulary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAA4C;IACrE,eAAe,EAAE,mBAAmB;IACpC,MAAM,EAAE,oBAAoB;IAC5B,MAAM,EAAE,eAAe;IACvB,KAAK,EAAE,cAAc;CACtB,CAAC"}
package/client.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@stigmer/plugin-package/client`: what every client that installs a
3
+ * plugin does identically.
4
+ *
5
+ * The main entry reads a package; this entry is for the client that holds
6
+ * a tree and must turn it into the push the server expects. Two clients do
7
+ * that today, the CLI from a directory and the console from a marketplace
8
+ * host's file list, and a plugin installed from either must have one
9
+ * digest for one tree, be refused with one sentence for one mistake, and be
10
+ * described in one vocabulary. So the pieces live here, once:
11
+ *
12
+ * - the gitignore-compatible ignore engine and the security defaults
13
+ * (`ignore/`), pure over the text of the ignore files;
14
+ * - the selection rule (`select.ts`): which files a push carries, in the
15
+ * order the CLI's original walk produced them;
16
+ * - the archive and its digest (`archive.ts`): the deterministic zip and
17
+ * the SHA-256 the server records;
18
+ * - the grammars (`refs.ts`): an install ref and a GitHub marketplace
19
+ * source, with their refusal sentences;
20
+ * - the vocabulary (`vocabulary.ts`): the labels both surfaces print.
21
+ *
22
+ * Nothing here touches the filesystem, the network or any `node:*` module.
23
+ * A client's edge (a directory walk, a fetch) produces the candidates and
24
+ * the bytes; this entry decides what becomes of them.
25
+ */
26
+ export { DEFAULT_PATTERNS } from "./client/ignore/defaults.js";
27
+ export { matchName } from "./client/ignore/match.js";
28
+ export { type IgnoreSources, type MatchReason, Matcher, Reason, REASON_TEXT, SOURCE_CLI, SOURCE_DEFAULTS, SOURCE_GITIGNORE, SOURCE_STIGMERIGNORE, buildMatcher, } from "./client/ignore/matcher.js";
29
+ export { MatchResult, type Pattern, parsePattern } from "./client/ignore/pattern.js";
30
+ export { type CandidateFile, type PluginSelection, type SelectPluginFilesOptions, type SelectionStats, IGNORE_FILE_NAMES, compareWalkOrder, selectPluginFiles, } from "./client/select.js";
31
+ export { DETERMINISTIC_ZIP_MTIME, archivePlugin, digestArchive } from "./client/archive.js";
32
+ export { type GitHubMarketplaceSource, type GitHubSourceOutcome, type InstallRef, type InstallRefOutcome, GITHUB_SOURCE_SHAPE, INSTALL_REF_SHAPE, OFFICIAL_MARKETPLACE_NAME, describeGitHubSource, formatInstallRef, isOwnerRepo, looksLikePath, parseGitHubSource, parseInstallRef, } from "./client/refs.js";
33
+ export { DIALECT_LABELS } from "./client/vocabulary.js";
34
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,OAAO,EACP,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}