@masslessai/push-todo 3.0.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/.claude-plugin/plugin.json +5 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/SKILL.md +180 -0
- package/bin/push-todo.js +23 -0
- package/commands/push-todo.md +78 -0
- package/hooks/hooks.json +26 -0
- package/hooks/session-end.js +99 -0
- package/hooks/session-start.js +134 -0
- package/lib/api.js +325 -0
- package/lib/cli.js +191 -0
- package/lib/config.js +279 -0
- package/lib/connect.js +380 -0
- package/lib/encryption.js +201 -0
- package/lib/fetch.js +371 -0
- package/lib/index.js +114 -0
- package/lib/machine-id.js +101 -0
- package/lib/project-registry.js +279 -0
- package/lib/utils/colors.js +126 -0
- package/lib/utils/format.js +253 -0
- package/lib/utils/git.js +149 -0
- package/lib/watch.js +343 -0
- package/natives/KeychainHelper.swift +134 -0
- package/package.json +54 -0
- package/scripts/postinstall.js +136 -0
package/lib/api.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase API client for Push CLI.
|
|
3
|
+
*
|
|
4
|
+
* Handles all HTTP requests to the Push backend.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getApiKey } from './config.js';
|
|
8
|
+
|
|
9
|
+
const API_BASE = 'https://jxuzqcbqhiaxmfitzxlo.supabase.co/functions/v1';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Make an authenticated API request.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} endpoint - API endpoint (without base URL)
|
|
15
|
+
* @param {Object} options - Fetch options
|
|
16
|
+
* @returns {Promise<Response>}
|
|
17
|
+
*/
|
|
18
|
+
async function apiRequest(endpoint, options = {}) {
|
|
19
|
+
const apiKey = getApiKey();
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
throw new Error('No API key configured. Run "push-todo connect" first.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const url = endpoint.startsWith('http') ? endpoint : `${API_BASE}/${endpoint}`;
|
|
25
|
+
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
...options,
|
|
28
|
+
headers: {
|
|
29
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
...options.headers
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetch tasks from the API.
|
|
40
|
+
*
|
|
41
|
+
* @param {string|null} gitRemote - Git remote to filter by (null for all projects)
|
|
42
|
+
* @param {Object} options - Query options
|
|
43
|
+
* @param {boolean} options.backlogOnly - Only return backlog items
|
|
44
|
+
* @param {boolean} options.includeBacklog - Include backlog items
|
|
45
|
+
* @param {boolean} options.completedOnly - Only return completed items
|
|
46
|
+
* @param {boolean} options.includeCompleted - Include completed items
|
|
47
|
+
* @returns {Promise<Object[]>} Array of todo objects
|
|
48
|
+
*/
|
|
49
|
+
export async function fetchTasks(gitRemote, options = {}) {
|
|
50
|
+
const params = new URLSearchParams();
|
|
51
|
+
|
|
52
|
+
if (gitRemote) {
|
|
53
|
+
params.set('git_remote', gitRemote);
|
|
54
|
+
}
|
|
55
|
+
if (options.backlogOnly) {
|
|
56
|
+
params.set('later_only', 'true');
|
|
57
|
+
}
|
|
58
|
+
if (options.includeBacklog) {
|
|
59
|
+
params.set('include_later', 'true');
|
|
60
|
+
}
|
|
61
|
+
if (options.completedOnly) {
|
|
62
|
+
params.set('completed_only', 'true');
|
|
63
|
+
}
|
|
64
|
+
if (options.includeCompleted) {
|
|
65
|
+
params.set('include_completed', 'true');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const queryString = params.toString();
|
|
69
|
+
const endpoint = queryString ? `synced-todos?${queryString}` : 'synced-todos';
|
|
70
|
+
|
|
71
|
+
const response = await apiRequest(endpoint);
|
|
72
|
+
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
if (response.status === 401) {
|
|
75
|
+
throw new Error('Invalid API key. Run "push-todo connect" to re-authenticate.');
|
|
76
|
+
}
|
|
77
|
+
if (response.status === 404) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
throw new Error(`API error (${response.status}): ${text}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
return data.todos || [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Fetch a specific task by display number.
|
|
90
|
+
*
|
|
91
|
+
* @param {number} displayNumber - The task's display number
|
|
92
|
+
* @returns {Promise<Object|null>} Task object or null if not found
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchTaskByNumber(displayNumber) {
|
|
95
|
+
const response = await apiRequest(`synced-todos?display_number=${displayNumber}`);
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
if (response.status === 401) {
|
|
99
|
+
throw new Error('Invalid API key. Run "push-todo connect" to re-authenticate.');
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 404) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const text = await response.text();
|
|
105
|
+
throw new Error(`API error (${response.status}): ${text}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
const todos = data.todos || [];
|
|
110
|
+
return todos.length > 0 ? todos[0] : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Mark a task as completed.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} taskId - UUID of the task
|
|
117
|
+
* @param {string} comment - Completion comment
|
|
118
|
+
* @returns {Promise<boolean>} True if successful
|
|
119
|
+
*/
|
|
120
|
+
export async function markTaskCompleted(taskId, comment = '') {
|
|
121
|
+
const response = await apiRequest('mark-todo-completed', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
todo_id: taskId,
|
|
125
|
+
completion_comment: comment
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const text = await response.text();
|
|
131
|
+
throw new Error(`Failed to mark task completed: ${text}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Queue a task for daemon execution.
|
|
139
|
+
*
|
|
140
|
+
* @param {number} displayNumber - The task's display number
|
|
141
|
+
* @returns {Promise<boolean>} True if successful
|
|
142
|
+
*/
|
|
143
|
+
export async function queueTask(displayNumber) {
|
|
144
|
+
const response = await apiRequest('queue-task', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
display_number: displayNumber
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
const text = await response.text();
|
|
153
|
+
throw new Error(`Failed to queue task: ${text}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Queue multiple tasks for daemon execution.
|
|
161
|
+
*
|
|
162
|
+
* @param {number[]} displayNumbers - Array of display numbers
|
|
163
|
+
* @returns {Promise<Object>} Result with success/failure counts
|
|
164
|
+
*/
|
|
165
|
+
export async function queueTasks(displayNumbers) {
|
|
166
|
+
const results = {
|
|
167
|
+
success: [],
|
|
168
|
+
failed: []
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (const num of displayNumbers) {
|
|
172
|
+
try {
|
|
173
|
+
await queueTask(num);
|
|
174
|
+
results.success.push(num);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
results.failed.push({ num, error: error.message });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Search tasks by query.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} query - Search query
|
|
187
|
+
* @param {string|null} gitRemote - Git remote to filter by
|
|
188
|
+
* @returns {Promise<Object[]>} Array of matching tasks
|
|
189
|
+
*/
|
|
190
|
+
export async function searchTasks(query, gitRemote = null) {
|
|
191
|
+
const params = new URLSearchParams();
|
|
192
|
+
params.set('query', query);
|
|
193
|
+
if (gitRemote) {
|
|
194
|
+
params.set('git_remote', gitRemote);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const response = await apiRequest(`search-todos?${params}`);
|
|
198
|
+
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
if (response.status === 401) {
|
|
201
|
+
throw new Error('Invalid API key. Run "push-todo connect" to re-authenticate.');
|
|
202
|
+
}
|
|
203
|
+
const text = await response.text();
|
|
204
|
+
throw new Error(`Search failed: ${text}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const data = await response.json();
|
|
208
|
+
return data.results || [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Update task execution status.
|
|
213
|
+
*
|
|
214
|
+
* @param {Object} payload - Execution update payload
|
|
215
|
+
* @returns {Promise<boolean>} True if successful
|
|
216
|
+
*/
|
|
217
|
+
export async function updateTaskExecution(payload) {
|
|
218
|
+
const response = await apiRequest('update-task-execution', {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
body: JSON.stringify(payload)
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
const text = await response.text();
|
|
225
|
+
throw new Error(`Failed to update execution: ${text}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Validate API key.
|
|
233
|
+
*
|
|
234
|
+
* @returns {Promise<Object>} Validation result with user info
|
|
235
|
+
*/
|
|
236
|
+
export async function validateApiKey() {
|
|
237
|
+
try {
|
|
238
|
+
const response = await apiRequest('validate-api-key');
|
|
239
|
+
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
if (response.status === 401) {
|
|
242
|
+
return { valid: false, reason: 'invalid_key' };
|
|
243
|
+
}
|
|
244
|
+
return { valid: false, reason: 'api_error' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const data = await response.json();
|
|
248
|
+
return {
|
|
249
|
+
valid: true,
|
|
250
|
+
userId: data.user_id,
|
|
251
|
+
email: data.email
|
|
252
|
+
};
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return { valid: false, reason: 'network_error', error: error.message };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Register a project with the backend.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} gitRemote - Normalized git remote
|
|
262
|
+
* @param {string[]} keywords - Project keywords
|
|
263
|
+
* @param {string} description - Project description
|
|
264
|
+
* @returns {Promise<boolean>} True if successful
|
|
265
|
+
*/
|
|
266
|
+
export async function registerProject(gitRemote, keywords = [], description = '') {
|
|
267
|
+
const response = await apiRequest('register-project', {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
git_remote: gitRemote,
|
|
271
|
+
keywords,
|
|
272
|
+
description
|
|
273
|
+
})
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
const text = await response.text();
|
|
278
|
+
throw new Error(`Failed to register project: ${text}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Validate machine registration.
|
|
286
|
+
*
|
|
287
|
+
* @param {string} machineId - Machine identifier
|
|
288
|
+
* @returns {Promise<Object>} Validation result
|
|
289
|
+
*/
|
|
290
|
+
export async function validateMachine(machineId) {
|
|
291
|
+
const response = await apiRequest('validate-machine', {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
machine_id: machineId
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
const text = await response.text();
|
|
300
|
+
throw new Error(`Machine validation failed: ${text}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const data = await response.json();
|
|
304
|
+
return data;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get the current CLI version from the server.
|
|
309
|
+
*
|
|
310
|
+
* @returns {Promise<string>} Latest version string
|
|
311
|
+
*/
|
|
312
|
+
export async function getLatestVersion() {
|
|
313
|
+
try {
|
|
314
|
+
const response = await fetch('https://raw.githubusercontent.com/MasslessAI/push-todo-cli/main/npm/push-todo/package.json');
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const data = await response.json();
|
|
319
|
+
return data.version;
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export { API_BASE };
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing and command routing for Push CLI.
|
|
3
|
+
*
|
|
4
|
+
* Handles all command-line options and dispatches to appropriate handlers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { parseArgs } from 'util';
|
|
8
|
+
import * as fetch from './fetch.js';
|
|
9
|
+
import { runConnect } from './connect.js';
|
|
10
|
+
import { startWatch } from './watch.js';
|
|
11
|
+
import { showSettings, toggleSetting } from './config.js';
|
|
12
|
+
import { bold, red, cyan, dim } from './utils/colors.js';
|
|
13
|
+
|
|
14
|
+
const VERSION = '3.0.0';
|
|
15
|
+
|
|
16
|
+
const HELP_TEXT = `
|
|
17
|
+
${bold('push-todo')} - Voice tasks from Push iOS app for Claude Code
|
|
18
|
+
|
|
19
|
+
${bold('USAGE:')}
|
|
20
|
+
push-todo [options] List active tasks
|
|
21
|
+
push-todo <number> Show specific task
|
|
22
|
+
push-todo connect Run connection doctor
|
|
23
|
+
push-todo search <query> Search tasks
|
|
24
|
+
push-todo review Review completed tasks
|
|
25
|
+
|
|
26
|
+
${bold('OPTIONS:')}
|
|
27
|
+
--all-projects, -a List tasks from all projects
|
|
28
|
+
--backlog, -b Only show backlog items
|
|
29
|
+
--include-backlog Include backlog items in listing
|
|
30
|
+
--completed, -c Only show completed items
|
|
31
|
+
--include-completed Include completed items in listing
|
|
32
|
+
--queue <numbers> Queue tasks for daemon (comma-separated)
|
|
33
|
+
--queue-batch Auto-queue a batch of tasks
|
|
34
|
+
--mark-completed <uuid> Mark a task as completed
|
|
35
|
+
--completion-comment <text> Comment for completion
|
|
36
|
+
--search <query> Search tasks
|
|
37
|
+
--status Show connection and daemon status
|
|
38
|
+
--watch, -w Live terminal UI
|
|
39
|
+
--setting [name] Show or toggle settings
|
|
40
|
+
--json Output as JSON
|
|
41
|
+
--version, -v Show version
|
|
42
|
+
--help, -h Show this help
|
|
43
|
+
|
|
44
|
+
${bold('EXAMPLES:')}
|
|
45
|
+
push-todo List active tasks for current project
|
|
46
|
+
push-todo 427 Show task #427
|
|
47
|
+
push-todo -a List all tasks across projects
|
|
48
|
+
push-todo --queue 1,2,3 Queue tasks 1, 2, 3 for daemon
|
|
49
|
+
push-todo search "auth bug" Search for tasks matching "auth bug"
|
|
50
|
+
push-todo connect Run connection diagnostics
|
|
51
|
+
|
|
52
|
+
${bold('SETTINGS:')}
|
|
53
|
+
push-todo setting Show all settings
|
|
54
|
+
push-todo setting auto-commit Toggle auto-commit
|
|
55
|
+
|
|
56
|
+
${dim('Documentation:')} https://pushto.do/docs/cli
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const options = {
|
|
60
|
+
'all-projects': { type: 'boolean', short: 'a' },
|
|
61
|
+
'backlog': { type: 'boolean', short: 'b' },
|
|
62
|
+
'include-backlog': { type: 'boolean' },
|
|
63
|
+
'completed': { type: 'boolean', short: 'c' },
|
|
64
|
+
'include-completed': { type: 'boolean' },
|
|
65
|
+
'queue': { type: 'string' },
|
|
66
|
+
'queue-batch': { type: 'boolean' },
|
|
67
|
+
'mark-completed': { type: 'string' },
|
|
68
|
+
'completion-comment': { type: 'string' },
|
|
69
|
+
'search': { type: 'string' },
|
|
70
|
+
'status': { type: 'boolean' },
|
|
71
|
+
'watch': { type: 'boolean', short: 'w' },
|
|
72
|
+
'setting': { type: 'string' },
|
|
73
|
+
'json': { type: 'boolean' },
|
|
74
|
+
'version': { type: 'boolean', short: 'v' },
|
|
75
|
+
'help': { type: 'boolean', short: 'h' }
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse command line arguments.
|
|
80
|
+
*
|
|
81
|
+
* @param {string[]} argv - Command line arguments
|
|
82
|
+
* @returns {Object} Parsed arguments with values and positionals
|
|
83
|
+
*/
|
|
84
|
+
function parseArguments(argv) {
|
|
85
|
+
try {
|
|
86
|
+
return parseArgs({
|
|
87
|
+
args: argv,
|
|
88
|
+
options,
|
|
89
|
+
allowPositionals: true
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(red(`Error: ${error.message}`));
|
|
93
|
+
console.log(`Run ${cyan('push-todo --help')} for usage.`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Main CLI entry point.
|
|
100
|
+
*
|
|
101
|
+
* @param {string[]} argv - Command line arguments (without node and script path)
|
|
102
|
+
*/
|
|
103
|
+
export async function run(argv) {
|
|
104
|
+
const { values, positionals } = parseArguments(argv);
|
|
105
|
+
|
|
106
|
+
// Help
|
|
107
|
+
if (values.help) {
|
|
108
|
+
console.log(HELP_TEXT);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Version
|
|
113
|
+
if (values.version) {
|
|
114
|
+
console.log(`push-todo ${VERSION}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Get the command (first positional)
|
|
119
|
+
const command = positionals[0];
|
|
120
|
+
|
|
121
|
+
// Connect command
|
|
122
|
+
if (command === 'connect') {
|
|
123
|
+
return runConnect(values);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Review command
|
|
127
|
+
if (command === 'review') {
|
|
128
|
+
return fetch.runReview(values);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Search command (positional form)
|
|
132
|
+
if (command === 'search' && positionals[1]) {
|
|
133
|
+
return fetch.searchTasks(positionals.slice(1).join(' '), values);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Search option
|
|
137
|
+
if (values.search) {
|
|
138
|
+
return fetch.searchTasks(values.search, values);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Watch mode
|
|
142
|
+
if (values.watch) {
|
|
143
|
+
return startWatch(values);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Status
|
|
147
|
+
if (values.status) {
|
|
148
|
+
return fetch.showStatus(values);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Settings
|
|
152
|
+
if ('setting' in values) {
|
|
153
|
+
const settingName = values.setting;
|
|
154
|
+
if (settingName && settingName !== 'true') {
|
|
155
|
+
return toggleSetting(settingName);
|
|
156
|
+
}
|
|
157
|
+
return showSettings();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Queue tasks
|
|
161
|
+
if (values.queue) {
|
|
162
|
+
return fetch.queueForExecution(values.queue);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Queue batch
|
|
166
|
+
if (values['queue-batch']) {
|
|
167
|
+
return fetch.offerBatch(values);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Mark completed
|
|
171
|
+
if (values['mark-completed']) {
|
|
172
|
+
const comment = values['completion-comment'] || '';
|
|
173
|
+
return fetch.markComplete(values['mark-completed'], comment);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Specific task by number
|
|
177
|
+
if (command && /^\d+$/.test(command)) {
|
|
178
|
+
const displayNumber = parseInt(command, 10);
|
|
179
|
+
return fetch.showTask(displayNumber, values);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Unknown command
|
|
183
|
+
if (command && !/^\d+$/.test(command)) {
|
|
184
|
+
console.error(red(`Unknown command: ${command}`));
|
|
185
|
+
console.log(`Run ${cyan('push-todo --help')} for usage.`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Default: list tasks
|
|
190
|
+
return fetch.listTasks(values);
|
|
191
|
+
}
|