botrun-msync 0.2.1 → 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.
package/README.md CHANGED
@@ -79,36 +79,38 @@ This way, even if a user modifies the config, they can't write to repos their to
79
79
 
80
80
  ## Config
81
81
 
82
- ### Base Path
82
+ ### Config 與 Data 分離
83
83
 
84
- All `bms` data lives under a single base directory:
84
+ config.json(scope 定義)和 data(clone 下來的 repo)可以放在不同位置:
85
85
 
86
- ```
87
- ~/.botrun/bms/ ← default base path
88
- ├── config.json ← scope definitions
89
- └── data/
90
- ├── my-notes/ ← git clone of my-notes scope
91
- ├── team1/ ← git clone of team1 scope
92
- └── team2/ ← git clone of team2 scope
93
- ```
86
+ - **config.json** — 集中管理,預設 `~/.botrun/bms/config.json`
87
+ - **data** 跟著使用場景走,由 `memory init` 決定位置,預設 `$PWD/data/`
94
88
 
95
- Override with CLI option or environment variable:
96
-
97
- ```bash
98
- npx botrun-msync --bms-path /tmp/test-bms memory init # CLI option (highest priority)
99
- BMS_PATH=/custom/path npx botrun-msync memory init # environment variable
100
89
  ```
90
+ ~/.botrun/bms/config.json ← scope 定義 + data_path 指向
101
91
 
102
- Priority: `--bms-path` > `BMS_PATH` > `~/.botrun/bms/`
92
+ /your/project/data/ ← data 跟著專案走
93
+ ├── my-notes/ ← git clone of my-notes scope
94
+ ├── team1/ ← git clone of team1 scope
95
+ └── team2/ ← git clone of team2 scope
96
+ ```
103
97
 
104
98
  ### Config File
105
99
 
106
- Located at `<BMS_PATH>/config.json` (default: `~/.botrun/bms/config.json`).
100
+ 預設路徑:`~/.botrun/bms/config.json`
101
+
102
+ 覆蓋方式:
103
+
104
+ ```bash
105
+ npx botrun-msync --config-path /path/to/config.json memory init # CLI option
106
+ BMS_CONFIG=/path/to/config.json npx botrun-msync memory init # env var
107
+ ```
107
108
 
108
- Override config path independently with: `BMS_CONFIG=/path/to/config.json`
109
+ Priority: `--config-path` > `BMS_CONFIG` > `~/.botrun/bms/config.json`
109
110
 
110
111
  ```json
