clawpilot 0.1.1 → 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/src/install.js CHANGED
@@ -1,181 +1,248 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
-
4
- const SKILL_ID = 'clawpilot-productivity';
5
- const MARKER_START = '<!-- clawpilot:productivity:start -->';
6
- const MARKER_END = '<!-- clawpilot:productivity:end -->';
7
- const DEFAULT_SCHEDULE = {
8
- morning: '09:00',
9
- midday: '14:00',
10
- evening: '21:30'
11
- };
12
-
13
- function copyDir(source, destination) {
14
- fs.mkdirSync(destination, { recursive: true });
15
- const entries = fs.readdirSync(source, { withFileTypes: true });
16
- for (const entry of entries) {
17
- const sourcePath = path.join(source, entry.name);
18
- const destinationPath = path.join(destination, entry.name);
19
- if (entry.isDirectory()) {
20
- copyDir(sourcePath, destinationPath);
21
- } else {
22
- fs.copyFileSync(sourcePath, destinationPath);
23
- }
24
- }
25
- }
26
-
27
- function deepMerge(target, source) {
28
- const result = { ...target };
29
- for (const [key, value] of Object.entries(source)) {
30
- if (value && typeof value === 'object' && !Array.isArray(value)) {
31
- result[key] = deepMerge(result[key] || {}, value);
32
- continue;
33
- }
34
- result[key] = value;
35
- }
36
- return result;
37
- }
38
-
39
- function readJson(filePath) {
40
- if (!fs.existsSync(filePath)) {
41
- return {};
42
- }
43
-
44
- try {
45
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
46
- } catch {
47
- return {};
48
- }
49
- }
50
-
51
- function writeJson(filePath, data) {
52
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
53
- }
54
-
55
- function upsertInstalledSkill(config) {
56
- if (!Array.isArray(config.installedSkills)) {
57
- config.installedSkills = [];
58
- }
59
- if (!config.installedSkills.includes(SKILL_ID)) {
60
- config.installedSkills.push(SKILL_ID);
61
- }
62
- }
63
-
64
- function normalizeSchedule(schedule) {
65
- return {
66
- ...DEFAULT_SCHEDULE,
67
- ...(schedule || {})
68
- };
69
- }
70
-
71
- function upsertWorkspaceSoul(soulPath, injectionText) {
72
- const normalizedInjection = `${MARKER_START}\n${injectionText.trim()}\n${MARKER_END}`;
73
- const existing = fs.existsSync(soulPath)
74
- ? fs.readFileSync(soulPath, 'utf8')
75
- : '# Agent Soul\n';
76
- const markerRegex = new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}\\n?`, 'g');
77
- const cleaned = existing.replace(markerRegex, '').trimEnd();
78
- const nextContent = `${cleaned}\n\n${normalizedInjection}\n`;
79
- fs.writeFileSync(soulPath, nextContent, 'utf8');
80
- }
81
-
82
- function ensureSkillManifest(skillDir) {
83
- const manifestPath = path.join(skillDir, 'manifest.json');
84
- if (fs.existsSync(manifestPath)) {
85
- return;
86
- }
87
-
88
- const manifest = {
89
- id: SKILL_ID,
90
- name: 'Clawpilot Productivity',
91
- version: '0.1.0',
92
- entry: 'SOUL.md'
93
- };
94
- writeJson(manifestPath, manifest);
95
- }
96
-
97
- async function installSkill({
98
- openClawHome,
99
- packageRoot,
100
- schedule,
101
- force = false,
102
- timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
103
- }) {
104
- const skillsDir = path.join(openClawHome, 'skills');
105
- const skillDir = path.join(openClawHome, 'skills', SKILL_ID);
106
- const workspaceDir = path.join(openClawHome, 'workspace');
107
- const configPath = path.join(openClawHome, 'openclaw.json');
108
- const soulPath = path.join(skillDir, 'SOUL.md');
109
- const workspaceSoulPath = path.join(workspaceDir, 'SOUL.md');
110
- const identityPath = path.join(workspaceDir, 'IDENTITY.md');
111
-
112
- const skillSourceDir = path.join(packageRoot, 'skill');
113
- const templateDir = path.join(packageRoot, 'templates');
114
- const baseSoulTemplate = fs.readFileSync(path.join(templateDir, 'soul.productivity.md'), 'utf8');
115
- const injectionTemplate = fs.readFileSync(path.join(templateDir, 'soul-injection.md'), 'utf8');
116
- const identityTemplate = fs.readFileSync(path.join(templateDir, 'identity.md'), 'utf8');
117
-
118
- fs.mkdirSync(openClawHome, { recursive: true });
119
- fs.mkdirSync(skillsDir, { recursive: true });
120
- fs.mkdirSync(workspaceDir, { recursive: true });
121
-
122
- if (fs.existsSync(skillDir)) {
123
- if (!force) {
124
- throw new Error(`Skill already installed: ${skillDir}. Re-run with force to replace.`);
125
- }
126
- fs.rmSync(skillDir, { recursive: true, force: true });
127
- }
128
-
129
- if (fs.existsSync(skillSourceDir)) {
130
- copyDir(skillSourceDir, skillDir);
131
- } else {
132
- fs.mkdirSync(skillDir, { recursive: true });
133
- }
134
-
135
- fs.writeFileSync(soulPath, baseSoulTemplate, 'utf8');
136
- ensureSkillManifest(skillDir);
137
- upsertWorkspaceSoul(workspaceSoulPath, injectionTemplate);
138
- fs.writeFileSync(identityPath, identityTemplate, 'utf8');
139
-
140
- let config = readJson(configPath);
141
- const existingEntry = (((config.skills || {}).entries || {})[SKILL_ID]) || {};
142
- const mergedEntry = deepMerge(existingEntry, {
143
- enabled: true,
144
- mode: 'productivity',
145
- timezone,
146
- schedule: normalizeSchedule(schedule)
147
- });
148
-
149
- config = deepMerge(config, {
150
- skills: {
151
- entries: {
152
- [SKILL_ID]: mergedEntry
153
- },
154
- load: {
155
- extraDirs: Array.isArray(config.skills?.load?.extraDirs)
156
- ? config.skills.load.extraDirs
157
- : []
158
- }
159
- }
160
- });
161
-
162
- if (!config.skills.load.extraDirs.includes(skillsDir)) {
163
- config.skills.load.extraDirs.push(skillsDir);
164
- }
165
-
166
- upsertInstalledSkill(config);
167
- writeJson(configPath, config);
168
-
169
- return {
170
- skillDir,
171
- configPath,
172
- workspaceSoulPath,
173
- identityPath
174
- };
175
- }
176
-
177
- module.exports = {
178
- installSkill,
179
- SKILL_ID,
180
- DEFAULT_SCHEDULE
181
- };
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { migrateConfig, CURRENT_CONFIG_SCHEMA_VERSION } = require('./config/migrations');
4
+
5
+ const SKILL_ID = 'clawpilot-productivity';
6
+ const MARKER_START = '<!-- clawpilot:productivity:start -->';
7
+ const MARKER_END = '<!-- clawpilot:productivity:end -->';
8
+ const DEFAULT_SCHEDULE = {
9
+ morning: '09:00',
10
+ midday: '14:00',
11
+ evening: '21:30'
12
+ };
13
+
14
+ function copyDir(source, destination) {
15
+ fs.mkdirSync(destination, { recursive: true });
16
+ const entries = fs.readdirSync(source, { withFileTypes: true });
17
+ for (const entry of entries) {
18
+ const sourcePath = path.join(source, entry.name);
19
+ const destinationPath = path.join(destination, entry.name);
20
+ if (entry.isDirectory()) {
21
+ copyDir(sourcePath, destinationPath);
22
+ } else {
23
+ fs.copyFileSync(sourcePath, destinationPath);
24
+ }
25
+ }
26
+ }
27
+
28
+ function deepMerge(target, source) {
29
+ const result = { ...target };
30
+ for (const [key, value] of Object.entries(source)) {
31
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
32
+ result[key] = deepMerge(result[key] || {}, value);
33
+ continue;
34
+ }
35
+ result[key] = value;
36
+ }
37
+ return result;
38
+ }
39
+
40
+ function readJson(filePath) {
41
+ if (!fs.existsSync(filePath)) {
42
+ return {};
43
+ }
44
+
45
+ try {
46
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
47
+ } catch {
48
+ return {};
49
+ }
50
+ }
51
+
52
+ function writeJson(filePath, data) {
53
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
54
+ }
55
+
56
+ function upsertInstalledSkill(config) {
57
+ if (!Array.isArray(config.installedSkills)) {
58
+ config.installedSkills = [];
59
+ }
60
+ if (!config.installedSkills.includes(SKILL_ID)) {
61
+ config.installedSkills.push(SKILL_ID);
62
+ }
63
+ }
64
+
65
+ function normalizeSchedule(schedule) {
66
+ return {
67
+ ...DEFAULT_SCHEDULE,
68
+ ...(schedule || {})
69
+ };
70
+ }
71
+
72
+ function resolveRolePack({ requestedRolePack, existingEntry }) {
73
+ if (requestedRolePack) {
74
+ return requestedRolePack;
75
+ }
76
+ if (existingEntry.runtime?.defaults?.rolePack) {
77
+ return existingEntry.runtime.defaults.rolePack;
78
+ }
79
+ if (existingEntry.rolePack) {
80
+ return existingEntry.rolePack;
81
+ }
82
+ return 'hana';
83
+ }
84
+
85
+ function resolveSchedule({ existingEntry, requestedSchedule }) {
86
+ const existingSchedule = existingEntry.runtime?.schedule || existingEntry.schedule || {};
87
+ return normalizeSchedule({
88
+ ...existingSchedule,
89
+ ...(requestedSchedule || {})
90
+ });
91
+ }
92
+
93
+ function upsertWorkspaceSoul(soulPath, injectionText) {
94
+ const normalizedInjection = `${MARKER_START}\n${injectionText.trim()}\n${MARKER_END}`;
95
+ const existing = fs.existsSync(soulPath)
96
+ ? fs.readFileSync(soulPath, 'utf8')
97
+ : '# Agent Soul\n';
98
+ const markerRegex = new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}\\n?`, 'g');
99
+ const cleaned = existing.replace(markerRegex, '').trimEnd();
100
+ const nextContent = `${cleaned}\n\n${normalizedInjection}\n`;
101
+ fs.writeFileSync(soulPath, nextContent, 'utf8');
102
+ }
103
+
104
+ function ensureSkillManifest(skillDir) {
105
+ const manifestPath = path.join(skillDir, 'manifest.json');
106
+ if (fs.existsSync(manifestPath)) {
107
+ return;
108
+ }
109
+
110
+ const manifest = {
111
+ id: SKILL_ID,
112
+ name: 'Clawpilot Productivity',
113
+ version: '0.1.0',
114
+ entry: 'SOUL.md'
115
+ };
116
+ writeJson(manifestPath, manifest);
117
+ }
118
+
119
+ async function installSkill({
120
+ openClawHome,
121
+ packageRoot,
122
+ schedule,
123
+ force = false,
124
+ channel = null,
125
+ rolePack,
126
+ onExisting = 'error',
127
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
128
+ }) {
129
+ const skillsDir = path.join(openClawHome, 'skills');
130
+ const skillDir = path.join(openClawHome, 'skills', SKILL_ID);
131
+ const workspaceDir = path.join(openClawHome, 'workspace');
132
+ const configPath = path.join(openClawHome, 'openclaw.json');
133
+ const soulPath = path.join(skillDir, 'SOUL.md');
134
+ const workspaceSoulPath = path.join(workspaceDir, 'SOUL.md');
135
+ const identityPath = path.join(workspaceDir, 'IDENTITY.md');
136
+
137
+ const skillSourceDir = path.join(packageRoot, 'skill');
138
+ const templateDir = path.join(packageRoot, 'templates');
139
+ const baseSoulTemplate = fs.readFileSync(path.join(templateDir, 'soul.productivity.md'), 'utf8');
140
+ const injectionTemplate = fs.readFileSync(path.join(templateDir, 'soul-injection.md'), 'utf8');
141
+ const identityTemplate = fs.readFileSync(path.join(templateDir, 'identity.md'), 'utf8');
142
+
143
+ fs.mkdirSync(openClawHome, { recursive: true });
144
+ fs.mkdirSync(skillsDir, { recursive: true });
145
+ fs.mkdirSync(workspaceDir, { recursive: true });
146
+
147
+ const existingMode = force ? 'reinstall' : onExisting;
148
+ const hadExistingSkill = fs.existsSync(skillDir);
149
+ if (!['error', 'update', 'skip', 'reinstall'].includes(existingMode)) {
150
+ throw new Error(`Unsupported onExisting mode: ${existingMode}`);
151
+ }
152
+
153
+ if (fs.existsSync(skillDir)) {
154
+ if (existingMode === 'skip') {
155
+ return {
156
+ action: 'skip',
157
+ skillDir,
158
+ configPath,
159
+ workspaceSoulPath,
160
+ identityPath
161
+ };
162
+ }
163
+ if (existingMode === 'error') {
164
+ throw new Error(
165
+ `Skill already installed: ${skillDir}. Re-run with --on-existing update|skip|reinstall or --force.`
166
+ );
167
+ }
168
+ if (existingMode === 'reinstall') {
169
+ fs.rmSync(skillDir, { recursive: true, force: true });
170
+ }
171
+ }
172
+
173
+ if (fs.existsSync(skillSourceDir)) {
174
+ copyDir(skillSourceDir, skillDir);
175
+ } else {
176
+ fs.mkdirSync(skillDir, { recursive: true });
177
+ }
178
+
179
+ fs.writeFileSync(soulPath, baseSoulTemplate, 'utf8');
180
+ ensureSkillManifest(skillDir);
181
+ upsertWorkspaceSoul(workspaceSoulPath, injectionTemplate);
182
+ fs.writeFileSync(identityPath, identityTemplate, 'utf8');
183
+
184
+ let config = migrateConfig(readJson(configPath));
185
+ config.configSchemaVersion = CURRENT_CONFIG_SCHEMA_VERSION;
186
+ const existingEntry = (((config.skills || {}).entries || {})[SKILL_ID]) || {};
187
+ const resolvedRolePack = resolveRolePack({
188
+ requestedRolePack: rolePack,
189
+ existingEntry
190
+ });
191
+ const resolvedSchedule = resolveSchedule({
192
+ existingEntry,
193
+ requestedSchedule: schedule
194
+ });
195
+ const mergedEntry = deepMerge(existingEntry, {
196
+ enabled: true,
197
+ mode: 'productivity',
198
+ timezone,
199
+ schedule: resolvedSchedule,
200
+ rolePack: resolvedRolePack,
201
+ runtime: {
202
+ defaults: {
203
+ timezone,
204
+ rolePack: resolvedRolePack
205
+ },
206
+ schedule: resolvedSchedule
207
+ },
208
+ delivery: {
209
+ mode: 'openclaw-gateway',
210
+ platform: 'telegram',
211
+ channel: channel ?? existingEntry?.delivery?.channel ?? null
212
+ }
213
+ });
214
+
215
+ config = deepMerge(config, {
216
+ skills: {
217
+ entries: {
218
+ [SKILL_ID]: mergedEntry
219
+ },
220
+ load: {
221
+ extraDirs: Array.isArray(config.skills?.load?.extraDirs)
222
+ ? config.skills.load.extraDirs
223
+ : []
224
+ }
225
+ }
226
+ });
227
+
228
+ if (!config.skills.load.extraDirs.includes(skillsDir)) {
229
+ config.skills.load.extraDirs.push(skillsDir);
230
+ }
231
+
232
+ upsertInstalledSkill(config);
233
+ writeJson(configPath, config);
234
+
235
+ return {
236
+ action: hadExistingSkill && existingMode === 'update' ? 'update' : 'install',
237
+ skillDir,
238
+ configPath,
239
+ workspaceSoulPath,
240
+ identityPath
241
+ };
242
+ }
243
+
244
+ module.exports = {
245
+ installSkill,
246
+ SKILL_ID,
247
+ DEFAULT_SCHEDULE
248
+ };