@memories.sh/cli 0.2.2 → 0.3.0

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.
@@ -143,6 +143,28 @@ async function runMigrations(db) {
143
143
  await db.execute(`CREATE INDEX IF NOT EXISTS idx_memories_scope_project ON memories(scope, project_id)`);
144
144
  } catch {
145
145
  }
146
+ await db.execute(
147
+ `CREATE TABLE IF NOT EXISTS files (
148
+ id TEXT PRIMARY KEY,
149
+ path TEXT NOT NULL,
150
+ content TEXT NOT NULL,
151
+ hash TEXT NOT NULL,
152
+ scope TEXT NOT NULL DEFAULT 'global',
153
+ source TEXT,
154
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
155
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
156
+ deleted_at TEXT,
157
+ UNIQUE(path, scope)
158
+ )`
159
+ );
160
+ try {
161
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_files_scope ON files(scope)`);
162
+ } catch {
163
+ }
164
+ try {
165
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_files_path ON files(path)`);
166
+ } catch {
167
+ }
146
168
  }
147
169
 
148
170
  export {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDb
4
- } from "./chunk-BDPB4ABQ.js";
4
+ } from "./chunk-GCWB2OVN.js";
5
5
 
6
6
  // src/lib/memory.ts
7
7
  import { nanoid } from "nanoid";
@@ -99,7 +99,7 @@ async function addMemory(content, opts) {
99
99
  }
100
100
  async function generateEmbeddingAsync(memoryId, content) {
101
101
  try {
102
- const { getEmbedding, storeEmbedding, ensureEmbeddingsSchema } = await import("./embeddings-GH4HB6X7.js");
102
+ const { getEmbedding, storeEmbedding, ensureEmbeddingsSchema } = await import("./embeddings-6N2NZPRX.js");
103
103
  await ensureEmbeddingsSchema();
104
104
  const embedding = await getEmbedding(content);
105
105
  await storeEmbedding(memoryId, embedding);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDb
4
- } from "./chunk-BDPB4ABQ.js";
4
+ } from "./chunk-GCWB2OVN.js";
5
5
 
6
6
  // src/lib/embeddings.ts
7
7
  import { pipeline } from "@xenova/transformers";
@@ -10,8 +10,8 @@ import {
10
10
  isModelAvailable,
11
11
  semanticSearch,
12
12
  storeEmbedding
13
- } from "./chunk-4GNYCHD4.js";
14
- import "./chunk-BDPB4ABQ.js";
13
+ } from "./chunk-ZSP6BMAR.js";
14
+ import "./chunk-GCWB2OVN.js";
15
15
  export {
16
16
  bufferToEmbedding,
17
17
  cosineSimilarity,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  ensureEmbeddingsSchema,
4
4
  getEmbedding,
5
5
  storeEmbedding
6
- } from "./chunk-4GNYCHD4.js";
6
+ } from "./chunk-ZSP6BMAR.js";
7
7
  import {
8
8
  addMemory,
9
9
  bulkForgetByIds,
@@ -17,7 +17,7 @@ import {
17
17
  listMemories,
18
18
  searchMemories,
19
19
  updateMemory
20
- } from "./chunk-JMN3U7AI.js";
20
+ } from "./chunk-TRACL3XY.js";
21
21
  import {
22
22
  getConfigDir,
23
23
  getDb,
@@ -25,10 +25,10 @@ import {
25
25
  resetDb,
26
26
  saveSyncConfig,
27
27
  syncDb
28
- } from "./chunk-BDPB4ABQ.js";
28
+ } from "./chunk-GCWB2OVN.js";
29
29
 
30
30
  // src/index.ts
31
- import { Command as Command28 } from "commander";
31
+ import { Command as Command29 } from "commander";
32
32
 
33
33
  // src/commands/init.ts
34
34
  import { Command } from "commander";
@@ -634,7 +634,7 @@ var searchCommand = new Command5("search").description("Search memories using fu
634
634
  }
