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.
- package/README.ja.md +89 -9
- package/README.md +89 -9
- package/dist/changelog.d.ts +13 -0
- package/dist/changelog.js +95 -0
- package/dist/cli.js +44 -1
- package/dist/commands/comment.d.ts +2 -0
- package/dist/commands/comment.js +67 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +123 -1
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +13 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +40 -3
- package/dist/db/index.js +20 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +6 -0
- package/dist/i18n/en.d.ts +237 -0
- package/dist/i18n/en.js +127 -3
- package/dist/i18n/ja.js +127 -3
- package/dist/index.js +14 -4
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +63 -5
- package/dist/ui/App.js +280 -25
- package/dist/ui/ModeSelector.d.ts +7 -0
- package/dist/ui/ModeSelector.js +37 -0
- package/dist/ui/SetupWizard.d.ts +6 -0
- package/dist/ui/SetupWizard.js +321 -0
- package/dist/ui/components/HelpModal.d.ts +2 -1
- package/dist/ui/components/HelpModal.js +118 -4
- package/dist/ui/components/KanbanBoard.d.ts +6 -0
- package/dist/ui/components/KanbanBoard.js +508 -0
- package/dist/ui/components/KanbanColumn.d.ts +12 -0
- package/dist/ui/components/KanbanColumn.js +11 -0
- package/dist/ui/components/ProgressBar.d.ts +7 -0
- package/dist/ui/components/ProgressBar.js +13 -0
- package/dist/ui/components/SearchBar.d.ts +8 -0
- package/dist/ui/components/SearchBar.js +11 -0
- package/dist/ui/components/SearchResults.d.ts +9 -0
- package/dist/ui/components/SearchResults.js +18 -0
- package/dist/ui/components/TaskItem.d.ts +6 -1
- package/dist/ui/components/TaskItem.js +3 -2
- package/package.json +1 -1
package/dist/commands/config.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { render } from 'ink';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
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, '
|
|
91
|
+
return join(DATA_DIR, 'floq-turso.db');
|
|
64
92
|
}
|
|
65
|
-
return join(DATA_DIR, '
|
|
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() {
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -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
|
+
});
|