111
112
  {
113
+ "data_path": "/your/project/data",
112
114
  "scopes": {
113
115
  "my-notes": {
114
116
  "repo": "github.com/org/member1-memory",
@@ -127,6 +129,13 @@ Override config path independently with: `BMS_CONFIG=/path/to/config.json`
127
129
  }
128
130
  ```
129
131
 
132
+ | Field | Required | Description |
133
+ |-------|----------|-------------|
134
+ | `data_path` | no | Absolute path to data directory. Written by `memory init` |
135
+ | `scopes` | yes | Scope definitions (see below) |
136
+
137
+ Scope fields:
138
+
130
139
  | Field | Required | Description |
131
140
  |-------|----------|-------------|
132
141
  | `repo` | yes | Git repo URL (without `https://`) |
@@ -141,6 +150,7 @@ Override config path independently with: `BMS_CONFIG=/path/to/config.json`
141
150
  ```bash
142
151
  npx botrun-msync config add-scope <name> --repo <url> [--branch <branch>] [--token-env <envVar>] [--description <text>] [--access <mode>]
143
152
  npx botrun-msync config remove-scope <name>
153
+ npx botrun-msync config set-data-path <path>
144
154
  npx botrun-msync config show
145
155
  ```
146
156
 
@@ -148,22 +158,23 @@ npx botrun-msync config show
148
158
 
149
159
  | Variable | Purpose |
150
160
  |----------|---------|
151
- | `BMS_PATH` | Base directory for all bms data (default: `~/.botrun/bms`) |
152
- | `BMS_CONFIG` | Config file path (overrides `<BMS_PATH>/config.json`) |
161
+ | `BMS_CONFIG` | Config file path (default: `~/.botrun/bms/config.json`) |
153
162
 
154
163
  Each scope's token is configured via `--token-env`, which points to an environment variable name. There are no global token variables — every scope must declare its own.
155
164
 
156
165
  ## Memory Commands
157
166
 
158
- ### `npx botrun-msync memory init`
167
+ ### `npx botrun-msync memory init [--data-path <path>]`
168
+
169
+ Clones all configured scope repos. Writes `data_path` to config.json.
159
170
 
160
- Clones all configured scope repos to `<BMS_PATH>/data/<scope-name>/`. If already cloned, pulls latest.
171
+ Data path priority: `--data-path` > config `data_path` > `$PWD/data/`
161
172
 
162
173
  ```json
163
174
  {
164
175
  "scopes": {
165
- "my-notes": { "local": "/root/.botrun/bms/data/my-notes" },
166
- "team1": { "local": "/root/.botrun/bms/data/team1" }
176
+ "my-notes": { "local": "/your/project/data/my-notes" },
177
+ "team1": { "local": "/your/project/data/team1" }
167
178
  }
168
179
  }
169
180
  ```
@@ -179,13 +190,13 @@ Lists all scopes with their repo, description, access, and local filesystem path
179
190
  "repo": "github.com/org/member1-memory",
180
191
  "description": "Personal research notes",
181
192
  "access": "readwrite",
182
- "local": "/root/.botrun/bms/data/my-notes"
193
+ "local": "/your/project/data/my-notes"
183
194
  },
184
195
  "team1": {
185
196
  "repo": "github.com/org/team1-memory",
186
197
  "description": "Team 1 memory",
187
198
  "access": "readonly",
188
- "local": "/root/.botrun/bms/data/team1"
199
+ "local": "/your/project/data/team1"
189
200
  }
190
201
  }
191
202
  }
@@ -216,7 +227,7 @@ npx botrun-msync memory --help
216
227
 
