glooit 0.5.4 โ†’ 0.5.6

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
@@ -29,17 +29,37 @@ glooit init
29
29
  This creates a `glooit.config.ts` file:
30
30
 
31
31
  ```typescript
32
- import { Config } from 'glooit';
32
+ import { defineRules } from 'glooit';
33
33
 
34
- export default {
34
+ export default defineRules({
35
+ configDir: '.glooit',
35
36
  rules: [
36
37
  {
37
- file: 'main.md',
38
+ name: 'main',
39
+ file: '.glooit/main.md',
38
40
  to: './',
39
41
  targets: ['claude', 'cursor', 'codex']
40
- }
41
- ]
42
- } satisfies Config;
42
+ },
43
+ {
44
+ name: 'frontend',
45
+ file: '.glooit/frontend.md',
46
+ to: './apps/frontend',
47
+ globs: './apps/frontend/{src,tests}/**/*.{ts,tsx,js,jsx,md}',
48
+ targets: [
49
+ 'claude',
50
+ 'codex',
51
+ ]
52
+ },
53
+ {
54
+ name: 'server',
55
+ file: '.glooit/server.md',
56
+ to: './apps/server',
57
+ globs: './apps/server/{src,tests}/**/*.{ts,tsx,js,jsx,md}',
58
+ targets: ['claude', 'codex']
59
+ },
60
+ ],
61
+ mcps: [],
62
+ });
43
63
  ```
44
64
 
45
65
  ### 2. Sync Your Rules
