@hasna/configs 0.1.0 → 0.1.1

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/dist/mcp/index.js CHANGED
@@ -365,11 +365,90 @@ async function applyConfigs(configs, opts = {}) {
365
365
  }
366
366
 
367
367
  // src/lib/sync.ts
368
+ import { extname, join as join3 } from "path";
369
+ import { homedir as homedir3 } from "os";
370
+
371
+ // src/lib/sync-dir.ts
368
372
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
369
- import { extname, join as join2, relative } from "path";
373
+ import { join as join2, relative } from "path";
370
374
  import { homedir as homedir2 } from "os";
375
+ var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
376
+ function shouldSkip(p) {
377
+ return SKIP.some((s) => p.includes(s));
378
+ }
379
+ async function syncFromDir(dir, opts = {}) {
380
+ const d = opts.db || getDatabase();
381
+ const absDir = expandPath(dir);
382
+ if (!existsSync3(absDir))
383
+ return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
384
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
385
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
386
+ const home = homedir2();
387
+ const allConfigs = listConfigs(undefined, d);
388
+ for (const file of files) {
389
+ if (shouldSkip(file)) {
390
+ result.skipped.push(file);
391
+ continue;
392
+ }
393
+ try {
394
+ const content = readFileSync2(file, "utf-8");
395
+ if (content.length > 500000) {
396
+ result.skipped.push(file + " (too large)");
397
+ continue;
398
+ }
399
+ const targetPath = file.replace(home, "~");
400
+ const existing = allConfigs.find((c) => c.target_path === targetPath);
401
+ if (!existing) {
402
+ if (!opts.dryRun)
403
+ createConfig({ name: relative(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content }, d);
404
+ result.added++;
405
+ } else if (existing.content !== content) {
406
+ if (!opts.dryRun)
407
+ updateConfig(existing.id, { content }, d);
408
+ result.updated++;
409
+ } else {
410
+ result.unchanged++;
411
+ }
412
+ } catch {
413
+ result.skipped.push(file);
414
+ }
415
+ }
416
+ return result;
417
+ }
418
+ async function syncToDir(dir, opts = {}) {
419
+ const d = opts.db || getDatabase();
420
+ const home = homedir2();
421
+ const absDir = expandPath(dir);
422
+ const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
423
+ const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
424
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
425
+ for (const config of configs) {
426
+ if (config.kind === "reference")
427
+ continue;
428
+ try {
429
+ const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
430
+ r.changed ? result.updated++ : result.unchanged++;
431
+ } catch {
432
+ result.skipped.push(config.target_path || config.id);
433
+ }
434
+ }
435
+ return result;
436
+ }
437
+ function walkDir(dir, files = []) {
438
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
439
+ const full = join2(dir, entry.name);
440
+ if (shouldSkip(full))
441
+ continue;
442
+ if (entry.isDirectory())
443
+ walkDir(full, files);
444
+ else if (entry.isFile())
445
+ files.push(full);
446
+ }
447
+ return files;
448
+ }
449
+ // src/lib/sync.ts
371
450
  function detectCategory(filePath) {
372
- const p = filePath.toLowerCase().replace(homedir2(), "~");
451
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
373
452
  if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
374
453
  return "rules";
375
454
  if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
@@ -387,7 +466,7 @@ function detectCategory(filePath) {
387
466
  return "tools";
388
467
  }
389
468
  function detectAgent(filePath) {
390
- const p = filePath.toLowerCase().replace(homedir2(), "~");
469
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
391
470
  if (p.includes("/.claude/") || p.endsWith("claude.md"))
392
471
  return "claude";
393
472
  if (p.includes("/.codex/") || p.endsWith("agents.md"))
@@ -416,91 +495,6 @@ function detectFormat(filePath) {
416
495
  return "ini";
417
496
  return "text";
418
497
  }
419
- var SKIP_PATTERNS = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
420
- function shouldSkip(p) {
421
- return SKIP_PATTERNS.some((pat) => p.includes(pat));
422
- }
423
- function walkDir(dir, files = []) {
424
- const entries = readdirSync(dir, { withFileTypes: true });
425
- for (const entry of entries) {
426
- const full = join2(dir, entry.name);
427
- if (shouldSkip(full))
428
- continue;
429
- if (entry.isDirectory()) {
430
- walkDir(full, files);
431
- } else if (entry.isFile()) {
432
- files.push(full);
433
- }
434
- }
435
- return files;
436
- }
437
- async function syncFromDir(dir, opts = {}) {
438
- const d = opts.db || getDatabase();
439
- const absDir = expandPath(dir);
440
- if (!existsSync3(absDir)) {
441
- return { added: 0, updated: 0, unchanged: 0, skipped: [`Directory not found: ${absDir}`] };
442
- }
443
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
444
- const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
445
- const allConfigs = listConfigs(undefined, d);
446
- for (const file of files) {
447
- if (shouldSkip(file)) {
448
- result.skipped.push(file);
449
- continue;
450
- }
451
- try {
452
- const content = readFileSync2(file, "utf-8");
453
- const targetPath = file.startsWith(homedir2()) ? file.replace(homedir2(), "~") : file;
454
- const existing = allConfigs.find((c) => c.target_path === targetPath);
455
- if (!existing) {
456
- if (!opts.dryRun) {
457
- const name = relative(absDir, file);
458
- createConfig({
459
- name,
460
- category: detectCategory(file),
461
- agent: detectAgent(file),
462
- target_path: targetPath,
463
- format: detectFormat(file),
464
- content
465
- }, d);
466
- }
467
- result.added++;
468
- } else if (existing.content !== content) {
469
- if (!opts.dryRun) {
470
- updateConfig(existing.id, { content }, d);
471
- }
472
- result.updated++;
473
- } else {
474
- result.unchanged++;
475
- }
476
- } catch {
477
- result.skipped.push(file);
478
- }
479
- }
480
- return result;
481
- }
482
- async function syncToDir(dir, opts = {}) {
483
- const d = opts.db || getDatabase();
484
- const absDir = expandPath(dir);
485
- const normalizedDir = dir.startsWith("~/") ? dir : absDir.replace(homedir2(), "~");
486
- const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalizedDir) || c.target_path.startsWith(absDir)));
487
- const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
488
- for (const config of configs) {
489
- if (config.kind === "reference")
490
- continue;
491
- try {
492
- const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
493
- if (r.changed) {
494
- existsSync3(expandPath(config.target_path)) ? result.updated++ : result.added++;
495
- } else {
496
- result.unchanged++;
497
- }
498
- } catch {
499
- result.skipped.push(config.target_path || config.id);
500
- }
501
- }
502
- return result;
503
- }
504
498
 