217
228
  ```
218
229
  VM starts
219
- → npx botrun-msync memory init # clone repos to <BMS_PATH>/data/
230
+ → npx botrun-msync memory init # clone repos to data/
220
231
  → agent reads/writes files # using native tools (Read, Write, grep)
221
232
  → npx botrun-msync memory sync # push changes
222
233
  VM destroyed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botrun-msync",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Git-backed memory sync CLI for AI agents (forked from botrun-mcli@0.2.2)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin.mjs CHANGED
@@ -5,6 +5,7 @@ import { getConfigPath } from './config.mjs';
5
5
  import { addScope } from './commands/config/add-scope.mjs';
6
6
  import { removeScope } from './commands/config/remove-scope.mjs';
7
7
  import { showConfig } from './commands/config/show.mjs';
8
+ import { setDataPath } from './commands/config/set-data-path.mjs';
8
9
  import { initMemory } from './commands/memory/init.mjs';
9
10
  import { syncMemory } from './commands/memory/sync.mjs';
10
11
  import { listScopes } from './commands/memory/scopes.mjs';
@@ -27,13 +28,13 @@ const program = new Command();
27
28
  program
28
29
  .name('bms')
29
30
  .description('Git-backed memory sync for agents')
30
- .version('0.2.0')
31
+ .version('0.3.0')
31
32
  .helpCommand(false)
32
- .option('--bms-path <path>', 'Base directory for all bms data')
33
+ .option('--config-path <path>', 'Path to config.json')
33
34
  .configureHelp({ formatHelp: (cmd) => JSON.stringify(jsonHelp(cmd), null, 2) })
34
35
  .hook('preAction', (thisCommand) => {
35
- const bmsPath = thisCommand.opts().bmsPath;
36
- if (bmsPath) process.env.BMS_PATH = bmsPath;
36
+ const configPath = thisCommand.opts().configPath;
37
+ if (configPath) process.env.BMS_CONFIG = configPath;
37
38
  });
38
39
 
39
40
  // --- config ---
@@ -69,6 +70,14 @@ configCmd
69
70
  console.log(JSON.stringify(result, null, 2));
70
71
  });
71
72
 
73
+ configCmd
74
+ .command('set-data-path <path>')
75
+ .description('Update the data directory path in config')
76
+ .action(async (path) => {
77
+ const result = await setDataPath(path, getConfigPath());
78
+ console.log(JSON.stringify(result, null, 2));
79
+ });
80
+
72
81
  // --- memory ---
73
82
  const memoryCmd = program.command('memory').description('Manage memory repos');
74
83
  memoryCmd.configureHelp({ formatHelp: (cmd) => JSON.stringify(jsonHelp(cmd), null, 2) });
@@ -76,8 +85,12 @@ memoryCmd.configureHelp({ formatHelp: (cmd) => JSON.stringify(jsonHelp(cmd), nul
76
85
  memoryCmd
77
86
  .command('init')
78
87
  .description('Clone all scope repos')
79
- .action(async () => {
80
- const result = await initMemory({ configPath: getConfigPath() });
88
+ .option('--data-path <path>', 'Directory to clone repos into (default: $PWD/data/)')
89
+ .action(async (opts) => {
90
+ const result = await initMemory({
91
+ configPath: getConfigPath(),
92
+ dataDir: opts.dataPath,
93
+ });
81
94
  console.log(JSON.stringify(result, null, 2));
82
95
  });
83
96
 
@@ -0,0 +1,8 @@
1
+ import { loadConfig, saveConfig } from '../../config.mjs';
2
+
3
+ export async function setDataPath(dataPath, configPath) {
4
+ const config = await loadConfig(configPath);
5
+ config.data_path = dataPath;
6
+ await saveConfig(configPath, config);
7
+ return { data_path: dataPath };
8
+ }
@@ -1,6 +1,6 @@
1
1
  import { mkdir, lstat } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { loadConfig, getBasePath } from '../../config.mjs';
2
+ import { join, resolve } from 'node:path';
3
+ import { loadConfig, saveConfig, getDataPath } from '../../config.mjs';
4
4
  import { gitExec } from '../../git-cmd.mjs';
5
5
  import { detectProvider, getProvider, resolveToken } from '../../git/provider.mjs';
6
6
 
@@ -51,10 +51,16 @@ async function cloneOrPull(repo, cloneDir, token, localMode, branch) {
51
51
 
52
52
  export async function initMemory(options = {}) {
53
53
  const configPath = options.configPath;
54
- const dataDir = options.dataDir || join(getBasePath(), 'data');
54
+ const config = await loadConfig(configPath);
55
55
  const localMode = options.localMode || false;
56
56
 
57
- const config = await loadConfig(configPath);
57
+ // data path priority: options.dataDir > config.data_path > $PWD/data/
58
+ const dataDir = options.dataDir || getDataPath(config) || resolve('data');
59
+
60
+ // Write data_path back to config
61
+ config.data_path = dataDir;
62
+ await saveConfig(configPath, config);
63
+
58
64
  const result = { scopes: {} };
59
65
 
60
66
  await mkdir(dataDir, { recursive: true });
@@ -1,10 +1,10 @@
1
1
  import { join } from 'node:path';
2
2
  import { lstat } from 'node:fs/promises';
3
- import { loadConfig, getBasePath } from '../../config.mjs';
3
+ import { loadConfig, getDataPath } from '../../config.mjs';
4
4
 
5
5
  export async function listScopes(options = {}) {
6
6
  const config = await loadConfig(options.configPath);
7
- const dataDir = options.dataDir || join(getBasePath(), 'data');
7
+ const dataDir = options.dataDir || getDataPath(config);
8
8
  const result = { scopes: {} };
9
9
 
10
10
  for (const [name, scope] of Object.entries(config.scopes)) {
@@ -12,11 +12,15 @@ export async function listScopes(options = {}) {
12
12
  if (scope.description) entry.description = scope.description;
13
13
  if (scope.access) entry.access = scope.access;
14
14
 
15
- const scopeDir = join(dataDir, name);
16
- try {
17
- await lstat(scopeDir);
18
- entry.local = scopeDir;
19
- } catch {
15
+ if (dataDir) {
16
+ const scopeDir = join(dataDir, name);
17
+ try {
18
+ await lstat(scopeDir);
19
+ entry.local = scopeDir;
20
+ } catch {
21
+ entry.local = null;
22
+ }
23
+ } else {
20
24
  entry.local = null;
21
25
  }
22
26
 
@@ -1,10 +1,14 @@
1
1
  import { join } from 'node:path';
2
- import { loadConfig, getBasePath } from '../../config.mjs';
2
+ import { loadConfig, getDataPath } from '../../config.mjs';
3
3
  import { gitExec } from '../../git-cmd.mjs';
4
4
 
5
5
  export async function syncMemory(options = {}) {
6
6
  const config = await loadConfig(options.configPath);
7
- const dataDir = options.dataDir || join(getBasePath(), 'data');
7
+ const dataDir = options.dataDir || getDataPath(config);
8
+
9
+ if (!dataDir) {
10
+ throw new Error('No data_path in config. Run "bms memory init" first.');
11
+ }
8
12
 
9
13
  const result = { synced: [], pulled: [], skipped: [] };
10
14
 
@@ -13,14 +17,12 @@ export async function syncMemory(options = {}) {
13
17
  let didPull = false;
14
18
  let didPush = false;
15
19
 
16
- // 1. Always pull remote changes first
17
20
  try {
18
21
  const before = await gitExec(['-C', cloneDir, 'rev-parse', 'HEAD']);
19
22
  await gitExec(['-C', cloneDir, 'pull', '--rebase']);
20
23
  const after = await gitExec(['-C', cloneDir, 'rev-parse', 'HEAD']);
21
24
  if (before !== after) didPull = true;
22
25
  } catch {
23
- // pull may fail if no upstream set; try setting it
24
26
  try {
25
27
  const branch = await gitExec(['-C', cloneDir, 'rev-parse', '--abbrev-ref', 'HEAD']);
26
28
  await gitExec(['-C', cloneDir, 'branch', '--set-upstream-to', `origin/${branch}`, branch]);
@@ -33,7 +35,6 @@ export async function syncMemory(options = {}) {
33
35
  }
34
36
  }
35
37
 
36
- // 2. Check for local uncommitted changes and push
37
38
  const status = await gitExec(['-C', cloneDir, 'status', '--porcelain']);
38
39
  if (status) {
39
40
  await gitExec(['-C', cloneDir, 'add', '-A']);
package/src/config.mjs CHANGED
@@ -2,14 +2,14 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
3
  import { join, dirname } from 'node:path';
4
4
 
5
- const DEFAULT_BASE_PATH = join(homedir(), '.botrun', 'bms');
5
+ const DEFAULT_CONFIG_PATH = join(homedir(), '.botrun', 'bms', 'config.json');
6
6
 
7
- export function getBasePath() {
8
- return process.env.BMS_PATH || DEFAULT_BASE_PATH;
7
+ export function getConfigPath() {
8
+ return process.env.BMS_CONFIG || DEFAULT_CONFIG_PATH;
9
9
  }
10
10
 
11
- export function getConfigPath() {
12
- return process.env.BMS_CONFIG || join(getBasePath(), 'config.json');
11
+ export function getDataPath(config) {
12
+ return config.data_path || null;
13
13
  }
14
14
 
15
15
  export async function loadConfig(configPath = getConfigPath()) {