floq 0.1.0 → 0.2.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 (42) hide show
  1. package/README.ja.md +89 -9
  2. package/README.md +89 -9
  3. package/dist/changelog.d.ts +13 -0
  4. package/dist/changelog.js +95 -0
  5. package/dist/cli.js +44 -1
  6. package/dist/commands/comment.d.ts +2 -0
  7. package/dist/commands/comment.js +67 -0
  8. package/dist/commands/config.d.ts +4 -0
  9. package/dist/commands/config.js +123 -1
  10. package/dist/commands/setup.d.ts +1 -0
  11. package/dist/commands/setup.js +13 -0
  12. package/dist/config.d.ts +5 -0
  13. package/dist/config.js +40 -3
  14. package/dist/db/index.js +20 -0
  15. package/dist/db/schema.d.ts +83 -0
  16. package/dist/db/schema.js +6 -0
  17. package/dist/i18n/en.d.ts +237 -0
  18. package/dist/i18n/en.js +127 -3
  19. package/dist/i18n/ja.js +127 -3
  20. package/dist/index.js +14 -4
  21. package/dist/paths.d.ts +4 -0
  22. package/dist/paths.js +63 -5
  23. package/dist/ui/App.js +280 -25
  24. package/dist/ui/ModeSelector.d.ts +7 -0
  25. package/dist/ui/ModeSelector.js +37 -0
  26. package/dist/ui/SetupWizard.d.ts +6 -0
  27. package/dist/ui/SetupWizard.js +321 -0
  28. package/dist/ui/components/HelpModal.d.ts +2 -1
  29. package/dist/ui/components/HelpModal.js +118 -4
  30. package/dist/ui/components/KanbanBoard.d.ts +6 -0
  31. package/dist/ui/components/KanbanBoard.js +508 -0
  32. package/dist/ui/components/KanbanColumn.d.ts +12 -0
  33. package/dist/ui/components/KanbanColumn.js +11 -0
  34. package/dist/ui/components/ProgressBar.d.ts +7 -0
  35. package/dist/ui/components/ProgressBar.js +13 -0
  36. package/dist/ui/components/SearchBar.d.ts +8 -0
  37. package/dist/ui/components/SearchBar.js +11 -0
  38. package/dist/ui/components/SearchResults.d.ts +9 -0
  39. package/dist/ui/components/SearchResults.js +18 -0
  40. package/dist/ui/components/TaskItem.d.ts +6 -1
  41. package/dist/ui/components/TaskItem.js +3 -2
  42. package/package.json +1 -1
@@ -1,11 +1,16 @@
1
1
  import { render } from 'ink';
2
2
  import React from 'react';
3
- import { loadConfig, saveConfig, getDbPath, isTursoEnabled, setTursoConfig } from '../config.js';
3
+ import { createInterface } from 'readline';
4
+ import { unlinkSync, existsSync, readdirSync } from 'fs';
5
+ import { dirname, basename, join } from 'path';
6
+ import { loadConfig, saveConfig, getDbPath, getViewMode, setViewMode, isTursoEnabled, getTursoConfig, setTursoConfig } from '../config.js';
4
7
  import { CONFIG_FILE } from '../paths.js';
5
8
  import { ThemeSelector } from '../ui/ThemeSelector.js';
9
+ import { ModeSelector } from '../ui/ModeSelector.js';
6
10
  import { syncDb } from '../db/index.js';
7
11
  import { VALID_THEMES, themes } from '../ui/theme/themes.js';
8
12
  const VALID_LOCALES = ['en', 'ja'];
13
+ const VALID_VIEW_MODES = ['gtd', 'kanban'];
9
14
  export async function showConfig() {
10
15
  const config = loadConfig();
11
16
  console.log('GTD CLI Configuration');
@@ -14,6 +19,7 @@ export async function showConfig() {
14
19
  console.log(`Language: ${config.locale}`);
15
20
  console.log(`Database: ${getDbPath()}`);
16
21
  console.log(`Theme: ${config.theme || 'modern'}`);
22
+ console.log(`View Mode: ${config.viewMode || 'gtd'}`);
17
23
  console.log(`Turso: ${isTursoEnabled() ? 'enabled' : 'disabled'}`);
18
24
  if (config.db_path) {
19
25
  console.log(` (custom: ${config.db_path})`);
@@ -66,6 +72,39 @@ export async function selectTheme() {
66
72
  }));
67
73
  });
