floq 1.4.1 → 1.6.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.
Files changed (58) hide show
  1. package/README.ja.md +4 -0
  2. package/README.md +4 -0
  3. package/dist/cli.js +32 -2
  4. package/dist/commands/config.js +2 -1
  5. package/dist/commands/done.js +1 -0
  6. package/dist/commands/insights.js +13 -10
  7. package/dist/commands/move.js +2 -1
  8. package/dist/config.d.ts +3 -0
  9. package/dist/config.js +11 -0
  10. package/dist/db/index.js +36 -0
  11. package/dist/db/schema.d.ts +116 -0
  12. package/dist/db/schema.js +8 -0
  13. package/dist/i18n/en.d.ts +2 -0
  14. package/dist/i18n/en.js +1 -0
  15. package/dist/i18n/ja.js +1 -0
  16. package/dist/index.js +2 -1
  17. package/dist/mcp/server.d.ts +1 -0
  18. package/dist/mcp/server.js +186 -0
  19. package/dist/ui/App.js +7 -4
  20. package/dist/ui/components/GtdDQ.js +4 -1
  21. package/dist/ui/components/GtdMario.js +4 -1
  22. package/dist/ui/components/InsightsModal.js +15 -8
  23. package/dist/ui/components/KanbanBoard.js +3 -0
  24. package/dist/ui/components/KanbanDQ.js +2 -0
  25. package/dist/ui/components/KanbanMario.js +2 -0
  26. package/dist/ui/components/TaskItem.d.ts +2 -1
  27. package/dist/ui/components/TaskItem.js +4 -3
  28. package/dist/ui/history/HistoryContext.js +8 -0
  29. package/dist/ui/history/HistoryManager.d.ts +9 -1
  30. package/dist/ui/history/HistoryManager.js +140 -16
  31. package/dist/ui/history/commands/ConvertToProjectCommand.d.ts +5 -1
  32. package/dist/ui/history/commands/ConvertToProjectCommand.js +13 -0
  33. package/dist/ui/history/commands/CreateCommentCommand.d.ts +5 -1
  34. package/dist/ui/history/commands/CreateCommentCommand.js +22 -0
  35. package/dist/ui/history/commands/CreateTaskCommand.d.ts +5 -1
  36. package/dist/ui/history/commands/CreateTaskCommand.js +26 -1
  37. package/dist/ui/history/commands/DeleteCommentCommand.d.ts +5 -1
  38. package/dist/ui/history/commands/DeleteCommentCommand.js +22 -0
  39. package/dist/ui/history/commands/DeleteTaskCommand.d.ts +7 -2
  40. package/dist/ui/history/commands/DeleteTaskCommand.js +41 -0
  41. package/dist/ui/history/commands/LinkTaskCommand.d.ts +5 -1
  42. package/dist/ui/history/commands/LinkTaskCommand.js +14 -0
  43. package/dist/ui/history/commands/MoveTaskCommand.d.ts +7 -1
  44. package/dist/ui/history/commands/MoveTaskCommand.js +25 -0
  45. package/dist/ui/history/commands/SetContextCommand.d.ts +5 -1
  46. package/dist/ui/history/commands/SetContextCommand.js +14 -0
  47. package/dist/ui/history/commands/SetEffortCommand.d.ts +5 -1
  48. package/dist/ui/history/commands/SetEffortCommand.js +14 -0
  49. package/dist/ui/history/commands/SetFocusCommand.d.ts +5 -1
  50. package/dist/ui/history/commands/SetFocusCommand.js +14 -0
  51. package/dist/ui/history/commands/index.d.ts +1 -0
  52. package/dist/ui/history/commands/index.js +1 -0
  53. package/dist/ui/history/commands/registry.d.ts +2 -0
  54. package/dist/ui/history/commands/registry.js +28 -0
  55. package/dist/ui/history/index.d.ts +2 -2
  56. package/dist/ui/history/index.js +1 -1
  57. package/dist/ui/history/types.d.ts +9 -0
  58. package/package.json +6 -5
