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/README.md +160 -67
- package/bin/cli.js +591 -100
- package/docs/PUBLISH_CHECKLIST.md +31 -22
- package/docs/plans/2026-02-13-v0.3-clawra-gap-plan.md +199 -0
- package/docs/plans/2026-02-13-v0.3-implementation.md +434 -0
- package/docs/releases/v0.1.2.md +57 -0
- package/docs/releases/v0.2.0-draft.md +24 -0
- package/docs/releases/v0.2.1-draft.md +19 -0
- package/docs/troubleshooting.md +44 -0
- package/package.json +1 -1
- package/skill/SKILL.md +33 -22
- package/src/config/migrations.js +68 -0
- package/src/install.js +248 -181
- package/src/preflight.js +288 -0
- package/src/runtime/index.js +127 -0
- package/src/runtime/openclaw-gateway.js +135 -0
- package/src/runtime/productivity.js +42 -0
- package/src/runtime/role-pack.js +11 -0
- package/src/runtime/social-formatters.js +22 -0
- package/src/runtime/state-store.js +33 -0
- package/src/runtime/template-renderer.js +7 -0
- package/src/runtime/weekly-report.js +19 -0
- package/templates/content/evening.md +7 -0
- package/templates/content/midday.md +8 -0
- package/templates/content/morning.md +8 -0
- package/templates/content/report.md +5 -0
- package/templates/content/social-linkedin.md +8 -0
- package/templates/content/social-telegram.md +8 -0
- package/templates/content/social-x.md +8 -0
- package/templates/role-packs/hana.json +7 -0
- package/templates/role-packs/minji.json +7 -0
- package/templates/soul-injection.md +38 -14
package/src/install.js
CHANGED
|
@@ -1,181 +1,248 @@
|
|
|
1
|
-
const fs = require('node:fs');
|
|
2
|
-
const path = require('node:path');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
};
|