68
74
  }
75
+ export async function showViewMode() {
76
+ const mode = getViewMode();
77
+ console.log(`Current view mode: ${mode}`);
78
+ }
79
+ export async function setViewModeCommand(mode) {
80
+ if (!VALID_VIEW_MODES.includes(mode)) {
81
+ console.error(`Invalid view mode: ${mode}`);
82
+ console.error(`Valid modes: ${VALID_VIEW_MODES.join(', ')}`);
83
+ process.exit(1);
84
+ }
85
+ setViewMode(mode);
86
+ const messages = {
87
+ gtd: 'View mode set to GTD',
88
+ kanban: 'View mode set to Kanban',
89
+ };
90
+ console.log(messages[mode]);
91
+ }
92
+ export async function selectMode() {
93
+ return new Promise((resolve) => {
94
+ const { unmount } = render(React.createElement(ModeSelector, {
95
+ onSelect: (mode) => {
96
+ unmount();
97
+ setViewMode(mode);
98
+ const messages = {
99
+ gtd: 'View mode set to GTD',
100
+ kanban: 'View mode set to Kanban',
101
+ };
102
+ console.log(messages[mode]);
103
+ resolve();
104
+ },
105
+ }));
106
+ });
107
+ }
69
108
  export async function setTurso(url, token) {
70
109
  setTursoConfig({ url, authToken: token });
71
110
  console.log('Turso sync enabled');
@@ -98,3 +137,86 @@ export async function syncCommand() {
98
137
  process.exit(1);
99
138
  }
100
139
  }
140
+ async function confirm(message) {
141
+ const rl = createInterface({
142
+ input: process.stdin,
143
+ output: process.stdout,
144
+ });
145
+ return new Promise((resolve) => {
146
+ rl.question(`${message} (y/N): `, (answer) => {
147
+ rl.close();
148
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
149
+ });
150
+ });
151
+ }
152
+ async function clearTursoData() {
153
+ const turso = getTursoConfig();
154
+ if (!turso)
155
+ return;
156
+ const { createClient } = await import('@libsql/client');
157
+ const client = createClient({
158
+ url: turso.url,
159
+ authToken: turso.authToken,
160
+ });
161
+ try {
162
+ await client.execute('DELETE FROM comments');
163
+ await client.execute('DELETE FROM tasks');
164
+ console.log('Turso cloud data cleared.');
165
+ }
166
+ finally {
167
+ client.close();
168
+ }
169
+ }
170
+ export async function resetDatabase(force) {
171
+ const dbPath = getDbPath();
172
+ const dbDir = dirname(dbPath);
173
+ const dbName = basename(dbPath, '.db');
174
+ const tursoEnabled = isTursoEnabled();
175
+ // Find all related database files
176
+ const relatedFiles = [];
177
+ if (existsSync(dbDir)) {
178
+ const files = readdirSync(dbDir);
179
+ for (const file of files) {
180
+ // Match: floq.db, floq.db-wal, floq.db-shm, floq-turso.db-info, etc.
181
+ if (file.startsWith(dbName)) {
182
+ relatedFiles.push(join(dbDir, file));
183
+ }
184
+ }
185
+ }
186
+ if (relatedFiles.length === 0 && !tursoEnabled) {
187
+ console.log('Database files do not exist.');
188
+ return;
189
+ }
190
+ if (!force) {
191
+ if (relatedFiles.length > 0) {
192
+ console.log('This will delete the following local files:');
193
+ for (const file of relatedFiles) {
194
+ console.log(` ${file}`);
195
+ }
196
+ }
197
+ if (tursoEnabled) {
198
+ console.log('This will also delete all data in Turso cloud.');
199
+ }
200
+ const confirmed = await confirm('Are you sure?');
201
+ if (!confirmed) {
202
+ console.log('Cancelled.');
203
+ return;
204
+ }
205
+ }
206
+ try {
207
+ // Clear Turso cloud data first
208
+ if (tursoEnabled) {
209
+ await clearTursoData();
210
+ }
211
+ // Delete local files
212
+ for (const file of relatedFiles) {
213
+ unlinkSync(file);
214
+ }
215
+ console.log('Database reset complete.');
216
+ }
217
+ catch (error) {
218
+ const message = error instanceof Error ? error.message : String(error);
219
+ console.error(`Failed to reset database: ${message}`);
220
+ process.exit(1);
221
+ }
222
+ }
@@ -0,0 +1 @@
1
+ export declare function runSetupWizard(): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { render } from 'ink';
2
+ import React from 'react';
3
+ import { SetupWizard } from '../ui/SetupWizard.js';
4
+ export async function runSetupWizard() {
5
+ return new Promise((resolve) => {
6
+ const { unmount } = render(React.createElement(SetupWizard, {
7
+ onComplete: () => {
8
+ unmount();
9
+ resolve();
10
+ },
11
+ }));
12
+ });
13
+ }
package/dist/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ThemeName } from './ui/theme/types.js';
2
2
  export type Locale = 'en' | 'ja';
