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/bin/cli.js CHANGED
@@ -1,214 +1,591 @@
1
- #!/usr/bin/env node
2
-
3
- const os = require('node:os');
4
- const path = require('node:path');
5
- const readline = require('node:readline');
6
- const { installSkill, DEFAULT_SCHEDULE } = require('../src/install');
7
-
8
- const SCHEDULE_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
9
-
10
- function printHelp() {
11
- console.log('clawpilot - OpenClaw productivity copilot');
12
- console.log('');
13
- console.log('Usage:');
14
- console.log(' clawpilot install [options]');
15
- console.log(' clawpilot --help');
16
- console.log('');
17
- console.log('Commands:');
18
- console.log(' install Install clawpilot-productivity skill into OpenClaw');
19
- console.log('');
20
- console.log('Options:');
21
- console.log(' --home <path> Override OpenClaw home directory');
22
- console.log(' --force Replace existing clawpilot skill installation');
23
- console.log(' --yes Non-interactive mode (fail if required input is missing)');
24
- console.log(' --timezone <IANA> Timezone override (e.g. Asia/Taipei, UTC)');
25
- console.log(` --morning <HH:mm> Morning check-in time (default: ${DEFAULT_SCHEDULE.morning})`);
26
- console.log(` --midday <HH:mm> Midday check-in time (default: ${DEFAULT_SCHEDULE.midday})`);
27
- console.log(` --evening <HH:mm> Evening check-in time (default: ${DEFAULT_SCHEDULE.evening})`);
28
- }
29
-
30
- function readValueArg(args, index, flagName, missingValueFlags) {
31
- const value = args[index + 1];
32
- if (value === undefined || value.startsWith('--')) {
33
- missingValueFlags.push(flagName);
34
- return { value: undefined, nextIndex: index };
35
- }
36
- return { value, nextIndex: index + 1 };
37
- }
38
-
39
- function parseOptions(args) {
40
- const options = {
41
- force: false,
42
- yes: false,
43
- schedule: {},
44
- missingValueFlags: []
45
- };
46
-
47
- for (let index = 0; index < args.length; index += 1) {
48
- const arg = args[index];
49
- if (arg === '--force') {
50
- options.force = true;
51
- continue;
52
- }
53
- if (arg === '--yes') {
54
- options.yes = true;
55
- continue;
56
- }
57
- if (arg === '--home') {
58
- const { value, nextIndex } = readValueArg(args, index, '--home', options.missingValueFlags);
59
- options.openClawHome = value;
60
- index = nextIndex;
61
- continue;
62
- }
63
- if (arg === '--timezone') {
64
- const { value, nextIndex } = readValueArg(args, index, '--timezone', options.missingValueFlags);
65
- options.timezone = value;
66
- index = nextIndex;
67
- continue;
68
- }
69
- if (arg === '--morning') {
70
- const { value, nextIndex } = readValueArg(args, index, '--morning', options.missingValueFlags);
71
- options.schedule.morning = value;
72
- index = nextIndex;
73
- continue;
74
- }
75
- if (arg === '--midday') {
76
- const { value, nextIndex } = readValueArg(args, index, '--midday', options.missingValueFlags);
77
- options.schedule.midday = value;
78
- index = nextIndex;
79
- continue;
80
- }
81
- if (arg === '--evening') {
82
- const { value, nextIndex } = readValueArg(args, index, '--evening', options.missingValueFlags);
83
- options.schedule.evening = value;
84
- index = nextIndex;
85
- continue;
86
- }
87
- throw new Error(`Unknown option: ${arg}`);
88
- }
89
-
90
- return options;
91
- }
92
-
93
- function prompt(question) {
94
- const rl = readline.createInterface({
95
- input: process.stdin,
96
- output: process.stdout
97
- });
98
-
99
- return new Promise((resolve) => {
100
- rl.question(question, (answer) => {
101
- rl.close();
102
- resolve(answer.trim());
103
- });
104
- });
105
- }
106
-
107
- function isValidTimezone(timezone) {
108
- try {
109
- Intl.DateTimeFormat('en-US', { timeZone: timezone }).format();
110
- return true;
111
- } catch {
112
- return false;
113
- }
114
- }
115
-
116
- function validateSchedule(schedule) {
117
- for (const [key, value] of Object.entries(schedule)) {
118
- if (!SCHEDULE_PATTERN.test(value)) {
119
- throw new Error(`Invalid ${key} time "${value}". Use HH:mm (24-hour format).`);
120
- }
121
- }
122
- }
123
-
124
- async function resolveMissingValues(options) {
125
- const missing = [...new Set(options.missingValueFlags)];
126
- if (missing.length === 0) {
127
- return;
128
- }
129
-
130
- if (options.yes) {
131
- throw new Error(`${missing[0]} requires a value when --yes is set.`);
132
- }
133
-
134
- if (!process.stdin.isTTY) {
135
- throw new Error(`${missing[0]} requires a value. Re-run with ${missing[0]} <value> or use interactive terminal.`);
136
- }
137
-
138
- for (const flag of missing) {
139
- if (flag === '--home') {
140
- options.openClawHome = await prompt('OpenClaw home path: ');
141
- continue;
142
- }
143
- if (flag === '--timezone') {
144
- options.timezone = await prompt('Timezone (e.g. UTC, Asia/Taipei): ');
145
- continue;
146
- }
147
- if (flag === '--morning') {
148
- options.schedule.morning = await prompt('Morning check-in time (HH:mm): ');
149
- continue;
150
- }
151
- if (flag === '--midday') {
152
- options.schedule.midday = await prompt('Midday check-in time (HH:mm): ');
153
- continue;
154
- }
155
- if (flag === '--evening') {
156
- options.schedule.evening = await prompt('Evening check-in time (HH:mm): ');
157
- }
158
- }
159
- }
160
-
161
- async function main() {
162
- const args = process.argv.slice(2);
163
- const command = args[0] || 'install';
164
- const commandArgs = args.slice(1);
165
-
166
- if (command === '--help' || command === '-h' || command === 'help') {
167
- printHelp();
168
- return;
169
- }
170
-
171
- if (command !== 'install') {
172
- console.error(`Unknown command: ${command}`);
173
- console.error('Run "clawpilot --help" for usage.');
174
- process.exitCode = 1;
175
- return;
176
- }
177
-
178
- const projectRoot = path.resolve(__dirname, '..');
179
- const options = parseOptions(commandArgs);
180
- await resolveMissingValues(options);
181
- validateSchedule(options.schedule);
182
-
183
- let timezone = options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
184
- if (!timezone) {
185
- if (options.yes) {
186
- throw new Error('Timezone could not be detected. Provide --timezone <IANA name>.');
187
- }
188
- timezone = await prompt('Timezone (e.g. UTC, Asia/Taipei): ');
189
- }
190
- if (!isValidTimezone(timezone)) {
191
- throw new Error(`Invalid timezone "${timezone}". Use a valid IANA timezone like UTC or Asia/Taipei.`);
192
- }
193
-
194
- const openClawHome = options.openClawHome || process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
195
-
196
- const result = await installSkill({
197
- openClawHome,
198
- packageRoot: projectRoot,
199
- schedule: options.schedule,
200
- force: options.force,
201
- timezone
202
- });
203
-
204
- console.log(`Installed skill at: ${result.skillDir}`);
205
- console.log(`Updated config at: ${result.configPath}`);
206
- console.log(`Updated workspace SOUL at: ${result.workspaceSoulPath}`);
207
- console.log(`Updated workspace identity at: ${result.identityPath}`);
208
- console.log('Next: run openclaw and start your morning Top-3 planning check-in.');
209
- }
210
-
211
- main().catch((error) => {
212
- console.error(error.message);
213
- process.exitCode = 1;
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
+ };