floq 1.7.0 → 1.9.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.
@@ -1,11 +1,38 @@
1
- import { getCalendarConfig, setCalendarConfig, setCalendarEnabled, getGoogleOAuthClient, setGoogleOAuthClient, setCalendarOAuthConfig, getCalendarOAuthConfig, getCalendarType, } from '../config.js';
1
+ import { getCalendarConfig, setCalendarConfig, getCalendarSources, addCalendarSource, removeCalendarSource, updateCalendarSource, setCalendarEnabled, getGoogleOAuthClient, setGoogleOAuthClient, getCalendarOAuthTokens, setCalendarOAuthTokens, } from '../config.js';
2
2
  import { fetchCalendarEvents, getTodayEvents, clearCalendarCache, formatEventTime } from '../calendar/index.js';
3
3
  import { getLocale } from '../config.js';
4
- import { startOAuthFlow, pollForTokens, clearOAuthTokens } from '../calendar/oauth.js';
4
+ import { startOAuthFlow, pollForTokens, clearOAuthTokens, getValidAccessToken } from '../calendar/oauth.js';
5
5
  import { listCalendars } from '../calendar/google-api.js';
6
6
  import * as readline from 'readline';
7
7
  /**
8
- * Add or update calendar URL (iCal mode)
8
+ * Resolve a calendar source by id, 1-based index, or name
9
+ */
10
+ function resolveSource(idOrIndex) {
11
+ const sources = getCalendarSources();
12
+ // Exact id match
13
+ const byId = sources.find(s => s.id === idOrIndex);
14
+ if (byId)
15
+ return byId;
16
+ // 1-based index
17
+ const index = parseInt(idOrIndex, 10);
18
+ if (!isNaN(index) && index >= 1 && index <= sources.length) {
19
+ return sources[index - 1];
20
+ }
21
+ // Name match
22
+ return sources.find(s => s.name === idOrIndex);
23
+ }
24
+ function printSourceList(sources) {
25
+ sources.forEach((source, index) => {
26
+ const status = source.enabled !== false ? 'enabled' : 'disabled';
27
+ const detail = source.type === 'ical'
28
+ ? (source.url && source.url.length > 50 ? source.url.substring(0, 50) + '...' : source.url)
29
+ : source.calendarId;
30
+ console.log(` ${index + 1}. [${source.id}] ${source.name} (${source.type}, ${status})`);
31
+ console.log(` ${detail}`);
32
+ });
33
+ }
34
+ /**
35
+ * Add a new iCal calendar (multiple calendars can be registered)
9
36
  */