505
499
  // src/db/profiles.ts
506
500
  function rowToProfile(row) {
@@ -2084,11 +2084,90 @@ async function applyConfigs(configs, opts = {}) {
2084
2084
  }
2085
2085
 
2086
2086
  // src/lib/sync.ts
2087
+ import { extname, join as join3 } from "path";
2088
+ import { homedir as homedir3 } from "os";
2089
+
2090
+ // src/lib/sync-dir.ts
2087
2091
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
2088
- import { extname, join as join2, relative } from "path";
2092
+ import { join as join2, relative } from "path";
2089
2093
  import { homedir as homedir2 } from "os";
2094
+ var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
2095
+ function shouldSkip(p) {
2096
+ return SKIP.some((s) => p.includes(s));
2097
+ }
2098
+ async function syncFromDir(dir, opts = {}) {
2099
+ const d = opts.db || getDatabase();
2100
+ const absDir = expandPath(dir);
2101
+ if (!existsSync3(absDir))
2102
+ return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
2103
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
2104
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2105
+ const home = homedir2();
2106
+ const allConfigs = listConfigs(undefined, d);
2107
+ for (const file of files) {
2108
+ if (shouldSkip(file)) {
2109
+ result.skipped.push(file);
2110
+ continue;
2111
+ }
2112
+ try {
2113
+ const content = readFileSync2(file, "utf-8");
2114
+ if (content.length > 500000) {
2115
+ result.skipped.push(file + " (too large)");
2116
+ continue;
2117
+ }
2118
+ const targetPath = file.replace(home, "~");
2119
+ const existing = allConfigs.find((c) => c.target_path === targetPath);
2120
+ if (!existing) {
2121
+ if (!opts.dryRun)
2122
+ createConfig({ name: relative(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content }, d);
2123
+ result.added++;
2124
+ } else if (existing.content !== content) {
2125
+ if (!opts.dryRun)
2126
+ updateConfig(existing.id, { content }, d);
2127
+ result.updated++;
2128
+ } else {
2129
+ result.unchanged++;
2130
+ }
2131
+ } catch {
2132
+ result.skipped.push(file);
2133
+ }
2134
+ }
2135
+ return result;
2136
+ }
2137
+ async function syncToDir(dir, opts = {}) {
2138
+ const d = opts.db || getDatabase();
2139
+ const home = homedir2();
2140
+ const absDir = expandPath(dir);
2141
+ const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
2142
+ const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
2143
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2144
+ for (const config of configs) {
2145
+ if (config.kind === "reference")
2146
+ continue;
2147
+ try {
2148
+ const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
2149
+ r.changed ? result.updated++ : result.unchanged++;
2150
+ } catch {
2151
+ result.skipped.push(config.target_path || config.id);
2152
+ }
2153
+ }
2154
+ return result;
2155
+ }
2156
+ function walkDir(dir, files = []) {
2157
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
2158
+ const full = join2(dir, entry.name);
2159
+ if (shouldSkip(full))
2160
+ continue;
2161
+ if (entry.isDirectory())
2162
+ walkDir(full, files);
2163
+ else if (entry.isFile())
2164
+ files.push(full);
2165
+ }
2166
+ return files;
2167
+ }
2168
+ // src/lib/sync.ts
2090
2169
  function detectCategory(filePath) {
2091
- const p = filePath.toLowerCase().replace(homedir2(), "~");
2170
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
2092
2171
  if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
2093
2172
  return "rules";
2094
2173
  if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
@@ -2106,7 +2185,7 @@ function detectCategory(filePath) {
2106
2185
  return "tools";
2107
2186
  }
2108
2187
  function detectAgent(filePath) {
2109
- const p = filePath.toLowerCase().replace(homedir2(), "~");
2188
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
2110
2189
  if (p.includes("/.claude/") || p.endsWith("claude.md"))
2111
2190
  return "claude";
2112
2191
  if (p.includes("/.codex/") || p.endsWith("agents.md"))
@@ -2135,91 +2214,6 @@ function detectFormat(filePath) {
2135
2214
  return "ini";
2136
2215
  return "text";
2137
2216
  }
2138
- var SKIP_PATTERNS = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
2139
- function shouldSkip(p) {
2140
- return SKIP_PATTERNS.some((pat) => p.includes(pat));
2141
- }
2142
- function walkDir(dir, files = []) {
2143
- const entries = readdirSync(dir, { withFileTypes: true });
2144
- for (const entry of entries) {
2145
- const full = join2(dir, entry.name);
2146
- if (shouldSkip(full))
2147
- continue;
2148
- if (entry.isDirectory()) {
2149
- walkDir(full, files);
2150
- } else if (entry.isFile()) {
2151
- files.push(full);
2152
- }
2153
- }
2154
- return files;
2155
- }
2156
- async function syncFromDir(dir, opts = {}) {
2157
- const d = opts.db || getDatabase();
2158
- const absDir = expandPath(dir);
2159
- if (!existsSync3(absDir)) {
2160
- return { added: 0, updated: 0, unchanged: 0, skipped: [`Directory not found: ${absDir}`] };
2161
- }
2162
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
2163
- const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2164
- const allConfigs = listConfigs(undefined, d);
2165
- for (const file of files) {
2166
- if (shouldSkip(file)) {
2167
- result.skipped.push(file);
2168
- continue;
2169
- }
2170
- try {
2171
- const content = readFileSync2(file, "utf-8");
2172
- const targetPath = file.startsWith(homedir2()) ? file.replace(homedir2(), "~") : file;
2173
- const existing = allConfigs.find((c) => c.target_path === targetPath);
2174
- if (!existing) {
2175
- if (!opts.dryRun) {
2176
- const name = relative(absDir, file);
2177
- createConfig({
2178
- name,
2179
- category: detectCategory(file),
2180
- agent: detectAgent(file),
2181
- target_path: targetPath,
2182
- format: detectFormat(file),
2183
- content
2184
- }, d);
2185
- }
2186
- result.added++;
2187
- } else if (existing.content !== content) {
2188
- if (!opts.dryRun) {
2189
- updateConfig(existing.id, { content }, d);
2190
- }
2191
- result.updated++;
2192
- } else {
2193
- result.unchanged++;
2194
- }
2195
- } catch {
2196
- result.skipped.push(file);
2197
- }
2198
- }
2199
- return result;
2200
- }
2201
- async function syncToDir(dir, opts = {}) {
2202
- const d = opts.db || getDatabase();
2203
- const absDir = expandPath(dir);
2204
- const normalizedDir = dir.startsWith("~/") ? dir : absDir.replace(homedir2(), "~");
2205
- const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalizedDir) || c.target_path.startsWith(absDir)));
2206
- const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2207
- for (const config of configs) {
2208
- if (config.kind === "reference")
2209
- continue;
2210
- try {
2211
- const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
2212
- if (r.changed) {
2213
- existsSync3(expandPath(config.target_path)) ? result.updated++ : result.added++;
2214
- } else {
2215
- result.unchanged++;
2216
- }
2217
- } catch {
2218
- result.skipped.push(config.target_path || config.id);
2219
- }
2220
- }
2221
- return result;
2222
- }
2223
2217
 
2224
2218
  // src/server/index.ts
2225
2219
  var PORT = Number(process.env["CONFIGS_PORT"] ?? 3457);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/configs",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AI coding agent configuration manager — store, version, apply, and share all your AI coding configs. CLI + MCP + REST API + Dashboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",