@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/cli/index.js +164 -118
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +225 -111
- package/dist/lib/sync-dir.d.ts +13 -0
- package/dist/lib/sync-dir.d.ts.map +1 -0
- package/dist/lib/sync.d.ts +24 -8
- package/dist/lib/sync.d.ts.map +1 -1
- package/dist/mcp/index.js +82 -88
- package/dist/server/index.js +82 -88
- package/package.json +1 -1
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 {
|
|
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(
|
|
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(
|
|
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) {
|
package/dist/server/index.js
CHANGED
|
@@ -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 {
|
|
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(
|
|
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(
|
|
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.
|
|
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",
|