openclaw-teleport 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 +26 -21
- package/dist/cli.mjs +125 -125
- package/package.json +1 -1
- package/src/commands.ts +65 -62
- package/src/pack.ts +45 -35
- package/src/utils.ts +66 -46
package/README.md
CHANGED
|
@@ -10,9 +10,7 @@ Built for [OpenClaw](https://github.com/nicepkg/openclaw) agents.
|
|
|
10
10
|
|
|
11
11
|
`openclaw-teleport` captures everything that makes an agent *that agent*:
|
|
12
12
|
|
|
13
|
-
- **
|
|
14
|
-
- **Memory** — daily notes, long-term memory, everything in `memory/`
|
|
15
|
-
- **Tool data** — SQLite databases and other `.db` files
|
|
13
|
+
- **Workspace** — entire workspace directory (identity files, memory, daily notes, workflows, skills, tool configs — everything except git repo subdirectories)
|
|
16
14
|
- **Config** — agent configuration from `openclaw.json`
|
|
17
15
|
- **Channel credentials** — Discord tokens, Feishu app secrets, all channel configs
|
|
18
16
|
- **Cron jobs** — full scheduled task definitions (not just file names)
|
|
@@ -22,10 +20,10 @@ Built for [OpenClaw](https://github.com/nicepkg/openclaw) agents.
|
|
|
22
20
|
All packed into a single `.soul` file (tar.gz). On a new machine, `unpack` does a **full one-command restoration**:
|
|
23
21
|
|
|
24
22
|
1. ✅ Installs OpenClaw (if missing)
|
|
25
|
-
2. ✅ Restores
|
|
23
|
+
2. ✅ Restores full workspace (files, memory, workflows, skills, databases)
|
|
26
24
|
3. ✅ Writes agent config + channel credentials to `openclaw.json`
|
|
27
25
|
4. ✅ Restores cron jobs
|
|
28
|
-
5. ✅ Clones GitHub repos (
|
|
26
|
+
5. ✅ Clones GitHub repos (auto-detects forks)
|
|
29
27
|
6. ✅ Guides through GitHub auth if needed
|
|
30
28
|
7. ✅ Starts the OpenClaw gateway
|
|
31
29
|
8. ✅ Prints a welcome summary
|
|
@@ -76,13 +74,12 @@ openclaw-teleport unpack kagura_20260320.soul --workspace /path/to/workspace
|
|
|
76
74
|
|
|
77
75
|
What happens:
|
|
78
76
|
1. **OpenClaw check** — installs via `npm install -g openclaw` if missing
|
|
79
|
-
2. **
|
|
80
|
-
3. **Config written** — agent config + channel credentials merged into `openclaw.json`
|
|
77
|
+
2. **Workspace restored** — full directory structure (identity, memory, workflows, skills, databases)
|
|
78
|
+
3. **Config written** — agent config + channel credentials merged into `openclaw.json`
|
|
81
79
|
4. **Cron jobs restored** — full job definitions written to `~/.openclaw/cron/jobs.json`
|
|
82
|
-
5. **GitHub repos cloned** — using `gh repo clone` (
|
|
83
|
-
6. **
|
|
84
|
-
7. **
|
|
85
|
-
8. **Welcome summary** — file counts, repo status, configured services
|
|
80
|
+
5. **GitHub repos cloned** — using `gh repo clone` (git repo subdirectories that were skipped during pack)
|
|
81
|
+
6. **Gateway started** — `openclaw gateway start`
|
|
82
|
+
7. **Welcome summary** — file counts, repo status, configured services
|
|
86
83
|
|
|
87
84
|
### Inspect a .soul file
|
|
88
85
|
|
|
@@ -99,29 +96,37 @@ Shows manifest info without unpacking: agent name, pack date, file count, repo l
|
|
|
99
96
|
├── openclaw.json ← agent config + channels extracted
|
|
100
97
|
├── cron/jobs.json ← full cron job definitions
|
|
101
98
|
└── workspace/
|
|
102
|
-
├── SOUL.md ← identity files
|
|
99
|
+
├── SOUL.md ← identity files
|
|
103
100
|
├── IDENTITY.md
|
|
104
101
|
├── USER.md
|
|
105
102
|
├── TOOLS.md
|
|
106
|
-
├──
|
|
103
|
+
├── HEARTBEAT.md
|
|
104
|
+
├── NUDGE.md
|
|
105
|
+
├── beliefs-candidates.md
|
|
106
|
+
├── memory/ ← daily notes + long-term memory
|
|
107
107
|
│ ├── 2026-03-15.md
|
|
108
108
|
│ └── ...
|
|
109
|
-
|
|
109
|
+
├── skills/ ← custom skills
|
|
110
|
+
├── flowforge/ ← git repo (skipped, cloned on unpack)
|
|
111
|
+
└── knowledge-base/ ← git repo (skipped, cloned on unpack)
|
|
110
112
|
|
|
111
113
|
↓ openclaw-teleport pack
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
kagura_20260324.soul (tar.gz archive)
|
|
114
116
|
├── manifest.json ← metadata, repos, channels, cron jobs
|
|
115
|
-
├──
|
|
116
|
-
├──
|
|
117
|
-
├──
|
|
117
|
+
├── workspace/ ← full workspace (minus git repos)
|
|
118
|
+
│ ├── SOUL.md
|
|
119
|
+
│ ├── memory/
|
|
120
|
+
│ ├── skills/
|
|
121
|
+
│ └── ...
|
|
118
122
|
├── config/ ← agent config
|
|
119
|
-
|
|
123
|
+
├── cron/ ← cron files
|
|
124
|
+
└── credentials/ ← pairing records
|
|
120
125
|
|
|
121
126
|
↓ openclaw-teleport unpack (on new machine)
|
|
122
127
|
|
|
123
128
|
1. Install OpenClaw (if needed)
|
|
124
|
-
2. Restore
|
|
129
|
+
2. Restore workspace files
|
|
125
130
|
3. Write config + credentials to openclaw.json
|
|
126
131
|
4. Restore cron jobs
|
|
127
132
|
5. Clone GitHub repos (via gh)
|
|
@@ -138,7 +143,7 @@ The manifest contains metadata and embedded configurations:
|
|
|
138
143
|
"agent_id": "kagura",
|
|
139
144
|
"agent_name": "Kagura",
|
|
140
145
|
"packed_at": "2026-03-20T04:25:00.000Z",
|
|
141
|
-
"files": ["
|
|
146
|
+
"files": ["workspace/SOUL.md", "workspace/memory/2026-03-15.md", "..."],
|
|
142
147
|
"github_repos": [
|
|
143
148
|
{ "name": "openclaw-teleport", "url": "https://github.com/kagura-agent/openclaw-teleport", "isFork": false }
|
|
144
149
|
],
|
package/dist/cli.mjs
CHANGED
|
@@ -39,58 +39,32 @@ function findAgent(config, agentId) {
|
|
|
39
39
|
}
|
|
40
40
|
return agents[0];
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (const entry of entries) {
|
|
46
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
47
|
-
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
48
|
-
files.push(entry.name);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return files;
|
|
42
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "__pycache__", ".venv", "venv"]);
|
|
43
|
+
function isGitRepo(dirPath) {
|
|
44
|
+
return fs.existsSync(path.join(dirPath, ".git"));
|
|
52
45
|
}
|
|
53
|
-
function
|
|
54
|
-
const memoryDir = path.join(workspace, "memory");
|
|
55
|
-
if (!fs.existsSync(memoryDir)) return [];
|
|
46
|
+
function collectWorkspaceFiles(workspace) {
|
|
56
47
|
const files = [];
|
|
57
48
|
const walk = (dir, prefix) => {
|
|
58
|
-
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
59
55
|
for (const entry of entries) {
|
|
60
|
-
|
|
56
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
57
|
+
const fullPath = path.join(dir, entry.name);
|
|
58
|
+
const rel = prefix ? path.join(prefix, entry.name) : entry.name;
|
|
61
59
|
if (entry.isDirectory()) {
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (isGitRepo(fullPath)) continue;
|
|
61
|
+
walk(fullPath, rel);
|
|
62
|
+
} else if (entry.isFile()) {
|
|
64
63
|
files.push(rel);
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
};
|
|
68
|
-
walk(
|
|
69
|
-
return files;
|
|
70
|
-
}
|
|
71
|
-
function collectDbFiles(workspace) {
|
|
72
|
-
const files = [];
|
|
73
|
-
const knownPaths = [
|
|
74
|
-
"gogetajob/data/gogetajob.db",
|
|
75
|
-
"flowforge/flowforge.db",
|
|
76
|
-
"data/gogetajob.db",
|
|
77
|
-
"data/flowforge.db"
|
|
78
|
-
];
|
|
79
|
-
for (const rel of knownPaths) {
|
|
80
|
-
const full = path.join(workspace, rel);
|
|
81
|
-
if (fs.existsSync(full)) {
|
|
82
|
-
files.push(rel);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const rootEntries = fs.readdirSync(workspace, { withFileTypes: true });
|
|
87
|
-
for (const entry of rootEntries) {
|
|
88
|
-
if (entry.isFile() && entry.name.endsWith(".db")) {
|
|
89
|
-
files.push(entry.name);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
}
|
|
67
|
+
walk(workspace, "");
|
|
94
68
|
return files;
|
|
95
69
|
}
|
|
96
70
|
function collectCronFiles(agentId) {
|
|
@@ -185,6 +159,24 @@ function commandExists(cmd) {
|
|
|
185
159
|
return false;
|
|
186
160
|
}
|
|
187
161
|
}
|
|
162
|
+
function installGh() {
|
|
163
|
+
try {
|
|
164
|
+
const platform2 = os.platform();
|
|
165
|
+
if (platform2 === "linux") {
|
|
166
|
+
execSync(
|
|
167
|
+
'(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) && sudo mkdir -p -m 755 /etc/apt/keyrings && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && sudo apt update && sudo apt install gh -y',
|
|
168
|
+
{ stdio: "pipe", timeout: 12e4 }
|
|
169
|
+
);
|
|
170
|
+
} else if (platform2 === "darwin") {
|
|
171
|
+
execSync("brew install gh", { stdio: "pipe", timeout: 12e4 });
|
|
172
|
+
} else {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return commandExists("gh");
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
188
180
|
function isGhAuthenticated() {
|
|
189
181
|
try {
|
|
190
182
|
execSync("gh auth status", { encoding: "utf-8", stdio: "pipe" });
|
|
@@ -216,36 +208,32 @@ async function pack(agentId, outputPath) {
|
|
|
216
208
|
}
|
|
217
209
|
fs2.mkdirSync(stageDir, { recursive: true });
|
|
218
210
|
const allFiles = [];
|
|
219
|
-
console.log("\u{
|
|
220
|
-
const
|
|
221
|
-
for (const f of
|
|
222
|
-
const src = path2.join(agent.workspace, f);
|
|
223
|
-
const dst = path2.join(stageDir, "identity", f);
|
|
224
|
-
fs2.mkdirSync(path2.dirname(dst), { recursive: true });
|
|
225
|
-
fs2.copyFileSync(src, dst);
|
|
226
|
-
allFiles.push(`identity/${f}`);
|
|
227
|
-
}
|
|
228
|
-
console.log(` \u2705 ${mdFiles.length} markdown files`);
|
|
229
|
-
console.log("\u{1F9E0} Collecting memory...");
|
|
230
|
-
const memFiles = collectMemoryDir(agent.workspace);
|
|
231
|
-
for (const f of memFiles) {
|
|
211
|
+
console.log("\u{1F4C2} Collecting workspace files...");
|
|
212
|
+
const wsFiles = collectWorkspaceFiles(agent.workspace);
|
|
213
|
+
for (const f of wsFiles) {
|
|
232
214
|
const src = path2.join(agent.workspace, f);
|
|
233
|
-
const dst = path2.join(stageDir, f);
|
|
215
|
+
const dst = path2.join(stageDir, "workspace", f);
|
|
234
216
|
fs2.mkdirSync(path2.dirname(dst), { recursive: true });
|
|
235
217
|
fs2.copyFileSync(src, dst);
|
|
236
|
-
allFiles.push(f);
|
|
218
|
+
allFiles.push(`workspace/${f}`);
|
|
237
219
|
}
|
|
238
|
-
console.log(` \u2705 ${
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
220
|
+
console.log(` \u2705 ${wsFiles.length} files (skipped git repo subdirs)`);
|
|
221
|
+
try {
|
|
222
|
+
const topEntries = fs2.readdirSync(agent.workspace, { withFileTypes: true });
|
|
223
|
+
const skippedRepos = [];
|
|
224
|
+
for (const entry of topEntries) {
|
|
225
|
+
if (entry.isDirectory()) {
|
|
226
|
+
const gitDir = path2.join(agent.workspace, entry.name, ".git");
|
|
227
|
+
if (fs2.existsSync(gitDir)) {
|
|
228
|
+
skippedRepos.push(entry.name);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (skippedRepos.length > 0) {
|
|
233
|
+
console.log(` \u23ED\uFE0F Skipped git repos (will clone on unpack): ${skippedRepos.join(", ")}`);
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
247
236
|
}
|
|
248
|
-
console.log(` \u2705 ${dbFiles.length} database files`);
|
|
249
237
|
console.log("\u2699\uFE0F Extracting agent config...");
|
|
250
238
|
const agentConfig = extractAgentConfig(config, agent.id);
|
|
251
239
|
const configPath = path2.join(stageDir, "config", "agent-config.json");
|
|
@@ -263,6 +251,21 @@ async function pack(agentId, outputPath) {
|
|
|
263
251
|
allFiles.push(`cron/${f}`);
|
|
264
252
|
}
|
|
265
253
|
console.log(` \u2705 ${cronFiles.length} cron files`);
|
|
254
|
+
console.log("\u{1F510} Collecting credentials...");
|
|
255
|
+
const credDir = path2.join(OPENCLAW_DIR2, "credentials");
|
|
256
|
+
let credCount = 0;
|
|
257
|
+
if (fs2.existsSync(credDir)) {
|
|
258
|
+
const credFiles = fs2.readdirSync(credDir).filter((f) => f.endsWith(".json"));
|
|
259
|
+
for (const f of credFiles) {
|
|
260
|
+
const src = path2.join(credDir, f);
|
|
261
|
+
const dst = path2.join(stageDir, "credentials", f);
|
|
262
|
+
fs2.mkdirSync(path2.dirname(dst), { recursive: true });
|
|
263
|
+
fs2.copyFileSync(src, dst);
|
|
264
|
+
allFiles.push(`credentials/${f}`);
|
|
265
|
+
credCount++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
console.log(` \u2705 ${credCount} credential files`);
|
|
266
269
|
console.log("\u23F0 Extracting cron job definitions...");
|
|
267
270
|
const cronJobs = loadCronJobs(agent.id);
|
|
268
271
|
console.log(` \u2705 ${cronJobs.length} cron jobs for ${agent.id}`);
|
|
@@ -278,6 +281,7 @@ async function pack(agentId, outputPath) {
|
|
|
278
281
|
const agentDefaults = sanitizeAgentDefaults(config.agents?.defaults ?? {});
|
|
279
282
|
const modelsConfig = config.models ?? {};
|
|
280
283
|
const bindingsConfig = config.bindings ?? [];
|
|
284
|
+
const gatewayConfig = config.gateway ?? {};
|
|
281
285
|
const manifest = {
|
|
282
286
|
agent_id: agent.id,
|
|
283
287
|
agent_name: agent.name,
|
|
@@ -289,7 +293,8 @@ async function pack(agentId, outputPath) {
|
|
|
289
293
|
cron_jobs: cronJobs,
|
|
290
294
|
agent_defaults: agentDefaults,
|
|
291
295
|
models_config: modelsConfig,
|
|
292
|
-
bindings: bindingsConfig
|
|
296
|
+
bindings: bindingsConfig,
|
|
297
|
+
gateway: gatewayConfig
|
|
293
298
|
};
|
|
294
299
|
const manifestPath = path2.join(stageDir, "manifest.json");
|
|
295
300
|
fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
@@ -450,6 +455,10 @@ function writeAgentConfig(manifest, stageDir, targetWorkspace) {
|
|
|
450
455
|
console.log(" \u23ED\uFE0F Bindings already exist, skipping");
|
|
451
456
|
}
|
|
452
457
|
}
|
|
458
|
+
if (manifest.gateway && Object.keys(manifest.gateway).length > 0) {
|
|
459
|
+
existingConfig.gateway = { ...existingConfig.gateway ?? {}, ...manifest.gateway };
|
|
460
|
+
console.log(" \u2705 Gateway config restored");
|
|
461
|
+
}
|
|
453
462
|
fs3.writeFileSync(CONFIG_PATH2, JSON.stringify(existingConfig, null, 2));
|
|
454
463
|
} else {
|
|
455
464
|
const newConfig = {
|
|
@@ -473,6 +482,10 @@ function writeAgentConfig(manifest, stageDir, targetWorkspace) {
|
|
|
473
482
|
newConfig.bindings = manifest.bindings;
|
|
474
483
|
console.log(" \u2705 Bindings restored");
|
|
475
484
|
}
|
|
485
|
+
if (manifest.gateway && Object.keys(manifest.gateway).length > 0) {
|
|
486
|
+
newConfig.gateway = manifest.gateway;
|
|
487
|
+
console.log(" \u2705 Gateway config restored");
|
|
488
|
+
}
|
|
476
489
|
fs3.writeFileSync(CONFIG_PATH2, JSON.stringify(newConfig, null, 2));
|
|
477
490
|
console.log(" \u2705 New openclaw.json created");
|
|
478
491
|
}
|
|
@@ -519,6 +532,22 @@ function restoreCronJobs(manifest, stageDir) {
|
|
|
519
532
|
}
|
|
520
533
|
return manifest.cron_jobs?.length ?? cronFileCount;
|
|
521
534
|
}
|
|
535
|
+
function restoreCredentials(stageDir) {
|
|
536
|
+
console.log("\u{1F510} Restoring credentials...");
|
|
537
|
+
const credSrc = path3.join(stageDir, "credentials");
|
|
538
|
+
if (!fs3.existsSync(credSrc)) {
|
|
539
|
+
console.log(" (none)");
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
const credDst = path3.join(OPENCLAW_DIR3, "credentials");
|
|
543
|
+
fs3.mkdirSync(credDst, { recursive: true });
|
|
544
|
+
const files = fs3.readdirSync(credSrc).filter((f) => f.endsWith(".json"));
|
|
545
|
+
for (const f of files) {
|
|
546
|
+
fs3.copyFileSync(path3.join(credSrc, f), path3.join(credDst, f));
|
|
547
|
+
}
|
|
548
|
+
console.log(` \u2705 ${files.length} credential file(s) restored`);
|
|
549
|
+
return files.length;
|
|
550
|
+
}
|
|
522
551
|
function cloneGitHubRepos(manifest, targetWorkspace) {
|
|
523
552
|
const result = { cloned: 0, skipped: 0, failed: 0 };
|
|
524
553
|
if (!manifest.github_repos || manifest.github_repos.length === 0) {
|
|
@@ -526,15 +555,19 @@ function cloneGitHubRepos(manifest, targetWorkspace) {
|
|
|
526
555
|
}
|
|
527
556
|
console.log("\n\u{1F419} Cloning GitHub repos...");
|
|
528
557
|
if (!commandExists("gh")) {
|
|
529
|
-
console.log(" \
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
console.log(`
|
|
558
|
+
console.log(" \u2B07\uFE0F GitHub CLI (gh) not found, installing...");
|
|
559
|
+
const installed = installGh();
|
|
560
|
+
if (!installed) {
|
|
561
|
+
console.log(" \u26A0\uFE0F Could not auto-install GitHub CLI");
|
|
562
|
+
console.log(" Install manually: https://cli.github.com/");
|
|
563
|
+
console.log(` Repos to clone manually (${manifest.github_repos.length}):`);
|
|
564
|
+
for (const repo of manifest.github_repos) {
|
|
565
|
+
console.log(` git clone ${repo.url}`);
|
|
566
|
+
}
|
|
567
|
+
result.failed = manifest.github_repos.length;
|
|
568
|
+
return result;
|
|
535
569
|
}
|
|
536
|
-
|
|
537
|
-
return result;
|
|
570
|
+
console.log(" \u2705 GitHub CLI installed");
|
|
538
571
|
}
|
|
539
572
|
if (!isGhAuthenticated()) {
|
|
540
573
|
console.log(" \u26A0\uFE0F GitHub CLI not authenticated");
|
|
@@ -622,23 +655,10 @@ async function unpack(soulFile, workspacePath) {
|
|
|
622
655
|
const openclawInstalled = ensureOpenClaw();
|
|
623
656
|
const targetWorkspace = workspacePath ? path3.resolve(workspacePath) : path3.join(OPENCLAW_DIR3, "workspace");
|
|
624
657
|
fs3.mkdirSync(targetWorkspace, { recursive: true });
|
|
625
|
-
console.log("\n\u{
|
|
626
|
-
let
|
|
627
|
-
const
|
|
628
|
-
if (fs3.existsSync(
|
|
629
|
-
const files = fs3.readdirSync(identityDir);
|
|
630
|
-
for (const f of files) {
|
|
631
|
-
const src = path3.join(identityDir, f);
|
|
632
|
-
const dst = path3.join(targetWorkspace, f);
|
|
633
|
-
fs3.copyFileSync(src, dst);
|
|
634
|
-
console.log(` \u2705 ${f}`);
|
|
635
|
-
identityCount++;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
console.log("\u{1F9E0} Restoring memory...");
|
|
639
|
-
let memoryCount = 0;
|
|
640
|
-
const memoryDir = path3.join(stageDir, "memory");
|
|
641
|
-
if (fs3.existsSync(memoryDir)) {
|
|
658
|
+
console.log("\n\u{1F4C2} Restoring workspace files...");
|
|
659
|
+
let workspaceCount = 0;
|
|
660
|
+
const workspaceDir = path3.join(stageDir, "workspace");
|
|
661
|
+
if (fs3.existsSync(workspaceDir)) {
|
|
642
662
|
const copyRecursive = (src, dst) => {
|
|
643
663
|
fs3.mkdirSync(dst, { recursive: true });
|
|
644
664
|
const entries = fs3.readdirSync(src, { withFileTypes: true });
|
|
@@ -649,36 +669,18 @@ async function unpack(soulFile, workspacePath) {
|
|
|
649
669
|
copyRecursive(srcPath, dstPath);
|
|
650
670
|
} else {
|
|
651
671
|
fs3.copyFileSync(srcPath, dstPath);
|
|
652
|
-
|
|
672
|
+
workspaceCount++;
|
|
653
673
|
}
|
|
654
674
|
}
|
|
655
675
|
};
|
|
656
|
-
copyRecursive(
|
|
657
|
-
console.log(` \u2705 ${
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
let dataCount = 0;
|
|
661
|
-
const dataDir = path3.join(stageDir, "data");
|
|
662
|
-
if (fs3.existsSync(dataDir)) {
|
|
663
|
-
const copyRecursive = (src, dst) => {
|
|
664
|
-
fs3.mkdirSync(dst, { recursive: true });
|
|
665
|
-
const entries = fs3.readdirSync(src, { withFileTypes: true });
|
|
666
|
-
for (const entry of entries) {
|
|
667
|
-
const srcPath = path3.join(src, entry.name);
|
|
668
|
-
const dstPath = path3.join(dst, entry.name);
|
|
669
|
-
if (entry.isDirectory()) {
|
|
670
|
-
copyRecursive(srcPath, dstPath);
|
|
671
|
-
} else {
|
|
672
|
-
fs3.copyFileSync(srcPath, dstPath);
|
|
673
|
-
console.log(` \u2705 ${entry.name}`);
|
|
674
|
-
dataCount++;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
copyRecursive(dataDir, targetWorkspace);
|
|
676
|
+
copyRecursive(workspaceDir, targetWorkspace);
|
|
677
|
+
console.log(` \u2705 ${workspaceCount} files restored`);
|
|
678
|
+
} else {
|
|
679
|
+
console.log(" \u26A0\uFE0F No workspace/ directory in archive");
|
|
679
680
|
}
|
|
680
681
|
writeAgentConfig(manifest, stageDir, targetWorkspace);
|
|
681
682
|
const cronCount = restoreCronJobs(manifest, stageDir);
|
|
683
|
+
const credCount = restoreCredentials(stageDir);
|
|
682
684
|
const repoResult = cloneGitHubRepos(manifest, targetWorkspace);
|
|
683
685
|
fs3.rmSync(tmpDir, { recursive: true });
|
|
684
686
|
let gatewayStarted = false;
|
|
@@ -698,7 +700,7 @@ async function unpack(soulFile, workspacePath) {
|
|
|
698
700
|
console.log("\u2550".repeat(50));
|
|
699
701
|
console.log(`\u{1F194} Agent: ${manifest.agent_name} (${manifest.agent_id})`);
|
|
700
702
|
console.log(`\u{1F4C2} Workspace: ${targetWorkspace}`);
|
|
701
|
-
console.log(`\u{1F4DD} Files: ${
|
|
703
|
+
console.log(`\u{1F4DD} Files: ${workspaceCount} workspace files`);
|
|
702
704
|
console.log(`\u23F0 Cron: ${cronCount} job(s)`);
|
|
703
705
|
if (manifest.github_repos && manifest.github_repos.length > 0) {
|
|
704
706
|
console.log(`\u{1F419} Repos: ${repoResult.cloned} cloned, ${repoResult.skipped} skipped, ${repoResult.failed} failed`);
|
|
@@ -778,17 +780,15 @@ async function inspect(soulFile) {
|
|
|
778
780
|
console.log(` \u2022 ${svc}`);
|
|
779
781
|
}
|
|
780
782
|
}
|
|
781
|
-
const
|
|
782
|
-
const memoryFiles = manifest.files.filter((f) => f.startsWith("memory/"));
|
|
783
|
-
const dataFiles = manifest.files.filter((f) => f.startsWith("data/"));
|
|
783
|
+
const workspaceFiles = manifest.files.filter((f) => f.startsWith("workspace/"));
|
|
784
784
|
const cronFiles = manifest.files.filter((f) => f.startsWith("cron/"));
|
|
785
785
|
const configFiles = manifest.files.filter((f) => f.startsWith("config/"));
|
|
786
|
+
const credFiles = manifest.files.filter((f) => f.startsWith("credentials/"));
|
|
786
787
|
console.log("\n\u{1F4CA} Contents breakdown:");
|
|
787
|
-
if (
|
|
788
|
-
if (memoryFiles.length > 0) console.log(` \u{1F9E0} Memory: ${memoryFiles.length} files`);
|
|
789
|
-
if (dataFiles.length > 0) console.log(` \u{1F5C4}\uFE0F Data: ${dataFiles.length} files`);
|
|
788
|
+
if (workspaceFiles.length > 0) console.log(` \u{1F4C2} Workspace: ${workspaceFiles.length} files`);
|
|
790
789
|
if (cronFiles.length > 0) console.log(` \u23F0 Cron: ${cronFiles.length} files`);
|
|
791
790
|
if (configFiles.length > 0) console.log(` \u2699\uFE0F Config: ${configFiles.length} files`);
|
|
791
|
+
if (credFiles.length > 0) console.log(` \u{1F510} Creds: ${credFiles.length} files`);
|
|
792
792
|
console.log("\u2550".repeat(50) + "\n");
|
|
793
793
|
} finally {
|
|
794
794
|
fs3.rmSync(tmpDir, { recursive: true });
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
|
-
import { loadConfig, commandExists, isGhAuthenticated, type Manifest, type OpenClawConfig, type CronJob } from './utils.js';
|
|
5
|
+
import { loadConfig, commandExists, installGh, isGhAuthenticated, type Manifest, type OpenClawConfig, type CronJob } from './utils.js';
|
|
6
6
|
|
|
7
7
|
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
8
8
|
const CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
|
|
@@ -169,6 +169,12 @@ function writeAgentConfig(
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// Merge gateway config
|
|
173
|
+
if (manifest.gateway && Object.keys(manifest.gateway).length > 0) {
|
|
174
|
+
existingConfig.gateway = { ...(existingConfig.gateway ?? {}), ...manifest.gateway };
|
|
175
|
+
console.log(' ✅ Gateway config restored');
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(existingConfig, null, 2));
|
|
173
179
|
} else {
|
|
174
180
|
// Create new config from scratch
|
|
@@ -200,6 +206,12 @@ function writeAgentConfig(
|
|
|
200
206
|
console.log(' ✅ Bindings restored');
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
// Add gateway config
|
|
210
|
+
if (manifest.gateway && Object.keys(manifest.gateway).length > 0) {
|
|
211
|
+
newConfig.gateway = manifest.gateway;
|
|
212
|
+
console.log(' ✅ Gateway config restored');
|
|
213
|
+
}
|
|
214
|
+
|
|
203
215
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newConfig, null, 2));
|
|
204
216
|
console.log(' ✅ New openclaw.json created');
|
|
205
217
|
}
|
|
@@ -261,6 +273,27 @@ function restoreCronJobs(manifest: Manifest, stageDir: string): number {
|
|
|
261
273
|
return manifest.cron_jobs?.length ?? cronFileCount;
|
|
262
274
|
}
|
|
263
275
|
|
|
276
|
+
// ── Step 3.5: Restore credentials (pairing, allowFrom) ────────────
|
|
277
|
+
|
|
278
|
+
function restoreCredentials(stageDir: string): number {
|
|
279
|
+
console.log('🔐 Restoring credentials...');
|
|
280
|
+
const credSrc = path.join(stageDir, 'credentials');
|
|
281
|
+
if (!fs.existsSync(credSrc)) {
|
|
282
|
+
console.log(' (none)');
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const credDst = path.join(OPENCLAW_DIR, 'credentials');
|
|
287
|
+
fs.mkdirSync(credDst, { recursive: true });
|
|
288
|
+
|
|
289
|
+
const files = fs.readdirSync(credSrc).filter(f => f.endsWith('.json'));
|
|
290
|
+
for (const f of files) {
|
|
291
|
+
fs.copyFileSync(path.join(credSrc, f), path.join(credDst, f));
|
|
292
|
+
}
|
|
293
|
+
console.log(` ✅ ${files.length} credential file(s) restored`);
|
|
294
|
+
return files.length;
|
|
295
|
+
}
|
|
296
|
+
|
|
264
297
|
// ── Step 4 & 5: GitHub auth + clone repos ──────────────────────────
|
|
265
298
|
|
|
266
299
|
function cloneGitHubRepos(manifest: Manifest, targetWorkspace: string): { cloned: number; skipped: number; failed: number } {
|
|
@@ -272,17 +305,21 @@ function cloneGitHubRepos(manifest: Manifest, targetWorkspace: string): { cloned
|
|
|
272
305
|
|
|
273
306
|
console.log('\n🐙 Cloning GitHub repos...');
|
|
274
307
|
|
|
275
|
-
// Check if gh CLI is available
|
|
308
|
+
// Check if gh CLI is available, install if not
|
|
276
309
|
if (!commandExists('gh')) {
|
|
277
|
-
console.log('
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
console.log(`
|
|
310
|
+
console.log(' ⬇️ GitHub CLI (gh) not found, installing...');
|
|
311
|
+
const installed = installGh();
|
|
312
|
+
if (!installed) {
|
|
313
|
+
console.log(' ⚠️ Could not auto-install GitHub CLI');
|
|
314
|
+
console.log(' Install manually: https://cli.github.com/');
|
|
315
|
+
console.log(` Repos to clone manually (${manifest.github_repos.length}):`);
|
|
316
|
+
for (const repo of manifest.github_repos) {
|
|
317
|
+
console.log(` git clone ${repo.url}`);
|
|
318
|
+
}
|
|
319
|
+
result.failed = manifest.github_repos.length;
|
|
320
|
+
return result;
|
|
283
321
|
}
|
|
284
|
-
|
|
285
|
-
return result;
|
|
322
|
+
console.log(' ✅ GitHub CLI installed');
|
|
286
323
|
}
|
|
287
324
|
|
|
288
325
|
// Check GitHub auth
|
|
@@ -400,49 +437,12 @@ export async function unpack(soulFile: string, workspacePath?: string): Promise<
|
|
|
400
437
|
|
|
401
438
|
fs.mkdirSync(targetWorkspace, { recursive: true });
|
|
402
439
|
|
|
403
|
-
// ── Step 2: Restore
|
|
404
|
-
console.log('\n
|
|
405
|
-
let
|
|
406
|
-
const identityDir = path.join(stageDir, 'identity');
|
|
407
|
-
if (fs.existsSync(identityDir)) {
|
|
408
|
-
const files = fs.readdirSync(identityDir);
|
|
409
|
-
for (const f of files) {
|
|
410
|
-
const src = path.join(identityDir, f);
|
|
411
|
-
const dst = path.join(targetWorkspace, f);
|
|
412
|
-
fs.copyFileSync(src, dst);
|
|
413
|
-
console.log(` ✅ ${f}`);
|
|
414
|
-
identityCount++;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// ── Step 3: Restore memory ──────────────────────────────────────
|
|
419
|
-
console.log('🧠 Restoring memory...');
|
|
420
|
-
let memoryCount = 0;
|
|
421
|
-
const memoryDir = path.join(stageDir, 'memory');
|
|
422
|
-
if (fs.existsSync(memoryDir)) {
|
|
423
|
-
const copyRecursive = (src: string, dst: string) => {
|
|
424
|
-
fs.mkdirSync(dst, { recursive: true });
|
|
425
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
426
|
-
for (const entry of entries) {
|
|
427
|
-
const srcPath = path.join(src, entry.name);
|
|
428
|
-
const dstPath = path.join(dst, entry.name);
|
|
429
|
-
if (entry.isDirectory()) {
|
|
430
|
-
copyRecursive(srcPath, dstPath);
|
|
431
|
-
} else {
|
|
432
|
-
fs.copyFileSync(srcPath, dstPath);
|
|
433
|
-
memoryCount++;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
copyRecursive(memoryDir, path.join(targetWorkspace, 'memory'));
|
|
438
|
-
console.log(` ✅ ${memoryCount} memory files restored`);
|
|
439
|
-
}
|
|
440
|
+
// ── Step 2: Restore workspace files ──────────────────────────────
|
|
441
|
+
console.log('\n📂 Restoring workspace files...');
|
|
442
|
+
let workspaceCount = 0;
|
|
440
443
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
let dataCount = 0;
|
|
444
|
-
const dataDir = path.join(stageDir, 'data');
|
|
445
|
-
if (fs.existsSync(dataDir)) {
|
|
444
|
+
const workspaceDir = path.join(stageDir, 'workspace');
|
|
445
|
+
if (fs.existsSync(workspaceDir)) {
|
|
446
446
|
const copyRecursive = (src: string, dst: string) => {
|
|
447
447
|
fs.mkdirSync(dst, { recursive: true });
|
|
448
448
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -453,12 +453,14 @@ export async function unpack(soulFile: string, workspacePath?: string): Promise<
|
|
|
453
453
|
copyRecursive(srcPath, dstPath);
|
|
454
454
|
} else {
|
|
455
455
|
fs.copyFileSync(srcPath, dstPath);
|
|
456
|
-
|
|
457
|
-
dataCount++;
|
|
456
|
+
workspaceCount++;
|
|
458
457
|
}
|
|
459
458
|
}
|
|
460
459
|
};
|
|
461
|
-
copyRecursive(
|
|
460
|
+
copyRecursive(workspaceDir, targetWorkspace);
|
|
461
|
+
console.log(` ✅ ${workspaceCount} files restored`);
|
|
462
|
+
} else {
|
|
463
|
+
console.log(' ⚠️ No workspace/ directory in archive');
|
|
462
464
|
}
|
|
463
465
|
|
|
464
466
|
// ── Step 5: Write full agent config (with channels, credentials) ─
|
|
@@ -467,6 +469,9 @@ export async function unpack(soulFile: string, workspacePath?: string): Promise<
|
|
|
467
469
|
// ── Step 6: Restore cron jobs ───────────────────────────────────
|
|
468
470
|
const cronCount = restoreCronJobs(manifest, stageDir);
|
|
469
471
|
|
|
472
|
+
// ── Step 6.5: Restore credentials ─────────────────────────────
|
|
473
|
+
const credCount = restoreCredentials(stageDir);
|
|
474
|
+
|
|
470
475
|
// ── Step 7: Clone GitHub repos ──────────────────────────────────
|
|
471
476
|
const repoResult = cloneGitHubRepos(manifest, targetWorkspace);
|
|
472
477
|
|
|
@@ -494,7 +499,7 @@ export async function unpack(soulFile: string, workspacePath?: string): Promise<
|
|
|
494
499
|
console.log('═'.repeat(50));
|
|
495
500
|
console.log(`🆔 Agent: ${manifest.agent_name} (${manifest.agent_id})`);
|
|
496
501
|
console.log(`📂 Workspace: ${targetWorkspace}`);
|
|
497
|
-
console.log(`📝 Files: ${
|
|
502
|
+
console.log(`📝 Files: ${workspaceCount} workspace files`);
|
|
498
503
|
console.log(`⏰ Cron: ${cronCount} job(s)`);
|
|
499
504
|
|
|
500
505
|
if (manifest.github_repos && manifest.github_repos.length > 0) {
|
|
@@ -593,18 +598,16 @@ export async function inspect(soulFile: string): Promise<void> {
|
|
|
593
598
|
}
|
|
594
599
|
|
|
595
600
|
// Show file breakdown
|
|
596
|
-
const
|
|
597
|
-
const memoryFiles = manifest.files.filter((f) => f.startsWith('memory/'));
|
|
598
|
-
const dataFiles = manifest.files.filter((f) => f.startsWith('data/'));
|
|
601
|
+
const workspaceFiles = manifest.files.filter((f) => f.startsWith('workspace/'));
|
|
599
602
|
const cronFiles = manifest.files.filter((f) => f.startsWith('cron/'));
|
|
600
603
|
const configFiles = manifest.files.filter((f) => f.startsWith('config/'));
|
|
604
|
+
const credFiles = manifest.files.filter((f) => f.startsWith('credentials/'));
|
|
601
605
|
|
|
602
606
|
console.log('\n📊 Contents breakdown:');
|
|
603
|
-
if (
|
|
604
|
-
if (memoryFiles.length > 0) console.log(` 🧠 Memory: ${memoryFiles.length} files`);
|
|
605
|
-
if (dataFiles.length > 0) console.log(` 🗄️ Data: ${dataFiles.length} files`);
|
|
607
|
+
if (workspaceFiles.length > 0) console.log(` 📂 Workspace: ${workspaceFiles.length} files`);
|
|
606
608
|
if (cronFiles.length > 0) console.log(` ⏰ Cron: ${cronFiles.length} files`);
|
|
607
609
|
if (configFiles.length > 0) console.log(` ⚙️ Config: ${configFiles.length} files`);
|
|
610
|
+
if (credFiles.length > 0) console.log(` 🔐 Creds: ${credFiles.length} files`);
|
|
608
611
|
|
|
609
612
|
console.log('═'.repeat(50) + '\n');
|
|
610
613
|
} finally {
|
package/src/pack.ts
CHANGED
|
@@ -5,9 +5,7 @@ import { execSync } from 'node:child_process';
|
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
7
7
|
findAgent,
|
|
8
|
-
|
|
9
|
-
collectMemoryDir,
|
|
10
|
-
collectDbFiles,
|
|
8
|
+
collectWorkspaceFiles,
|
|
11
9
|
collectCronFiles,
|
|
12
10
|
getGitHubRepos,
|
|
13
11
|
detectServices,
|
|
@@ -49,41 +47,34 @@ export async function pack(agentId?: string, outputPath?: string): Promise<void>
|
|
|
49
47
|
|
|
50
48
|
const allFiles: string[] = [];
|
|
51
49
|
|
|
52
|
-
// 1. Collect
|
|
53
|
-
console.log('
|
|
54
|
-
const
|
|
55
|
-
for (const f of
|
|
50
|
+
// 1. Collect entire workspace recursively (skips git repos, node_modules, etc.)
|
|
51
|
+
console.log('📂 Collecting workspace files...');
|
|
52
|
+
const wsFiles = collectWorkspaceFiles(agent.workspace);
|
|
53
|
+
for (const f of wsFiles) {
|
|
56
54
|
const src = path.join(agent.workspace, f);
|
|
57
|
-
const dst = path.join(stageDir, '
|
|
55
|
+
const dst = path.join(stageDir, 'workspace', f);
|
|
58
56
|
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
59
57
|
fs.copyFileSync(src, dst);
|
|
60
|
-
allFiles.push(`
|
|
58
|
+
allFiles.push(`workspace/${f}`);
|
|
61
59
|
}
|
|
62
|
-
console.log(` ✅ ${
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const src = path.join(agent.workspace, f);
|
|
81
|
-
const dst = path.join(stageDir, 'data', f);
|
|
82
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
83
|
-
fs.copyFileSync(src, dst);
|
|
84
|
-
allFiles.push(`data/${f}`);
|
|
85
|
-
}
|
|
86
|
-
console.log(` ✅ ${dbFiles.length} database files`);
|
|
60
|
+
console.log(` ✅ ${wsFiles.length} files (skipped git repo subdirs)`);
|
|
61
|
+
|
|
62
|
+
// List skipped git repos for transparency
|
|
63
|
+
try {
|
|
64
|
+
const topEntries = fs.readdirSync(agent.workspace, { withFileTypes: true });
|
|
65
|
+
const skippedRepos: string[] = [];
|
|
66
|
+
for (const entry of topEntries) {
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
const gitDir = path.join(agent.workspace, entry.name, '.git');
|
|
69
|
+
if (fs.existsSync(gitDir)) {
|
|
70
|
+
skippedRepos.push(entry.name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (skippedRepos.length > 0) {
|
|
75
|
+
console.log(` ⏭️ Skipped git repos (will clone on unpack): ${skippedRepos.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
} catch {}
|
|
87
78
|
|
|
88
79
|
// 4. Extract agent config
|
|
89
80
|
console.log('⚙️ Extracting agent config...');
|
|
@@ -106,6 +97,23 @@ export async function pack(agentId?: string, outputPath?: string): Promise<void>
|
|
|
106
97
|
}
|
|
107
98
|
console.log(` ✅ ${cronFiles.length} cron files`);
|
|
108
99
|
|
|
100
|
+
// 5.5. Collect credentials (pairing records, allowFrom lists)
|
|
101
|
+
console.log('🔐 Collecting credentials...');
|
|
102
|
+
const credDir = path.join(OPENCLAW_DIR, 'credentials');
|
|
103
|
+
let credCount = 0;
|
|
104
|
+
if (fs.existsSync(credDir)) {
|
|
105
|
+
const credFiles = fs.readdirSync(credDir).filter(f => f.endsWith('.json'));
|
|
106
|
+
for (const f of credFiles) {
|
|
107
|
+
const src = path.join(credDir, f);
|
|
108
|
+
const dst = path.join(stageDir, 'credentials', f);
|
|
109
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
110
|
+
fs.copyFileSync(src, dst);
|
|
111
|
+
allFiles.push(`credentials/${f}`);
|
|
112
|
+
credCount++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log(` ✅ ${credCount} credential files`);
|
|
116
|
+
|
|
109
117
|
// 6. Load full cron job content for this agent
|
|
110
118
|
console.log('⏰ Extracting cron job definitions...');
|
|
111
119
|
const cronJobs = loadCronJobs(agent.id);
|
|
@@ -126,10 +134,11 @@ export async function pack(agentId?: string, outputPath?: string): Promise<void>
|
|
|
126
134
|
const channelCount = Object.keys(channelsConfig).length;
|
|
127
135
|
console.log(` ✅ ${channelCount} channel(s) saved`);
|
|
128
136
|
|
|
129
|
-
// 10. Extract agent defaults and
|
|
137
|
+
// 10. Extract agent defaults, models config, and gateway config
|
|
130
138
|
const agentDefaults = sanitizeAgentDefaults(config.agents?.defaults ?? {});
|
|
131
139
|
const modelsConfig = config.models ?? {};
|
|
132
140
|
const bindingsConfig = config.bindings ?? [];
|
|
141
|
+
const gatewayConfig = config.gateway ?? {};
|
|
133
142
|
|
|
134
143
|
// 11. Generate manifest
|
|
135
144
|
const manifest: Manifest = {
|
|
@@ -144,6 +153,7 @@ export async function pack(agentId?: string, outputPath?: string): Promise<void>
|
|
|
144
153
|
agent_defaults: agentDefaults,
|
|
145
154
|
models_config: modelsConfig,
|
|
146
155
|
bindings: bindingsConfig as Array<Record<string, unknown>>,
|
|
156
|
+
gateway: gatewayConfig as Record<string, unknown>,
|
|
147
157
|
};
|
|
148
158
|
|
|
149
159
|
const manifestPath = path.join(stageDir, 'manifest.json');
|
package/src/utils.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface Manifest {
|
|
|
35
35
|
models_config?: Record<string, unknown>;
|
|
36
36
|
/** Bindings configuration (added in v0.2) */
|
|
37
37
|
bindings?: Array<Record<string, unknown>>;
|
|
38
|
+
/** Gateway configuration (added in v0.2.1) */
|
|
39
|
+
gateway?: Record<string, unknown>;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export interface CronJob {
|
|
@@ -91,62 +93,51 @@ export function findAgent(config: OpenClawConfig, agentId?: string): AgentConfig
|
|
|
91
93
|
|
|
92
94
|
// ── File collection ────────────────────────────────────────────────
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
96
|
+
/** Directories to always skip when recursively walking the workspace. */
|
|
97
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '__pycache__', '.venv', 'venv']);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check whether a directory is the root of its own git repository.
|
|
101
|
+
* Used to skip cloneable sub-repos (knowledge-base, gogetajob, etc.)
|
|
102
|
+
* so they are restored via `gh repo clone` instead of being packed.
|
|
103
|
+
*/
|
|
104
|
+
function isGitRepo(dirPath: string): boolean {
|
|
105
|
+
return fs.existsSync(path.join(dirPath, '.git'));
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Recursively collect all files in the workspace, preserving the
|
|
110
|
+
* directory structure. Skips:
|
|
111
|
+
* - `node_modules`, `.git`, `dist`, build/cache dirs
|
|
112
|
+
* - Sub-directories that are their own git repos (restored via clone)
|
|
113
|
+
*
|
|
114
|
+
* Returns paths relative to `workspace`.
|
|
115
|
+
*/
|
|
116
|
+
export function collectWorkspaceFiles(workspace: string): string[] {
|
|
109
117
|
const files: string[] = [];
|
|
110
118
|
const walk = (dir: string, prefix: string) => {
|
|
111
|
-
|
|
119
|
+
let entries: fs.Dirent[];
|
|
120
|
+
try {
|
|
121
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
+
} catch {
|
|
123
|
+
return; // permission errors, broken symlinks, etc.
|
|
124
|
+
}
|
|
112
125
|
for (const entry of entries) {
|
|
113
|
-
|
|
126
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
127
|
+
|
|
128
|
+
const fullPath = path.join(dir, entry.name);
|
|
129
|
+
const rel = prefix ? path.join(prefix, entry.name) : entry.name;
|
|
130
|
+
|
|
114
131
|
if (entry.isDirectory()) {
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
// Skip sub-directories that are standalone git repos
|
|
133
|
+
if (isGitRepo(fullPath)) continue;
|
|
134
|
+
walk(fullPath, rel);
|
|
135
|
+
} else if (entry.isFile()) {
|
|
117
136
|
files.push(rel);
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
139
|
};
|
|
121
|
-
walk(
|
|
122
|
-
return files;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function collectDbFiles(workspace: string): string[] {
|
|
126
|
-
const files: string[] = [];
|
|
127
|
-
// Only collect .db files from known tool data directories, not recursively
|
|
128
|
-
// This prevents grabbing test DBs or unrelated data from project subdirs
|
|
129
|
-
const knownPaths = [
|
|
130
|
-
'gogetajob/data/gogetajob.db',
|
|
131
|
-
'flowforge/flowforge.db',
|
|
132
|
-
'data/gogetajob.db',
|
|
133
|
-
'data/flowforge.db',
|
|
134
|
-
];
|
|
135
|
-
for (const rel of knownPaths) {
|
|
136
|
-
const full = path.join(workspace, rel);
|
|
137
|
-
if (fs.existsSync(full)) {
|
|
138
|
-
files.push(rel);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Also check workspace root for any .db files
|
|
142
|
-
try {
|
|
143
|
-
const rootEntries = fs.readdirSync(workspace, { withFileTypes: true });
|
|
144
|
-
for (const entry of rootEntries) {
|
|
145
|
-
if (entry.isFile() && entry.name.endsWith('.db')) {
|
|
146
|
-
files.push(entry.name);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} catch {}
|
|
140
|
+
walk(workspace, '');
|
|
150
141
|
return files;
|
|
151
142
|
}
|
|
152
143
|
|
|
@@ -297,6 +288,35 @@ export function commandExists(cmd: string): boolean {
|
|
|
297
288
|
}
|
|
298
289
|
}
|
|
299
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Install GitHub CLI (gh) automatically.
|
|
293
|
+
* Supports apt (Debian/Ubuntu) and brew (macOS).
|
|
294
|
+
*/
|
|
295
|
+
export function installGh(): boolean {
|
|
296
|
+
try {
|
|
297
|
+
const platform = os.platform();
|
|
298
|
+
if (platform === 'linux') {
|
|
299
|
+
// Try apt-based install (Debian/Ubuntu)
|
|
300
|
+
execSync(
|
|
301
|
+
'(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) && ' +
|
|
302
|
+
'sudo mkdir -p -m 755 /etc/apt/keyrings && ' +
|
|
303
|
+
'wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null && ' +
|
|
304
|
+
'sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && ' +
|
|
305
|
+
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && ' +
|
|
306
|
+
'sudo apt update && sudo apt install gh -y',
|
|
307
|
+
{ stdio: 'pipe', timeout: 120000 }
|
|
308
|
+
);
|
|
309
|
+
} else if (platform === 'darwin') {
|
|
310
|
+
execSync('brew install gh', { stdio: 'pipe', timeout: 120000 });
|
|
311
|
+
} else {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return commandExists('gh');
|
|
315
|
+
} catch {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
300
320
|
/**
|
|
301
321
|
* Check GitHub CLI auth status.
|
|
302
322
|
* Returns true if authenticated.
|