floq 1.5.0 → 1.7.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/README.ja.md +4 -0
- package/README.md +4 -0
- package/dist/cli.js +38 -4
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +18 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/index.js +2 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +186 -0
- package/dist/ui/components/InsightsModal.js +2 -1
- package/package.json +8 -5
package/README.ja.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Floq
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/floq)
|
|
4
|
+
[](https://www.npmjs.com/package/floq)
|
|
5
|
+
[](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
|
+
[](https://www.npmjs.com/package/floq)
|
|
4
|
+
[](https://www.npmjs.com/package/floq)
|
|
5
|
+
[](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
|
@@ -7,7 +7,7 @@ import { listTasks, listProjects } from './commands/list.js';
|
|
|
7
7
|
import { moveTask } from './commands/move.js';
|
|
8
8
|
import { markDone } from './commands/done.js';
|
|
9
9
|
import { addProject, listProjectsCommand, showProject, completeProject, } from './commands/project.js';
|
|
10
|
-
import { showConfig, setLanguage, setDbPath, resetDbPath, setTheme, selectTheme, setViewModeCommand, selectMode, setTurso, disableTurso, enableTurso, clearTurso, syncCommand, resetDatabase, setSplashCommand, showSplash, showDateFormatCommand, setDateFormatCommand } from './commands/config.js';
|
|
10
|
+
import { showConfig, setLanguage, setDbPath, resetDbPath, setTheme, selectTheme, setViewModeCommand, selectMode, setTurso, disableTurso, enableTurso, clearTurso, showTursoQr, syncCommand, resetDatabase, setSplashCommand, showSplash, showDateFormatCommand, setDateFormatCommand } from './commands/config.js';
|
|
11
11
|
import { addComment, listComments } from './commands/comment.js';
|
|
12
12
|
import { listContexts, addContextCommand, removeContextCommand } from './commands/context.js';
|
|
13
13
|
import { showInsights } from './commands/insights.js';
|
|
@@ -153,8 +153,12 @@ configCmd
|
|
|
153
153
|
.option('--disable', 'Temporarily disable Turso sync (preserves config)')
|
|
154
154
|
.option('--enable', 'Re-enable Turso sync')
|
|
155
155
|
.option('--clear', 'Remove Turso configuration completely')
|
|
156
|
+
.option('--qr', 'Display Turso config as QR code')
|
|
156
157
|
.action(async (options) => {
|
|
157
|
-
if (options.
|
|
158
|
+
if (options.qr) {
|
|
159
|
+
await showTursoQr();
|
|
160
|
+
}
|
|
161
|
+
else if (options.clear) {
|
|
158
162
|
await clearTurso();
|
|
159
163
|
}
|
|
160
164
|
else if (options.disable) {
|
|
@@ -196,6 +200,24 @@ configCmd
|
|
|
196
200
|
await showDateFormatCommand();
|
|
197
201
|
}
|
|
198
202
|
});
|
|
203
|
+
configCmd
|
|
204
|
+
.command('insights-weeks [weeks]')
|
|
205
|
+
.description('Set number of weeks for insights (default: 2)')
|
|
206
|
+
.action(async (weeks) => {
|
|
207
|
+
const { getInsightsWeeks, setInsightsWeeks } = await import('./config.js');
|
|
208
|
+
if (weeks !== undefined) {
|
|
209
|
+
const n = parseInt(weeks, 10);
|
|
210
|
+
if (isNaN(n) || n < 1) {
|
|
211
|
+
console.error('Weeks must be a positive integer');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
setInsightsWeeks(n);
|
|
215
|
+
console.log(`Insights weeks set to ${n}`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log(`Insights weeks: ${getInsightsWeeks()}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
199
221
|
configCmd
|
|
200
222
|
.command('pomodoro')
|
|
201
223
|
.description('Configure pomodoro settings')
|
|
@@ -216,9 +238,13 @@ configCmd
|
|
|
216
238
|
program
|
|
217
239
|
.command('insights')
|
|
218
240
|
.description('Show task completion insights and statistics')
|
|
219
|
-
.option('-w, --weeks <n>', 'Number of weeks to analyze (default
|
|
241
|
+
.option('-w, --weeks <n>', 'Number of weeks to analyze (uses config default)')
|
|
220
242
|
.action(async (options) => {
|
|
221
|
-
const
|
|
243
|
+
const { getInsightsWeeks } = await import('./config.js');
|
|
244
|
+
const defaultWeeks = getInsightsWeeks();
|
|
245
|
+
const weeks = options.weeks
|
|
246
|
+
? Math.max(1, parseInt(options.weeks, 10) || defaultWeeks)
|
|
247
|
+
: defaultWeeks;
|
|
222
248
|
await showInsights(weeks);
|
|
223
249
|
});
|
|
224
250
|
// Sync command
|
|
@@ -358,4 +384,12 @@ calendarCmd
|
|
|
358
384
|
.action(async () => {
|
|
359
385
|
await selectCalendar();
|
|
360
386
|
});
|
|
387
|
+
// MCP server command
|
|
388
|
+
program
|
|
389
|
+
.command('mcp')
|
|
390
|
+
.description('Start MCP server for LLM integration')
|
|
391
|
+
.action(async () => {
|
|
392
|
+
const { startMcpServer } = await import('./mcp/server.js');
|
|
393
|
+
await startMcpServer();
|
|
394
|
+
});
|
|
361
395
|
export { program };
|
|
@@ -7,6 +7,7 @@ export declare function selectTheme(): Promise<void>;
|
|
|
7
7
|
export declare function showViewMode(): Promise<void>;
|
|
8
8
|
export declare function setViewModeCommand(mode: string): Promise<void>;
|
|
9
9
|
export declare function selectMode(): Promise<void>;
|
|
10
|
+
export declare function showTursoQr(): Promise<void>;
|
|
10
11
|
export declare function setTurso(url: string, token: string): Promise<void>;
|
|
11
12
|
export declare function disableTurso(): Promise<void>;
|
|
12
13
|
export declare function enableTurso(): Promise<void>;
|
package/dist/commands/config.js
CHANGED
|
@@ -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})`);
|
|
@@ -126,6 +127,22 @@ export async function selectMode() {
|
|
|
126
127
|
}));
|
|
127
128
|
});
|
|
128
129
|
}
|
|
130
|
+
export async function showTursoQr() {
|
|
131
|
+
const turso = getTursoConfig();
|
|
132
|
+
if (!turso || !turso.url || !turso.authToken) {
|
|
133
|
+
console.error('Turso is not configured.');
|
|
134
|
+
console.error('Use "floq config turso --url <url> --token <token>" to configure first.');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const qrcode = await import('qrcode');
|
|
138
|
+
const data = JSON.stringify({ url: turso.url, authToken: turso.authToken });
|
|
139
|
+
const qr = await qrcode.default.toString(data, { type: 'terminal', small: true });
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log('Turso Configuration QR Code:');
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(qr);
|
|
144
|
+
console.log('⚠️ This QR code contains your auth token. Do not share publicly.');
|
|
145
|
+
}
|
|
129
146
|
export async function setTurso(url, token) {
|
|
130
147
|
setTursoConfig({ url, authToken: token, enabled: true });
|
|
131
148
|
console.log('Turso sync enabled');
|
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/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>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import { eq, like, ne, and } from 'drizzle-orm';
|
|
6
|
+
import { getDb, schema } from '../db/index.js';
|
|
7
|
+
import { VERSION } from '../version.js';
|
|
8
|
+
export async function startMcpServer() {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: 'floq',
|
|
11
|
+
version: VERSION,
|
|
12
|
+
});
|
|
13
|
+
// floq_add_task
|
|
14
|
+
server.tool('floq_add_task', 'Add a new task to Floq. By default, tasks are added to the inbox.', {
|
|
15
|
+
title: z.string().describe('Task title'),
|
|
16
|
+
description: z.string().optional().describe('Task description'),
|
|
17
|
+
status: z.enum(['inbox', 'next', 'waiting', 'someday']).optional().describe('Initial status (default: inbox)'),
|
|
18
|
+
context: z.string().optional().describe('Task context (e.g., work, home)'),
|
|
19
|
+
}, async ({ title, description, status, context }) => {
|
|
20
|
+
const db = getDb();
|
|
21
|
+
const now = new Date();
|
|
22
|
+
const id = uuidv4();
|
|
23
|
+
await db.insert(schema.tasks).values({
|
|
24
|
+
id,
|
|
25
|
+
title,
|
|
26
|
+
description: description ?? null,
|
|
27
|
+
status: status ?? 'inbox',
|
|
28
|
+
context: context?.toLowerCase().replace(/^@/, '') ?? null,
|
|
29
|
+
createdAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify({ id, title, status: status ?? 'inbox' }),
|
|
36
|
+
}],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
// floq_list_tasks
|
|
40
|
+
server.tool('floq_list_tasks', 'List tasks from Floq. Use status filter to narrow results. "all" returns all non-done tasks.', {
|
|
41
|
+
status: z.enum(['inbox', 'next', 'waiting', 'someday', 'done', 'all']).optional().describe('Filter by status (default: all, which excludes done)'),
|
|
42
|
+
}, async ({ status }) => {
|
|
43
|
+
const db = getDb();
|
|
44
|
+
const filter = status ?? 'all';
|
|
45
|
+
let tasks;
|
|
46
|
+
if (filter === 'all') {
|
|
47
|
+
tasks = await db
|
|
48
|
+
.select()
|
|
49
|
+
.from(schema.tasks)
|
|
50
|
+
.where(and(ne(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false)));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
tasks = await db
|
|
54
|
+
.select()
|
|
55
|
+
.from(schema.tasks)
|
|
56
|
+
.where(and(eq(schema.tasks.status, filter), eq(schema.tasks.isProject, false)));
|
|
57
|
+
}
|
|
58
|
+
const result = tasks.map(task => ({
|
|
59
|
+
id: task.id,
|
|
60
|
+
shortId: task.id.slice(0, 8),
|
|
61
|
+
title: task.title,
|
|
62
|
+
description: task.description,
|
|
63
|
+
status: task.status,
|
|
64
|
+
context: task.context,
|
|
65
|
+
waitingFor: task.waitingFor,
|
|
66
|
+
dueDate: task.dueDate?.toISOString() ?? null,
|
|
67
|
+
createdAt: task.createdAt.toISOString(),
|
|
68
|
+
}));
|
|
69
|
+
return {
|
|
70
|
+
content: [{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: JSON.stringify(result),
|
|
73
|
+
}],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
// floq_complete_task
|
|
77
|
+
server.tool('floq_complete_task', 'Mark a task as done. Accepts full ID or ID prefix (first 8 chars).', {
|
|
78
|
+
taskId: z.string().describe('Task ID or ID prefix'),
|
|
79
|
+
}, async ({ taskId }) => {
|
|
80
|
+
const db = getDb();
|
|
81
|
+
const tasks = await db
|
|
82
|
+
.select()
|
|
83
|
+
.from(schema.tasks)
|
|
84
|
+
.where(like(schema.tasks.id, `${taskId}%`));
|
|
85
|
+
if (tasks.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: JSON.stringify({ error: `No task found with ID prefix: ${taskId}` }),
|
|
90
|
+
}],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (tasks.length > 1) {
|
|
95
|
+
const matches = tasks.map(t => ({ id: t.id.slice(0, 8), title: t.title }));
|
|
96
|
+
return {
|
|
97
|
+
content: [{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: JSON.stringify({ error: 'Multiple tasks match this prefix', matches }),
|
|
100
|
+
}],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const task = tasks[0];
|
|
105
|
+
if (task.status === 'done') {
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: JSON.stringify({ id: task.id, title: task.title, status: 'done', message: 'Task is already done' }),
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const previousStatus = task.status;
|
|
114
|
+
await db.update(schema.tasks)
|
|
115
|
+
.set({
|
|
116
|
+
status: 'done',
|
|
117
|
+
completedAt: new Date(),
|
|
118
|
+
updatedAt: new Date(),
|
|
119
|
+
})
|
|
120
|
+
.where(eq(schema.tasks.id, task.id));
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: 'text',
|
|
124
|
+
text: JSON.stringify({ id: task.id, title: task.title, previousStatus, status: 'done' }),
|
|
125
|
+
}],
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
// floq_move_task
|
|
129
|
+
server.tool('floq_move_task', 'Move a task to a different status. Accepts full ID or ID prefix.', {
|
|
130
|
+
taskId: z.string().describe('Task ID or ID prefix'),
|
|
131
|
+
status: z.enum(['inbox', 'next', 'waiting', 'someday', 'done']).describe('Target status'),
|
|
132
|
+
waitingFor: z.string().optional().describe('Who/what the task is waiting for (required when status is "waiting")'),
|
|
133
|
+
}, async ({ taskId, status, waitingFor }) => {
|
|
134
|
+
const db = getDb();
|
|
135
|
+
if (status === 'waiting' && !waitingFor) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{
|
|
138
|
+
type: 'text',
|
|
139
|
+
text: JSON.stringify({ error: 'waitingFor is required when status is "waiting"' }),
|
|
140
|
+
}],
|
|
141
|
+
isError: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const tasks = await db
|
|
145
|
+
.select()
|
|
146
|
+
.from(schema.tasks)
|
|
147
|
+
.where(like(schema.tasks.id, `${taskId}%`));
|
|
148
|
+
if (tasks.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: 'text',
|
|
152
|
+
text: JSON.stringify({ error: `No task found with ID prefix: ${taskId}` }),
|
|
153
|
+
}],
|
|
154
|
+
isError: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (tasks.length > 1) {
|
|
158
|
+
const matches = tasks.map(t => ({ id: t.id.slice(0, 8), title: t.title }));
|
|
159
|
+
return {
|
|
160
|
+
content: [{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: JSON.stringify({ error: 'Multiple tasks match this prefix', matches }),
|
|
163
|
+
}],
|
|
164
|
+
isError: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const task = tasks[0];
|
|
168
|
+
const previousStatus = task.status;
|
|
169
|
+
await db.update(schema.tasks)
|
|
170
|
+
.set({
|
|
171
|
+
status,
|
|
172
|
+
waitingFor: status === 'waiting' ? (waitingFor ?? null) : null,
|
|
173
|
+
completedAt: status === 'done' ? new Date() : null,
|
|
174
|
+
updatedAt: new Date(),
|
|
175
|
+
})
|
|
176
|
+
.where(eq(schema.tasks.id, task.id));
|
|
177
|
+
return {
|
|
178
|
+
content: [{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: JSON.stringify({ id: task.id, title: task.title, previousStatus, status }),
|
|
181
|
+
}],
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
const transport = new StdioServerTransport();
|
|
185
|
+
await server.connect(transport);
|
|
186
|
+
}
|
|
@@ -4,6 +4,7 @@ import { Box, Text, useInput } from 'ink';
|
|
|
4
4
|
import { eq, and } from 'drizzle-orm';
|
|
5
5
|
import { getDb, schema } from '../../db/index.js';
|
|
6
6
|
import { t, fmt } from '../../i18n/index.js';
|
|
7
|
+
import { getInsightsWeeks } from '../../config.js';
|
|
7
8
|
import { useTheme } from '../theme/index.js';
|
|
8
9
|
const VISIBLE_LINES = 16;
|
|
9
10
|
function stringWidth(str) {
|
|
@@ -75,7 +76,7 @@ export function InsightsModal({ onClose }) {
|
|
|
75
76
|
useEffect(() => {
|
|
76
77
|
const loadInsights = async () => {
|
|
77
78
|
const db = getDb();
|
|
78
|
-
const weeks =
|
|
79
|
+
const weeks = getInsightsWeeks();
|
|
79
80
|
const lines = [];
|
|
80
81
|
const now = new Date();
|
|
81
82
|
const startDate = getWeekStart(now);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "floq",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Floq - Getting Things Done Task Manager with MS-DOS style themes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -39,20 +39,23 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@inkjs/ui": "^2.0.0",
|
|
41
41
|
"@libsql/client": "^0.17.0",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
42
43
|
"better-sqlite3": "^11.7.0",
|
|
43
44
|
"commander": "^13.1.0",
|
|
44
45
|
"drizzle-orm": "^0.39.1",
|
|
46
|
+
"googleapis": "^144.0.0",
|
|
47
|
+
"ical.js": "^2.1.0",
|
|
45
48
|
"ink": "^5.1.0",
|
|
46
49
|
"ink-text-input": "^6.0.0",
|
|
50
|
+
"open": "^10.1.0",
|
|
51
|
+
"qrcode": "^1.5.4",
|
|
47
52
|
"react": "^18.3.1",
|
|
48
|
-
"uuid": "^11.0.5"
|
|
49
|
-
"ical.js": "^2.1.0",
|
|
50
|
-
"googleapis": "^144.0.0",
|
|
51
|
-
"open": "^10.1.0"
|
|
53
|
+
"uuid": "^11.0.5"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@types/better-sqlite3": "^7.6.12",
|
|
55
57
|
"@types/node": "^22.10.7",
|
|
58
|
+
"@types/qrcode": "^1.5.6",
|
|
56
59
|
"@types/react": "^18.3.18",
|
|
57
60
|
"@types/uuid": "^10.0.0",
|
|
58
61
|
"drizzle-kit": "^0.31.8",
|