package/README.ja.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Floq
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/floq.svg)](https://www.npmjs.com/package/floq)
4
+ [![npm downloads](https://img.shields.io/npm/dm/floq.svg)](https://www.npmjs.com/package/floq)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
3
7
  [English](./README.md)
4
8
 
5
9
  MS-DOSスタイルのテーマを備えたターミナルベースのGTD(Getting Things Done)タスクマネージャー。
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Floq
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/floq.svg)](https://www.npmjs.com/package/floq)
4
+ [![npm downloads](https://img.shields.io/npm/dm/floq.svg)](https://www.npmjs.com/package/floq)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
3
7
  [日本語](./README.ja.md)
4
8
 
5
9
  A terminal-based GTD (Getting Things Done) task manager with MS-DOS style themes.
package/dist/cli.js CHANGED
@@ -196,6 +196,24 @@ configCmd
196
196
  await showDateFormatCommand();
197
197
  }
198
198
  });
199
+ configCmd
200
+ .command('insights-weeks [weeks]')
201
+ .description('Set number of weeks for insights (default: 2)')
202
+ .action(async (weeks) => {
203
+ const { getInsightsWeeks, setInsightsWeeks } = await import('./config.js');
204
+ if (weeks !== undefined) {
205
+ const n = parseInt(weeks, 10);
206
+ if (isNaN(n) || n < 1) {
207
+ console.error('Weeks must be a positive integer');
208
+ process.exit(1);
209
+ }
210
+ setInsightsWeeks(n);
211
+ console.log(`Insights weeks set to ${n}`);
212
+ }
213
+ else {
214
+ console.log(`Insights weeks: ${getInsightsWeeks()}`);
215
+ }
216
+ });
199
217
  configCmd
200
218
  .command('pomodoro')
201
219
  .description('Configure pomodoro settings')
@@ -216,9 +234,13 @@ configCmd
216
234
  program
217
235
  .command('insights')
218
236
  .description('Show task completion insights and statistics')
219
- .option('-w, --weeks <n>', 'Number of weeks to analyze (default: 2)', '2')
237
+ .option('-w, --weeks <n>', 'Number of weeks to analyze (uses config default)')
220
238
  .action(async (options) => {
221
- const weeks = Math.max(1, parseInt(options.weeks ?? '2', 10) || 2);
239
+ const { getInsightsWeeks } = await import('./config.js');
240
+ const defaultWeeks = getInsightsWeeks();
241
+ const weeks = options.weeks
242
+ ? Math.max(1, parseInt(options.weeks, 10) || defaultWeeks)
243
+ : defaultWeeks;
222
244
  await showInsights(weeks);
223
245
  });
224
246
  // Sync command
@@ -358,4 +380,12 @@ calendarCmd
358
380
  .action(async () => {
359
381
  await selectCalendar();
360
382
  });
383
+ // MCP server command
384
+ program
385
+ .command('mcp')
386
+ .description('Start MCP server for LLM integration')
387
+ .action(async () => {
388
+ const { startMcpServer } = await import('./mcp/server.js');
389
+ await startMcpServer();
390
+ });
361
391
  export { program };
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { createInterface } from 'readline';
4
4
  import { unlinkSync, existsSync, readdirSync } from 'fs';
5
5
  import { dirname, basename, join } from 'path';
6
- import { loadConfig, saveConfig, getDbPath, getViewMode, setViewMode, isTursoEnabled, getTursoConfig, setTursoConfig, setTursoEnabled, getSplashDuration, setSplashDuration, getDateFormat, setDateFormat, getCalendarConfig, isCalendarEnabled } from '../config.js';
6
+ import { loadConfig, saveConfig, getDbPath, getViewMode, setViewMode, isTursoEnabled, getTursoConfig, setTursoConfig, setTursoEnabled, getSplashDuration, setSplashDuration, getDateFormat, setDateFormat, getCalendarConfig, isCalendarEnabled, getInsightsWeeks } from '../config.js';
7
7
  import { CONFIG_FILE } from '../paths.js';
8
8
  import { ThemeSelector } from '../ui/ThemeSelector.js';
9
9
  import { ModeSelector } from '../ui/ModeSelector.js';
@@ -25,6 +25,7 @@ export async function showConfig() {
25
25
  console.log(`View Mode: ${config.viewMode || 'gtd'}`);
26
26
  console.log(`Date Format: ${dateFormat}`);
27
27
  console.log(`Splash: ${splashDuration === 0 ? 'disabled' : splashDuration === -1 ? 'wait for key' : `${splashDuration}ms`}`);
28
+ console.log(`Insights Weeks: ${getInsightsWeeks()}`);
28
29
  console.log(`Turso: ${isTursoEnabled() ? 'enabled' : 'disabled'}`);
29
30
  if (config.db_path) {
30
31
  console.log(` (custom: ${config.db_path})`);
@@ -28,6 +28,7 @@ export async function markDone(taskId) {
28
28
  await db.update(schema.tasks)
29
29
  .set({
30
30
  status: 'done',
31
+ completedAt: new Date(),
31
32
  updatedAt: new Date(),
32
33
  })
33
34
  .where(eq(schema.tasks.id, task.id));
@@ -1,4 +1,4 @@
1
- import { eq, and, gte } from 'drizzle-orm';
1
+ import { eq, and } from 'drizzle-orm';
2
2
  import { getDb, schema } from '../db/index.js';
3
3
  import { t, fmt } from '../i18n/index.js';
4
4
  function getWeekStart(date) {
@@ -53,8 +53,8 @@ function groupByWeek(tasks, weeks) {
53
53
  const weekEnd = new Date(weekStart);
54
54
  weekEnd.setDate(weekEnd.getDate() + 7);
55
55
  const weekTasks = tasks.filter(task => {
56
- const updated = task.updatedAt;
57
- return updated >= weekStart && updated < weekEnd;
56
+ const completionDate = task.completedAt ?? task.updatedAt;
57
+ return completionDate >= weekStart && completionDate < weekEnd;
58
58
  });
59
59
  result.push({
60
60
  weekStart,
@@ -69,7 +69,7 @@ function groupByDayOfWeek(tasks) {
69
69
  for (let i = 0; i < 7; i++)
70
70
  days.set(i, 0);
71
71
  for (const task of tasks) {
72
- const day = task.updatedAt.getDay();
72
+ const day = (task.completedAt ?? task.updatedAt).getDay();
73
73
  days.set(day, (days.get(day) || 0) + 1);
74
74
  }
75
75
  return days;
@@ -95,9 +95,9 @@ function calculateAverageCompletionDays(tasks) {
95
95
  let validCount = 0;
96
96
  for (const task of tasks) {
97
97
  const created = task.createdAt.getTime();
98
- const updated = task.updatedAt.getTime();
99
- if (updated > created) {
100
- totalMs += updated - created;
98
+ const completed = (task.completedAt ?? task.updatedAt).getTime();
99
+ if (completed > created) {
100
+ totalMs += completed - created;
101
101
  validCount++;
102
102
  }
103
103
  }
@@ -138,11 +138,14 @@ export async function showInsights(weeks) {
138
138
  startDate.setDate(startDate.getDate() - (weeks - 1) * 7);
139
139
  const endDate = new Date(now);
140
140
  endDate.setHours(23, 59, 59, 999);
141
- // Query all completed tasks in the period
142
- const completedTasks = await db
141
+ // Query completed tasks (use completedAt with updatedAt fallback)
142
+ const completedTasks = (await db
143
143
  .select()
144
144
  .from(schema.tasks)
145
- .where(and(eq(schema.tasks.status, 'done'), gte(schema.tasks.updatedAt, startDate), eq(schema.tasks.isProject, false)));
145
+ .where(and(eq(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false)))).filter(task => {
146
+ const completionDate = task.completedAt ?? task.updatedAt;
147
+ return completionDate >= startDate;
148
+ });
146
149
  // Header
147
150
  console.log();
148
151
  console.log(l.title);
@@ -4,7 +4,7 @@ import { t, fmt } from '../i18n/index.js';
4
4
  export async function moveTask(taskId, targetStatus, waitingFor) {
5
5
  const db = getDb();
6
6
  const i18n = t();
7
- const validStatuses = ['inbox', 'next', 'waiting', 'someday'];
7
+ const validStatuses = ['inbox', 'next', 'waiting', 'someday', 'done'];
8
8
  if (!validStatuses.includes(targetStatus)) {
9
9
  console.error(fmt(i18n.commands.move.invalidStatus, { status: targetStatus }));
10
10
  console.error(fmt(i18n.commands.move.validStatuses, { statuses: validStatuses.join(', ') }));
@@ -35,6 +35,7 @@ export async function moveTask(taskId, targetStatus, waitingFor) {
35
35
  .set({
36
36
  status: targetStatus,
37
37
  waitingFor: targetStatus === 'waiting' ? waitingFor : null,
38
+ completedAt: targetStatus === 'done' ? new Date() : null,
38
39
  updatedAt: new Date(),
39
40
  })
40
41
  .where(eq(schema.tasks.id, task.id));
package/dist/config.d.ts CHANGED
@@ -43,6 +43,7 @@ export interface Config {
43
43
  pomodoroFocusMode?: boolean;
44
44
  dateFormat?: DateFormat;
45
45
  calendar?: CalendarConfig;
46
+ insightsWeeks?: number;
46
47
  }
47
48
  export declare function loadConfig(): Config;
48
49
  export declare function saveConfig(updates: Partial<Config>): void;
@@ -80,3 +81,5 @@ export declare function setGoogleOAuthClient(client: GoogleOAuthClient): void;
80
81
  export declare function getCalendarOAuthConfig(): CalendarOAuthConfig | undefined;
81
82
  export declare function setCalendarOAuthConfig(oauth: CalendarOAuthConfig | undefined): void;
82
83
  export declare function getCalendarType(): 'ical' | 'oauth' | undefined;
84
+ export declare function getInsightsWeeks(): number;
85
+ export declare function setInsightsWeeks(weeks: number): void;
package/dist/config.js CHANGED
@@ -244,3 +244,14 @@ export function getCalendarType() {
244
244
  return undefined;
245
245
  return calendar.type || (calendar.url ? 'ical' : undefined);
246
246
  }
247
+ const DEFAULT_INSIGHTS_WEEKS = 2;
248
+ export function getInsightsWeeks() {
249
+ const weeks = loadConfig().insightsWeeks;
250
+ if (weeks === undefined || weeks < 1) {
251
+ return DEFAULT_INSIGHTS_WEEKS;
252
+ }
253
+ return weeks;
254
+ }
255
+ export function setInsightsWeeks(weeks) {
256
+ saveConfig({ insightsWeeks: Math.max(1, weeks) });
257
+ }
package/dist/db/index.js CHANGED
@@ -26,6 +26,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
26
26
  const hasContext = tableInfo.some(col => col.name === 'context');
27
27
  const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
28
28
  const hasEffort = tableInfo.some(col => col.name === 'effort');
29
+ const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
29
30
  if (!tableExists) {
30
31
  // Fresh install: create new schema on remote
31
32
  await remoteClient.execute(`
@@ -41,6 +42,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
41
42
  is_focused INTEGER NOT NULL DEFAULT 0,
42
43
  effort TEXT,
43
44
  due_date INTEGER,
45
+ completed_at INTEGER,
44
46
  created_at INTEGER NOT NULL,
45
47
  updated_at INTEGER NOT NULL
46
48
  )
@@ -63,6 +65,11 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
63
65
  if (tableExists && !hasEffort) {
64
66
  await remoteClient.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
65
67
  }
68
+ // Migration: add completed_at column if missing
69
+ if (tableExists && !hasCompletedAt) {
70
+ await remoteClient.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
71
+ await remoteClient.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
72
+ }
66
73
  // Create comments table
67
74
  await remoteClient.execute(`
68
75
  CREATE TABLE IF NOT EXISTS comments (
@@ -86,6 +93,17 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
86
93
  updated_at INTEGER NOT NULL
87
94
  )
88
95
  `);
96
+ // Create operation_history table for crash-safe undo
97
+ await remoteClient.execute(`
98
+ CREATE TABLE IF NOT EXISTS operation_history (
99
+ id TEXT PRIMARY KEY,
100
+ command_type TEXT NOT NULL,
101
+ command_data TEXT NOT NULL,
102
+ executed_at INTEGER NOT NULL,
103
+ is_undone INTEGER NOT NULL DEFAULT 0
104
+ )
105
+ `);
106
+ await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
89
107
  }
90
108
  finally {
91
109
  remoteClient.close();
@@ -102,6 +120,7 @@ async function initializeLocalSchema() {
102
120
  const hasContext = tableInfo.some(col => col.name === 'context');
103
121
  const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
104
122
  const hasEffort = tableInfo.some(col => col.name === 'effort');
123
+ const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
105
124
  const tableExists = tableInfo.length > 0;
106
125
  if (tableExists && hasProjectId && !hasIsProject) {
107
126
  // Migration: old schema -> new schema
@@ -136,6 +155,7 @@ async function initializeLocalSchema() {
136
155
  is_focused INTEGER NOT NULL DEFAULT 0,
137
156
  effort TEXT,
138
157
  due_date INTEGER,
158
+ completed_at INTEGER,
139
159
  created_at INTEGER NOT NULL,
140
160
  updated_at INTEGER NOT NULL
141
161
  )
@@ -158,6 +178,11 @@ async function initializeLocalSchema() {
158
178
  if (tableExists && !hasEffort) {
159
179
  await client.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
160
180
  }
181
+ // Migration: add completed_at column if missing
182
+ if (tableExists && !hasCompletedAt) {
183
+ await client.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
184
+ await client.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
185
+ }
161
186
  // Create comments table
162
187
  await client.execute(`
163
188
  CREATE TABLE IF NOT EXISTS comments (
@@ -181,6 +206,17 @@ async function initializeLocalSchema() {
181
206
  updated_at INTEGER NOT NULL
182
207
  )
183
208
  `);
209
+ // Create operation_history table for crash-safe undo
210
+ await client.execute(`
211
+ CREATE TABLE IF NOT EXISTS operation_history (
212
+ id TEXT PRIMARY KEY,
213
+ command_type TEXT NOT NULL,
214
+ command_data TEXT NOT NULL,
215
+ executed_at INTEGER NOT NULL,
216
+ is_undone INTEGER NOT NULL DEFAULT 0
217
+ )
218
+ `);
219
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
184
220
  }
185
221
  // DB 初期化
186
222
  export async function initDb() {
@@ -205,6 +205,23 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
205
205
  identity: undefined;
206
206
  generated: undefined;
207
207
  }, {}, {}>;
208
+ completedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
209
+ name: "completed_at";
210
+ tableName: "tasks";
211
+ dataType: "date";
212
+ columnType: "SQLiteTimestamp";
213
+ data: Date;
214
+ driverParam: number;
215
+ notNull: false;
216
+ hasDefault: false;
217
+ isPrimaryKey: false;
218
+ isAutoincrement: false;
219
+ hasRuntimeDefault: false;
220
+ enumValues: undefined;
221
+ baseColumn: never;
222
+ identity: undefined;
223
+ generated: undefined;
224
+ }, {}, {}>;
208
225
  createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
209
226
  name: "created_at";
210
227
  tableName: "tasks";
@@ -482,3 +499,102 @@ export declare const pomodoroSessions: import("drizzle-orm/sqlite-core").SQLiteT
482
499
  }>;
483
500
  export type PomodoroSession = typeof pomodoroSessions.$inferSelect;
484
501
  export type NewPomodoroSession = typeof pomodoroSessions.$inferInsert;
502
+ export declare const operationHistory: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
503
+ name: "operation_history";
504
+ schema: undefined;
505
+ columns: {
506
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
507
+ name: "id";
508
+ tableName: "operation_history";
509
+ dataType: "string";
510
+ columnType: "SQLiteText";
511
+ data: string;
512
+ driverParam: string;
513
+ notNull: true;
514
+ hasDefault: false;
515
+ isPrimaryKey: true;
516
+ isAutoincrement: false;
517
+ hasRuntimeDefault: false;
518
+ enumValues: [string, ...string[]];
519
+ baseColumn: never;
520
+ identity: undefined;
521
+ generated: undefined;
522
+ }, {}, {
523
+ length: number | undefined;
524
+ }>;
525
+ commandType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
526
+ name: "command_type";
527
+ tableName: "operation_history";
528
+ dataType: "string";
529
+ columnType: "SQLiteText";
530
+ data: string;
531
+ driverParam: string;
532
+ notNull: true;
533
+ hasDefault: false;
534
+ isPrimaryKey: false;
535
+ isAutoincrement: false;
536
+ hasRuntimeDefault: false;
537
+ enumValues: [string, ...string[]];
538
+ baseColumn: never;
539
+ identity: undefined;
540
+ generated: undefined;
541
+ }, {}, {
542
+ length: number | undefined;
543
+ }>;
544
+ commandData: import("drizzle-orm/sqlite-core").SQLiteColumn<{
545
+ name: "command_data";
546
+ tableName: "operation_history";
547
+ dataType: "string";
548
+ columnType: "SQLiteText";
549
+ data: string;
550
+ driverParam: string;
551
+ notNull: true;
552
+ hasDefault: false;
553
+ isPrimaryKey: false;
554
+ isAutoincrement: false;
555
+ hasRuntimeDefault: false;
556
+ enumValues: [string, ...string[]];
557
+ baseColumn: never;
558
+ identity: undefined;
559
+ generated: undefined;
560
+ }, {}, {
561
+ length: number | undefined;
562
+ }>;
563
+ executedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
564
+ name: "executed_at";
565
+ tableName: "operation_history";
566
+ dataType: "date";
567
+ columnType: "SQLiteTimestamp";
568
+ data: Date;
569
+ driverParam: number;
570
+ notNull: true;
571
+ hasDefault: false;
572
+ isPrimaryKey: false;
573
+ isAutoincrement: false;
574
+ hasRuntimeDefault: false;
575
+ enumValues: undefined;
576
+ baseColumn: never;
577
+ identity: undefined;
578
+ generated: undefined;
579
+ }, {}, {}>;
580
+ isUndone: import("drizzle-orm/sqlite-core").SQLiteColumn<{
581
+ name: "is_undone";
582
+ tableName: "operation_history";
583
+ dataType: "boolean";
584
+ columnType: "SQLiteBoolean";
585
+ data: boolean;
586
+ driverParam: number;
587
+ notNull: true;
588
+ hasDefault: true;
589
+ isPrimaryKey: false;
590
+ isAutoincrement: false;
591
+ hasRuntimeDefault: false;
592
+ enumValues: undefined;
593
+ baseColumn: never;
594
+ identity: undefined;
595
+ generated: undefined;
596
+ }, {}, {}>;
597
+ };
598
+ dialect: "sqlite";
599
+ }>;
600
+ export type OperationHistory = typeof operationHistory.$inferSelect;
package/dist/db/schema.js CHANGED
@@ -11,6 +11,7 @@ export const tasks = sqliteTable('tasks', {
11
11
  isFocused: integer('is_focused', { mode: 'boolean' }).notNull().default(false),
12
12
  effort: text('effort'),
13
13
  dueDate: integer('due_date', { mode: 'timestamp' }),
14
+ completedAt: integer('completed_at', { mode: 'timestamp' }),
14
15
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
15
16
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
16
17
  });
@@ -32,3 +33,10 @@ export const pomodoroSessions = sqliteTable('pomodoro_sessions', {
32
33
  completedCount: integer('completed_count').notNull().default(0),
33
34
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
34
35
  });
36
+ export const operationHistory = sqliteTable('operation_history', {
37
+ id: text('id').primaryKey(),
38
+ commandType: text('command_type').notNull(),
39
+ commandData: text('command_data').notNull(), // JSON serialized
40
+ executedAt: integer('executed_at', { mode: 'timestamp' }).notNull(),
41
+ isUndone: integer('is_undone', { mode: 'boolean' }).notNull().default(false),
42
+ });
package/dist/i18n/en.d.ts CHANGED
@@ -243,6 +243,7 @@ export declare const en: {
243
243
  taskDetailTitle: string;
244
244
  taskDetailFooter: string;
245
245
  taskDetailStatus: string;
246
+ taskDetailCompletedAt: string;
246
247
  deleteConfirm: string;
247
248
  deleted: string;
248
249
  deleteCancelled: string;
@@ -670,6 +671,7 @@ export type TuiTranslations = {
670
671
  taskDetailTitle: string;
671
672
  taskDetailFooter: string;
672
673
  taskDetailStatus: string;
674
+ taskDetailCompletedAt?: string;
673
675
  deleteConfirm: string;
674
676
  deleted: string;
675
677
  deleteCancelled: string;
package/dist/i18n/en.js CHANGED
@@ -256,6 +256,7 @@ export const en = {
256
256
  taskDetailTitle: 'Task Details',
257
257
  taskDetailFooter: 'j/k=select i=comment d=delete P=link b/Esc=back',
258
258
  taskDetailStatus: 'Status',
259
+ taskDetailCompletedAt: 'Completed',
259
260
  deleteConfirm: 'Delete "{title}"? (y/n)',
260
261
  deleted: 'Deleted: "{title}"',
261
262
  deleteCancelled: 'Delete cancelled',
package/dist/i18n/ja.js CHANGED
@@ -256,6 +256,7 @@ export const ja = {
256
256
  taskDetailTitle: 'タスク詳細',
257
257
  taskDetailFooter: 'j/k=選択 i=コメント d=削除 P=紐づけ b/Esc=戻る',
258
258
  taskDetailStatus: 'ステータス',
259
+ taskDetailCompletedAt: '完了日',
259
260
  deleteConfirm: '「{title}」を削除しますか? (y/n)',
260
261
  deleted: '削除しました: 「{title}」',
261
262
  deleteCancelled: '削除をキャンセルしました',
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ async function main() {
10
10
  const isConfigCommand = args[0] === 'config';
11
11
  const isSyncCommand = args[0] === 'sync';
12
12
  const isSetupCommand = args[0] === 'setup';
13
+ const isMcpCommand = args[0] === 'mcp';
13
14
  // 初回起動時(引数なし + config未作成)はウィザードを起動
14
15
  if (isTuiMode && isFirstRun()) {
15
16
  await runSetupWizard();
@@ -19,7 +20,7 @@ async function main() {
19
20
  return;
20
21
  }
21
22
  // config/syncコマンド以外でTursoモードの場合は接続先を表示
22
- if (!isTuiMode && !isConfigCommand && !isSyncCommand && !isSetupCommand && isTursoEnabled()) {
23
+ if (!isTuiMode && !isConfigCommand && !isSyncCommand && !isSetupCommand && !isMcpCommand && isTursoEnabled()) {
23
24
  const turso = getTursoConfig();
24
25
  if (turso) {
25
26
  const host = new URL(turso.url).host;
@@ -0,0 +1 @@
1
+ export declare function startMcpServer(): Promise<void>;