3
+ export type ViewMode = 'gtd' | 'kanban';
3
4
  export type { ThemeName };
4
5
  export interface TursoConfig {
5
6
  url: string;
@@ -9,6 +10,7 @@ export interface Config {
9
10
  locale: Locale;
10
11
  db_path?: string;
11
12
  theme: ThemeName;
13
+ viewMode: ViewMode;
12
14
  turso?: TursoConfig;
13
15
  }
14
16
  export declare function loadConfig(): Config;
@@ -21,3 +23,6 @@ export declare function getLocale(): Locale;
21
23
  export declare function setLocale(locale: Locale): void;
22
24
  export declare function getThemeName(): ThemeName;
23
25
  export declare function setThemeName(theme: ThemeName): void;
26
+ export declare function getViewMode(): ViewMode;
27
+ export declare function setViewMode(viewMode: ViewMode): void;
28
+ export declare function isFirstRun(): boolean;
package/dist/config.js CHANGED
@@ -1,9 +1,37 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
2
2
  import { dirname, join, isAbsolute } from 'path';
3
3
  import { CONFIG_FILE, DATA_DIR } from './paths.js';
4
+ // Migrate legacy DB file names (including related metadata files)
5
+ function migrateDbFiles() {
6
+ const legacyDb = join(DATA_DIR, 'gtd.db');
7
+ const newDb = join(DATA_DIR, 'floq.db');
8
+ const legacyTursoDb = join(DATA_DIR, 'gtd-turso.db');
9
+ const newTursoDb = join(DATA_DIR, 'floq-turso.db');
10
+ // Turso/libsql related file suffixes
11
+ const tursoSuffixes = ['', '-info', '-shm', '-wal'];
12
+ try {
13
+ if (existsSync(legacyDb) && !existsSync(newDb)) {
14
+ renameSync(legacyDb, newDb);
15
+ }
16
+ // Migrate Turso DB and all related metadata files
17
+ for (const suffix of tursoSuffixes) {
18
+ const legacyFile = legacyTursoDb + suffix;
19
+ const newFile = newTursoDb + suffix;
20
+ if (existsSync(legacyFile) && !existsSync(newFile)) {
21
+ renameSync(legacyFile, newFile);
22
+ }
23
+ }
24
+ }
25
+ catch {
26
+ // Ignore migration errors
27
+ }
28
+ }
29
+ // Run DB file migration on module load
30
+ migrateDbFiles();
4
31
  const DEFAULT_CONFIG = {
5
32
  locale: 'en',
6
33
  theme: 'modern',
34
+ viewMode: 'gtd',
7
35
  };
8
36
  let configCache = null;
9
37
  export function loadConfig() {
@@ -60,9 +88,9 @@ export function getDbPath() {
60
88
  }
61
89
  // Turso モードでは別のDBファイルを使用(embedded replica 用)
62
90
  if (isTursoEnabled()) {
63
- return join(DATA_DIR, 'gtd-turso.db');
91
+ return join(DATA_DIR, 'floq-turso.db');
64
92
  }
65
- return join(DATA_DIR, 'gtd.db');
93
+ return join(DATA_DIR, 'floq.db');
66
94
  }
67
95
  export function getLocale() {
68
96
  return loadConfig().locale;
@@ -76,3 +104,12 @@ export function getThemeName() {
76
104
  export function setThemeName(theme) {
77
105
  saveConfig({ theme });
78
106
  }
107
+ export function getViewMode() {
108
+ return loadConfig().viewMode || 'gtd';
109
+ }
110
+ export function setViewMode(viewMode) {
111
+ saveConfig({ viewMode });
112
+ }
113
+ export function isFirstRun() {
114
+ return !existsSync(CONFIG_FILE);
115
+ }
package/dist/db/index.js CHANGED
@@ -43,6 +43,16 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
43
43
  await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
44
44
  await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
45
45
  }
46
+ // Create comments table
47
+ await remoteClient.execute(`
48
+ CREATE TABLE IF NOT EXISTS comments (
49
+ id TEXT PRIMARY KEY,
50
+ task_id TEXT NOT NULL,
51
+ content TEXT NOT NULL,
52
+ created_at INTEGER NOT NULL
53
+ )
54
+ `);
55
+ await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_comments_task_id ON comments(task_id)");
46
56
  }
47
57
  finally {
48
58
  remoteClient.close();
@@ -95,6 +105,16 @@ async function initializeLocalSchema() {
95
105
  await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
96
106
  await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
97
107
  }
108
+ // Create comments table
109
+ await client.execute(`
110
+ CREATE TABLE IF NOT EXISTS comments (
111
+ id TEXT PRIMARY KEY,
112
+ task_id TEXT NOT NULL,
113
+ content TEXT NOT NULL,
114
+ created_at INTEGER NOT NULL
115
+ )
116
+ `);
117
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_comments_task_id ON comments(task_id)");
98
118
  }
99
119
  // DB 初期化
100
120
  export async function initDb() {
@@ -190,3 +190,86 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
190
190
  export type Task = typeof tasks.$inferSelect;
191
191
  export type NewTask = typeof tasks.$inferInsert;
192
192
  export type TaskStatus = 'inbox' | 'next' | 'waiting' | 'someday' | 'done';
193
+ export declare const comments: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
194
+ name: "comments";
195
+ schema: undefined;
196
+ columns: {
197
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
198
+ name: "id";
199
+ tableName: "comments";
200
+ dataType: "string";
201
+ columnType: "SQLiteText";
202
+ data: string;
203
+ driverParam: string;
204
+ notNull: true;
205
+ hasDefault: false;
206
+ isPrimaryKey: true;
207
+ isAutoincrement: false;
208
+ hasRuntimeDefault: false;
209
+ enumValues: [string, ...string[]];
210
+ baseColumn: never;
211
+ identity: undefined;
212
+ generated: undefined;
213
+ }, {}, {
214
+ length: number | undefined;
215
+ }>;
216
+ taskId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
217
+ name: "task_id";
218
+ tableName: "comments";
219
+ dataType: "string";
220
+ columnType: "SQLiteText";
221
+ data: string;
222
+ driverParam: string;
223
+ notNull: true;
224
+ hasDefault: false;
225
+ isPrimaryKey: false;
226
+ isAutoincrement: false;
227
+ hasRuntimeDefault: false;
228
+ enumValues: [string, ...string[]];
229
+ baseColumn: never;
230
+ identity: undefined;
231
+ generated: undefined;
232
+ }, {}, {
233
+ length: number | undefined;
234
+ }>;
235
+ content: import("drizzle-orm/sqlite-core").SQLiteColumn<{
236
+ name: "content";
237
+ tableName: "comments";
238
+ dataType: "string";
239
+ columnType: "SQLiteText";
240
+ data: string;
241
+ driverParam: string;
242
+ notNull: true;
243
+ hasDefault: false;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: [string, ...string[]];
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {
252
+ length: number | undefined;
253
+ }>;
254
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
255
+ name: "created_at";
256
+ tableName: "comments";
257
+ dataType: "date";
258
+ columnType: "SQLiteTimestamp";
259
+ data: Date;
260
+ driverParam: number;
261
+ notNull: true;
262
+ hasDefault: false;
263
+ isPrimaryKey: false;
264
+ isAutoincrement: false;
265
+ hasRuntimeDefault: false;
266
+ enumValues: undefined;
267
+ baseColumn: never;
268
+ identity: undefined;
269
+ generated: undefined;
270
+ }, {}, {}>;
271
+ };
272
+ dialect: "sqlite";
273
+ }>;
274
+ export type Comment = typeof comments.$inferSelect;
275
+ export type NewComment = typeof comments.$inferInsert;
package/dist/db/schema.js CHANGED
@@ -11,3 +11,9 @@ export const tasks = sqliteTable('tasks', {
11
11
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
12
12
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
13
13
  });
14
+ export const comments = sqliteTable('comments', {
15
+ id: text('id').primaryKey(),
16
+ taskId: text('task_id').notNull(),
17
+ content: text('content').notNull(),
18
+ createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
19
+ });