@@ -179,12 +199,14 @@ export default defineRules({
179
199
  ```
180
200
 
181
201
  **๐Ÿ•’ `addTimestamp`** - Replace `__TIMESTAMP__` with current date/time
202
+
182
203
  ```markdown
183
204
  Last updated: __TIMESTAMP__
184
205
  <!-- Becomes: Last updated: December 24, 2024, 03:30 PM -->
185
206
  ```
186
207
 
187
208
  **๐ŸŒ `replaceEnv`** - Replace `__ENV_VAR__` patterns with environment variables
209
+
188
210
  ```markdown
189
211
  Database: __ENV_DATABASE_URL__
190
212
  API Key: __ENV_API_KEY__
@@ -192,6 +214,7 @@ API Key: __ENV_API_KEY__
192
214
  ```
193
215
 
194
216
  **๐Ÿ“ `replaceStructure`** - Replace `__STRUCTURE__` with project tree
217
+
195
218
  ```markdown
196
219
  Project structure:
197
220
  __STRUCTURE__
@@ -199,6 +222,7 @@ __STRUCTURE__
199
222
  ```
200
223
 
201
224
  **๐Ÿ—œ๏ธ `compact`** - Clean up and compress your markdown
225
+
202
226
  ```typescript
203
227
  hooks.compact({
204
228
  maxConsecutiveNewlines: 2,
@@ -278,3 +302,11 @@ glooit backup list # List backups
278
302
  ## License
279
303
 
280
304
  MIT
305
+
306
+ ## Credits
307
+
308
+ Without these amazing projects, this project would not be possible.
309
+
310
+ - [Antfu](https://antfu.me) for the amazing packages
311
+ - [Ruler](https://github.com/intellectronica/ruler) Inspiration for the project & ideias
312
+ - [Claude](https://www.anthropic.com/home/claude) for the amazing AI that made this project possible in a few hours.
package/bin/glooit-linux CHANGED
Binary file
package/bin/glooit-macos CHANGED
Binary file
Binary file
package/dist/cli/index.js CHANGED
@@ -2214,7 +2214,8 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2214
2214
 
2215
2215
  class CursorWriter {
2216
2216
  formatContent(content, rule) {
2217
- const ruleName = this.extractRuleName(rule.file);
2217
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
2218
+ const ruleName = this.extractRuleName(firstFile);
2218
2219
  const hasGlobs = rule.globs && rule.globs.length > 0;
2219
2220
  const frontmatter = [
2220
2221
  "---",
@@ -2336,7 +2337,8 @@ class AgentDistributor {
2336
2337
  this.config = config;
2337
2338
  }
2338
2339
  async distributeRule(rule) {
2339
- const content = this.loadRuleContent(rule.file);
2340
+ const filePath = Array.isArray(rule.file) ? rule.file : [rule.file];
2341
+ const content = this.loadRuleContent(filePath);
2340
2342
  for (const agent of rule.targets) {
2341
2343
  await this.distributeToAgent(agent, rule, content);
2342
2344
  }
@@ -2354,7 +2356,8 @@ class AgentDistributor {
2354
2356
  if (customPath) {
2355
2357
  targetPath = customPath;
2356
2358
  } else {
2357
- const ruleName = this.extractRuleName(rule.file);
2359
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
2360
+ const ruleName = this.extractRuleName(firstFile);
2358
2361
  const agentPath = getAgentPath(agentName, ruleName);
2359
2362
  targetPath = join2(rule.to, agentPath);
2360
2363
  }
@@ -2371,11 +2374,27 @@ class AgentDistributor {
2371
2374
  const finalContent = await this.applyHooks(context);
2372
2375
  writeFileSync(targetPath, finalContent, "utf-8");
2373
2376
  }
2374
- loadRuleContent(filePath) {
2377
+ loadRuleContent(filePaths) {
2375
2378
  try {
2376
- return readFileSync3(filePath, "utf-8");
2379
+ if (filePaths.length === 1) {
2380
+ return readFileSync3(filePaths[0], "utf-8");
2381
+ }
2382
+ const mergedContent = [];
2383
+ for (let i = 0;i < filePaths.length; i++) {
2384
+ const filePath = filePaths[i];
2385
+ const content = readFileSync3(filePath, "utf-8");
2386
+ mergedContent.push(`<!-- Source: ${filePath} -->`);
2387
+ mergedContent.push(content);
2388
+ if (i < filePaths.length - 1) {
2389
+ mergedContent.push(`
2390
+ ---
2391
+ `);
2392
+ }
2393
+ }
2394
+ return mergedContent.join(`
2395
+ `);
2377
2396
  } catch (error) {
2378
- throw new Error(`Failed to read rule file ${filePath}: ${error}`);
2397
+ throw new Error(`Failed to read rule file(s): ${error}`);
2379
2398
  }
2380
2399
  }
2381
2400
  extractRuleName(filePath) {
@@ -2556,15 +2575,22 @@ class GitIgnoreManager {
2556
2575
  writeFileSync3(this.gitignorePath, cleanedContent, "utf-8");
2557
2576
  }
2558
2577
  collectGeneratedPaths() {
2578
+ if (this.config.gitignore === false) {
2579
+ return [];
2580
+ }
2559
2581
  const paths = new Set;
2560
2582
  for (const rule of this.config.rules) {
2583
+ if (rule.gitignore === false) {
2584
+ continue;
2585
+ }
2561
2586
  for (const agent of rule.targets) {
2562
2587
  const agentName = this.getAgentName(agent);
2563
2588
  const customPath = this.getCustomPath(agent);
2564
2589
  if (customPath) {
2565
2590
  paths.add(customPath);
2566
2591
  } else {
2567
- const ruleName = this.extractRuleName(rule.file);
2592
+ const filePath = Array.isArray(rule.file) ? rule.file[0] : rule.file;
2593
+ const ruleName = this.extractRuleName(filePath);
2568
2594
  const agentPath = getAgentPath(agentName, ruleName);
2569
2595
  const fullPath = `${rule.to}/${agentPath}`.replace(/\/+/g, "/");
2570
2596
  paths.add(fullPath);
@@ -2706,8 +2732,11 @@ class AIRulesCore {
2706
2732
  async validate() {
2707
2733
  try {
2708
2734
  for (const rule of this.config.rules) {
2709
- if (!existsSync5(rule.file)) {
2710
- throw new Error(`Rule file not found: ${rule.file}`);
2735
+ const files = Array.isArray(rule.file) ? rule.file : [rule.file];
2736
+ for (const file of files) {
2737
+ if (!existsSync5(file)) {
2738
+ throw new Error(`Rule file not found: ${file}`);
2739
+ }
2711
2740
  }
2712
2741
  }
2713
2742
  if (this.config.commands) {
@@ -2801,7 +2830,8 @@ class AIRulesCore {
2801
2830
  if (customPath) {
2802
2831
  paths.push(customPath);
2803
2832
  } else {
2804
- const ruleName = rule.file.split("/").pop()?.replace(".md", "") || "rule";
2833
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
2834
+ const ruleName = firstFile.split("/").pop()?.replace(".md", "") || "rule";
2805
2835
  const agentPath = getAgentPath(agentName, ruleName);
2806
2836
  let fullPath = `${rule.to}/${agentPath}`.replace(/\/+/g, "/");
2807
2837
  if (fullPath.startsWith("./")) {
@@ -2914,6 +2944,7 @@ Original error: ${jitiError}`);
2914
2944
  });
2915
2945
  c.configDir = c.configDir || ".glooit";
2916
2946
  c.mergeMcps = c.mergeMcps ?? true;
2947
+ c.gitignore = c.gitignore ?? true;
2917
2948
  if (c.backup) {
2918
2949
  const backup = c.backup;
2919
2950
  backup.enabled = backup.enabled ?? true;
@@ -2933,8 +2964,10 @@ Original error: ${jitiError}`);
2933
2964
  throw new Error(`Rule at index ${index}: Rule must be an object`);
2934
2965
  }
2935
2966
  const r = rule;
2936
- if (typeof r.file !== "string") {
2937
- throw new Error(`Rule at index ${index}: Rule.file must be a string`);
2967
+ const isFileString = typeof r.file === "string";
2968
+ const isFileArray = Array.isArray(r.file) && r.file.length > 0 && r.file.every((f) => typeof f === "string");
2969
+ if (!isFileString && !isFileArray) {
2970
+ throw new Error(`Rule at index ${index}: Rule.file must be a string or a non-empty array of strings`);
2938
2971
  }
2939
2972
  if (typeof r.to !== "string") {
2940
2973
  throw new Error(`Rule at index ${index}: Rule.to must be a string`);
@@ -2955,6 +2988,14 @@ Original error: ${jitiError}`);
2955
2988
  })) {
2956
2989
  throw new Error(`Rule at index ${index}: Rule.targets must contain valid agents: ${validAgentNames.join(", ")}, or objects with {name, to?}`);
2957
2990
  }
2991
+ if (isFileArray) {
2992
+ const allTargetsAreObjects = r.targets.every((agent) => {
2993
+ return typeof agent === "object" && agent !== null && "name" in agent && "to" in agent;
2994
+ });
2995
+ if (!allTargetsAreObjects) {
2996
+ throw new Error(`Rule at index ${index}: When using file array (merge mode), all targets must be objects with {name, to} properties`);
2997
+ }
2998
+ }
2958
2999
  }
2959
3000
  static createInitialConfig() {
2960
3001
  return this.createTypedConfig();
@@ -2998,12 +3039,15 @@ class ConfigValidator {
2998
3039
  static async validate(config) {
2999
3040
  const errors = [];
3000
3041
  for (const [index, rule] of config.rules.entries()) {
3001
- if (!existsSync7(rule.file)) {
3002
- errors.push({
3003
- field: `rules[${index}].file`,
3004
- message: `Rule file not found: ${rule.file}`,
3005
- path: rule.file
3006
- });
3042
+ const files = Array.isArray(rule.file) ? rule.file : [rule.file];
3043
+ for (const file of files) {
3044
+ if (!existsSync7(file)) {
3045
+ errors.push({
3046
+ field: `rules[${index}].file`,
3047
+ message: `Rule file not found: ${file}`,
3048
+ path: file
3049
+ });
3050
+ }
3007
3051
  }
3008
3052
  if (rule.targets.length === 0) {
3009
3053
  errors.push({
@@ -3351,7 +3395,7 @@ function constructCommand(value, args) {
3351
3395
  import { execSync } from "child_process";
3352
3396
  import { createInterface } from "readline";
3353
3397
  // package.json
3354
- var version = "0.5.4";
3398
+ var version = "0.5.6";
3355
3399
 
3356
3400
  // src/cli/index.ts
3357
3401
  var args = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -3388,8 +3388,10 @@ function validateRule(rule) {
3388
3388
  throw new Error("Rule must be an object");
3389
3389
  }
3390
3390
  const r = rule;
3391
- if (typeof r.file !== "string") {
3392
- throw new Error("Rule.file must be a string");
3391
+ const isFileString = typeof r.file === "string";
3392
+ const isFileArray = Array.isArray(r.file) && r.file.length > 0 && r.file.every((f) => typeof f === "string");
3393
+ if (!isFileString && !isFileArray) {
3394
+ throw new Error("Rule.file must be a string or a non-empty array of strings");
3393
3395
  }
3394
3396
  if (typeof r.to !== "string") {
3395
3397
  throw new Error("Rule.to must be a string");
@@ -3400,6 +3402,14 @@ function validateRule(rule) {
3400
3402
  if (!r.targets.every(isValidAgent)) {
3401
3403
  throw new Error("Rule.targets must contain valid agents: claude, cursor, codex, roocode, generic, or objects with {name, to?}");
3402
3404
  }
3405
+ if (isFileArray) {
3406
+ const allTargetsAreObjects = r.targets.every((target) => {
3407
+ return typeof target === "object" && target !== null && "name" in target && "to" in target;
3408
+ });
3409
+ if (!allTargetsAreObjects) {
3410
+ throw new Error("When using file array (merge mode), all targets must be objects with {name, to} properties");
3411
+ }
3412
+ }
3403
3413
  }
3404
3414
  function validateConfig(config) {
3405
3415
  if (!config || typeof config !== "object") {
@@ -3418,6 +3428,7 @@ function validateConfig(config) {
3418
3428
  });
3419
3429
  c.configDir = c.configDir || ".glooit";
3420
3430
  c.mergeMcps = c.mergeMcps ?? true;
3431
+ c.gitignore = c.gitignore ?? true;
3421
3432
  if (c.backup) {
3422
3433
  const backup = c.backup;
3423
3434
  backup.enabled = backup.enabled ?? true;
@@ -3522,7 +3533,8 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3522
3533
 
3523
3534
  class CursorWriter {
3524
3535
  formatContent(content, rule) {
3525
- const ruleName = this.extractRuleName(rule.file);
3536
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
3537
+ const ruleName = this.extractRuleName(firstFile);
3526
3538
  const hasGlobs = rule.globs && rule.globs.length > 0;
3527
3539
  const frontmatter = [
3528
3540
  "---",
@@ -3644,7 +3656,8 @@ class AgentDistributor {
3644
3656
  this.config = config;
3645
3657
  }
3646
3658
  async distributeRule(rule) {
3647
- const content = this.loadRuleContent(rule.file);
3659
+ const filePath = Array.isArray(rule.file) ? rule.file : [rule.file];
3660
+ const content = this.loadRuleContent(filePath);
3648
3661
  for (const agent of rule.targets) {
3649
3662
  await this.distributeToAgent(agent, rule, content);
3650
3663
  }
@@ -3662,7 +3675,8 @@ class AgentDistributor {
3662
3675
  if (customPath) {
3663
3676
  targetPath = customPath;
3664
3677
  } else {
3665
- const ruleName = this.extractRuleName(rule.file);
3678
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
3679
+ const ruleName = this.extractRuleName(firstFile);
3666
3680
  const agentPath = getAgentPath(agentName, ruleName);
3667
3681
  targetPath = join2(rule.to, agentPath);
3668
3682
  }
@@ -3679,11 +3693,27 @@ class AgentDistributor {
3679
3693
  const finalContent = await this.applyHooks(context);
3680
3694
  writeFileSync(targetPath, finalContent, "utf-8");
3681
3695
  }
3682
- loadRuleContent(filePath) {
3696
+ loadRuleContent(filePaths) {
3683
3697
  try {
3684
- return readFileSync3(filePath, "utf-8");
3698
+ if (filePaths.length === 1) {
3699
+ return readFileSync3(filePaths[0], "utf-8");
3700
+ }
3701
+ const mergedContent = [];
3702
+ for (let i = 0;i < filePaths.length; i++) {
3703
+ const filePath = filePaths[i];
3704
+ const content = readFileSync3(filePath, "utf-8");
3705
+ mergedContent.push(`<!-- Source: ${filePath} -->`);
3706
+ mergedContent.push(content);
3707
+ if (i < filePaths.length - 1) {
3708
+ mergedContent.push(`
3709
+ ---
3710
+ `);
3711
+ }
3712
+ }
3713
+ return mergedContent.join(`
3714
+ `);
3685
3715
  } catch (error) {
3686
- throw new Error(`Failed to read rule file ${filePath}: ${error}`);
3716
+ throw new Error(`Failed to read rule file(s): ${error}`);
3687
3717
  }
3688
3718
  }
3689
3719
  extractRuleName(filePath) {
@@ -3864,15 +3894,22 @@ class GitIgnoreManager {
3864
3894
  writeFileSync3(this.gitignorePath, cleanedContent, "utf-8");
3865
3895
  }
3866
3896
  collectGeneratedPaths() {
3897
+ if (this.config.gitignore === false) {
3898
+ return [];
3899
+ }
3867
3900
  const paths = new Set;
3868
3901
  for (const rule of this.config.rules) {
3902
+ if (rule.gitignore === false) {
3903
+ continue;
3904
+ }
3869
3905
  for (const agent of rule.targets) {
3870
3906
  const agentName = this.getAgentName(agent);
3871
3907
  const customPath = this.getCustomPath(agent);
3872
3908
  if (customPath) {
3873
3909
  paths.add(customPath);
3874
3910
  } else {
3875
- const ruleName = this.extractRuleName(rule.file);
3911
+ const filePath = Array.isArray(rule.file) ? rule.file[0] : rule.file;
3912
+ const ruleName = this.extractRuleName(filePath);
3876
3913
  const agentPath = getAgentPath(agentName, ruleName);
3877
3914
  const fullPath = `${rule.to}/${agentPath}`.replace(/\/+/g, "/");
3878
3915
  paths.add(fullPath);
@@ -4014,8 +4051,11 @@ class AIRulesCore {
4014
4051
  async validate() {
4015
4052
  try {
4016
4053
  for (const rule of this.config.rules) {
4017
- if (!existsSync5(rule.file)) {
4018
- throw new Error(`Rule file not found: ${rule.file}`);
4054
+ const files = Array.isArray(rule.file) ? rule.file : [rule.file];
4055
+ for (const file of files) {
4056
+ if (!existsSync5(file)) {
4057
+ throw new Error(`Rule file not found: ${file}`);
4058
+ }
4019
4059
  }
4020
4060
  }
4021
4061
  if (this.config.commands) {
@@ -4109,7 +4149,8 @@ class AIRulesCore {
4109
4149
  if (customPath) {
4110
4150
  paths.push(customPath);
4111
4151
  } else {
4112
- const ruleName = rule.file.split("/").pop()?.replace(".md", "") || "rule";
4152
+ const firstFile = Array.isArray(rule.file) ? rule.file[0] : rule.file;
4153
+ const ruleName = firstFile.split("/").pop()?.replace(".md", "") || "rule";
4113
4154
  const agentPath = getAgentPath(agentName, ruleName);
4114
4155
  let fullPath = `${rule.to}/${agentPath}`.replace(/\/+/g, "/");
4115
4156
  if (fullPath.startsWith("./")) {
@@ -4221,6 +4262,7 @@ Original error: ${jitiError}`);
4221
4262
  });
4222
4263
  c.configDir = c.configDir || ".glooit";
4223
4264
  c.mergeMcps = c.mergeMcps ?? true;
4265
+ c.gitignore = c.gitignore ?? true;
4224
4266
  if (c.backup) {
4225
4267
  const backup = c.backup;
4226
4268
  backup.enabled = backup.enabled ?? true;
@@ -4240,8 +4282,10 @@ Original error: ${jitiError}`);
4240
4282
  throw new Error(`Rule at index ${index}: Rule must be an object`);
4241
4283
  }
4242
4284
  const r = rule;
4243
- if (typeof r.file !== "string") {
4244
- throw new Error(`Rule at index ${index}: Rule.file must be a string`);
4285
+ const isFileString = typeof r.file === "string";
4286
+ const isFileArray = Array.isArray(r.file) && r.file.length > 0 && r.file.every((f) => typeof f === "string");
4287
+ if (!isFileString && !isFileArray) {
4288
+ throw new Error(`Rule at index ${index}: Rule.file must be a string or a non-empty array of strings`);
4245
4289
  }
4246
4290
  if (typeof r.to !== "string") {
4247
4291
  throw new Error(`Rule at index ${index}: Rule.to must be a string`);
@@ -4262,6 +4306,14 @@ Original error: ${jitiError}`);
4262
4306
  })) {
4263
4307
  throw new Error(`Rule at index ${index}: Rule.targets must contain valid agents: ${validAgentNames.join(", ")}, or objects with {name, to?}`);
4264
4308
  }
4309
+ if (isFileArray) {
4310
+ const allTargetsAreObjects = r.targets.every((agent) => {
4311
+ return typeof agent === "object" && agent !== null && "name" in agent && "to" in agent;
4312
+ });
4313
+ if (!allTargetsAreObjects) {
4314
+ throw new Error(`Rule at index ${index}: When using file array (merge mode), all targets must be objects with {name, to} properties`);
4315
+ }
4316
+ }
4265
4317
  }
4266
4318
  static createInitialConfig() {
4267
4319
  return this.createTypedConfig();
@@ -4,14 +4,22 @@ export interface AgentTarget {
4
4
  to?: string;
5
5
  }
6
6
  export type Agent = AgentName | AgentTarget;
7
- export interface Rule {
7
+ interface BaseRule {
8
8
  name?: string;
9
- file: string;
10
9
  to: string;
11
10
  globs?: string;
12
- targets: Agent[];
13
11
  hooks?: string[];
12
+ gitignore?: boolean;
13
+ }
14
+ export interface SingleFileRule extends BaseRule {
15
+ file: string;
16
+ targets: Agent[];
17
+ }
18
+ export interface MergedFileRule extends BaseRule {
19
+ file: string[];
20
+ targets: AgentTarget[];
14
21
  }
22
+ export type Rule = SingleFileRule | MergedFileRule;
15
23
  export interface Command {
16
24
  command: string;
17
25
  file: string;
@@ -56,6 +64,7 @@ export interface Config {
56
64
  mergeMcps?: boolean;
57
65
  hooks?: Hooks;
58
66
  backup?: BackupConfig;
67
+ gitignore?: boolean;
59
68
  }
60
69
  export interface AgentMapping {
61
70
  path: string;
@@ -78,3 +87,4 @@ export interface BackupEntry {
78
87
  }[];
79
88
  }
80
89
  export declare function defineRules(config: Config): Config;
90
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glooit",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "๐Ÿงด Sync your AI agent configurations and rules across platforms with ease",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -24,6 +24,7 @@
24
24
  "format": "oxlint src tests --fix",
25
25
  "check": "bun run typecheck && bun run lint && bun run test",
26
26
  "taze": "bunx taze",
27
+ "release": "bun run check && npx bumpp && git push --follow-tags",
27
28
  "prepublishOnly": "bun run check && bun run build",
28
29
  "install:local": "bun run build:binary && sudo mv bin/glooit /usr/local/bin/"
29
30
  },