openclaw-teleport 0.2.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 +189 -0
- package/README.zh.md +109 -0
- package/dist/cli.mjs +831 -0
- package/package.json +31 -0
- package/src/cli.ts +53 -0
- package/src/commands.ts +613 -0
- package/src/pack.ts +184 -0
- package/src/utils.ts +311 -0
- package/tsconfig.json +18 -0
package/src/pack.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import {
|
|
6
|
+
loadConfig,
|
|
7
|
+
findAgent,
|
|
8
|
+
collectMarkdownFiles,
|
|
9
|
+
collectMemoryDir,
|
|
10
|
+
collectDbFiles,
|
|
11
|
+
collectCronFiles,
|
|
12
|
+
getGitHubRepos,
|
|
13
|
+
detectServices,
|
|
14
|
+
extractAgentConfig,
|
|
15
|
+
extractChannelsConfig,
|
|
16
|
+
sanitizeAgentDefaults,
|
|
17
|
+
loadCronJobs,
|
|
18
|
+
type Manifest,
|
|
19
|
+
} from './utils.js';
|
|
20
|
+
|
|
21
|
+
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
22
|
+
const CRON_DIR = path.join(OPENCLAW_DIR, 'cron');
|
|
23
|
+
|
|
24
|
+
export async function pack(agentId?: string, outputPath?: string): Promise<void> {
|
|
25
|
+
console.log('\nšø openclaw-teleport ā packing agent soul...\n');
|
|
26
|
+
|
|
27
|
+
// Load config and find agent
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const agent = findAgent(config, agentId);
|
|
30
|
+
|
|
31
|
+
console.log(`š¦ Agent: ${agent.name} (${agent.id})`);
|
|
32
|
+
console.log(`š Workspace: ${agent.workspace}\n`);
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(agent.workspace)) {
|
|
35
|
+
throw new Error(`ā Workspace not found: ${agent.workspace}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create temp directory for staging
|
|
39
|
+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
40
|
+
const soulName = `${agent.id}_${date}`;
|
|
41
|
+
const tmpDir = path.join(os.tmpdir(), `openclaw-teleport-${soulName}`);
|
|
42
|
+
const stageDir = path.join(tmpDir, 'soul');
|
|
43
|
+
|
|
44
|
+
// Clean up any previous staging
|
|
45
|
+
if (fs.existsSync(tmpDir)) {
|
|
46
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
const allFiles: string[] = [];
|
|
51
|
+
|
|
52
|
+
// 1. Collect identity files (.md in workspace root)
|
|
53
|
+
console.log('š Collecting identity files...');
|
|
54
|
+
const mdFiles = collectMarkdownFiles(agent.workspace);
|
|
55
|
+
for (const f of mdFiles) {
|
|
56
|
+
const src = path.join(agent.workspace, f);
|
|
57
|
+
const dst = path.join(stageDir, 'identity', f);
|
|
58
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
59
|
+
fs.copyFileSync(src, dst);
|
|
60
|
+
allFiles.push(`identity/${f}`);
|
|
61
|
+
}
|
|
62
|
+
console.log(` ā
${mdFiles.length} markdown files`);
|
|
63
|
+
|
|
64
|
+
// 2. Collect memory directory
|
|
65
|
+
console.log('š§ Collecting memory...');
|
|
66
|
+
const memFiles = collectMemoryDir(agent.workspace);
|
|
67
|
+
for (const f of memFiles) {
|
|
68
|
+
const src = path.join(agent.workspace, f);
|
|
69
|
+
const dst = path.join(stageDir, f);
|
|
70
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
71
|
+
fs.copyFileSync(src, dst);
|
|
72
|
+
allFiles.push(f);
|
|
73
|
+
}
|
|
74
|
+
console.log(` ā
${memFiles.length} memory files`);
|
|
75
|
+
|
|
76
|
+
// 3. Collect .db files
|
|
77
|
+
console.log('šļø Collecting tool data...');
|
|
78
|
+
const dbFiles = collectDbFiles(agent.workspace);
|
|
79
|
+
for (const f of dbFiles) {
|
|
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`);
|
|
87
|
+
|
|
88
|
+
// 4. Extract agent config
|
|
89
|
+
console.log('āļø Extracting agent config...');
|
|
90
|
+
const agentConfig = extractAgentConfig(config, agent.id);
|
|
91
|
+
const configPath = path.join(stageDir, 'config', 'agent-config.json');
|
|
92
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
93
|
+
fs.writeFileSync(configPath, JSON.stringify(agentConfig, null, 2));
|
|
94
|
+
allFiles.push('config/agent-config.json');
|
|
95
|
+
console.log(' ā
Agent config saved');
|
|
96
|
+
|
|
97
|
+
// 5. Collect cron job files
|
|
98
|
+
console.log('ā° Collecting cron jobs...');
|
|
99
|
+
const cronFiles = collectCronFiles(agent.id);
|
|
100
|
+
for (const f of cronFiles) {
|
|
101
|
+
const src = path.join(CRON_DIR, f);
|
|
102
|
+
const dst = path.join(stageDir, 'cron', f);
|
|
103
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
104
|
+
fs.copyFileSync(src, dst);
|
|
105
|
+
allFiles.push(`cron/${f}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(` ā
${cronFiles.length} cron files`);
|
|
108
|
+
|
|
109
|
+
// 6. Load full cron job content for this agent
|
|
110
|
+
console.log('ā° Extracting cron job definitions...');
|
|
111
|
+
const cronJobs = loadCronJobs(agent.id);
|
|
112
|
+
console.log(` ā
${cronJobs.length} cron jobs for ${agent.id}`);
|
|
113
|
+
|
|
114
|
+
// 7. Get GitHub repos
|
|
115
|
+
console.log('š Fetching GitHub repos...');
|
|
116
|
+
const repos = getGitHubRepos('kagura-agent');
|
|
117
|
+
console.log(` ā
${repos.length} repos found`);
|
|
118
|
+
|
|
119
|
+
// 8. Detect services
|
|
120
|
+
const services = detectServices(config);
|
|
121
|
+
console.log(`š Services to rebind: ${services.length > 0 ? services.join(', ') : 'none'}`);
|
|
122
|
+
|
|
123
|
+
// 9. Extract channels config (with credentials)
|
|
124
|
+
console.log('š Extracting channel credentials...');
|
|
125
|
+
const channelsConfig = extractChannelsConfig(config, agent.id);
|
|
126
|
+
const channelCount = Object.keys(channelsConfig).length;
|
|
127
|
+
console.log(` ā
${channelCount} channel(s) saved`);
|
|
128
|
+
|
|
129
|
+
// 10. Extract agent defaults and models config
|
|
130
|
+
const agentDefaults = sanitizeAgentDefaults(config.agents?.defaults ?? {});
|
|
131
|
+
const modelsConfig = config.models ?? {};
|
|
132
|
+
const bindingsConfig = config.bindings ?? [];
|
|
133
|
+
|
|
134
|
+
// 11. Generate manifest
|
|
135
|
+
const manifest: Manifest = {
|
|
136
|
+
agent_id: agent.id,
|
|
137
|
+
agent_name: agent.name,
|
|
138
|
+
packed_at: new Date().toISOString(),
|
|
139
|
+
files: allFiles,
|
|
140
|
+
github_repos: repos,
|
|
141
|
+
services_to_rebind: services,
|
|
142
|
+
channels: channelsConfig,
|
|
143
|
+
cron_jobs: cronJobs,
|
|
144
|
+
agent_defaults: agentDefaults,
|
|
145
|
+
models_config: modelsConfig,
|
|
146
|
+
bindings: bindingsConfig as Array<Record<string, unknown>>,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const manifestPath = path.join(stageDir, 'manifest.json');
|
|
150
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
151
|
+
|
|
152
|
+
// 12. Create tarball
|
|
153
|
+
const outputFile = outputPath ? path.resolve(outputPath) : path.resolve(`${soulName}.soul`);
|
|
154
|
+
console.log('\nš¦ Packing soul archive...');
|
|
155
|
+
|
|
156
|
+
execSync(`tar -czf "${outputFile}" -C "${tmpDir}" soul`, {
|
|
157
|
+
encoding: 'utf-8',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Clean up staging
|
|
161
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
162
|
+
|
|
163
|
+
// Summary
|
|
164
|
+
const stats = fs.statSync(outputFile);
|
|
165
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
166
|
+
|
|
167
|
+
console.log('\n' + 'ā'.repeat(50));
|
|
168
|
+
console.log('šø Soul packed successfully!');
|
|
169
|
+
console.log('ā'.repeat(50));
|
|
170
|
+
console.log(`š¦ File: ${outputFile}`);
|
|
171
|
+
console.log(`š Size: ${sizeMB} MB`);
|
|
172
|
+
console.log(`š Agent: ${agent.name} (${agent.id})`);
|
|
173
|
+
console.log(`š Files: ${allFiles.length}`);
|
|
174
|
+
console.log(`š Repos: ${repos.length}`);
|
|
175
|
+
console.log(`š Services: ${services.join(', ') || 'none'}`);
|
|
176
|
+
console.log(`š Channels: ${channelCount}`);
|
|
177
|
+
console.log(`ā° Cron: ${cronJobs.length} jobs`);
|
|
178
|
+
console.log(`š
Packed: ${manifest.packed_at}`);
|
|
179
|
+
console.log('ā'.repeat(50));
|
|
180
|
+
|
|
181
|
+
console.log('\nā ļø SECURITY WARNING: The .soul file contains credentials');
|
|
182
|
+
console.log(' (API tokens, app secrets). Treat it like a password file.');
|
|
183
|
+
console.log(' Do NOT commit it to git or share publicly.\n');
|
|
184
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
|
|
6
|
+
// āā Paths āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
7
|
+
|
|
8
|
+
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
9
|
+
const CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
|
|
10
|
+
const CRON_DIR = path.join(OPENCLAW_DIR, 'cron');
|
|
11
|
+
|
|
12
|
+
// āā Types āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
13
|
+
|
|
14
|
+
export interface AgentConfig {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
workspace: string;
|
|
18
|
+
agentDir: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Manifest {
|
|
22
|
+
agent_id: string;
|
|
23
|
+
agent_name: string;
|
|
24
|
+
packed_at: string;
|
|
25
|
+
files: string[];
|
|
26
|
+
github_repos: Array<{ name: string; url: string; isFork: boolean }>;
|
|
27
|
+
services_to_rebind: string[];
|
|
28
|
+
/** Channel configurations with credentials (added in v0.2) */
|
|
29
|
+
channels?: Record<string, unknown>;
|
|
30
|
+
/** Full cron jobs content (added in v0.2) */
|
|
31
|
+
cron_jobs?: CronJob[];
|
|
32
|
+
/** Agent defaults from openclaw.json (added in v0.2) */
|
|
33
|
+
agent_defaults?: Record<string, unknown>;
|
|
34
|
+
/** Models configuration (added in v0.2) */
|
|
35
|
+
models_config?: Record<string, unknown>;
|
|
36
|
+
/** Bindings configuration (added in v0.2) */
|
|
37
|
+
bindings?: Array<Record<string, unknown>>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CronJob {
|
|
41
|
+
id: string;
|
|
42
|
+
agentId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
schedule: Record<string, unknown>;
|
|
46
|
+
sessionTarget?: string;
|
|
47
|
+
wakeMode?: string;
|
|
48
|
+
payload: Record<string, unknown>;
|
|
49
|
+
delivery?: Record<string, unknown>;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface OpenClawConfig {
|
|
54
|
+
agents?: {
|
|
55
|
+
defaults?: Record<string, unknown>;
|
|
56
|
+
list?: AgentConfig[];
|
|
57
|
+
};
|
|
58
|
+
channels?: Record<string, unknown>;
|
|
59
|
+
models?: Record<string, unknown>;
|
|
60
|
+
bindings?: Array<Record<string, unknown>>;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// āā Config helpers āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
65
|
+
|
|
66
|
+
export function loadConfig(): OpenClawConfig {
|
|
67
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
68
|
+
throw new Error(`ā Config not found: ${CONFIG_PATH}\n Is OpenClaw installed?`);
|
|
69
|
+
}
|
|
70
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function findAgent(config: OpenClawConfig, agentId?: string): AgentConfig {
|
|
74
|
+
const agents = config.agents?.list ?? [];
|
|
75
|
+
if (agents.length === 0) {
|
|
76
|
+
throw new Error('ā No agents configured in openclaw.json');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (agentId) {
|
|
80
|
+
const agent = agents.find((a) => a.id === agentId);
|
|
81
|
+
if (!agent) {
|
|
82
|
+
const ids = agents.map((a) => a.id).join(', ');
|
|
83
|
+
throw new Error(`ā Agent "${agentId}" not found. Available: ${ids}`);
|
|
84
|
+
}
|
|
85
|
+
return agent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Default to first agent
|
|
89
|
+
return agents[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// āā File collection āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
93
|
+
|
|
94
|
+
export function collectMarkdownFiles(workspace: string): string[] {
|
|
95
|
+
const files: string[] = [];
|
|
96
|
+
const entries = fs.readdirSync(workspace, { withFileTypes: true });
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
99
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
100
|
+
files.push(entry.name);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return files;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function collectMemoryDir(workspace: string): string[] {
|
|
107
|
+
const memoryDir = path.join(workspace, 'memory');
|
|
108
|
+
if (!fs.existsSync(memoryDir)) return [];
|
|
109
|
+
const files: string[] = [];
|
|
110
|
+
const walk = (dir: string, prefix: string) => {
|
|
111
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const rel = path.join(prefix, entry.name);
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
walk(path.join(dir, entry.name), rel);
|
|
116
|
+
} else {
|
|
117
|
+
files.push(rel);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
walk(memoryDir, 'memory');
|
|
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 {}
|
|
150
|
+
return files;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function collectCronFiles(agentId: string): string[] {
|
|
154
|
+
if (!fs.existsSync(CRON_DIR)) return [];
|
|
155
|
+
const files: string[] = [];
|
|
156
|
+
const entries = fs.readdirSync(CRON_DIR, { withFileTypes: true });
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
// Include jobs.json and any agent-specific files
|
|
159
|
+
if (entry.isFile()) {
|
|
160
|
+
files.push(entry.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return files;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// āā Cron job content extraction āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Load full cron jobs for a specific agent from jobs.json.
|
|
170
|
+
* Returns the actual job objects (not just file names).
|
|
171
|
+
*/
|
|
172
|
+
export function loadCronJobs(agentId: string): CronJob[] {
|
|
173
|
+
const jobsPath = path.join(CRON_DIR, 'jobs.json');
|
|
174
|
+
if (!fs.existsSync(jobsPath)) return [];
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const data = JSON.parse(fs.readFileSync(jobsPath, 'utf-8'));
|
|
178
|
+
const jobs: CronJob[] = data.jobs ?? [];
|
|
179
|
+
// Filter to this agent's jobs
|
|
180
|
+
return jobs.filter((j) => j.agentId === agentId);
|
|
181
|
+
} catch {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// āā GitHub repos āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
187
|
+
|
|
188
|
+
export function getGitHubRepos(owner: string): Array<{ name: string; url: string; isFork: boolean }> {
|
|
189
|
+
try {
|
|
190
|
+
const output = execSync(`gh repo list ${owner} --json name,url,isFork --limit 100`, {
|
|
191
|
+
encoding: 'utf-8',
|
|
192
|
+
timeout: 30000,
|
|
193
|
+
});
|
|
194
|
+
return JSON.parse(output);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.log('ā ļø Could not fetch GitHub repos (gh CLI not available or not authenticated)');
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// āā Services detection āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
202
|
+
|
|
203
|
+
export function detectServices(config: OpenClawConfig): string[] {
|
|
204
|
+
const services = new Set<string>();
|
|
205
|
+
const channels = config.channels ?? (config as Record<string, unknown>);
|
|
206
|
+
|
|
207
|
+
// Walk the config looking for channel-like keys
|
|
208
|
+
for (const key of Object.keys(config)) {
|
|
209
|
+
if (['feishu', 'discord', 'telegram', 'slack', 'whatsapp', 'github', 'twitter', 'email'].includes(key)) {
|
|
210
|
+
services.add(key);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Also check if channels object exists
|
|
215
|
+
if (config.channels && typeof config.channels === 'object') {
|
|
216
|
+
for (const key of Object.keys(config.channels)) {
|
|
217
|
+
services.add(key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Array.from(services);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// āā Agent config extraction āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
225
|
+
|
|
226
|
+
export function extractAgentConfig(config: OpenClawConfig, agentId: string): Record<string, unknown> {
|
|
227
|
+
const agent = config.agents?.list?.find((a) => a.id === agentId);
|
|
228
|
+
const defaults = config.agents?.defaults ?? {};
|
|
229
|
+
return {
|
|
230
|
+
agent,
|
|
231
|
+
defaults,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// āā Channel config extraction āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Extract channel configurations (including credentials) relevant to an agent.
|
|
239
|
+
* Strips absolute paths but preserves tokens, appIds, appSecrets, etc.
|
|
240
|
+
*/
|
|
241
|
+
export function extractChannelsConfig(config: OpenClawConfig, agentId: string): Record<string, unknown> {
|
|
242
|
+
if (!config.channels) return {};
|
|
243
|
+
|
|
244
|
+
// Deep clone to avoid mutating original
|
|
245
|
+
const channels = JSON.parse(JSON.stringify(config.channels));
|
|
246
|
+
|
|
247
|
+
// Strip absolute paths from the cloned config
|
|
248
|
+
stripAbsolutePaths(channels);
|
|
249
|
+
|
|
250
|
+
return channels;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Recursively strip values that look like absolute paths.
|
|
255
|
+
* We preserve tokens, keys, IDs ā only remove filesystem paths.
|
|
256
|
+
*/
|
|
257
|
+
function stripAbsolutePaths(obj: Record<string, unknown>): void {
|
|
258
|
+
for (const key of Object.keys(obj)) {
|
|
259
|
+
const val = obj[key];
|
|
260
|
+
if (typeof val === 'string' && val.startsWith('/') && (val.includes('/home/') || val.includes('/Users/') || val.includes('/root/'))) {
|
|
261
|
+
// Mark as path-to-regenerate
|
|
262
|
+
obj[key] = `__PATH_PLACEHOLDER__`;
|
|
263
|
+
} else if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
264
|
+
stripAbsolutePaths(val as Record<string, unknown>);
|
|
265
|
+
} else if (Array.isArray(val)) {
|
|
266
|
+
for (let i = 0; i < val.length; i++) {
|
|
267
|
+
if (val[i] && typeof val[i] === 'object') {
|
|
268
|
+
stripAbsolutePaths(val[i] as Record<string, unknown>);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Strip absolute paths from agent defaults config.
|
|
277
|
+
* Replaces workspace, agentDir, and other path-like values with placeholders.
|
|
278
|
+
*/
|
|
279
|
+
export function sanitizeAgentDefaults(defaults: Record<string, unknown>): Record<string, unknown> {
|
|
280
|
+
const sanitized = JSON.parse(JSON.stringify(defaults));
|
|
281
|
+
// Remove workspace ā it will be set dynamically on unpack
|
|
282
|
+
delete sanitized.workspace;
|
|
283
|
+
return sanitized;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// āā Command helpers āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if a command exists on the system.
|
|
290
|
+
*/
|
|
291
|
+
export function commandExists(cmd: string): boolean {
|
|
292
|
+
try {
|
|
293
|
+
execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
294
|
+
return true;
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check GitHub CLI auth status.
|
|
302
|
+
* Returns true if authenticated.
|
|
303
|
+
*/
|
|
304
|
+
export function isGhAuthenticated(): boolean {
|
|
305
|
+
try {
|
|
306
|
+
execSync('gh auth status', { encoding: 'utf-8', stdio: 'pipe' });
|
|
307
|
+
return true;
|
|
308
|
+
} catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"allowImportingTsExtensions": false
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|