@masslessai/push-todo 4.2.9 → 4.4.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/SKILL.md +5 -0
- package/lib/api.js +10 -3
- package/lib/cli.js +230 -71
- package/lib/context-engine.js +6 -0
- package/lib/cron.js +104 -221
- package/lib/daemon.js +26 -130
- package/lib/reminder-parser.js +418 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -433,6 +433,11 @@ The `push-todo` CLI supports these commands:
|
|
|
433
433
|
| `push-todo --status` | Show connection status |
|
|
434
434
|
| `push-todo --mark-completed <uuid>` | Mark task as completed |
|
|
435
435
|
| `push-todo --json` | Output as JSON |
|
|
436
|
+
| `push-todo schedule add` | Create a remote schedule (Supabase-backed) |
|
|
437
|
+
| `push-todo schedule list` | List all remote schedules |
|
|
438
|
+
| `push-todo schedule remove <id>` | Remove a schedule |
|
|
439
|
+
| `push-todo schedule enable <id>` | Enable a schedule |
|
|
440
|
+
| `push-todo schedule disable <id>` | Disable a schedule |
|
|
436
441
|
|
|
437
442
|
## What is Push?
|
|
438
443
|
|
package/lib/api.js
CHANGED
|
@@ -154,9 +154,12 @@ export async function markTaskCompleted(taskId, comment = '') {
|
|
|
154
154
|
* @param {string} options.title - Todo title (required)
|
|
155
155
|
* @param {string|null} options.content - Detailed content (optional)
|
|
156
156
|
* @param {boolean} options.backlog - Whether to create as backlog item
|
|
157
|
-
* @
|
|
157
|
+
* @param {string|null} options.reminderDate - ISO8601 reminder date (optional)
|
|
158
|
+
* @param {string|null} options.reminderTimeSource - Time source enum (optional)
|
|
159
|
+
* @param {boolean} options.alarmEnabled - Whether to mark as urgent (optional)
|
|
160
|
+
* @returns {Promise<Object>} Created todo with { id, displayNumber, title, createdAt, reminderDate, reminderEnabled }
|
|
158
161
|
*/
|
|
159
|
-
export async function createTodo({ title, content = null, backlog = false }) {
|
|
162
|
+
export async function createTodo({ title, content = null, backlog = false, reminderDate = null, reminderTimeSource = null, alarmEnabled = false }) {
|
|
160
163
|
const response = await apiRequest('create-todo', {
|
|
161
164
|
method: 'POST',
|
|
162
165
|
body: JSON.stringify({
|
|
@@ -164,6 +167,10 @@ export async function createTodo({ title, content = null, backlog = false }) {
|
|
|
164
167
|
normalizedContent: content || null,
|
|
165
168
|
isBacklog: backlog,
|
|
166
169
|
createdByClient: 'cli',
|
|
170
|
+
reminderDate: reminderDate || null,
|
|
171
|
+
reminderEnabled: reminderDate != null,
|
|
172
|
+
reminderTimeSource: reminderTimeSource || null,
|
|
173
|
+
alarmEnabled,
|
|
167
174
|
}),
|
|
168
175
|
});
|
|
169
176
|
|
|
@@ -521,4 +528,4 @@ export async function requestInput(todoId, question, timeoutMs = 300000) {
|
|
|
521
528
|
return null; // Timeout
|
|
522
529
|
}
|
|
523
530
|
|
|
524
|
-
export { API_BASE };
|
|
531
|
+
export { API_BASE, apiRequest };
|
package/lib/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { install as installLaunchAgent, uninstall as uninstallLaunchAgent, getSt
|
|
|
19
19
|
import { getScreenshotPath, screenshotExists, openScreenshot } from './utils/screenshots.js';
|
|
20
20
|
import { bold, red, cyan, dim, green } from './utils/colors.js';
|
|
21
21
|
import { getMachineId } from './machine-id.js';
|
|
22
|
+
import { parseReminder } from './reminder-parser.js';
|
|
22
23
|
|
|
23
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
25
|
const __dirname = dirname(__filename);
|
|
@@ -45,10 +46,14 @@ ${bold('USAGE:')}
|
|
|
45
46
|
push-todo [options] List active tasks
|
|
46
47
|
push-todo <number> Show specific task
|
|
47
48
|
push-todo create <title> Create a new todo
|
|
49
|
+
--remind <text> Set reminder ("tomorrow night", "in 2 hours")
|
|
50
|
+
--remind-at <iso> Set reminder at exact ISO8601 time
|
|
51
|
+
--alarm Mark as urgent (bypasses Focus)
|
|
48
52
|
push-todo connect Run connection doctor
|
|
49
53
|
push-todo search <query> Search tasks
|
|
50
54
|
push-todo review Review completed tasks
|
|
51
55
|
push-todo update Update CLI, check agents, refresh projects
|
|
56
|
+
push-todo schedule Manage remote schedules (add/list/remove)
|
|
52
57
|
|
|
53
58
|
${bold('OPTIONS:')}
|
|
54
59
|
--all-projects, -a List tasks from all projects
|
|
@@ -91,6 +96,8 @@ ${bold('EXAMPLES:')}
|
|
|
91
96
|
push-todo List active tasks for current project
|
|
92
97
|
push-todo 427 Show task #427
|
|
93
98
|
push-todo create "Fix auth bug" Create a new todo
|
|
99
|
+
push-todo create "Debug" --remind "tomorrow night"
|
|
100
|
+
push-todo create "Call" --remind "at 3pm" --alarm
|
|
94
101
|
push-todo create "Item" --backlog Create as backlog item
|
|
95
102
|
push-todo -a List all tasks across projects
|
|
96
103
|
push-todo --queue 1,2,3 Queue tasks 1, 2, 3 for daemon
|
|
@@ -116,17 +123,20 @@ ${bold('CONFIRM (for daemon skills):')}
|
|
|
116
123
|
--metadata <json> Optional JSON metadata for rich rendering
|
|
117
124
|
--task <number> Display number (auto-detected in daemon)
|
|
118
125
|
|
|
119
|
-
${bold('
|
|
120
|
-
push-todo
|
|
121
|
-
--name <name>
|
|
122
|
-
--every <interval>
|
|
123
|
-
--at <iso-date>
|
|
124
|
-
--cron <expression>
|
|
125
|
-
--
|
|
126
|
-
--
|
|
127
|
-
--
|
|
128
|
-
|
|
129
|
-
push-todo
|
|
126
|
+
${bold('SCHEDULE (remote scheduled jobs):')}
|
|
127
|
+
push-todo schedule add Create a schedule
|
|
128
|
+
--name <name> Schedule name (required)
|
|
129
|
+
--every <interval> Repeat interval: 30m, 1h, 24h, 7d
|
|
130
|
+
--at <iso-date> One-shot at specific time
|
|
131
|
+
--cron <expression> 5-field cron expression
|
|
132
|
+
--create-todo <title> Create a new todo each fire
|
|
133
|
+
--queue-todo <todoId> Re-queue an existing todo each fire
|
|
134
|
+
--git-remote <remote> Route created todos to a project
|
|
135
|
+
--content <body> Expanded content for created todos
|
|
136
|
+
push-todo schedule list List all schedules
|
|
137
|
+
push-todo schedule remove <id> Remove a schedule
|
|
138
|
+
push-todo schedule enable <id> Enable a schedule
|
|
139
|
+
push-todo schedule disable <id> Disable a schedule
|
|
130
140
|
|
|
131
141
|
${bold('SETTINGS:')}
|
|
132
142
|
push-todo setting Show all settings
|
|
@@ -174,22 +184,24 @@ const options = {
|
|
|
174
184
|
'store-e2ee-key': { type: 'string' },
|
|
175
185
|
'description': { type: 'string' },
|
|
176
186
|
'auto': { type: 'boolean' },
|
|
187
|
+
// Create command options
|
|
188
|
+
'remind': { type: 'string' },
|
|
189
|
+
'remind-at': { type: 'string' },
|
|
190
|
+
'alarm': { type: 'boolean' },
|
|
177
191
|
// Confirm command options
|
|
178
192
|
'type': { type: 'string' },
|
|
179
193
|
'title': { type: 'string' },
|
|
180
194
|
'content': { type: 'string' },
|
|
181
195
|
'metadata': { type: 'string' },
|
|
182
196
|
'task': { type: 'string' },
|
|
183
|
-
//
|
|
197
|
+
// Schedule command options
|
|
184
198
|
'name': { type: 'string' },
|
|
185
199
|
'every': { type: 'string' },
|
|
186
200
|
'at': { type: 'string' },
|
|
187
201
|
'cron': { type: 'string' },
|
|
188
202
|
'create-todo': { type: 'string' },
|
|
189
|
-
'
|
|
190
|
-
'queue-
|
|
191
|
-
'health-check': { type: 'string' },
|
|
192
|
-
'scope': { type: 'string' },
|
|
203
|
+
'git-remote': { type: 'string' },
|
|
204
|
+
'queue-todo': { type: 'string' },
|
|
193
205
|
// Skill CLI options (Phase 3)
|
|
194
206
|
'report-progress': { type: 'string' },
|
|
195
207
|
'phase': { type: 'string' },
|
|
@@ -682,22 +694,74 @@ export async function run(argv) {
|
|
|
682
694
|
console.error('');
|
|
683
695
|
console.error('Usage:');
|
|
684
696
|
console.error(' push-todo create "Fix the auth bug"');
|
|
685
|
-
console.error(' push-todo create "
|
|
686
|
-
console.error(' push-todo create "
|
|
697
|
+
console.error(' push-todo create "Debug daemon" --remind "tomorrow night"');
|
|
698
|
+
console.error(' push-todo create "Call dentist" --remind "at 3pm" --alarm');
|
|
699
|
+
console.error(' push-todo create "Deploy" --remind-at "2026-03-03T14:00:00"');
|
|
687
700
|
process.exit(1);
|
|
688
701
|
}
|
|
689
702
|
|
|
703
|
+
// Conflict check
|
|
704
|
+
if (values.remind && values['remind-at']) {
|
|
705
|
+
console.error(red('Error: Use --remind OR --remind-at, not both.'));
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Parse reminder
|
|
710
|
+
let reminderDate = null;
|
|
711
|
+
let reminderTimeSource = null;
|
|
712
|
+
const alarmEnabled = values.alarm || false;
|
|
713
|
+
|
|
714
|
+
if (values['remind-at']) {
|
|
715
|
+
const parsed = new Date(values['remind-at']);
|
|
716
|
+
if (isNaN(parsed.getTime())) {
|
|
717
|
+
console.error(red('Error: --remind-at must be a valid ISO8601 date.'));
|
|
718
|
+
console.error(dim(' Example: 2026-03-03T14:00:00'));
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
if (parsed <= new Date()) {
|
|
722
|
+
console.error(red('Error: --remind-at must be in the future.'));
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
reminderDate = parsed.toISOString();
|
|
726
|
+
reminderTimeSource = 'userSpecified';
|
|
727
|
+
} else if (values.remind) {
|
|
728
|
+
const result = parseReminder(values.remind);
|
|
729
|
+
if (!result.date) {
|
|
730
|
+
console.error(red(`Error: Could not parse reminder: "${values.remind}"`));
|
|
731
|
+
console.error('');
|
|
732
|
+
console.error('Examples:');
|
|
733
|
+
console.error(' --remind "tomorrow"');
|
|
734
|
+
console.error(' --remind "tonight"');
|
|
735
|
+
console.error(' --remind "in 2 hours"');
|
|
736
|
+
console.error(' --remind "next monday at 3pm"');
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
reminderDate = result.date.toISOString();
|
|
740
|
+
reminderTimeSource = result.timeSource;
|
|
741
|
+
}
|
|
742
|
+
|
|
690
743
|
try {
|
|
691
744
|
const todo = await api.createTodo({
|
|
692
745
|
title,
|
|
693
746
|
content: values.content || null,
|
|
694
747
|
backlog: values.backlog || false,
|
|
748
|
+
reminderDate,
|
|
749
|
+
reminderTimeSource,
|
|
750
|
+
alarmEnabled,
|
|
695
751
|
});
|
|
696
752
|
|
|
697
753
|
if (values.json) {
|
|
698
754
|
console.log(JSON.stringify(todo, null, 2));
|
|
699
755
|
} else {
|
|
700
|
-
|
|
756
|
+
let msg = green(`Created todo #${todo.displayNumber}: ${todo.title}`);
|
|
757
|
+
if (reminderDate) {
|
|
758
|
+
const d = new Date(reminderDate);
|
|
759
|
+
msg += `\n ${dim('Reminder:')} ${d.toLocaleString()}`;
|
|
760
|
+
}
|
|
761
|
+
if (alarmEnabled) {
|
|
762
|
+
msg += ` ${dim('(urgent)')}`;
|
|
763
|
+
}
|
|
764
|
+
console.log(msg);
|
|
701
765
|
}
|
|
702
766
|
} catch (error) {
|
|
703
767
|
console.error(red(`Failed to create todo: ${error.message}`));
|
|
@@ -706,99 +770,194 @@ export async function run(argv) {
|
|
|
706
770
|
return;
|
|
707
771
|
}
|
|
708
772
|
|
|
709
|
-
//
|
|
710
|
-
if (command === '
|
|
711
|
-
const {
|
|
773
|
+
// Schedule command - Supabase-backed unified scheduling
|
|
774
|
+
if (command === 'schedule') {
|
|
775
|
+
const { computeNextRun } = await import('./cron.js');
|
|
712
776
|
const subCommand = positionals[1];
|
|
713
777
|
|
|
714
778
|
if (subCommand === 'add') {
|
|
715
779
|
if (!values.name) {
|
|
716
|
-
console.error(red('--name is required for
|
|
780
|
+
console.error(red('--name is required for schedule add'));
|
|
717
781
|
process.exit(1);
|
|
718
782
|
}
|
|
719
783
|
|
|
720
|
-
// Determine schedule
|
|
721
|
-
let
|
|
784
|
+
// Determine schedule timing
|
|
785
|
+
let scheduleType, scheduleValue;
|
|
722
786
|
if (values.every) {
|
|
723
|
-
|
|
787
|
+
scheduleType = 'every';
|
|
788
|
+
scheduleValue = values.every;
|
|
724
789
|
} else if (values.at) {
|
|
725
|
-
|
|
790
|
+
scheduleType = 'at';
|
|
791
|
+
scheduleValue = values.at;
|
|
726
792
|
} else if (values.cron) {
|
|
727
|
-
|
|
793
|
+
scheduleType = 'cron';
|
|
794
|
+
scheduleValue = values.cron;
|
|
728
795
|
} else {
|
|
729
796
|
console.error(red('Schedule required: --every, --at, or --cron'));
|
|
730
797
|
process.exit(1);
|
|
731
798
|
}
|
|
732
799
|
|
|
800
|
+
// Validate schedule and compute next run
|
|
801
|
+
let nextRunAt;
|
|
802
|
+
try {
|
|
803
|
+
nextRunAt = computeNextRun({ type: scheduleType, value: scheduleValue });
|
|
804
|
+
if (!nextRunAt) {
|
|
805
|
+
console.error(red('Schedule has no future run time'));
|
|
806
|
+
process.exit(1);
|
|
807
|
+
}
|
|
808
|
+
} catch (error) {
|
|
809
|
+
console.error(red(`Invalid schedule: ${error.message}`));
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
|
|
733
813
|
// Determine action
|
|
734
|
-
let
|
|
814
|
+
let actionType, actionTitle, actionContent, gitRemote, todoId;
|
|
735
815
|
if (values['create-todo']) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
type: 'health-check',
|
|
744
|
-
projectPath: values['health-check'],
|
|
745
|
-
scope: values.scope || 'general',
|
|
746
|
-
};
|
|
816
|
+
actionType = 'create-todo';
|
|
817
|
+
actionTitle = values['create-todo'];
|
|
818
|
+
actionContent = values.content || null;
|
|
819
|
+
gitRemote = values['git-remote'] || null;
|
|
820
|
+
} else if (values['queue-todo']) {
|
|
821
|
+
actionType = 'queue-todo';
|
|
822
|
+
todoId = values['queue-todo'];
|
|
747
823
|
} else {
|
|
748
|
-
console.error(red('Action required: --create-todo
|
|
824
|
+
console.error(red('Action required: --create-todo <title> or --queue-todo <todoId>'));
|
|
749
825
|
process.exit(1);
|
|
750
826
|
}
|
|
751
827
|
|
|
752
828
|
try {
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
829
|
+
const response = await api.apiRequest('manage-schedules', {
|
|
830
|
+
method: 'POST',
|
|
831
|
+
body: JSON.stringify({
|
|
832
|
+
name: values.name,
|
|
833
|
+
scheduleType,
|
|
834
|
+
scheduleValue,
|
|
835
|
+
actionType,
|
|
836
|
+
actionTitle,
|
|
837
|
+
actionContent,
|
|
838
|
+
gitRemote,
|
|
839
|
+
todoId,
|
|
840
|
+
nextRunAt,
|
|
841
|
+
}),
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
if (!response.ok) {
|
|
845
|
+
const data = await response.json();
|
|
846
|
+
console.error(red(`Failed to create schedule: ${data.error || response.status}`));
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const data = await response.json();
|
|
851
|
+
const s = data.schedule;
|
|
852
|
+
console.log(green(`Created schedule: ${s.name} (ID: ${s.id.slice(0, 8)})`));
|
|
853
|
+
console.log(dim(`Next run: ${s.next_run_at}`));
|
|
756
854
|
} catch (error) {
|
|
757
|
-
console.error(red(`Failed to create
|
|
855
|
+
console.error(red(`Failed to create schedule: ${error.message}`));
|
|
758
856
|
process.exit(1);
|
|
759
857
|
}
|
|
760
858
|
return;
|
|
761
859
|
}
|
|
762
860
|
|
|
763
861
|
if (subCommand === 'list') {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
862
|
+
try {
|
|
863
|
+
const response = await api.apiRequest('manage-schedules', { method: 'GET' });
|
|
864
|
+
if (!response.ok) {
|
|
865
|
+
console.error(red(`Failed to list schedules: HTTP ${response.status}`));
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const data = await response.json();
|
|
870
|
+
const schedules = data.schedules || [];
|
|
871
|
+
|
|
872
|
+
if (schedules.length === 0) {
|
|
873
|
+
console.log('No schedules configured.');
|
|
874
|
+
console.log(dim('Add one with: push-todo schedule add --name "..." --every "4h" --create-todo "..."'));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
console.log(bold('Schedules:'));
|
|
879
|
+
for (const s of schedules) {
|
|
880
|
+
const status = s.enabled ? green('ON') : dim('OFF');
|
|
881
|
+
const schedStr = s.schedule_type === 'every' ? `every ${s.schedule_value}` :
|
|
882
|
+
s.schedule_type === 'at' ? `at ${s.schedule_value}` :
|
|
883
|
+
`cron: ${s.schedule_value}`;
|
|
884
|
+
const actionStr = s.action_type === 'create-todo'
|
|
885
|
+
? `create-todo: ${s.action_title || ''}`
|
|
886
|
+
: `queue-todo: ${s.todo_id || '?'}`;
|
|
887
|
+
console.log(` ${status} ${s.name} [${schedStr}] → ${actionStr}`);
|
|
888
|
+
console.log(dim(` ID: ${s.id.slice(0, 8)} | Next: ${s.next_run_at || 'N/A'} | Last: ${s.last_run_at || 'never'}`));
|
|
889
|
+
}
|
|
890
|
+
} catch (error) {
|
|
891
|
+
console.error(red(`Failed to list schedules: ${error.message}`));
|
|
892
|
+
process.exit(1);
|
|
778
893
|
}
|
|
779
894
|
return;
|
|
780
895
|
}
|
|
781
896
|
|
|
782
897
|
if (subCommand === 'remove') {
|
|
783
|
-
const
|
|
784
|
-
if (!
|
|
785
|
-
console.error(red('Usage: push-todo
|
|
898
|
+
const scheduleId = positionals[2];
|
|
899
|
+
if (!scheduleId) {
|
|
900
|
+
console.error(red('Usage: push-todo schedule remove <id>'));
|
|
786
901
|
process.exit(1);
|
|
787
902
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
const response = await api.apiRequest(`manage-schedules?id=${scheduleId}`, {
|
|
906
|
+
method: 'DELETE',
|
|
907
|
+
});
|
|
908
|
+
if (response.ok) {
|
|
909
|
+
console.log(green(`Removed schedule ${scheduleId}`));
|
|
910
|
+
} else {
|
|
911
|
+
const data = await response.json();
|
|
912
|
+
console.error(red(`Failed to remove schedule: ${data.error || response.status}`));
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
} catch (error) {
|
|
916
|
+
console.error(red(`Failed to remove schedule: ${error.message}`));
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (subCommand === 'enable' || subCommand === 'disable') {
|
|
923
|
+
const scheduleId = positionals[2];
|
|
924
|
+
if (!scheduleId) {
|
|
925
|
+
console.error(red(`Usage: push-todo schedule ${subCommand} <id>`));
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const enabled = subCommand === 'enable';
|
|
930
|
+
try {
|
|
931
|
+
const response = await api.apiRequest('manage-schedules', {
|
|
932
|
+
method: 'PATCH',
|
|
933
|
+
body: JSON.stringify({ id: scheduleId, enabled }),
|
|
934
|
+
});
|
|
935
|
+
if (response.ok) {
|
|
936
|
+
console.log(green(`Schedule ${scheduleId} ${enabled ? 'enabled' : 'disabled'}`));
|
|
937
|
+
} else {
|
|
938
|
+
const data = await response.json();
|
|
939
|
+
console.error(red(`Failed to ${subCommand} schedule: ${data.error || response.status}`));
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.error(red(`Failed to ${subCommand} schedule: ${error.message}`));
|
|
792
944
|
process.exit(1);
|
|
793
945
|
}
|
|
794
946
|
return;
|
|
795
947
|
}
|
|
796
948
|
|
|
797
|
-
// Default: show help for
|
|
798
|
-
console.log(`${bold('
|
|
799
|
-
push-todo
|
|
800
|
-
push-todo
|
|
801
|
-
push-todo
|
|
949
|
+
// Default: show help for schedule
|
|
950
|
+
console.log(`${bold('Schedule Commands:')}
|
|
951
|
+
push-todo schedule add Create a new remote schedule
|
|
952
|
+
push-todo schedule list List all schedules
|
|
953
|
+
push-todo schedule remove Remove a schedule by ID
|
|
954
|
+
push-todo schedule enable Enable a schedule
|
|
955
|
+
push-todo schedule disable Disable a schedule
|
|
956
|
+
|
|
957
|
+
${bold('Examples:')}
|
|
958
|
+
push-todo schedule add --name "Daily standup" --every 24h --create-todo "Write standup update"
|
|
959
|
+
push-todo schedule add --name "Weekly review" --cron "0 9 * * 1" --create-todo "Weekly code review" --git-remote github.com/user/repo
|
|
960
|
+
push-todo schedule add --name "Re-run task" --every 4h --queue-todo <todoId>
|
|
802
961
|
`);
|
|
803
962
|
return;
|
|
804
963
|
}
|
package/lib/context-engine.js
CHANGED
|
@@ -110,10 +110,16 @@ export function scanProjectSkills(projectPath) {
|
|
|
110
110
|
const content = readFileSync(skillFile, 'utf8');
|
|
111
111
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
112
112
|
|
|
113
|
+
// Parse tools field: comma-separated list of tool names/patterns
|
|
114
|
+
const tools = frontmatter.tools
|
|
115
|
+
? frontmatter.tools.split(',').map(t => t.trim()).filter(Boolean)
|
|
116
|
+
: [];
|
|
117
|
+
|
|
113
118
|
skills.push({
|
|
114
119
|
name: frontmatter.name || entry,
|
|
115
120
|
description: frontmatter.description || '',
|
|
116
121
|
requiresConfirmation: body.includes('push-todo confirm'),
|
|
122
|
+
tools,
|
|
117
123
|
});
|
|
118
124
|
} catch {
|
|
119
125
|
// Skip unreadable skill files
|