635
635
  if (opts.semantic) {
636
636
  try {
637
- const { semanticSearch, isModelAvailable } = await import("./embeddings-GH4HB6X7.js");
637
+ const { semanticSearch, isModelAvailable } = await import("./embeddings-6N2NZPRX.js");
638
638
  if (!await isModelAvailable()) {
639
639
  console.log(chalk6.yellow("\u26A0") + " Loading embedding model for first time (this may take a moment)...");
640
640
  }
@@ -654,7 +654,7 @@ var searchCommand = new Command5("search").description("Search memories using fu
654
654
  console.log(chalk6.bold(`Semantic results for "${query}":`));
655
655
  console.log("");
656
656
  for (const r of results) {
657
- const { getMemoryById: getMemoryById2 } = await import("./memory-57CD5DYG.js");
657
+ const { getMemoryById: getMemoryById2 } = await import("./memory-B4NNFKXV.js");
658
658
  const m = await getMemoryById2(r.id);
659
659
  if (m) {
660
660
  console.log(formatMemory2(m, r.score));
@@ -3599,8 +3599,293 @@ var logoutCommand = new Command27("logout").description("Log out of memories.sh"
3599
3599
  success("Logged out successfully.");
3600
3600
  });
3601
3601
 
3602
+ // src/commands/files.ts
3603
+ import { Command as Command28 } from "commander";
3604
+ import chalk26 from "chalk";
3605
+ import ora4 from "ora";
3606
+ import { nanoid as nanoid4 } from "nanoid";
3607
+ import { createHash } from "crypto";
3608
+ import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir4, readdir } from "fs/promises";
3609
+ import { existsSync as existsSync9 } from "fs";
3610
+ import { join as join8, dirname as dirname2 } from "path";
3611
+ import { homedir as homedir4 } from "os";
3612
+ function hashContent(content) {
3613
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
3614
+ }
3615
+ var CONFIG_DIRS = [
3616
+ { dir: ".agents", name: "Agents" },
3617
+ { dir: ".claude", name: "Claude" },
3618
+ { dir: ".cursor", name: "Cursor" },
3619
+ { dir: ".github/copilot", name: "Copilot" },
3620
+ { dir: ".windsurf", name: "Windsurf" },
3621
+ { dir: ".cline", name: "Cline" },
3622
+ { dir: ".codex", name: "Codex" },
3623
+ { dir: ".gemini", name: "Gemini" },
3624
+ { dir: ".roo", name: "Roo" },
3625
+ { dir: ".amp", name: "Amp" }
3626
+ ];
3627
+ var INCLUDE_PATTERNS = [
3628
+ /\.md$/,
3629
+ /\.txt$/,
3630
+ /\.json$/,
3631
+ /\.yaml$/,
3632
+ /\.yml$/,
3633
+ /\.toml$/,
3634
+ /rules$/,
3635
+ /config$/
3636
+ ];
3637
+ var EXCLUDE_PATTERNS = [
3638
+ /node_modules/,
3639
+ /\.git\//,
3640
+ /\.DS_Store/,
3641
+ /\.log$/,
3642
+ /cache/i,
3643
+ /history/i,
3644
+ /session/i,
3645
+ /debug/i,
3646
+ /\.lock$/,
3647
+ /stats-cache/,
3648
+ /telemetry/,
3649
+ /todos/
3650
+ ];
3651
+ function shouldIncludeFile(filePath) {
3652
+ for (const pattern of EXCLUDE_PATTERNS) {
3653
+ if (pattern.test(filePath)) return false;
3654
+ }
3655
+ for (const pattern of INCLUDE_PATTERNS) {
3656
+ if (pattern.test(filePath)) return true;
3657
+ }
3658
+ return false;
3659
+ }
3660
+ async function scanDirectory(dir, basePath = "") {
3661
+ const files = [];
3662
+ if (!existsSync9(dir)) return files;
3663
+ const entries = await readdir(dir, { withFileTypes: true });
3664
+ for (const entry of entries) {
3665
+ const fullPath = join8(dir, entry.name);
3666
+ const relativePath = basePath ? join8(basePath, entry.name) : entry.name;
3667
+ if (entry.isDirectory()) {
3668
+ const subFiles = await scanDirectory(fullPath, relativePath);
3669
+ files.push(...subFiles);
3670
+ } else if (entry.isFile() && shouldIncludeFile(relativePath)) {
3671
+ files.push({ path: relativePath, fullPath });
3672
+ }
3673
+ }
3674
+ return files;
3675
+ }
3676
+ var filesCommand = new Command28("files").description("Manage synced config files (.agents, .cursor, .claude, etc.)");
3677
+ filesCommand.command("list").alias("ls").description("List synced files").option("-s, --scope <scope>", "Filter by scope (global or project path)").action(async (opts) => {
3678
+ const db = await getDb();
3679
+ let sql = "SELECT id, path, hash, scope, source, updated_at FROM files WHERE deleted_at IS NULL";
3680
+ const args = [];
3681
+ if (opts.scope) {
3682
+ sql += " AND scope = ?";
3683
+ args.push(opts.scope);
3684
+ }
3685
+ sql += " ORDER BY scope, path";
3686
+ const result = await db.execute({ sql, args });
3687
+ const files = result.rows;
3688
+ if (files.length === 0) {
3689
+ console.log(chalk26.dim("No synced files yet."));
3690
+ console.log(chalk26.dim(`Run ${chalk26.cyan("memories files ingest")} to import config files.`));
3691
+ return;
3692
+ }
3693
+ const byScope = /* @__PURE__ */ new Map();
3694
+ for (const file of files) {
3695
+ const scope = file.scope;
3696
+ if (!byScope.has(scope)) byScope.set(scope, []);
3697
+ byScope.get(scope).push(file);
3698
+ }
3699
+ for (const [scope, scopeFiles] of byScope) {
3700
+ const scopeLabel = scope === "global" ? chalk26.blue("Global") : chalk26.yellow(scope.replace("github.com/", ""));
3701
+ console.log(`
3702
+ ${scopeLabel} ${chalk26.dim(`(${scopeFiles.length} files)`)}`);
3703
+ console.log(chalk26.dim("\u2500".repeat(50)));
3704
+ for (const file of scopeFiles) {
3705
+ const source = file.source ? chalk26.dim(` [${file.source}]`) : "";
3706
+ console.log(` ${chalk26.white(file.path)}${source}`);
3707
+ }
3708
+ }
3709
+ console.log();
3710
+ });
3711
+ filesCommand.command("ingest").description("Import files from .agents, .cursor, .claude and other config directories").option("-g, --global", "Ingest global configs from home directory", true).option("-p, --project", "Ingest project configs from current directory").option("--dry-run", "Show what would be imported without making changes").action(async (opts) => {
3712
+ const db = await getDb();
3713
+ const home = homedir4();
3714
+ const cwd = process.cwd();
3715
+ const filesToIngest = [];
3716
+ if (opts.global !== false) {
3717
+ for (const { dir, name } of CONFIG_DIRS) {
3718
+ const globalDir = join8(home, dir);
3719
+ const files = await scanDirectory(globalDir);
3720
+ for (const file of files) {
3721
+ filesToIngest.push({
3722
+ path: join8(dir, file.path),
3723
+ fullPath: file.fullPath,
3724
+ scope: "global",
3725
+ source: name
3726
+ });
3727
+ }
3728
+ }
3729
+ }
3730
+ if (opts.project) {
3731
+ for (const { dir, name } of CONFIG_DIRS) {
3732
+ const projectDir = join8(cwd, dir);
3733
+ const files = await scanDirectory(projectDir);
3734
+ for (const file of files) {
3735
+ filesToIngest.push({
3736
+ path: join8(dir, file.path),
3737
+ fullPath: file.fullPath,
3738
+ scope: "project",
3739
+ // Will be resolved to git remote
3740
+ source: name
3741
+ });
3742
+ }
3743
+ }
3744
+ }
3745
+ if (filesToIngest.length === 0) {
3746
+ console.log(chalk26.dim("No config files found to import."));
3747
+ return;
3748
+ }
3749
+ if (opts.dryRun) {
3750
+ console.log(chalk26.bold(`Would import ${filesToIngest.length} files:
3751
+ `));
3752
+ for (const file of filesToIngest) {
3753
+ const scopeLabel = file.scope === "global" ? chalk26.blue("G") : chalk26.yellow("P");
3754
+ console.log(` ${scopeLabel} ${file.path} ${chalk26.dim(`[${file.source}]`)}`);
3755
+ }
3756
+ return;
3757
+ }
3758
+ const spinner = ora4(`Importing ${filesToIngest.length} files...`).start();
3759
+ let imported = 0;
3760
+ let updated = 0;
3761
+ let skipped = 0;
3762
+ for (const file of filesToIngest) {
3763
+ try {
3764
+ const content = await readFile8(file.fullPath, "utf-8");
3765
+ const hash = hashContent(content);
3766
+ const existing = await db.execute({
3767
+ sql: "SELECT id, hash FROM files WHERE path = ? AND scope = ? AND deleted_at IS NULL",
3768
+ args: [file.path, file.scope]
3769
+ });
3770
+ if (existing.rows.length > 0) {
3771
+ const existingFile = existing.rows[0];
3772
+ if (existingFile.hash === hash) {
3773
+ skipped++;
3774
+ continue;
3775
+ }
3776
+ await db.execute({
3777
+ sql: "UPDATE files SET content = ?, hash = ?, updated_at = datetime('now') WHERE id = ?",
3778
+ args: [content, hash, existingFile.id]
3779
+ });
3780
+ updated++;
3781
+ } else {
3782
+ const id = nanoid4(12);
3783
+ await db.execute({
3784
+ sql: `INSERT INTO files (id, path, content, hash, scope, source) VALUES (?, ?, ?, ?, ?, ?)`,
3785
+ args: [id, file.path, content, hash, file.scope, file.source]
3786
+ });
3787
+ imported++;
3788
+ }
3789
+ } catch (err) {
3790
+ }
3791
+ }
3792
+ const sync = await readSyncConfig();
3793
+ if (sync) {
3794
+ spinner.text = "Syncing to cloud...";
3795
+ await syncDb();
3796
+ }
3797
+ spinner.succeed(`Imported ${imported} new, updated ${updated}, skipped ${skipped} unchanged`);
3798
+ });
3799
+ filesCommand.command("apply").description("Write synced files to disk (restore from cloud)").option("-g, --global", "Apply global files to home directory").option("-p, --project", "Apply project files to current directory").option("--dry-run", "Show what would be written without making changes").option("-f, --force", "Overwrite existing files without prompting").action(async (opts) => {
3800
+ const db = await getDb();
3801
+ const home = homedir4();
3802
+ const cwd = process.cwd();
3803
+ let sql = "SELECT id, path, content, scope, source FROM files WHERE deleted_at IS NULL";
3804
+ const args = [];
3805
+ if (opts.global && !opts.project) {
3806
+ sql += " AND scope = 'global'";
3807
+ } else if (opts.project && !opts.global) {
3808
+ sql += " AND scope != 'global'";
3809
+ }
3810
+ const result = await db.execute({ sql, args });
3811
+ const files = result.rows;
3812
+ if (files.length === 0) {
3813
+ console.log(chalk26.dim("No files to apply."));
3814
+ return;
3815
+ }
3816
+ if (opts.dryRun) {
3817
+ console.log(chalk26.bold(`Would write ${files.length} files:
3818
+ `));
3819
+ for (const file of files) {
3820
+ const baseDir = file.scope === "global" ? home : cwd;
3821
+ const targetPath = join8(baseDir, file.path);
3822
+ const exists = existsSync9(targetPath);
3823
+ const status = exists ? chalk26.yellow("(overwrite)") : chalk26.green("(new)");
3824
+ console.log(` ${targetPath} ${status}`);
3825
+ }
3826
+ return;
3827
+ }
3828
+ const spinner = ora4(`Applying ${files.length} files...`).start();
3829
+ let written = 0;
3830
+ let skippedExisting = 0;
3831
+ for (const file of files) {
3832
+ const baseDir = file.scope === "global" ? home : cwd;
3833
+ const targetPath = join8(baseDir, file.path);
3834
+ if (existsSync9(targetPath) && !opts.force) {
3835
+ const existingContent = await readFile8(targetPath, "utf-8");
3836
+ const existingHash = hashContent(existingContent);
3837
+ const newHash = hashContent(file.content);
3838
+ if (existingHash !== newHash) {
3839
+ skippedExisting++;
3840
+ continue;
3841
+ }
3842
+ }
3843
+ await mkdir4(dirname2(targetPath), { recursive: true });
3844
+ await writeFile6(targetPath, file.content, "utf-8");
3845
+ written++;
3846
+ }
3847
+ if (skippedExisting > 0) {
3848
+ spinner.succeed(`Applied ${written} files, skipped ${skippedExisting} (use --force to overwrite)`);
3849
+ } else {
3850
+ spinner.succeed(`Applied ${written} files`);
3851
+ }
3852
+ });
3853
+ filesCommand.command("show <path>").description("Show content of a synced file").action(async (path) => {
3854
+ const db = await getDb();
3855
+ const result = await db.execute({
3856
+ sql: "SELECT content, scope, source, updated_at FROM files WHERE path = ? AND deleted_at IS NULL",
3857
+ args: [path]
3858
+ });
3859
+ if (result.rows.length === 0) {
3860
+ console.log(chalk26.red(`File not found: ${path}`));
3861
+ return;
3862
+ }
3863
+ const file = result.rows[0];
3864
+ console.log(chalk26.dim(`# ${path}`));
3865
+ console.log(chalk26.dim(`# Scope: ${file.scope} | Source: ${file.source || "unknown"}`));
3866
+ console.log(chalk26.dim(`# Updated: ${file.updated_at}`));
3867
+ console.log(chalk26.dim("\u2500".repeat(50)));
3868
+ console.log(file.content);
3869
+ });
3870
+ filesCommand.command("forget <path>").description("Remove a file from sync (soft delete)").action(async (path) => {
3871
+ const db = await getDb();
3872
+ const result = await db.execute({
3873
+ sql: "UPDATE files SET deleted_at = datetime('now') WHERE path = ? AND deleted_at IS NULL",
3874
+ args: [path]
3875
+ });
3876
+ if (result.rowsAffected === 0) {
3877
+ console.log(chalk26.red(`File not found: ${path}`));
3878
+ return;
3879
+ }
3880
+ const sync = await readSyncConfig();
3881
+ if (sync) {
3882
+ await syncDb();
3883
+ }
3884
+ console.log(chalk26.green(`\u2713 Removed ${path} from sync`));
3885
+ });
3886
+
3602
3887
  // src/index.ts
3603
- var program = new Command28().name("memories").description("A local-first memory layer for AI agents").version("0.2.2");
3888
+ var program = new Command29().name("memories").description("A local-first memory layer for AI agents").version("0.3.0");
3604
3889
  program.addCommand(initCommand);
3605
3890
  program.addCommand(addCommand);
3606
3891
  program.addCommand(recallCommand);
@@ -3631,6 +3916,7 @@ program.addCommand(templateCommand);
3631
3916
  program.addCommand(historyCommand);
3632
3917
  program.addCommand(revertCommand);
3633
3918
  program.addCommand(embedCommand);
3919
+ program.addCommand(filesCommand);
3634
3920
  program.addCommand(loginCommand);
3635
3921
  program.addCommand(logoutCommand);
3636
3922
  program.parse();
@@ -11,8 +11,8 @@ import {
11
11
  recordMemoryHistory,
12
12
  searchMemories,
13
13
  updateMemory
14
- } from "./chunk-JMN3U7AI.js";
15
- import "./chunk-BDPB4ABQ.js";
14
+ } from "./chunk-TRACL3XY.js";
15
+ import "./chunk-GCWB2OVN.js";
16
16
  export {
17
17
  addMemory,
18
18
  bulkForgetByIds,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memories.sh/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "A local-first memory layer for AI coding agents — persistent context for Claude, Cursor, and more",
5
5
  "type": "module",
6
6
  "license": "MIT",