botrun-msync 0.2.0 → 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 +38 -27
- package/package.json +1 -1
- package/src/bin.mjs +19 -6
- package/src/commands/config/set-data-path.mjs +8 -0
- package/src/commands/memory/init.mjs +10 -4
- package/src/commands/memory/scopes.mjs +11 -7
- package/src/commands/memory/sync.mjs +6 -5
- package/src/config.mjs +5 -5
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
|
-
###
|
|
82
|
+
### Config 與 Data 分離
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
config.json(scope 定義)和 data(clone 下來的 repo)可以放在不同位置:
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
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": "/
|
|
166
|
-
"team1": { "local": "/
|
|
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": "/
|
|
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": "/
|
|
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
|
|
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
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.
|
|
31
|
+
.version('0.3.0')
|
|
31
32
|
.helpCommand(false)
|
|
32
|
-
.option('--
|
|
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
|
|
36
|
-
if (
|
|
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
|
-
.
|
|
80
|
-
|
|
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,
|
|
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
|
|
54
|
+
const config = await loadConfig(configPath);
|
|
55
55
|
const localMode = options.localMode || false;
|
|
56
56
|
|
|
57
|
-
|
|
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,
|
|
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 ||
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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,
|
|
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 ||
|
|
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
|
|
5
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), '.botrun', 'bms', 'config.json');
|
|
6
6
|
|
|
7
|
-
export function
|
|
8
|
-
return process.env.
|
|
7
|
+
export function getConfigPath() {
|
|
8
|
+
return process.env.BMS_CONFIG || DEFAULT_CONFIG_PATH;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function
|
|
12
|
-
return
|
|
11
|
+
export function getDataPath(config) {
|
|
12
|
+
return config.data_path || null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export async function loadConfig(configPath = getConfigPath()) {
|