10
37
  export async function addCalendar(url, options) {
11
38
  // Validate URL
@@ -13,50 +40,112 @@ export async function addCalendar(url, options) {
13
40
  console.error('Error: Invalid URL. Must start with https://, webcal://, or http://');
14
41
  process.exit(1);
15
42
  }
16
- setCalendarConfig({
17
- url,
18
- name: options.name,
43
+ const existing = getCalendarSources().find(s => s.url === url);
44
+ if (existing) {
45
+ console.error(`Error: This URL is already registered as "${existing.name}" [${existing.id}].`);
46
+ process.exit(1);
47
+ }
48
+ // Default name from URL hostname when not provided
49
+ let name = options.name;
50
+ if (!name) {
51
+ try {
52
+ name = new URL(url.replace('webcal://', 'https://')).hostname;
53
+ }
54
+ catch {
55
+ name = `Calendar ${getCalendarSources().length + 1}`;
56
+ }
57
+ }
58
+ const source = addCalendarSource({
59
+ name,
19
60
  type: 'ical',
61
+ url,
20
62
  enabled: true,
21
63
  });
22
64
  console.log('Calendar added successfully!');
65
+ console.log(` ID: ${source.id}`);
66
+ console.log(` Name: ${source.name}`);
23
67
  console.log(` URL: ${url}`);
24
- if (options.name) {
25
- console.log(` Name: ${options.name}`);
26
- }
27
68
  // Try to fetch events to validate the URL
28
69
  console.log('\nFetching events...');
29
70
  try {
30
71
  const events = await fetchCalendarEvents(url);
31
72
  console.log(`Found ${events.length} events.`);
32
73
  }
33
- catch (error) {
74
+ catch {
34
75
  console.log('Warning: Could not fetch events. Please check the URL.');
35
76
  }
77
+ if (getCalendarConfig()?.enabled === false) {
78
+ console.log('Note: calendar display is currently disabled. Run "floq calendar enable" to show events.');
79
+ }
36
80
  }
37
81
  /**
38
- * Remove calendar configuration
82
+ * List registered calendars
39
83
  */
40
- export async function removeCalendar() {
41
- const config = getCalendarConfig();
42
- if (!config) {
43
- console.log('No calendar configured.');
84
+ export async function listCalendarSourcesCommand() {
85
+ const sources = getCalendarSources();
86
+ if (sources.length === 0) {
87
+ console.log('No calendars registered.');
88
+ console.log('Use "floq calendar add <url>" or "floq calendar select" to add one.');
89
+ return;
90
+ }
91
+ console.log(`Registered calendars (${sources.length})`);
92
+ console.log('-'.repeat(40));
93
+ printSourceList(sources);
94
+ }
95
+ /**
96
+ * Remove a calendar (by id/index/name), or all calendar configuration with --all
97
+ */
98
+ export async function removeCalendar(idOrIndex, options = {}) {
99
+ const sources = getCalendarSources();
100
+ if (options.all) {
101
+ if (sources.length === 0 && !getCalendarConfig()) {
102
+ console.log('No calendar configured.');
103
+ return;
104
+ }
105
+ setCalendarConfig(undefined);
106
+ clearCalendarCache();
107
+ console.log('All calendar configuration removed.');
44
108
  return;
45
109
  }
46
- setCalendarConfig(undefined);
110
+ if (sources.length === 0) {
111
+ console.log('No calendars registered.');
112
+ return;
113
+ }
114
+ let target;
115
+ if (idOrIndex) {
116
+ target = resolveSource(idOrIndex);
117
+ if (!target) {
118
+ console.error(`Error: Calendar "${idOrIndex}" not found.`);
119
+ console.log('');
120
+ printSourceList(sources);
121
+ process.exit(1);
122
+ }
123
+ }
124
+ else if (sources.length === 1) {
125
+ target = sources[0];
126
+ }
127
+ else {
128
+ console.log('Multiple calendars are registered. Specify which one to remove:');
129
+ console.log('');
130
+ printSourceList(sources);
131
+ console.log('');
132
+ console.log('Usage: floq calendar remove <id|number|name>');
133
+ console.log(' floq calendar remove --all (remove everything)');
134
+ return;
135
+ }
136
+ removeCalendarSource(target.id);
47
137
  clearCalendarCache();
48
- console.log('Calendar removed.');
138
+ console.log(`Calendar "${target.name}" [${target.id}] removed.`);
49
139
  }
50
140
  /**
51
141
  * Show calendar configuration and today's events
52
142
  */
53
143
  export async function showCalendar() {
54
- const config = getCalendarConfig();
144
+ const sources = getCalendarSources();
55
145
  const locale = getLocale();
56
- const calendarType = getCalendarType();
57
146
  console.log('Calendar Configuration');
58
147
  console.log('-'.repeat(40));
59
- if (!config || (!config.url && !config.oauth)) {
148
+ if (sources.length === 0) {
60
149
  console.log('No calendar configured.');
61
150
  console.log('');
62
151
  console.log('Option 1: iCal URL (no authentication required)');
@@ -68,18 +157,9 @@ export async function showCalendar() {
68
157
  console.log(' floq calendar select');
69
158
  return;
70
159
  }
71
- console.log(`Type: ${calendarType || 'unknown'}`);
72
- if (calendarType === 'oauth' && config.oauth) {
73
- console.log(`Calendar: ${config.oauth.calendarName}`);
74
- console.log(`Calendar ID: ${config.oauth.calendarId}`);
75
- }
76
- else if (config.url) {
77
- console.log(`URL: ${config.url}`);
78
- if (config.name) {
79
- console.log(`Name: ${config.name}`);
80
- }
81
- }
82
- console.log(`Status: ${config.enabled !== false ? 'enabled' : 'disabled'}`);
160
+ printSourceList(sources);
161
+ console.log('');
162
+ console.log(`Display: ${getCalendarConfig()?.enabled !== false ? 'enabled' : 'disabled'}`);
83
163
  console.log('');
84
164
  console.log("Today's Events");
85
165
  console.log('-'.repeat(40));
@@ -90,11 +170,13 @@ export async function showCalendar() {
90
170
  console.log(locale === 'ja' ? '今日の予定はありません' : 'No events today');
91
171
  return;
92
172
  }
173
+ const showCalendarName = sources.length > 1;
93
174
  for (const event of todayEvents) {
94
175
  const timeStr = event.allDay
95
176
  ? (locale === 'ja' ? '終日' : 'All day')
96
177
  : `${formatEventTime(event.start)} - ${formatEventTime(event.end)}`;
97
- console.log(` ${timeStr} ${event.title}`);
178
+ const calLabel = showCalendarName && event.calendarName ? ` [${event.calendarName}]` : '';
179
+ console.log(` ${timeStr} ${event.title}${calLabel}`);
98
180
  if (event.location) {
99
181
  console.log(` 📍 ${event.location}`);
100
182
  }
@@ -104,16 +186,16 @@ export async function showCalendar() {
104
186
  * Sync/refresh calendar cache
105
187
  */
106
188
  export async function syncCalendar() {
107
- const config = getCalendarConfig();
108
- if (!config) {
189
+ const sources = getCalendarSources();
190
+ if (sources.length === 0) {
109
191
  console.log('No calendar configured.');
110
192
  return;
111
193
  }
112
- console.log('Syncing calendar...');
194
+ console.log('Syncing calendars...');
113
195
  clearCalendarCache();
114
196
  try {
115
197
  const events = await fetchCalendarEvents();
116
- console.log(`Synced ${events.length} events.`);
198
+ console.log(`Synced ${events.length} events from ${sources.filter(s => s.enabled !== false).length} calendar(s).`);
117
199
  const todayEvents = getTodayEvents();
118
200
  console.log(`${todayEvents.length} events today.`);
119
201
  }
@@ -123,29 +205,43 @@ export async function syncCalendar() {
123
205
  }
124
206
  }
125
207
  /**
126
- * Enable calendar display
208
+ * Toggle calendar display (overall, or a specific calendar by id)
127
209
  */
128
- export async function enableCalendar() {
129
- const config = getCalendarConfig();
130
- if (!config) {
210
+ async function setCalendarDisplay(enabled, idOrIndex) {
211
+ const sources = getCalendarSources();
212
+ const label = enabled ? 'enabled' : 'disabled';
213
+ if (sources.length === 0) {
131
214
  console.log('No calendar configured.');
132
- console.log('Use "floq calendar add <url>" to add a calendar first.');
215
+ if (enabled) {
216
+ console.log('Use "floq calendar add <url>" to add a calendar first.');
217
+ }
133
218
  return;
134
219
  }
135
- setCalendarEnabled(true);
136
- console.log('Calendar display enabled.');
137
- }
138
- /**
139
- * Disable calendar display
140
- */
141
- export async function disableCalendar() {
142
- const config = getCalendarConfig();
143
- if (!config) {
144
- console.log('No calendar configured.');
220
+ if (idOrIndex) {
221
+ const target = resolveSource(idOrIndex);
222
+ if (!target) {
223
+ console.error(`Error: Calendar "${idOrIndex}" not found.`);
224
+ process.exit(1);
225
+ }
226
+ updateCalendarSource(target.id, { enabled });
227
+ // Enabling a specific calendar implies the user wants events shown —
228
+ // also lift a global disable so the change is actually visible
229
+ if (enabled && getCalendarConfig()?.enabled === false) {
230
+ setCalendarEnabled(true);
231
+ console.log('Calendar display was disabled — re-enabled.');
232
+ }
233
+ clearCalendarCache();
234
+ console.log(`Calendar "${target.name}" ${label}.`);
145
235
  return;
146
236
  }
147
- setCalendarEnabled(false);
148
- console.log('Calendar display disabled.');
237
+ setCalendarEnabled(enabled);
238
+ console.log(`Calendar display ${label}.`);
239
+ }
240
+ export async function enableCalendar(idOrIndex) {
241
+ await setCalendarDisplay(true, idOrIndex);
242
+ }
243
+ export async function disableCalendar(idOrIndex) {
244
+ await setCalendarDisplay(false, idOrIndex);
149
245
  }
150
246
  /**
151
247
  * Configure OAuth client credentials
@@ -156,7 +252,7 @@ export async function configOAuthClient(clientId, clientSecret) {
156
252
  console.log('');
157
253
  console.log('Next steps:');
158
254
  console.log(' 1. Run "floq calendar login" to authenticate');
159
- console.log(' 2. Run "floq calendar select" to choose a calendar');
255
+ console.log(' 2. Run "floq calendar select" to choose calendars');
160
256
  }
161
257
  /**
162
258
  * Start OAuth login flow
@@ -206,21 +302,11 @@ export async function loginCalendar() {
206
302
  console.log('');
207
303
  console.log('Waiting for authorization...');
208
304
  const tokens = await pollForTokens(deviceCode, interval);
209
- // Save tokens temporarily (calendar selection happens next)
210
- const config = getCalendarConfig() || {};
211
- setCalendarConfig({
212
- ...config,
213
- type: 'oauth',
214
- oauth: {
215
- tokens,
216
- calendarId: '',
217
- calendarName: '',
218
- },
219
- });
305
+ setCalendarOAuthTokens(tokens);
220
306
  console.log('');
221
307
  console.log('Login successful!');
222
308
  console.log('');
223
- console.log('Now run "floq calendar select" to choose a calendar.');
309
+ console.log('Now run "floq calendar select" to choose calendars.');
224
310
  }
225
311
  catch (error) {
226
312
  console.error('Login failed:', error instanceof Error ? error.message : error);
@@ -231,24 +317,27 @@ export async function loginCalendar() {
231
317
  * Logout from OAuth (clear tokens)
232
318
  */
233
319
  export async function logoutCalendar() {
234
- const oauthConfig = getCalendarOAuthConfig();
235
- if (!oauthConfig) {
320
+ const tokens = getCalendarOAuthTokens();
321
+ if (!tokens) {
236
322
  console.log('Not logged in.');
237
323
  return;
238
324
  }
239
325
  clearOAuthTokens();
240
326
  console.log('Logged out successfully.');
327
+ const oauthSources = getCalendarSources().filter(s => s.type === 'oauth');
328
+ if (oauthSources.length > 0) {
329
+ console.log(`Note: ${oauthSources.length} Google calendar(s) remain registered but need login to fetch events.`);
330
+ }
241
331
  }
242
332
  /**
243
- * Select a calendar from the user's calendars
333
+ * Select Google calendars to register (multiple selections supported)
244
334
  */
245
335
  export async function selectCalendar() {
246
- const oauthConfig = getCalendarOAuthConfig();
247
- if (!oauthConfig) {
336
+ const tokens = getCalendarOAuthTokens();
337
+ if (!tokens) {
248
338
  console.log('Not logged in. Run "floq calendar login" first.');
249
339
  return;
250
340
  }
251
- const { getValidAccessToken } = await import('../calendar/oauth.js');
252
341
  const accessToken = await getValidAccessToken();
253
342
  if (!accessToken) {
254
343
  console.log('Failed to get access token. Try logging in again.');
@@ -262,38 +351,58 @@ export async function selectCalendar() {
262
351
  console.log('No calendars found.');
263
352
  return;
264
353
  }
354
+ const registeredIds = new Set(getCalendarSources()
355
+ .filter(s => s.type === 'oauth')
356
+ .map(s => s.calendarId));
265
357
  console.log('Available calendars:');
266
358
  console.log('');
267
359
  calendars.forEach((cal, index) => {
268
360
  const primary = cal.primary ? ' (primary)' : '';
269
- console.log(` ${index + 1}. ${cal.summary}${primary}`);
361
+ const registered = registeredIds.has(cal.id) ? ' [registered]' : '';
362
+ console.log(` ${index + 1}. ${cal.summary}${primary}${registered}`);
270
363
  });
271
364
  console.log('');
272
- // Interactive selection
365
+ // Interactive selection (comma-separated numbers for multiple calendars)
273
366
  const rl = readline.createInterface({
274
367
  input: process.stdin,
275
368
  output: process.stdout,
276
369
  });
277
370
  const answer = await new Promise((resolve) => {
278
- rl.question('Select calendar (number): ', resolve);
371
+ rl.question('Select calendars (numbers, comma-separated): ', resolve);
279
372
  });
280
373
  rl.close();
281
- const selection = parseInt(answer, 10);
282
- if (isNaN(selection) || selection < 1 || selection > calendars.length) {
374
+ const selections = answer
375
+ .split(',')
376
+ .map(s => parseInt(s.trim(), 10))
377
+ .filter(n => !isNaN(n) && n >= 1 && n <= calendars.length);
378
+ if (selections.length === 0) {
283
379
  console.log('Invalid selection.');
284
380
  return;
285
381
  }
286
- const selectedCalendar = calendars[selection - 1];
287
- // Update config with selected calendar
288
- setCalendarOAuthConfig({
289
- tokens: oauthConfig.tokens,
290
- calendarId: selectedCalendar.id,
291
- calendarName: selectedCalendar.summary,
292
- });
293
382
  console.log('');
294
- console.log(`Calendar "${selectedCalendar.summary}" selected!`);
383
+ for (const selection of [...new Set(selections)]) {
384
+ const selectedCalendar = calendars[selection - 1];
385
+ if (registeredIds.has(selectedCalendar.id)) {
386
+ console.log(`Calendar "${selectedCalendar.summary}" is already registered. Skipped.`);
387
+ continue;
388
+ }
389
+ const source = addCalendarSource({
390
+ name: selectedCalendar.summary,
391
+ type: 'oauth',
392
+ calendarId: selectedCalendar.id,
393
+ enabled: true,
394
+ });
395
+ registeredIds.add(selectedCalendar.id);
396
+ console.log(`Calendar "${selectedCalendar.summary}" added! [${source.id}]`);
397
+ }
398
+ clearCalendarCache();
295
399
  console.log('');
296
- console.log('Calendar integration is now active. Run "floq calendar show" to see events.');
400
+ if (getCalendarConfig()?.enabled === false) {
401
+ console.log('Note: calendar display is currently disabled. Run "floq calendar enable" to show events.');
402
+ }
403
+ else {
404
+ console.log('Calendar integration is now active. Run "floq schedule" to see events.');
405
+ }
297
406
  }
298
407
  catch (error) {
299
408
  console.error('Failed to list calendars:', error instanceof Error ? error.message : error);
@@ -35,19 +35,11 @@ export async function showConfig() {
35
35
  }
36
36
  // Calendar configuration
37
37
  const calendarConfig = getCalendarConfig();
38
- console.log(`Calendar: ${isCalendarEnabled() ? 'enabled' : calendarConfig ? 'disabled' : 'not configured'}`);
39
- if (calendarConfig) {
40
- const calendarType = calendarConfig.type || (calendarConfig.url ? 'ical' : 'unknown');
41
- console.log(` Type: ${calendarType}`);
42
- if (calendarConfig.name) {
43
- console.log(` Name: ${calendarConfig.name}`);
44
- }
45
- if (calendarConfig.url) {
46
- console.log(` URL: ${calendarConfig.url.substring(0, 50)}...`);
47
- }
48
- if (calendarConfig.oauth) {
49
- console.log(` Calendar: ${calendarConfig.oauth.calendarName}`);
50
- }
38
+ const calendarSources = calendarConfig?.calendars || [];
39
+ console.log(`Calendar: ${isCalendarEnabled() ? 'enabled' : calendarSources.length > 0 ? 'disabled' : 'not configured'}`);
40
+ for (const source of calendarSources) {
41
+ const status = source.enabled !== false ? '' : ' (disabled)';
42
+ console.log(` - ${source.name} [${source.type}]${status}`);
51
43
  }
52
44
  }
53
45
  export async function setLanguage(locale) {
@@ -5,4 +5,10 @@ export declare function addProject(name: string, options: AddProjectOptions): Pr
5
5
  export declare function listProjectsCommand(): Promise<void>;
6
6
  export declare function showProject(projectId: string): Promise<void>;
7
7
  export declare function completeProject(projectId: string): Promise<void>;
8
+ interface DeleteProjectOptions {
9
+ withTasks?: boolean;
10
+ keepTasks?: boolean;
11
+ force?: boolean;
12
+ }
13
+ export declare function deleteProjectCommand(projectId: string, options: DeleteProjectOptions): Promise<void>;
8
14
  export {};
@@ -1,6 +1,8 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
+ import { createInterface } from 'readline';
2
3
  import { eq, like, and } from 'drizzle-orm';
3
4
  import { getDb, schema } from '../db/index.js';
5
+ import { deleteProject } from '../db/projectDelete.js';
4
6
  import { t, fmt } from '../i18n/index.js';
5
7
  export async function addProject(name, options) {
6
8
  const db = getDb();
@@ -150,3 +152,96 @@ export async function completeProject(projectId) {
150
152
  .where(eq(schema.tasks.id, project.id));
151
153
  console.log(fmt(i18n.commands.project.completed, { name: project.title }));
152
154
  }
155
+ function ask(rl, question) {
156
+ return new Promise((resolve) => {
157
+ rl.question(question, (answer) => {
158
+ resolve(answer.trim().toLowerCase());
159
+ });
160
+ });
161
+ }
162
+ export async function deleteProjectCommand(projectId, options) {
163
+ const db = getDb();
164
+ const i18n = t();
165
+ // Find project by ID prefix, then by exact name.
166
+ let projects = await db
167
+ .select()
168
+ .from(schema.tasks)
169
+ .where(and(eq(schema.tasks.isProject, true), like(schema.tasks.id, `${projectId}%`)));
170
+ if (projects.length === 0) {
171
+ projects = await db
172
+ .select()
173
+ .from(schema.tasks)
174
+ .where(and(eq(schema.tasks.isProject, true), eq(schema.tasks.title, projectId)));
175
+ }
176
+ if (projects.length === 0) {
177
+ console.error(fmt(i18n.commands.project.notFound, { id: projectId }));
178
+ process.exit(1);
179
+ }
180
+ if (projects.length > 1) {
181
+ console.error(fmt(i18n.commands.project.multipleMatch, { id: projectId }));
182
+ for (const p of projects) {
183
+ console.error(` [${p.id.slice(0, 8)}] ${p.title}`);
184
+ }
185
+ process.exit(1);
186
+ }
187
+ const project = projects[0];
188
+ const children = await db
189
+ .select()
190
+ .from(schema.tasks)
191
+ .where(eq(schema.tasks.parentId, project.id));
192
+ const childCount = children.length;
193
+ // Decide how to handle child tasks. When neither flag is given and the
194
+ // project has tasks, the choice is resolved interactively below.
195
+ let mode = options.withTasks ? 'cascade' :
196
+ options.keepTasks ? 'keep' :
197
+ childCount === 0 ? 'cascade' :
198
+ null;
199
+ if (!options.force) {
200
+ // A single prompt keeps this reliable for piped/non-TTY input.
201
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
202
+ try {
203
+ if (mode !== null) {
204
+ const answer = await ask(rl, `${fmt(i18n.commands.project.deletePrompt, { name: project.title })} (y/N): `);
205
+ if (answer !== 'y' && answer !== 'yes') {
206
+ console.log(i18n.commands.project.deleteCancelled);
207
+ return;
208
+ }
209
+ }
210
+ else {
211
+ const answer = await ask(rl, `${fmt(i18n.commands.project.deleteChildrenPrompt, { name: project.title, count: childCount })}: `);
212
+ if (answer === 'a' || answer === 'all') {
213
+ mode = 'cascade';
214
+ }
215
+ else if (answer === 'k' || answer === 'keep') {
216
+ mode = 'keep';
217
+ }
218
+ else {
219
+ console.log(i18n.commands.project.deleteCancelled);
220
+ return;
221
+ }
222
+ }
223
+ }
224
+ finally {
225
+ rl.close();
226
+ }
227
+ }
228
+ else if (mode === null) {
229
+ // Forced with no explicit choice: keep tasks to avoid silent data loss.
230
+ mode = 'keep';
231
+ }
232
+ await deleteProject(project, mode);
233
+ if (mode === 'cascade') {
234
+ if (childCount > 0) {
235
+ console.log(fmt(i18n.commands.project.deletedWithTasks, { name: project.title, count: childCount }));
236
+ }
237
+ else {
238
+ console.log(fmt(i18n.commands.project.deleted, { name: project.title }));
239
+ }
240
+ }
241
+ else {
242
+ console.log(fmt(i18n.commands.project.deleted, { name: project.title }));
243
+ if (childCount > 0) {
244
+ console.log(fmt(i18n.commands.project.tasksMovedToInbox, { count: childCount }));
245
+ }
246
+ }
247
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Show schedule from all registered calendars
3
+ */
4
+ export declare function showSchedule(period?: string, options?: {
5
+ days?: string;
6
+ }): Promise<void>;