clawpilot 0.1.2 → 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 -100
- package/bin/cli.js +591 -214
- package/docs/PUBLISH_CHECKLIST.md +31 -23
- 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 -28
- package/docs/plans/2026-02-12-0.1.2-plan.md +0 -52
- package/docs/plans/2026-02-13-0.1.2-design.md +0 -115
package/bin/cli.js
CHANGED
|
@@ -1,214 +1,591 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.log('
|
|
15
|
-
console.log('
|
|
16
|
-
console.log('');
|
|
17
|
-
console.log('
|
|
18
|
-
console.log('
|
|
19
|
-
console.log('');
|
|
20
|
-
console.log('
|
|
21
|
-
console.log('
|
|
22
|
-
console.log('
|
|
23
|
-
console.log('
|
|
24
|
-
console.log('
|
|
25
|
-
console.log(
|
|
26
|
-
console.log(
|
|
27
|
-
console.log(
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const readline = require('node:readline');
|
|
7
|
+
const { runPreflight, TROUBLESHOOTING_DOC } = require('../src/preflight');
|
|
8
|
+
const { installSkill, DEFAULT_SCHEDULE, SKILL_ID } = require('../src/install');
|
|
9
|
+
|
|
10
|
+
const SCHEDULE_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
11
|
+
const ON_EXISTING_MODES = ['error', 'update', 'skip', 'reinstall'];
|
|
12
|
+
|
|
13
|
+
function printHelp() {
|
|
14
|
+
console.log('clawpilot - OpenClaw productivity copilot');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log('Usage:');
|
|
17
|
+
console.log(' clawpilot install [options]');
|
|
18
|
+
console.log(' clawpilot run [options]');
|
|
19
|
+
console.log(' clawpilot --help');
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log('Commands:');
|
|
22
|
+
console.log(' install Install clawpilot-productivity skill into OpenClaw');
|
|
23
|
+
console.log(' run Run productivity runtime command');
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log('Options:');
|
|
26
|
+
console.log(' --home <path> Override OpenClaw home directory');
|
|
27
|
+
console.log(' --force Replace existing clawpilot skill installation');
|
|
28
|
+
console.log(' --yes Non-interactive mode (fail if required input is missing)');
|
|
29
|
+
console.log(' --timezone <IANA> Timezone override (e.g. Asia/Taipei, UTC)');
|
|
30
|
+
console.log(` --morning <HH:mm> Morning check-in time (default: ${DEFAULT_SCHEDULE.morning})`);
|
|
31
|
+
console.log(` --midday <HH:mm> Midday check-in time (default: ${DEFAULT_SCHEDULE.midday})`);
|
|
32
|
+
console.log(` --evening <HH:mm> Evening check-in time (default: ${DEFAULT_SCHEDULE.evening})`);
|
|
33
|
+
console.log(' --command <name> Runtime command (morning|midday|evening|report)');
|
|
34
|
+
console.log(' --channel <target> Channel target (run) and default delivery channel (install)');
|
|
35
|
+
console.log(' --dry-run Return payload only, do not send');
|
|
36
|
+
console.log(' --role-pack <name> Role pack id (run) and default role pack (install)');
|
|
37
|
+
console.log(' --task <text> Task item (repeatable for morning)');
|
|
38
|
+
console.log(' --status <value> Status item (repeatable for midday)');
|
|
39
|
+
console.log(' --state-file <path> Runtime state file override');
|
|
40
|
+
console.log(' --on-existing <mode> Existing install policy (error|update|skip|reinstall)');
|
|
41
|
+
console.log(' --preflight Run environment checks only (no install changes)');
|
|
42
|
+
console.log(' --json-errors Output machine-readable JSON errors to stderr');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createCliError(code, reason, fix, docs = TROUBLESHOOTING_DOC) {
|
|
46
|
+
const error = new Error(reason);
|
|
47
|
+
error.code = code;
|
|
48
|
+
error.reason = reason;
|
|
49
|
+
error.fix = fix;
|
|
50
|
+
error.docs = docs;
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeCliError(error) {
|
|
55
|
+
if (error && typeof error === 'object' && error.code && error.reason) {
|
|
56
|
+
return {
|
|
57
|
+
code: error.code,
|
|
58
|
+
reason: error.reason,
|
|
59
|
+
fix: error.fix || 'See troubleshooting guide.',
|
|
60
|
+
docs: error.docs || TROUBLESHOOTING_DOC
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const message = error && error.message ? error.message : String(error || 'Unknown error');
|
|
65
|
+
|
|
66
|
+
if (/channel is required/i.test(message)) {
|
|
67
|
+
return {
|
|
68
|
+
code: 'channel_required',
|
|
69
|
+
reason: 'Channel is required for non-dry-run delivery.',
|
|
70
|
+
fix: 'Re-run with --channel <target> or use --dry-run.',
|
|
71
|
+
docs: TROUBLESHOOTING_DOC
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (/unsupported runtime command/i.test(message)) {
|
|
76
|
+
return {
|
|
77
|
+
code: 'invalid_command',
|
|
78
|
+
reason: message,
|
|
79
|
+
fix: 'Use one of: morning, midday, evening, report.',
|
|
80
|
+
docs: TROUBLESHOOTING_DOC
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
code: 'unknown',
|
|
86
|
+
reason: message,
|
|
87
|
+
fix: 'Inspect the error details and troubleshooting guide.',
|
|
88
|
+
docs: TROUBLESHOOTING_DOC
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printCliError(error, useJsonErrors) {
|
|
93
|
+
const normalized = normalizeCliError(error);
|
|
94
|
+
if (useJsonErrors) {
|
|
95
|
+
process.stderr.write(`${JSON.stringify(normalized)}\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.error(`${normalized.code}: ${normalized.reason}`);
|
|
100
|
+
console.error(`Fix: ${normalized.fix}`);
|
|
101
|
+
if (normalized.docs) {
|
|
102
|
+
console.error(`Docs: ${normalized.docs}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function printPreflightSummary(summary, useJsonErrors) {
|
|
107
|
+
const issues = Array.isArray(summary.issues) ? summary.issues : [];
|
|
108
|
+
const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
|
|
109
|
+
if (useJsonErrors) {
|
|
110
|
+
process.stdout.write(`${JSON.stringify({ ...summary, issues, warnings })}\n`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('Preflight summary');
|
|
115
|
+
console.log(`- ok: ${summary.ok}`);
|
|
116
|
+
if (issues.length > 0) {
|
|
117
|
+
console.log('- issues:');
|
|
118
|
+
for (const issue of issues) {
|
|
119
|
+
console.log(` - [${issue.code}] ${issue.reason}`);
|
|
120
|
+
console.log(` fix: ${issue.fix}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (warnings.length > 0) {
|
|
124
|
+
console.log('- warnings:');
|
|
125
|
+
for (const warning of warnings) {
|
|
126
|
+
console.log(` - [${warning.code}] ${warning.reason}`);
|
|
127
|
+
console.log(` fix: ${warning.fix}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isValidOnExistingMode(value) {
|
|
133
|
+
return ON_EXISTING_MODES.includes(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeOnExistingChoice(choice) {
|
|
137
|
+
const normalized = (choice || '').trim().toLowerCase();
|
|
138
|
+
if (!normalized) {
|
|
139
|
+
return 'update';
|
|
140
|
+
}
|
|
141
|
+
if (normalized === '1') {
|
|
142
|
+
return 'update';
|
|
143
|
+
}
|
|
144
|
+
if (normalized === '2') {
|
|
145
|
+
return 'skip';
|
|
146
|
+
}
|
|
147
|
+
if (normalized === '3') {
|
|
148
|
+
return 'reinstall';
|
|
149
|
+
}
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readValueArg(args, index, flagName, missingValueFlags) {
|
|
154
|
+
const value = args[index + 1];
|
|
155
|
+
if (value === undefined || value.startsWith('--')) {
|
|
156
|
+
missingValueFlags.push(flagName);
|
|
157
|
+
return { value: undefined, nextIndex: index };
|
|
158
|
+
}
|
|
159
|
+
return { value, nextIndex: index + 1 };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseOptions(args) {
|
|
163
|
+
const options = {
|
|
164
|
+
force: false,
|
|
165
|
+
yes: false,
|
|
166
|
+
jsonErrors: false,
|
|
167
|
+
schedule: {},
|
|
168
|
+
missingValueFlags: []
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
172
|
+
const arg = args[index];
|
|
173
|
+
if (arg === '--force') {
|
|
174
|
+
options.force = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (arg === '--yes') {
|
|
178
|
+
options.yes = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === '--json-errors') {
|
|
182
|
+
options.jsonErrors = true;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (arg === '--preflight') {
|
|
186
|
+
options.preflightOnly = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === '--home') {
|
|
190
|
+
const { value, nextIndex } = readValueArg(args, index, '--home', options.missingValueFlags);
|
|
191
|
+
options.openClawHome = value;
|
|
192
|
+
index = nextIndex;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (arg === '--timezone') {
|
|
196
|
+
const { value, nextIndex } = readValueArg(args, index, '--timezone', options.missingValueFlags);
|
|
197
|
+
options.timezone = value;
|
|
198
|
+
index = nextIndex;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg === '--morning') {
|
|
202
|
+
const { value, nextIndex } = readValueArg(args, index, '--morning', options.missingValueFlags);
|
|
203
|
+
options.schedule.morning = value;
|
|
204
|
+
index = nextIndex;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (arg === '--midday') {
|
|
208
|
+
const { value, nextIndex } = readValueArg(args, index, '--midday', options.missingValueFlags);
|
|
209
|
+
options.schedule.midday = value;
|
|
210
|
+
index = nextIndex;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (arg === '--evening') {
|
|
214
|
+
const { value, nextIndex } = readValueArg(args, index, '--evening', options.missingValueFlags);
|
|
215
|
+
options.schedule.evening = value;
|
|
216
|
+
index = nextIndex;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (arg === '--channel') {
|
|
220
|
+
const { value, nextIndex } = readValueArg(args, index, '--channel', options.missingValueFlags);
|
|
221
|
+
options.channel = value;
|
|
222
|
+
index = nextIndex;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (arg === '--role-pack') {
|
|
226
|
+
const { value, nextIndex } = readValueArg(args, index, '--role-pack', options.missingValueFlags);
|
|
227
|
+
options.rolePack = value;
|
|
228
|
+
index = nextIndex;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (arg === '--on-existing') {
|
|
232
|
+
const { value, nextIndex } = readValueArg(args, index, '--on-existing', options.missingValueFlags);
|
|
233
|
+
options.onExisting = value;
|
|
234
|
+
index = nextIndex;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return options;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseRunOptions(args) {
|
|
244
|
+
const options = {
|
|
245
|
+
command: 'morning',
|
|
246
|
+
dryRun: false,
|
|
247
|
+
jsonErrors: false,
|
|
248
|
+
channel: null,
|
|
249
|
+
tasks: [],
|
|
250
|
+
statuses: []
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
254
|
+
const arg = args[index];
|
|
255
|
+
if (arg === '--dry-run') {
|
|
256
|
+
options.dryRun = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (arg === '--json-errors') {
|
|
260
|
+
options.jsonErrors = true;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (arg === '--command') {
|
|
264
|
+
const next = args[index + 1];
|
|
265
|
+
if (!next || next.startsWith('--')) {
|
|
266
|
+
throw new Error('--command requires a value.');
|
|
267
|
+
}
|
|
268
|
+
options.command = next;
|
|
269
|
+
index += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (arg === '--channel') {
|
|
273
|
+
const next = args[index + 1];
|
|
274
|
+
if (!next || next.startsWith('--')) {
|
|
275
|
+
throw new Error('--channel requires a value.');
|
|
276
|
+
}
|
|
277
|
+
options.channel = next;
|
|
278
|
+
index += 1;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (arg === '--task') {
|
|
282
|
+
const next = args[index + 1];
|
|
283
|
+
if (!next || next.startsWith('--')) {
|
|
284
|
+
throw new Error('--task requires a value.');
|
|
285
|
+
}
|
|
286
|
+
options.tasks.push(next);
|
|
287
|
+
index += 1;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (arg === '--status') {
|
|
291
|
+
const next = args[index + 1];
|
|
292
|
+
if (!next || next.startsWith('--')) {
|
|
293
|
+
throw new Error('--status requires a value.');
|
|
294
|
+
}
|
|
295
|
+
options.statuses.push(next);
|
|
296
|
+
index += 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (arg === '--role-pack') {
|
|
300
|
+
const next = args[index + 1];
|
|
301
|
+
if (!next || next.startsWith('--')) {
|
|
302
|
+
throw new Error('--role-pack requires a value.');
|
|
303
|
+
}
|
|
304
|
+
options.rolePack = next;
|
|
305
|
+
index += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (arg === '--state-file') {
|
|
309
|
+
const next = args[index + 1];
|
|
310
|
+
if (!next || next.startsWith('--')) {
|
|
311
|
+
throw new Error('--state-file requires a value.');
|
|
312
|
+
}
|
|
313
|
+
options.stateFile = next;
|
|
314
|
+
index += 1;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (arg === '--timezone') {
|
|
318
|
+
const next = args[index + 1];
|
|
319
|
+
if (!next || next.startsWith('--')) {
|
|
320
|
+
throw new Error('--timezone requires a value.');
|
|
321
|
+
}
|
|
322
|
+
options.timezone = next;
|
|
323
|
+
index += 1;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return options;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function prompt(question) {
|
|
333
|
+
const rl = readline.createInterface({
|
|
334
|
+
input: process.stdin,
|
|
335
|
+
output: process.stdout
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
rl.question(question, (answer) => {
|
|
340
|
+
rl.close();
|
|
341
|
+
resolve(answer.trim());
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function isValidTimezone(timezone) {
|
|
347
|
+
try {
|
|
348
|
+
Intl.DateTimeFormat('en-US', { timeZone: timezone }).format();
|
|
349
|
+
return true;
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function validateSchedule(schedule) {
|
|
356
|
+
for (const [key, value] of Object.entries(schedule)) {
|
|
357
|
+
if (!SCHEDULE_PATTERN.test(value)) {
|
|
358
|
+
throw new Error(`Invalid ${key} time "${value}". Use HH:mm (24-hour format).`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function resolveMissingValues(options) {
|
|
364
|
+
const missing = [...new Set(options.missingValueFlags)];
|
|
365
|
+
if (missing.length === 0) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (options.yes) {
|
|
370
|
+
throw new Error(`${missing[0]} requires a value when --yes is set.`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!process.stdin.isTTY) {
|
|
374
|
+
throw new Error(`${missing[0]} requires a value. Re-run with ${missing[0]} <value> or use interactive terminal.`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const flag of missing) {
|
|
378
|
+
if (flag === '--home') {
|
|
379
|
+
options.openClawHome = await prompt('OpenClaw home path: ');
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (flag === '--timezone') {
|
|
383
|
+
options.timezone = await prompt('Timezone (e.g. UTC, Asia/Taipei): ');
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (flag === '--morning') {
|
|
387
|
+
options.schedule.morning = await prompt('Morning check-in time (HH:mm): ');
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (flag === '--midday') {
|
|
391
|
+
options.schedule.midday = await prompt('Midday check-in time (HH:mm): ');
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (flag === '--evening') {
|
|
395
|
+
options.schedule.evening = await prompt('Evening check-in time (HH:mm): ');
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (flag === '--channel') {
|
|
399
|
+
options.channel = await prompt('Default delivery channel (optional, press Enter to skip): ');
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (flag === '--role-pack') {
|
|
403
|
+
options.rolePack = await prompt('Default role pack (hana|minji, Enter for hana): ');
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (flag === '--on-existing') {
|
|
407
|
+
options.onExisting = await prompt('Existing install policy (error|update|skip|reinstall): ');
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`${flag} requires a value.`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function resolveOnExistingMode({
|
|
415
|
+
options,
|
|
416
|
+
openClawHome,
|
|
417
|
+
promptFn = prompt,
|
|
418
|
+
fsOps = fs,
|
|
419
|
+
pathOps = path,
|
|
420
|
+
isInteractive = process.stdin.isTTY
|
|
421
|
+
}) {
|
|
422
|
+
if (options.onExisting) {
|
|
423
|
+
return options.onExisting;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const skillDir = pathOps.join(openClawHome, 'skills', SKILL_ID);
|
|
427
|
+
if (!fsOps.existsSync(skillDir)) {
|
|
428
|
+
options.onExisting = 'error';
|
|
429
|
+
return options.onExisting;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (options.yes || !isInteractive) {
|
|
433
|
+
options.onExisting = 'error';
|
|
434
|
+
return options.onExisting;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const answer = await promptFn(
|
|
438
|
+
`Existing installation found at ${skillDir}. Choose mode [1:update, 2:skip, 3:reinstall] (default: update): `
|
|
439
|
+
);
|
|
440
|
+
const mode = normalizeOnExistingChoice(answer);
|
|
441
|
+
if (!['update', 'skip', 'reinstall'].includes(mode)) {
|
|
442
|
+
throw createCliError(
|
|
443
|
+
'invalid_on_existing_mode',
|
|
444
|
+
`Invalid existing install mode "${answer}".`,
|
|
445
|
+
'Choose update, skip, reinstall (or 1/2/3).'
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
options.onExisting = mode;
|
|
450
|
+
return options.onExisting;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function main() {
|
|
454
|
+
const args = process.argv.slice(2);
|
|
455
|
+
const command = args[0] || 'install';
|
|
456
|
+
const commandArgs = args.slice(1);
|
|
457
|
+
|
|
458
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
459
|
+
printHelp();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (command === 'run') {
|
|
464
|
+
const runtime = require('../src/runtime');
|
|
465
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
466
|
+
const runOptions = parseRunOptions(commandArgs);
|
|
467
|
+
const openClawHome = process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
|
|
468
|
+
const payload = await runtime.runRuntimeCommand({
|
|
469
|
+
...runOptions,
|
|
470
|
+
openClawHome,
|
|
471
|
+
packageRoot: projectRoot
|
|
472
|
+
});
|
|
473
|
+
console.log(JSON.stringify(payload));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (command !== 'install') {
|
|
478
|
+
console.error(`Unknown command: ${command}`);
|
|
479
|
+
console.error('Run "clawpilot --help" for usage.');
|
|
480
|
+
process.exitCode = 1;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
485
|
+
const options = parseOptions(commandArgs);
|
|
486
|
+
await resolveMissingValues(options);
|
|
487
|
+
validateSchedule(options.schedule);
|
|
488
|
+
|
|
489
|
+
let timezone = options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
490
|
+
if (!timezone) {
|
|
491
|
+
if (options.yes) {
|
|
492
|
+
throw new Error('Timezone could not be detected. Provide --timezone <IANA name>.');
|
|
493
|
+
}
|
|
494
|
+
timezone = await prompt('Timezone (e.g. UTC, Asia/Taipei): ');
|
|
495
|
+
}
|
|
496
|
+
if (!isValidTimezone(timezone)) {
|
|
497
|
+
throw new Error(`Invalid timezone "${timezone}". Use a valid IANA timezone like UTC or Asia/Taipei.`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const openClawHome = options.openClawHome || process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
|
|
501
|
+
await resolveOnExistingMode({ options, openClawHome });
|
|
502
|
+
|
|
503
|
+
if (options.onExisting && !isValidOnExistingMode(options.onExisting)) {
|
|
504
|
+
throw createCliError(
|
|
505
|
+
'invalid_on_existing_mode',
|
|
506
|
+
`Invalid --on-existing value "${options.onExisting}".`,
|
|
507
|
+
'Use one of: error, update, skip, reinstall.'
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
let preflightSummary = { ok: true, checks: [], issues: [], warnings: [] };
|
|
512
|
+
if (process.env.CLAWPILOT_TEST_FORCE_PREFLIGHT_FAIL === '1') {
|
|
513
|
+
preflightSummary = {
|
|
514
|
+
ok: false,
|
|
515
|
+
checks: [],
|
|
516
|
+
issues: [
|
|
517
|
+
{
|
|
518
|
+
code: 'gateway_missing',
|
|
519
|
+
reason: 'openclaw CLI not found.',
|
|
520
|
+
fix: 'Install OpenClaw CLI and verify with: openclaw --version',
|
|
521
|
+
docs: TROUBLESHOOTING_DOC
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
};
|
|
525
|
+
} else if (process.env.CLAWPILOT_SKIP_PREFLIGHT !== '1') {
|
|
526
|
+
preflightSummary = runPreflight({ openClawHome });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (options.preflightOnly) {
|
|
530
|
+
printPreflightSummary(preflightSummary, options.jsonErrors);
|
|
531
|
+
if (!preflightSummary.ok) {
|
|
532
|
+
process.exitCode = 1;
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!preflightSummary.ok) {
|
|
538
|
+
const issue = preflightSummary.issues[0];
|
|
539
|
+
throw createCliError(issue.code, issue.reason, issue.fix, issue.docs);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (preflightSummary.warnings && preflightSummary.warnings.length > 0 && !options.jsonErrors) {
|
|
543
|
+
for (const warning of preflightSummary.warnings) {
|
|
544
|
+
console.error(`warning ${warning.code}: ${warning.reason}`);
|
|
545
|
+
console.error(`fix: ${warning.fix}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const result = await installSkill({
|
|
550
|
+
openClawHome,
|
|
551
|
+
packageRoot: projectRoot,
|
|
552
|
+
schedule: options.schedule,
|
|
553
|
+
force: options.force,
|
|
554
|
+
timezone,
|
|
555
|
+
channel: options.channel,
|
|
556
|
+
rolePack: options.rolePack,
|
|
557
|
+
onExisting: options.onExisting
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
if (result.action === 'skip') {
|
|
561
|
+
console.log(`Skipped installation; existing skill kept at: ${result.skillDir}`);
|
|
562
|
+
console.log(`Config remains at: ${result.configPath}`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const installVerb = result.action === 'update' ? 'Updated' : 'Installed';
|
|
567
|
+
console.log(`${installVerb} skill at: ${result.skillDir}`);
|
|
568
|
+
console.log(`Updated config at: ${result.configPath}`);
|
|
569
|
+
console.log(`Updated workspace SOUL at: ${result.workspaceSoulPath}`);
|
|
570
|
+
console.log(`Updated workspace identity at: ${result.identityPath}`);
|
|
571
|
+
console.log('Next: run openclaw and start your morning Top-3 planning check-in.');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (require.main === module) {
|
|
575
|
+
main().catch((error) => {
|
|
576
|
+
const useJsonErrors = process.argv.includes('--json-errors');
|
|
577
|
+
printCliError(error, useJsonErrors);
|
|
578
|
+
process.exitCode = 1;
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
module.exports = {
|
|
583
|
+
createCliError,
|
|
584
|
+
normalizeCliError,
|
|
585
|
+
parseOptions,
|
|
586
|
+
parseRunOptions,
|
|
587
|
+
printCliError,
|
|
588
|
+
printHelp,
|
|
589
|
+
resolveOnExistingMode,
|
|
590
|
+
printPreflightSummary
|
|
591
|
+
};
|