@danieldeusing/seedr 0.1.135 → 0.1.136

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.
package/README.md CHANGED
@@ -34,6 +34,10 @@ paths, no IP stored (the server keeps only the country it derives from the reque
34
34
  Events are deleted after 90 days. Nothing is sent for failed installs or `--dry-run`, and a
35
35
  failing endpoint never affects an installation.
36
36
 
37
+ Only installs from seedr's own registry are counted. With `SEEDR_REGISTRY_URL` or
38
+ `SEEDR_REGISTRY_DIR` pointing at another registry nothing is sent, unless
39
+ `SEEDR_ANALYTICS_URL` names that registry's own endpoint.
40
+
37
41
  Opt out by setting the variable to any value:
38
42
 
39
43
  ```bash
@@ -4,7 +4,7 @@
4
4
  import { readFile as readFile3, readdir, writeFile as writeFile2, mkdir as mkdir2, mkdtemp, rm as rm2 } from "fs/promises";
5
5
  import { existsSync as existsSync11 } from "fs";
6
6
  import { tmpdir } from "os";
7
- import { join as join13, dirname as dirname6 } from "path";
7
+ import { basename as basename6, join as join13, dirname as dirname6 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // ../registry-ops/src/paths.ts
@@ -519,6 +519,7 @@ function resolveLocalRegistryPath() {
519
519
  var REGISTRY_PATH = process.env.SEEDR_REGISTRY_DIR || resolveLocalRegistryPath();
520
520
  var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/danieldeusing/seedr/main/registry";
521
521
  var REGISTRY_URL = (process.env.SEEDR_REGISTRY_URL || DEFAULT_REGISTRY_URL).replace(/\/+$/, "");
522
+ var USES_DEFAULT_REGISTRY = !process.env.SEEDR_REGISTRY_DIR && REGISTRY_URL === DEFAULT_REGISTRY_URL && (REGISTRY_PATH === null || basename6(REGISTRY_PATH) === DEFAULT_REGISTRY_DIR);
522
523
  var REGISTRY_ROOT_URL = REGISTRY_URL.replace(/\/registry$/, "");
523
524
  function assertSafeNodeName(name) {
524
525
  if (name === "" || name === "." || name.includes("/") || name.includes("\\") || name.includes("..")) {
@@ -1368,6 +1369,7 @@ export {
1368
1369
  copyDirectory,
1369
1370
  parseGitHubRepo,
1370
1371
  getEffectiveSourceRevision,
1372
+ USES_DEFAULT_REGISTRY,
1371
1373
  loadManifest,
1372
1374
  getItem,
1373
1375
  getItemsBySlug,
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  CODING_AGENTS,
7
7
  MAX_SLUG_LENGTH,
8
8
  SLUG_PATTERN,
9
+ USES_DEFAULT_REGISTRY,
9
10
  assertDirectoryWithin,
10
11
  assertOverwritable,
11
12
  assertSafePathSegment,
@@ -67,7 +68,7 @@ import {
67
68
  writeFileAtomic,
68
69
  writeJson,
69
70
  writeTextFile
70
- } from "./chunk-7RMRI2TO.js";
71
+ } from "./chunk-D4Y67NUX.js";
71
72
 
72
73
  // src/cli.ts
73
74
  import { Command as Command5 } from "commander";
@@ -141,17 +142,21 @@ function validateType(value) {
141
142
  // src/utils/analytics.ts
142
143
  var ANALYTICS_URL = "https://seedr.danieldeusing.de/api/installs";
143
144
  var TELEMETRY_OPT_OUT_VARIABLE = "SEEDR_NO_TELEMETRY";
145
+ var ANALYTICS_URL_VARIABLE = "SEEDR_ANALYTICS_URL";
144
146
  var REQUEST_TIMEOUT_MS = 4e3;
145
- var TELEMETRY_HELP_TEXT = `Sends one anonymous install event per successful agent target to ${ANALYTICS_URL}; set ${TELEMETRY_OPT_OUT_VARIABLE}=1 to disable`;
147
+ var TELEMETRY_HELP_TEXT = `Sends one anonymous install event per successful agent target to ${ANALYTICS_URL} (installs from another registry send nothing unless ${ANALYTICS_URL_VARIABLE} names an endpoint); set ${TELEMETRY_OPT_OUT_VARIABLE}=1 to disable`;
146
148
  function isTelemetryDisabled(env = process.env) {
147
149
  return env[TELEMETRY_OPT_OUT_VARIABLE] !== void 0;
148
150
  }
151
+ function analyticsEndpoint(env = process.env) {
152
+ return env[ANALYTICS_URL_VARIABLE] || (USES_DEFAULT_REGISTRY ? ANALYTICS_URL : null);
153
+ }
149
154
  function cliVersion() {
150
- return true ? "0.1.135" : "dev";
155
+ return true ? "0.1.136" : "dev";
151
156
  }
152
- async function sendEvent(event) {
157
+ async function sendEvent(endpoint, event) {
153
158
  try {
154
- await fetch(ANALYTICS_URL, {
159
+ await fetch(endpoint, {
155
160
  method: "POST",
156
161
  headers: { "Content-Type": "application/json" },
157
162
  body: JSON.stringify(event),
@@ -161,9 +166,10 @@ async function sendEvent(event) {
161
166
  }
162
167
  }
163
168
  function trackInstalls(slug, type, results, scope) {
164
- if (isTelemetryDisabled()) return Promise.resolve();
169
+ const endpoint = analyticsEndpoint();
170
+ if (isTelemetryDisabled() || endpoint === null) return Promise.resolve();
165
171
  const version = cliVersion();
166
- const sends = results.filter((result) => result.success).map((result) => sendEvent({ slug, type, tool: result.agent, scope, version }));
172
+ const sends = results.filter((result) => result.success).map((result) => sendEvent(endpoint, { slug, type, tool: result.agent, scope, version }));
167
173
  return Promise.all(sends).then(() => void 0);
168
174
  }
169
175
 
@@ -907,32 +913,59 @@ function transportOf(config) {
907
913
  function definedEntries(object) {
908
914
  return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
909
915
  }
916
+ var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
917
+ var WHOLE_PLACEHOLDER = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
918
+ var BEARER_PLACEHOLDER = /^Bearer \$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
919
+ var toOpenCodeRef = (value) => value.replace(PLACEHOLDER, "{env:$1}");
920
+ function mapValues(record, map) {
921
+ return record && Object.fromEntries(Object.entries(record).map(([key, value]) => [key, map(value)]));
922
+ }
910
923
  function toOpenCodeServer(config) {
911
924
  if (transportOf(config) === "stdio") {
912
925
  if (!config.command) throw new Error(NO_COMMAND_ERROR);
913
926
  return definedEntries({
914
927
  type: "local",
915
- command: [config.command, ...config.args ?? []],
916
- environment: config.env,
928
+ command: [config.command, ...config.args ?? []].map(toOpenCodeRef),
929
+ environment: mapValues(config.env, toOpenCodeRef),
917
930
  enabled: true
918
931
  });
919
932
  }
920
933
  if (!config.url) throw new Error(NO_URL_ERROR);
921
- return definedEntries({ type: "remote", url: config.url, headers: config.headers, enabled: true });
922
- }
923
- function codexMainEntries(config) {
934
+ return definedEntries({ type: "remote", url: toOpenCodeRef(config.url), headers: mapValues(config.headers, toOpenCodeRef), enabled: true });
935
+ }
936
+ function codexPassthrough(config) {
937
+ const env = {};
938
+ const envVars = [];
939
+ for (const [key, value] of Object.entries(config.env ?? {})) {
940
+ if (WHOLE_PLACEHOLDER.exec(value)?.[1] === key) envVars.push(key);
941
+ else env[key] = value;
942
+ }
943
+ const headers = {};
944
+ const envHeaders = {};
945
+ let bearer = config.bearer_token_env_var || void 0;
946
+ for (const [key, value] of Object.entries(config.headers ?? {})) {
947
+ const bearerVar = key.toLowerCase() === "authorization" ? BEARER_PLACEHOLDER.exec(value)?.[1] : void 0;
948
+ const wholeVar = WHOLE_PLACEHOLDER.exec(value)?.[1];
949
+ if (bearerVar && !bearer) bearer = bearerVar;
950
+ else if (wholeVar) envHeaders[key] = wholeVar;
951
+ else headers[key] = value;
952
+ }
953
+ return { env, envVars, headers, envHeaders, bearer };
954
+ }
955
+ function codexMainEntries(config, passthrough) {
924
956
  if (transportOf(config) === "stdio") {
925
957
  if (!config.command) throw new Error(NO_COMMAND_ERROR);
926
958
  return definedEntries({
927
959
  command: config.command,
928
960
  args: config.args && config.args.length > 0 ? config.args : void 0,
929
- cwd: config.cwd || void 0
961
+ cwd: config.cwd || void 0,
962
+ env_vars: passthrough.envVars.length > 0 ? passthrough.envVars : void 0
930
963
  });
931
964
  }
932
965
  if (!config.url) throw new Error(NO_URL_ERROR);
933
966
  return definedEntries({
934
967
  url: config.url,
935
- bearer_token_env_var: config.bearer_token_env_var || void 0
968
+ bearer_token_env_var: passthrough.bearer
936
969
  });
937
970
  }
938
971
  function hasEntries(record) {
@@ -940,12 +973,16 @@ function hasEntries(record) {
940
973
  }
941
974
  function toCodexTables(name, config) {
942
975
  const base = [CODEX_TABLE, name];
943
- const tables = [{ keyPath: base, entries: codexMainEntries(config) }];
944
- if (hasEntries(config.env)) {
945
- tables.push({ keyPath: [...base, "env"], entries: { ...config.env } });
976
+ const passthrough = codexPassthrough(config);
977
+ const tables = [{ keyPath: base, entries: codexMainEntries(config, passthrough) }];
978
+ if (hasEntries(passthrough.env)) {
979
+ tables.push({ keyPath: [...base, "env"], entries: passthrough.env });
980
+ }
981
+ if (hasEntries(passthrough.headers)) {
982
+ tables.push({ keyPath: [...base, "http_headers"], entries: passthrough.headers });
946
983
  }
947
- if (hasEntries(config.headers)) {
948
- tables.push({ keyPath: [...base, "http_headers"], entries: { ...config.headers } });
984
+ if (hasEntries(passthrough.envHeaders)) {
985
+ tables.push({ keyPath: [...base, "env_http_headers"], entries: passthrough.envHeaders });
949
986
  }
950
987
  return tables;
951
988
  }
@@ -2113,19 +2150,22 @@ var claudeTarget = {
2113
2150
  kind: "file",
2114
2151
  dir: (scope, cwd) => join7(scope === "user" ? claudeUserRoot() : join7(cwd, ".claude"), "rules"),
2115
2152
  fileName: markdownFile,
2116
- keepsFrontmatter: true
2153
+ keepsFrontmatter: true,
2154
+ scopeKey: "paths"
2117
2155
  };
2118
2156
  var antigravityTarget = {
2119
2157
  kind: "file",
2120
2158
  dir: (scope, cwd) => join7(scope === "user" ? join7(home2, ".gemini", "config") : join7(cwd, ".agents"), "rules"),
2121
2159
  fileName: markdownFile,
2122
- keepsFrontmatter: true
2160
+ keepsFrontmatter: true,
2161
+ scopeKey: "glob"
2123
2162
  };
2124
2163
  var copilotTarget = {
2125
2164
  kind: "file",
2126
2165
  dir: (scope, cwd) => join7(scope === "user" ? copilotUserRoot() : join7(cwd, ".github"), "instructions"),
2127
2166
  fileName: (slug) => `${slug}.instructions.md`,
2128
- keepsFrontmatter: true
2167
+ keepsFrontmatter: true,
2168
+ scopeKey: "applyTo"
2129
2169
  };
2130
2170
  var codexTarget = {
2131
2171
  kind: "section",
@@ -2159,9 +2199,59 @@ function stripFrontmatter(content) {
2159
2199
  const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(content);
2160
2200
  return match ? content.slice(match[0].length) : content;
2161
2201
  }
2202
+ var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/;
2203
+ var SCOPE_LINE = /^(applyTo|paths|glob):(.*)$/;
2204
+ var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/, "$2").trim();
2205
+ function continuationLength(lines, index) {
2206
+ let count = 0;
2207
+ while (index + count + 1 < lines.length && /^(\s+\S|-\s)/.test(lines[index + count + 1])) count++;
2208
+ return count;
2209
+ }
2210
+ function splitGlobs(value) {
2211
+ const parts = [""];
2212
+ let depth = 0;
2213
+ for (const char of value) {
2214
+ if (char === "{") depth++;
2215
+ else if (char === "}") depth = Math.max(0, depth - 1);
2216
+ if (char === "," && depth === 0) parts.push("");
2217
+ else parts[parts.length - 1] += char;
2218
+ }
2219
+ return parts;
2220
+ }
2221
+ function ruleScope(content) {
2222
+ const lines = FRONTMATTER.exec(content)?.[1]?.split(/\r?\n/) ?? [];
2223
+ for (let i = 0; i < lines.length; i++) {
2224
+ const inline = SCOPE_LINE.exec(lines[i])?.[2]?.trim();
2225
+ if (inline === void 0) continue;
2226
+ const values = inline ? splitGlobs(inline.startsWith("[") ? inline.slice(1, -1) : unquote(inline)) : lines.slice(i + 1, i + 1 + continuationLength(lines, i)).map((line) => line.replace(/^\s*-\s*/, ""));
2227
+ return values.map(unquote).filter((glob) => glob.length > 0);
2228
+ }
2229
+ return [];
2230
+ }
2231
+ function withScopeKey(content, key) {
2232
+ const globs = ruleScope(content);
2233
+ const match = FRONTMATTER.exec(content);
2234
+ if (globs.length === 0 || !match) return content;
2235
+ const lines = match[1].split(/\r?\n/);
2236
+ if (lines.some((line) => SCOPE_LINE.exec(line)?.[1] === key)) return content;
2237
+ const kept = [];
2238
+ for (let i = 0; i < lines.length; i++) {
2239
+ if (SCOPE_LINE.test(lines[i])) i += continuationLength(lines, i);
2240
+ else kept.push(lines[i]);
2241
+ }
2242
+ const scope = key === "paths" ? ["paths:", ...globs.map((glob) => ` - ${JSON.stringify(glob)}`)] : [`${key}: ${JSON.stringify(globs.join(", "))}`];
2243
+ return `---
2244
+ ${[...kept, ...scope].join("\n")}
2245
+ ---${match[2]}${content.slice(match[0].length)}`;
2246
+ }
2247
+ var scopeProse = (globs) => `Applies to files matching ${globs.map((glob) => `\`${glob}\``).join(", ")}.`;
2162
2248
  function upsertSection(document, slug, content) {
2249
+ const globs = ruleScope(content);
2250
+ const body = stripFrontmatter(content).trim();
2163
2251
  const block = `${sectionStart(slug)}
2164
- ${stripFrontmatter(content).trim()}
2252
+ ${globs.length > 0 ? `${scopeProse(globs)}
2253
+
2254
+ ` : ""}${body}
2165
2255
  ${sectionEnd(slug)}`;
2166
2256
  const existing = sectionPattern(slug);
2167
2257
  if (existing.test(document)) {
@@ -2220,7 +2310,7 @@ async function installRuleForAgent(item, agent, scope, force, cwd, content) {
2220
2310
  await assertOverwritable(destination, force);
2221
2311
  await writeFile2(
2222
2312
  destination,
2223
- target.keepsFrontmatter ? content : stripFrontmatter(content),
2313
+ target.keepsFrontmatter ? withScopeKey(content, target.scopeKey) : stripFrontmatter(content),
2224
2314
  "utf-8"
2225
2315
  );
2226
2316
  } else {
@@ -2279,6 +2369,8 @@ async function getInstalledRules(agent, scope, cwd = process.cwd()) {
2279
2369
  async function planRule(item, agents, scope, _method, cwd) {
2280
2370
  assertValidSlug(item.slug, SLUG_LABEL4);
2281
2371
  const changes = [];
2372
+ const merges = agents.some((agent) => ruleTargetFor(agent).kind === "section");
2373
+ const globs = merges ? ruleScope(await readRuleContent(item)) : [];
2282
2374
  for (const agent of agents) {
2283
2375
  const target = ruleTargetFor(agent);
2284
2376
  const destination = await destinationFor(target, item.slug, scope, cwd);
@@ -2287,7 +2379,7 @@ async function planRule(item, agents, scope, _method, cwd) {
2287
2379
  agent,
2288
2380
  kind: present ? "modify" : "create",
2289
2381
  path: destination,
2290
- detail: target.kind === "file" ? "rule file" : `merged section <!-- seedr:rule:${item.slug} --> (the rest of the file is untouched)`
2382
+ detail: target.kind === "file" ? "rule file" : `merged section <!-- seedr:rule:${item.slug} --> (the rest of the file is untouched)` + (globs.length > 0 ? `; ${CODING_AGENTS[agent].name} has no path scope, so ${globs.map((glob) => `\`${glob}\``).join(", ")} is advisory prose` : "")
2291
2383
  });
2292
2384
  }
2293
2385
  return changes;
@@ -2993,7 +3085,7 @@ var initCommand = new Command4("init").description("Initialize coding agent conf
2993
3085
  // package.json
2994
3086
  var package_default = {
2995
3087
  name: "@danieldeusing/seedr",
2996
- version: "0.1.135",
3088
+ version: "0.1.136",
2997
3089
  description: "Seed your projects with AI configurations",
2998
3090
  type: "module",
2999
3091
  bin: {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  loadManifest,
16
16
  searchItems,
17
17
  uninstallSkill
18
- } from "./chunk-7RMRI2TO.js";
18
+ } from "./chunk-D4Y67NUX.js";
19
19
  export {
20
20
  ALL_AGENTS,
21
21
  CODING_AGENTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieldeusing/seedr",
3
- "version": "0.1.135",
3
+ "version": "0.1.136",
4
4
  "description": "Seed your projects with AI configurations",
5
5
  "type": "module",
6
6
  "bin": {