shipsheet 0.0.1 → 0.0.4
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.md +94 -0
- package/dist/export.d.ts +6 -0
- package/dist/export.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +188 -0
- package/dist/store.d.ts +20 -0
- package/dist/store.js +93 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +1 -0
- package/package.json +30 -9
- package/skill/SKILL.md +74 -0
- package/skill/spec.md +90 -0
- package/.commandcode/taste/cli/taste.md +0 -22
- package/.commandcode/taste/taste.md +0 -4
- package/tsconfig.json +0 -15
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# shipsheet
|
|
2
|
+
|
|
3
|
+
CLI for managing a local `tasks.json` ship sheet - exportable to Kanban or Google Sheets.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g shipsheet
|
|
9
|
+
# or
|
|
10
|
+
npx shipsheet
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Add tasks
|
|
17
|
+
ss add "Build authentication" -p high -t "feature,backend" --due 2026-02-01
|
|
18
|
+
ss add "Fix login bug" -d "Users can't login with SSO" -p critical
|
|
19
|
+
|
|
20
|
+
# List tasks
|
|
21
|
+
ss list # All tasks
|
|
22
|
+
ss ls -s in-progress # Filter by status
|
|
23
|
+
ss ls -p high # Filter by priority
|
|
24
|
+
ss ls -t feature # Filter by tag
|
|
25
|
+
|
|
26
|
+
# Manage tasks
|
|
27
|
+
ss show <id> # Show task details
|
|
28
|
+
ss update <id> -t "New title" -p medium
|
|
29
|
+
ss move <id> in-progress # Move to status
|
|
30
|
+
ss done <id> # Mark as done
|
|
31
|
+
ss rm <id> # Remove task
|
|
32
|
+
|
|
33
|
+
# Export
|
|
34
|
+
ss export # JSON (default)
|
|
35
|
+
ss export -f csv # CSV for spreadsheets
|
|
36
|
+
ss export -f kanban # Kanban board format
|
|
37
|
+
ss export -f sheets # Google Sheets array format
|
|
38
|
+
ss export -f markdown # Markdown format
|
|
39
|
+
ss export -f csv -o tasks.csv # Save to file
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Task Structure
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"id": "mkwbbi25gbew2",
|
|
47
|
+
"title": "Build authentication",
|
|
48
|
+
"description": "Optional description",
|
|
49
|
+
"status": "todo",
|
|
50
|
+
"priority": "high",
|
|
51
|
+
"tags": ["feature", "backend"],
|
|
52
|
+
"dueDate": "2026-02-01",
|
|
53
|
+
"createdAt": "2026-01-27T08:00:00.000Z",
|
|
54
|
+
"updatedAt": "2026-01-27T08:00:00.000Z"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Statuses
|
|
59
|
+
|
|
60
|
+
- `todo` - Not started
|
|
61
|
+
- `in-progress` - Currently working
|
|
62
|
+
- `done` - Completed
|
|
63
|
+
- `blocked` - Blocked by something
|
|
64
|
+
|
|
65
|
+
## Priorities
|
|
66
|
+
|
|
67
|
+
- `critical` - 🔴 Drop everything
|
|
68
|
+
- `high` - 🟠 Important
|
|
69
|
+
- `medium` - 🟡 Normal (default)
|
|
70
|
+
- `low` - 🟢 When time permits
|
|
71
|
+
|
|
72
|
+
## Export Formats
|
|
73
|
+
|
|
74
|
+
| Format | Use Case |
|
|
75
|
+
|--------|----------|
|
|
76
|
+
| `json` | Full data backup, API integration |
|
|
77
|
+
| `csv` | Google Sheets, Excel import |
|
|
78
|
+
| `kanban` | Kanban board tools (Trello, Notion) |
|
|
79
|
+
| `sheets` | Google Sheets API (2D array) |
|
|
80
|
+
| `markdown` | Documentation, GitHub issues |
|
|
81
|
+
|
|
82
|
+
## AI Skills
|
|
83
|
+
|
|
84
|
+
Add shipsheet as a skill for AI coding agents:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx skills ahmadawais/shipsheet
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This teaches AI agents how to use shipsheet commands in your projects.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Task } from './types.js';
|
|
2
|
+
export declare function toCSV(): string;
|
|
3
|
+
export declare function toKanban(): Record<string, Task[]>;
|
|
4
|
+
export declare function toGoogleSheetsFormat(): string[][];
|
|
5
|
+
export declare function toJSON(): string;
|
|
6
|
+
export declare function toMarkdown(): string;
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { loadSheet } from './store.js';
|
|
2
|
+
export function toCSV() {
|
|
3
|
+
const sheet = loadSheet();
|
|
4
|
+
const headers = ['ID', 'Title', 'Description', 'Status', 'Priority', 'Tags', 'Due Date', 'Created', 'Updated'];
|
|
5
|
+
const rows = sheet.tasks.map((t) => [
|
|
6
|
+
t.id,
|
|
7
|
+
`"${t.title.replace(/"/g, '""')}"`,
|
|
8
|
+
`"${(t.description || '').replace(/"/g, '""')}"`,
|
|
9
|
+
t.status,
|
|
10
|
+
t.priority,
|
|
11
|
+
`"${(t.tags || []).join(', ')}"`,
|
|
12
|
+
t.dueDate || '',
|
|
13
|
+
t.createdAt,
|
|
14
|
+
t.updatedAt,
|
|
15
|
+
]);
|
|
16
|
+
return [headers.join(','), ...rows.map((r) => r.join(','))].join('\n');
|
|
17
|
+
}
|
|
18
|
+
export function toKanban() {
|
|
19
|
+
const sheet = loadSheet();
|
|
20
|
+
return {
|
|
21
|
+
todo: sheet.tasks.filter((t) => t.status === 'todo'),
|
|
22
|
+
'in-progress': sheet.tasks.filter((t) => t.status === 'in-progress'),
|
|
23
|
+
done: sheet.tasks.filter((t) => t.status === 'done'),
|
|
24
|
+
blocked: sheet.tasks.filter((t) => t.status === 'blocked'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function toGoogleSheetsFormat() {
|
|
28
|
+
const sheet = loadSheet();
|
|
29
|
+
const headers = ['ID', 'Title', 'Description', 'Status', 'Priority', 'Tags', 'Due Date', 'Created', 'Updated'];
|
|
30
|
+
const rows = sheet.tasks.map((t) => [
|
|
31
|
+
t.id,
|
|
32
|
+
t.title,
|
|
33
|
+
t.description || '',
|
|
34
|
+
t.status,
|
|
35
|
+
t.priority,
|
|
36
|
+
(t.tags || []).join(', '),
|
|
37
|
+
t.dueDate || '',
|
|
38
|
+
t.createdAt,
|
|
39
|
+
t.updatedAt,
|
|
40
|
+
]);
|
|
41
|
+
return [headers, ...rows];
|
|
42
|
+
}
|
|
43
|
+
export function toJSON() {
|
|
44
|
+
const sheet = loadSheet();
|
|
45
|
+
return JSON.stringify(sheet, null, 2);
|
|
46
|
+
}
|
|
47
|
+
export function toMarkdown() {
|
|
48
|
+
const kanban = toKanban();
|
|
49
|
+
let md = '# Ship Sheet\n\n';
|
|
50
|
+
for (const [status, tasks] of Object.entries(kanban)) {
|
|
51
|
+
md += `## ${status.charAt(0).toUpperCase() + status.slice(1)}\n\n`;
|
|
52
|
+
if (tasks.length === 0) {
|
|
53
|
+
md += '_No tasks_\n\n';
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const task of tasks) {
|
|
57
|
+
const priority = task.priority === 'critical' ? '🔴' : task.priority === 'high' ? '🟠' : task.priority === 'medium' ? '🟡' : '🟢';
|
|
58
|
+
md += `- ${priority} **${task.title}**`;
|
|
59
|
+
if (task.description)
|
|
60
|
+
md += ` - ${task.description}`;
|
|
61
|
+
if (task.dueDate)
|
|
62
|
+
md += ` (Due: ${task.dueDate})`;
|
|
63
|
+
md += '\n';
|
|
64
|
+
}
|
|
65
|
+
md += '\n';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return md;
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { writeFileSync } from 'node:fs';
|
|
4
|
+
import { addTask, listTasks, updateTask, removeTask, moveTask, getTask } from './store.js';
|
|
5
|
+
import { toCSV, toKanban, toJSON, toMarkdown, toGoogleSheetsFormat } from './export.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name('shipsheet')
|
|
9
|
+
.description('CLI for managing a local tasks.json ship sheet')
|
|
10
|
+
.version('1.0.0');
|
|
11
|
+
program
|
|
12
|
+
.command('add <title>')
|
|
13
|
+
.description('Add a new task')
|
|
14
|
+
.option('-d, --description <desc>', 'Task description')
|
|
15
|
+
.option('-s, --status <status>', 'Task status (todo, in-progress, done, blocked)', 'todo')
|
|
16
|
+
.option('-p, --priority <priority>', 'Task priority (low, medium, high, critical)', 'medium')
|
|
17
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
18
|
+
.option('--due <date>', 'Due date (YYYY-MM-DD)')
|
|
19
|
+
.action((title, opts) => {
|
|
20
|
+
const task = addTask(title, {
|
|
21
|
+
description: opts.description,
|
|
22
|
+
status: opts.status,
|
|
23
|
+
priority: opts.priority,
|
|
24
|
+
tags: opts.tags?.split(',').map((t) => t.trim()),
|
|
25
|
+
dueDate: opts.due,
|
|
26
|
+
});
|
|
27
|
+
console.log(`✓ Added task: ${task.title} [${task.id}]`);
|
|
28
|
+
});
|
|
29
|
+
program
|
|
30
|
+
.command('list')
|
|
31
|
+
.alias('ls')
|
|
32
|
+
.description('List all tasks')
|
|
33
|
+
.option('-s, --status <status>', 'Filter by status')
|
|
34
|
+
.option('-p, --priority <priority>', 'Filter by priority')
|
|
35
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
36
|
+
.action((opts) => {
|
|
37
|
+
const tasks = listTasks({
|
|
38
|
+
status: opts.status,
|
|
39
|
+
priority: opts.priority,
|
|
40
|
+
tag: opts.tag,
|
|
41
|
+
});
|
|
42
|
+
if (tasks.length === 0) {
|
|
43
|
+
console.log('No tasks found.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const statusIcon = {
|
|
47
|
+
todo: '○',
|
|
48
|
+
'in-progress': '◐',
|
|
49
|
+
done: '●',
|
|
50
|
+
blocked: '✕',
|
|
51
|
+
};
|
|
52
|
+
const priorityColor = {
|
|
53
|
+
critical: '\x1b[31m',
|
|
54
|
+
high: '\x1b[33m',
|
|
55
|
+
medium: '\x1b[36m',
|
|
56
|
+
low: '\x1b[90m',
|
|
57
|
+
};
|
|
58
|
+
const reset = '\x1b[0m';
|
|
59
|
+
for (const task of tasks) {
|
|
60
|
+
const icon = statusIcon[task.status];
|
|
61
|
+
const color = priorityColor[task.priority];
|
|
62
|
+
const tags = task.tags?.length ? ` [${task.tags.join(', ')}]` : '';
|
|
63
|
+
const due = task.dueDate ? ` (due: ${task.dueDate})` : '';
|
|
64
|
+
console.log(`${icon} ${color}${task.id.slice(0, 7)}${reset} ${task.title}${tags}${due}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
program
|
|
68
|
+
.command('show <id>')
|
|
69
|
+
.description('Show task details')
|
|
70
|
+
.action((id) => {
|
|
71
|
+
const task = getTask(id);
|
|
72
|
+
if (!task) {
|
|
73
|
+
console.error(`Task not found: ${id}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
console.log(`ID: ${task.id}`);
|
|
77
|
+
console.log(`Title: ${task.title}`);
|
|
78
|
+
console.log(`Description: ${task.description || '-'}`);
|
|
79
|
+
console.log(`Status: ${task.status}`);
|
|
80
|
+
console.log(`Priority: ${task.priority}`);
|
|
81
|
+
console.log(`Tags: ${task.tags?.join(', ') || '-'}`);
|
|
82
|
+
console.log(`Due: ${task.dueDate || '-'}`);
|
|
83
|
+
console.log(`Created: ${task.createdAt}`);
|
|
84
|
+
console.log(`Updated: ${task.updatedAt}`);
|
|
85
|
+
});
|
|
86
|
+
program
|
|
87
|
+
.command('update <id>')
|
|
88
|
+
.description('Update a task')
|
|
89
|
+
.option('-t, --title <title>', 'New title')
|
|
90
|
+
.option('-d, --description <desc>', 'New description')
|
|
91
|
+
.option('-s, --status <status>', 'New status')
|
|
92
|
+
.option('-p, --priority <priority>', 'New priority')
|
|
93
|
+
.option('--tags <tags>', 'New tags (comma-separated)')
|
|
94
|
+
.option('--due <date>', 'New due date')
|
|
95
|
+
.action((id, opts) => {
|
|
96
|
+
const updates = {};
|
|
97
|
+
if (opts.title)
|
|
98
|
+
updates.title = opts.title;
|
|
99
|
+
if (opts.description)
|
|
100
|
+
updates.description = opts.description;
|
|
101
|
+
if (opts.status)
|
|
102
|
+
updates.status = opts.status;
|
|
103
|
+
if (opts.priority)
|
|
104
|
+
updates.priority = opts.priority;
|
|
105
|
+
if (opts.tags)
|
|
106
|
+
updates.tags = opts.tags.split(',').map((t) => t.trim());
|
|
107
|
+
if (opts.due)
|
|
108
|
+
updates.dueDate = opts.due;
|
|
109
|
+
const task = updateTask(id, updates);
|
|
110
|
+
if (!task) {
|
|
111
|
+
console.error(`Task not found: ${id}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(`✓ Updated task: ${task.title}`);
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command('remove <id>')
|
|
118
|
+
.alias('rm')
|
|
119
|
+
.description('Remove a task')
|
|
120
|
+
.action((id) => {
|
|
121
|
+
const removed = removeTask(id);
|
|
122
|
+
if (!removed) {
|
|
123
|
+
console.error(`Task not found: ${id}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
console.log(`✓ Removed task`);
|
|
127
|
+
});
|
|
128
|
+
program
|
|
129
|
+
.command('move <id> <status>')
|
|
130
|
+
.description('Move task to a status (todo, in-progress, done, blocked)')
|
|
131
|
+
.action((id, status) => {
|
|
132
|
+
const validStatuses = ['todo', 'in-progress', 'done', 'blocked'];
|
|
133
|
+
if (!validStatuses.includes(status)) {
|
|
134
|
+
console.error(`Invalid status. Use: ${validStatuses.join(', ')}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const task = moveTask(id, status);
|
|
138
|
+
if (!task) {
|
|
139
|
+
console.error(`Task not found: ${id}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
console.log(`✓ Moved "${task.title}" to ${status}`);
|
|
143
|
+
});
|
|
144
|
+
program
|
|
145
|
+
.command('done <id>')
|
|
146
|
+
.description('Mark task as done')
|
|
147
|
+
.action((id) => {
|
|
148
|
+
const task = moveTask(id, 'done');
|
|
149
|
+
if (!task) {
|
|
150
|
+
console.error(`Task not found: ${id}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
console.log(`✓ Completed: ${task.title}`);
|
|
154
|
+
});
|
|
155
|
+
program
|
|
156
|
+
.command('export')
|
|
157
|
+
.description('Export tasks to various formats')
|
|
158
|
+
.option('-f, --format <format>', 'Export format (csv, json, kanban, markdown, sheets)', 'json')
|
|
159
|
+
.option('-o, --output <file>', 'Output file (prints to stdout if not specified)')
|
|
160
|
+
.action((opts) => {
|
|
161
|
+
let output;
|
|
162
|
+
switch (opts.format) {
|
|
163
|
+
case 'csv':
|
|
164
|
+
output = toCSV();
|
|
165
|
+
break;
|
|
166
|
+
case 'kanban':
|
|
167
|
+
output = JSON.stringify(toKanban(), null, 2);
|
|
168
|
+
break;
|
|
169
|
+
case 'markdown':
|
|
170
|
+
case 'md':
|
|
171
|
+
output = toMarkdown();
|
|
172
|
+
break;
|
|
173
|
+
case 'sheets':
|
|
174
|
+
output = JSON.stringify(toGoogleSheetsFormat(), null, 2);
|
|
175
|
+
break;
|
|
176
|
+
case 'json':
|
|
177
|
+
default:
|
|
178
|
+
output = toJSON();
|
|
179
|
+
}
|
|
180
|
+
if (opts.output) {
|
|
181
|
+
writeFileSync(opts.output, output);
|
|
182
|
+
console.log(`✓ Exported to ${opts.output}`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(output);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
program.parse();
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ShipSheet, Task, TaskStatus, TaskPriority } from './types.js';
|
|
2
|
+
export declare function loadSheet(): ShipSheet;
|
|
3
|
+
export declare function saveSheet(sheet: ShipSheet): void;
|
|
4
|
+
export declare function generateId(): string;
|
|
5
|
+
export declare function addTask(title: string, options?: {
|
|
6
|
+
description?: string;
|
|
7
|
+
status?: TaskStatus;
|
|
8
|
+
priority?: TaskPriority;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
dueDate?: string;
|
|
11
|
+
}): Task;
|
|
12
|
+
export declare function listTasks(filter?: {
|
|
13
|
+
status?: TaskStatus;
|
|
14
|
+
priority?: TaskPriority;
|
|
15
|
+
tag?: string;
|
|
16
|
+
}): Task[];
|
|
17
|
+
export declare function getTask(id: string): Task | undefined;
|
|
18
|
+
export declare function updateTask(id: string, updates: Partial<Omit<Task, 'id' | 'createdAt'>>): Task | null;
|
|
19
|
+
export declare function removeTask(id: string): boolean;
|
|
20
|
+
export declare function moveTask(id: string, status: TaskStatus): Task | null;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const TASKS_FILE = 'tasks.json';
|
|
4
|
+
function getTasksPath() {
|
|
5
|
+
return join(process.cwd(), TASKS_FILE);
|
|
6
|
+
}
|
|
7
|
+
function createDefaultSheet() {
|
|
8
|
+
return {
|
|
9
|
+
name: 'Ship Sheet',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
tasks: [],
|
|
12
|
+
createdAt: new Date().toISOString(),
|
|
13
|
+
updatedAt: new Date().toISOString(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function loadSheet() {
|
|
17
|
+
const path = getTasksPath();
|
|
18
|
+
if (!existsSync(path)) {
|
|
19
|
+
const sheet = createDefaultSheet();
|
|
20
|
+
saveSheet(sheet);
|
|
21
|
+
return sheet;
|
|
22
|
+
}
|
|
23
|
+
const data = readFileSync(path, 'utf-8');
|
|
24
|
+
return JSON.parse(data);
|
|
25
|
+
}
|
|
26
|
+
export function saveSheet(sheet) {
|
|
27
|
+
sheet.updatedAt = new Date().toISOString();
|
|
28
|
+
writeFileSync(getTasksPath(), JSON.stringify(sheet, null, 2));
|
|
29
|
+
}
|
|
30
|
+
export function generateId() {
|
|
31
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
32
|
+
}
|
|
33
|
+
export function addTask(title, options = {}) {
|
|
34
|
+
const sheet = loadSheet();
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
const task = {
|
|
37
|
+
id: generateId(),
|
|
38
|
+
title,
|
|
39
|
+
description: options.description,
|
|
40
|
+
status: options.status || 'todo',
|
|
41
|
+
priority: options.priority || 'medium',
|
|
42
|
+
tags: options.tags,
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
dueDate: options.dueDate,
|
|
46
|
+
};
|
|
47
|
+
sheet.tasks.push(task);
|
|
48
|
+
saveSheet(sheet);
|
|
49
|
+
return task;
|
|
50
|
+
}
|
|
51
|
+
export function listTasks(filter) {
|
|
52
|
+
const sheet = loadSheet();
|
|
53
|
+
let tasks = sheet.tasks;
|
|
54
|
+
if (filter?.status) {
|
|
55
|
+
tasks = tasks.filter((t) => t.status === filter.status);
|
|
56
|
+
}
|
|
57
|
+
if (filter?.priority) {
|
|
58
|
+
tasks = tasks.filter((t) => t.priority === filter.priority);
|
|
59
|
+
}
|
|
60
|
+
if (filter?.tag) {
|
|
61
|
+
tasks = tasks.filter((t) => t.tags?.includes(filter.tag));
|
|
62
|
+
}
|
|
63
|
+
return tasks;
|
|
64
|
+
}
|
|
65
|
+
export function getTask(id) {
|
|
66
|
+
const sheet = loadSheet();
|
|
67
|
+
return sheet.tasks.find((t) => t.id === id || t.id.startsWith(id));
|
|
68
|
+
}
|
|
69
|
+
export function updateTask(id, updates) {
|
|
70
|
+
const sheet = loadSheet();
|
|
71
|
+
const index = sheet.tasks.findIndex((t) => t.id === id || t.id.startsWith(id));
|
|
72
|
+
if (index === -1)
|
|
73
|
+
return null;
|
|
74
|
+
sheet.tasks[index] = {
|
|
75
|
+
...sheet.tasks[index],
|
|
76
|
+
...updates,
|
|
77
|
+
updatedAt: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
saveSheet(sheet);
|
|
80
|
+
return sheet.tasks[index];
|
|
81
|
+
}
|
|
82
|
+
export function removeTask(id) {
|
|
83
|
+
const sheet = loadSheet();
|
|
84
|
+
const index = sheet.tasks.findIndex((t) => t.id === id || t.id.startsWith(id));
|
|
85
|
+
if (index === -1)
|
|
86
|
+
return false;
|
|
87
|
+
sheet.tasks.splice(index, 1);
|
|
88
|
+
saveSheet(sheet);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
export function moveTask(id, status) {
|
|
92
|
+
return updateTask(id, { status });
|
|
93
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type TaskStatus = 'todo' | 'in-progress' | 'done' | 'blocked';
|
|
2
|
+
export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
|
|
3
|
+
export interface Task {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
status: TaskStatus;
|
|
8
|
+
priority: TaskPriority;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
createdAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
dueDate?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ShipSheet {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
tasks: Task[];
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipsheet",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "CLI for managing a local tasks.json ship sheet - exportable to Kanban or Google Sheets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,19 +8,40 @@
|
|
|
8
8
|
"shipsheet": "dist/index.js",
|
|
9
9
|
"ss": "dist/index.js"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"tasks",
|
|
14
|
+
"todo",
|
|
15
|
+
"kanban",
|
|
16
|
+
"shipsheet",
|
|
17
|
+
"skills"
|
|
18
|
+
],
|
|
18
19
|
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/ahmadawais/shipsheet.git"
|
|
23
|
+
},
|
|
24
|
+
"author": "Ahmad Awais",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"skill"
|
|
28
|
+
],
|
|
19
29
|
"devDependencies": {
|
|
30
|
+
"@changesets/changelog-github": "^0.5.2",
|
|
31
|
+
"@changesets/cli": "^2.29.8",
|
|
20
32
|
"@types/node": "^20.10.0",
|
|
21
33
|
"typescript": "^5.3.0"
|
|
22
34
|
},
|
|
23
35
|
"dependencies": {
|
|
24
36
|
"commander": "^12.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"dev": "tsc --watch",
|
|
41
|
+
"start": "node dist/index.js",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"changeset": "changeset",
|
|
44
|
+
"version": "changeset version",
|
|
45
|
+
"release": "pnpm build && changeset publish && git push --follow-tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes"
|
|
25
46
|
}
|
|
26
|
-
}
|
|
47
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# ShipSheet Skill
|
|
2
|
+
|
|
3
|
+
Manage local task lists with shipsheet CLI. Use when asked to track tasks, create todo lists, or manage a ship sheet.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- User wants to track tasks or todos locally
|
|
8
|
+
- User needs a ship sheet for project management
|
|
9
|
+
- User wants to export tasks to Kanban or Google Sheets
|
|
10
|
+
- User asks to "add a task", "list tasks", "mark done", etc.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
### Add Task
|
|
15
|
+
```bash
|
|
16
|
+
npx shipsheet add "Task title" -p <priority> -t "tag1,tag2" -d "description" --due YYYY-MM-DD
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
- `-p, --priority`: low, medium, high, critical
|
|
21
|
+
- `-t, --tags`: Comma-separated tags
|
|
22
|
+
- `-d, --description`: Task description
|
|
23
|
+
- `--due`: Due date
|
|
24
|
+
|
|
25
|
+
### List Tasks
|
|
26
|
+
```bash
|
|
27
|
+
npx shipsheet list
|
|
28
|
+
npx shipsheet ls -s <status> -p <priority> -t <tag>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Update Task
|
|
32
|
+
```bash
|
|
33
|
+
npx shipsheet update <id> -t "New title" -s <status> -p <priority>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Move Task Status
|
|
37
|
+
```bash
|
|
38
|
+
npx shipsheet move <id> <status>
|
|
39
|
+
npx shipsheet done <id>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Statuses: `todo`, `in-progress`, `done`, `blocked`
|
|
43
|
+
|
|
44
|
+
### Remove Task
|
|
45
|
+
```bash
|
|
46
|
+
npx shipsheet rm <id>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Export Tasks
|
|
50
|
+
```bash
|
|
51
|
+
npx shipsheet export -f <format> -o <output-file>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Formats: `json`, `csv`, `kanban`, `markdown`, `sheets`
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Add high priority task with tags
|
|
60
|
+
npx shipsheet add "Fix auth bug" -p critical -t "bug,auth"
|
|
61
|
+
|
|
62
|
+
# List only in-progress tasks
|
|
63
|
+
npx shipsheet ls -s in-progress
|
|
64
|
+
|
|
65
|
+
# Mark task done (use first few chars of ID)
|
|
66
|
+
npx shipsheet done mkwbb
|
|
67
|
+
|
|
68
|
+
# Export to CSV for Google Sheets
|
|
69
|
+
npx shipsheet export -f csv -o tasks.csv
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## File Location
|
|
73
|
+
|
|
74
|
+
Tasks are stored in `tasks.json` in the current directory.
|
package/skill/spec.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ShipSheet Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
ShipSheet is a CLI tool for managing local task lists stored in `tasks.json`. It provides a simple interface for task management with export capabilities for Kanban boards and Google Sheets.
|
|
6
|
+
|
|
7
|
+
## Data Model
|
|
8
|
+
|
|
9
|
+
### ShipSheet (Root)
|
|
10
|
+
```typescript
|
|
11
|
+
interface ShipSheet {
|
|
12
|
+
name: string; // Sheet name
|
|
13
|
+
version: string; // Schema version
|
|
14
|
+
tasks: Task[]; // Array of tasks
|
|
15
|
+
createdAt: string; // ISO timestamp
|
|
16
|
+
updatedAt: string; // ISO timestamp
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Task
|
|
21
|
+
```typescript
|
|
22
|
+
interface Task {
|
|
23
|
+
id: string; // Unique identifier (base36 timestamp + random)
|
|
24
|
+
title: string; // Task title (required)
|
|
25
|
+
description?: string; // Optional description
|
|
26
|
+
status: TaskStatus; // Current status
|
|
27
|
+
priority: TaskPriority; // Priority level
|
|
28
|
+
tags?: string[]; // Optional tags array
|
|
29
|
+
dueDate?: string; // Optional due date (YYYY-MM-DD)
|
|
30
|
+
createdAt: string; // ISO timestamp
|
|
31
|
+
updatedAt: string; // ISO timestamp
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type TaskStatus = 'todo' | 'in-progress' | 'done' | 'blocked';
|
|
35
|
+
type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## CLI Commands
|
|
39
|
+
|
|
40
|
+
| Command | Alias | Description |
|
|
41
|
+
|---------|-------|-------------|
|
|
42
|
+
| `add <title>` | - | Add new task |
|
|
43
|
+
| `list` | `ls` | List all tasks |
|
|
44
|
+
| `show <id>` | - | Show task details |
|
|
45
|
+
| `update <id>` | - | Update task properties |
|
|
46
|
+
| `move <id> <status>` | - | Change task status |
|
|
47
|
+
| `done <id>` | - | Mark task as done |
|
|
48
|
+
| `remove <id>` | `rm` | Delete task |
|
|
49
|
+
| `export` | - | Export to various formats |
|
|
50
|
+
|
|
51
|
+
## Export Formats
|
|
52
|
+
|
|
53
|
+
### JSON
|
|
54
|
+
Full `tasks.json` content with all metadata.
|
|
55
|
+
|
|
56
|
+
### CSV
|
|
57
|
+
```
|
|
58
|
+
ID,Title,Description,Status,Priority,Tags,Due Date,Created,Updated
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Kanban
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"todo": [...],
|
|
65
|
+
"in-progress": [...],
|
|
66
|
+
"done": [...],
|
|
67
|
+
"blocked": [...]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Google Sheets (2D Array)
|
|
72
|
+
```json
|
|
73
|
+
[
|
|
74
|
+
["ID", "Title", "Description", "Status", "Priority", "Tags", "Due Date", "Created", "Updated"],
|
|
75
|
+
["id1", "Task 1", "", "todo", "high", "tag1, tag2", "", "2026-01-27", "2026-01-27"]
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Markdown
|
|
80
|
+
Kanban-style markdown with priority indicators and grouped by status.
|
|
81
|
+
|
|
82
|
+
## ID Matching
|
|
83
|
+
|
|
84
|
+
Task IDs support partial matching - you can use the first few characters of an ID to reference a task (e.g., `mkwbb` instead of `mkwbbi25gbew2`).
|
|
85
|
+
|
|
86
|
+
## File Storage
|
|
87
|
+
|
|
88
|
+
- Location: `./tasks.json` in current working directory
|
|
89
|
+
- Format: Pretty-printed JSON (2-space indent)
|
|
90
|
+
- Auto-created on first task add
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Cli
|
|
2
|
-
- Use pnpm as the package manager for CLI projects. Confidence: 1.00
|
|
3
|
-
- Use TypeScript for CLI projects. Confidence: 0.95
|
|
4
|
-
- Use tsup as the build tool for CLI projects. Confidence: 0.95
|
|
5
|
-
- Use vitest for testing CLI projects. Confidence: 0.95
|
|
6
|
-
- Use Commander.js for CLI command handling. Confidence: 0.95
|
|
7
|
-
- Use clack for interactive user input in CLI projects. Confidence: 0.95
|
|
8
|
-
- Check for existing CLI name conflicts before running npm link. Confidence: 0.95
|
|
9
|
-
- Organize CLI commands in a dedicated commands folder with each module separated. Confidence: 0.95
|
|
10
|
-
- Include a small 150px ASCII art welcome banner displaying the CLI name. Confidence: 0.95
|
|
11
|
-
- Use lowercase flags for version and help commands (-v, --version, -h, --help). Confidence: 0.85
|
|
12
|
-
- Start projects with version 0.0.1 instead of 1.0.0. Confidence: 0.85
|
|
13
|
-
- Version command should output only the version number with no ASCII art, banner, or additional information. Confidence: 0.90
|
|
14
|
-
- Read CLI version from package.json instead of hardcoding it in the source code. Confidence: 0.75
|
|
15
|
-
- Always use ora for loading spinners in CLI projects. Confidence: 0.95
|
|
16
|
-
- Use picocolors for terminal string coloring in CLI projects. Confidence: 0.90
|
|
17
|
-
- Use Ink for building interactive CLI UIs in CommandCode projects. Confidence: 0.80
|
|
18
|
-
- Use ink-spinner for loading animations in Ink-based CLIs. Confidence: 0.70
|
|
19
|
-
- Hide internal flags from help: .addOption(new Option('--local').hideHelp()). Confidence: 0.90
|
|
20
|
-
- Use pnpm.onlyBuiltDependencies in package.json to pre-approve native binary builds. Confidence: 0.60
|
|
21
|
-
- Use ANSI Shadow font for ASCII art at large terminal widths and ANSI Compact for small widths. Confidence: 0.85
|
|
22
|
-
- Use minimal white, gray, and black colors for ASCII art banners. Confidence: 0.85
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"rootDir": "src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"declaration": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*"],
|
|
14
|
-
"exclude": ["node_modules", "dist"]
|
|
15
|
-
}
|