think-cli 0.2.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 gudong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: think-cli
3
+ Version: 0.2.0
4
+ Summary: 思咚咚 - 记录灵感和想法的 CLI 工具
5
+ Author-email: gudong <gudong@example.com>
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: typer>=0.9.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: dong-core>=0.3.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
14
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
15
+ Dynamic: license-file
16
+
17
+ # 思咚咚
18
+
19
+ > 💡 记录灵感和思考的 CLI 工具
20
+
21
+ ## 安装
22
+
23
+ ```bash
24
+ pip install think-cli
25
+ ```
26
+
27
+ ## 快速开始
28
+
29
+ ```bash
30
+ # 初始化
31
+ think init
32
+
33
+ # 记录想法
34
+ think add "Agent Hub 可以去中心化"
35
+ think add "想法:用 IPFS 存 Agent 包" --tag ipfs
36
+
37
+ # 列出想法
38
+ think list
39
+ think list --today
40
+ think list --tag agenthub
41
+
42
+ # 搜索
43
+ think search "去中心化"
44
+
45
+ # 回顾
46
+ think review --week
47
+
48
+ # 统计
49
+ think stats
50
+ ```
51
+
52
+ ## 命令
53
+
54
+ | 命令 | 说明 |
55
+ |------|------|
56
+ | `think init` | 初始化数据库 |
57
+ | `think add` | 记录想法 |
58
+ | `think list` | 列出想法 |
59
+ | `think get` | 获取详情 |
60
+ | `think search` | 搜索想法 |
61
+ | `think update` | 更新想法 |
62
+ | `think delete` | 删除想法 |
63
+ | `think review` | 回顾想法 |
64
+ | `think stats` | 统计信息 |
65
+
66
+ ## 数据存储
67
+
68
+ ```
69
+ ~/.think/think.db
70
+ ```
71
+
72
+ ## 开发
73
+
74
+ ```bash
75
+ # 克隆
76
+ git clone https://github.com/gudong/think-cli.git
77
+ cd think-cli
78
+
79
+ # 安装依赖
80
+ python -m venv venv
81
+ source venv/bin/activate
82
+ pip install -e ".[dev]"
83
+
84
+ # 运行测试
85
+ pytest
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT
@@ -0,0 +1,74 @@
1
+ # 思咚咚
2
+
3
+ > 💡 记录灵感和思考的 CLI 工具
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install think-cli
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ```bash
14
+ # 初始化
15
+ think init
16
+
17
+ # 记录想法
18
+ think add "Agent Hub 可以去中心化"
19
+ think add "想法:用 IPFS 存 Agent 包" --tag ipfs
20
+
21
+ # 列出想法
22
+ think list
23
+ think list --today
24
+ think list --tag agenthub
25
+
26
+ # 搜索
27
+ think search "去中心化"
28
+
29
+ # 回顾
30
+ think review --week
31
+
32
+ # 统计
33
+ think stats
34
+ ```
35
+
36
+ ## 命令
37
+
38
+ | 命令 | 说明 |
39
+ |------|------|
40
+ | `think init` | 初始化数据库 |
41
+ | `think add` | 记录想法 |
42
+ | `think list` | 列出想法 |
43
+ | `think get` | 获取详情 |
44
+ | `think search` | 搜索想法 |
45
+ | `think update` | 更新想法 |
46
+ | `think delete` | 删除想法 |
47
+ | `think review` | 回顾想法 |
48
+ | `think stats` | 统计信息 |
49
+
50
+ ## 数据存储
51
+
52
+ ```
53
+ ~/.think/think.db
54
+ ```
55
+
56
+ ## 开发
57
+
58
+ ```bash
59
+ # 克隆
60
+ git clone https://github.com/gudong/think-cli.git
61
+ cd think-cli
62
+
63
+ # 安装依赖
64
+ python -m venv venv
65
+ source venv/bin/activate
66
+ pip install -e ".[dev]"
67
+
68
+ # 运行测试
69
+ pytest
70
+ ```
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "think-cli"
3
+ version = "0.2.0"
4
+ description = "思咚咚 - 记录灵感和想法的 CLI 工具"
5
+ authors = [{name = "gudong", email = "gudong@example.com"}]
6
+ readme = "README.md"
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "typer>=0.9.0",
10
+ "rich>=13.0.0",
11
+ "dong-core>=0.3.0",
12
+ ]
13
+
14
+ [project.optional-dependencies]
15
+ dev = [
16
+ "pytest>=7.0.0",
17
+ "pytest-cov>=4.0.0",
18
+ ]
19
+
20
+ [project.scripts]
21
+ think = "think.cli:app"
22
+
23
+ [build-system]
24
+ requires = ["setuptools>=61.0"]
25
+ build-backend = "setuptools.build_meta"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """思咚咚 - 记录灵感和想法的 CLI 工具"""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """CLI 入口点"""
2
+
3
+ from .cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,28 @@
1
+ """CLI 入口"""
2
+
3
+ import typer
4
+ from . import const
5
+
6
+ app = typer.Typer(
7
+ name="think",
8
+ help=f"思咚咚 - 记录灵感和想法 (v{const.VERSION})"
9
+ )
10
+
11
+ # 导入命令
12
+ from .commands import init, add, ls, get, delete, search, update, review, stats
13
+
14
+ app.command()(init.init)
15
+ app.command()(add.add)
16
+ app.command(name="list")(ls.list_ideas)
17
+ app.command()(get.get)
18
+ app.command()(delete.delete)
19
+ app.command()(search.search)
20
+ app.command()(update.update)
21
+ app.command()(review.review)
22
+ app.command()(stats.stats)
23
+
24
+ def main():
25
+ app()
26
+
27
+ if __name__ == "__main__":
28
+ main()
@@ -0,0 +1,5 @@
1
+ """命令模块初始化"""
2
+
3
+ from . import init, add, ls, get, delete, search, update, review, stats
4
+
5
+ __all__ = ["init", "add", "ls", "get", "delete", "search", "update", "review", "stats"]
@@ -0,0 +1,72 @@
1
+ """add 命令"""
2
+
3
+ import typer
4
+ import json
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from ..core.models import Idea
8
+ from ..db import get_connection
9
+ from ..const import PRIORITIES, STATUSES
10
+ from dong import json_output, ValidationError
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ console = Console()
15
+
16
+
17
+ @json_output
18
+ def add(
19
+ content: str = typer.Argument(..., help="想法内容"),
20
+ tag: str = typer.Option(None, "--tag", "-t", help="标签,多个用逗号分隔"),
21
+ priority: str = typer.Option("normal", "--priority", "-p",
22
+ help="优先级: low/normal/high"),
23
+ context: str = typer.Option(None, "--context", "-c", help="上下文"),
24
+ source_agent: str = typer.Option(None, "--source", "-s", help="来源智能体"),
25
+ note: str = typer.Option(None, "--note", "-n", help="备注"),
26
+ ):
27
+ """记录想法"""
28
+ if not content or not content.strip():
29
+ raise ValidationError("content", "想法内容不能为空")
30
+
31
+ conn = get_connection()
32
+
33
+ # 处理标签
34
+ tags = []
35
+ if tag:
36
+ tags = [t.strip() for t in tag.split(",") if t.strip()]
37
+
38
+ # 创建想法
39
+ idea = Idea(
40
+ content=content.strip(),
41
+ tags=tags if tags else None,
42
+ priority=priority,
43
+ context=context,
44
+ source_agent=source_agent,
45
+ note=note,
46
+ )
47
+
48
+ # 插入数据库
49
+ cursor = conn.cursor()
50
+ cursor.execute(
51
+ """
52
+ INSERT INTO ideas (content, tags, priority, context, source_agent, note, created_at, updated_at)
53
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
54
+ """,
55
+ (
56
+ idea.content,
57
+ json.dumps(idea.tags) if idea.tags else None,
58
+ idea.priority,
59
+ idea.context,
60
+ idea.source_agent,
61
+ idea.note,
62
+ datetime.now().isoformat(),
63
+ datetime.now().isoformat(),
64
+ ),
65
+ )
66
+ conn.commit()
67
+
68
+ idea_id = cursor.lastrowid
69
+ conn.close()
70
+
71
+ idea.id = idea_id
72
+ return idea.to_dict()
@@ -0,0 +1,48 @@
1
+ """delete 命令"""
2
+
3
+ import typer
4
+ import json
5
+ from ..core.models import Idea
6
+ from ..db import get_connection
7
+ from dong import json_output, NotFoundError
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+ @json_output
13
+ def delete(
14
+ idea_id: int = typer.Argument(..., help="想法 ID"),
15
+ force: bool = typer.Option(False, "--force", "-f", help="强制删除,不提示"),
16
+ ):
17
+ """删除想法
18
+
19
+ Args:
20
+ idea_id: 想法 ID
21
+ force: 强制删除,不提示
22
+ """
23
+ conn = get_connection()
24
+ cursor = conn.cursor()
25
+ cursor.execute("SELECT * FROM ideas WHERE id = ?", (idea_id,))
26
+ row = cursor.fetchone()
27
+
28
+ if not row:
29
+ conn.close()
30
+ raise NotFoundError("Idea", idea_id, message=f"未找到 ID 为 {idea_id} 的想法")
31
+
32
+ idea = Idea.from_row(row)
33
+
34
+ if not force:
35
+ confirm = typer.confirm(f"确定要删除想法吗?\n{idea.content}")
36
+ if not confirm:
37
+ conn.close()
38
+ return {"cancelled": True, "message": "已取消删除"}
39
+
40
+ cursor.execute("DELETE FROM ideas WHERE id = ?", (idea_id,))
41
+ conn.commit()
42
+ conn.close()
43
+
44
+ return {"deleted": True, "id": idea_id}
45
+
46
+
47
+ if __name__ == "__main__":
48
+ delete()
@@ -0,0 +1,34 @@
1
+ """get 命令"""
2
+
3
+ import typer
4
+ import json
5
+ from ..core.models import Idea
6
+ from ..db import get_connection
7
+ from dong import json_output, NotFoundError
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+ @json_output
13
+ def get(
14
+ idea_id: int = typer.Argument(..., help="想法 ID"),
15
+ ):
16
+ """获取想法详情
17
+
18
+ Args:
19
+ idea_id: 想法 ID """
20
+ conn = get_connection()
21
+ cursor = conn.cursor()
22
+ cursor.execute("SELECT * FROM ideas WHERE id = ?", (idea_id,))
23
+ row = cursor.fetchone()
24
+ conn.close()
25
+
26
+ if not row:
27
+ raise NotFoundError("Idea", idea_id, message=f"未找到 ID 为 {idea_id} 的想法")
28
+
29
+ idea = Idea.from_row(row)
30
+ return idea.to_dict()
31
+
32
+
33
+ if __name__ == "__main__":
34
+ get()
@@ -0,0 +1,42 @@
1
+ """初始化命令"""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from pathlib import Path
6
+ from ..db import init_database
7
+ from ..const import APP_NAME, VERSION, DATA_DIR
8
+
9
+ console = Console()
10
+
11
+ def init(
12
+ db_path: Path = None,
13
+ yes: bool = typer.Option(False, "--yes", "-y", help="不提示,直接初始化"),
14
+ ):
15
+ """初始化数据库
16
+
17
+ Args:
18
+ db_path: 数据库文件路径,默认使用 ~/.think/think.db
19
+ yes: 不提示,直接初始化
20
+ """
21
+ if db_path is None:
22
+ db_path = DATA_DIR / "think.db"
23
+
24
+ # 检查是否已存在
25
+ if db_path.exists():
26
+ if not yes:
27
+ confirm = typer.confirm(
28
+ f"{db_path} 已存在,是否覆盖?",
29
+ default=False,
30
+ )
31
+ if not confirm:
32
+ console.print("[yellow]已取消初始化[/yellow]")
33
+ return
34
+
35
+ try:
36
+ init_database(db_path)
37
+ console.print(f"[green]✅ {APP_NAME} 初始化成功[/green]")
38
+ console.print(f" 数据库路径: {db_path}")
39
+ console.print(f" 版本: v{VERSION}")
40
+ except Exception as e:
41
+ console.print(f"[red]❌ 初始化失败: {e}[/red]")
42
+ raise typer.Exit(code=1)
@@ -0,0 +1,145 @@
1
+ """ls 命令"""
2
+
3
+ import typer
4
+ import json
5
+ from datetime import datetime
6
+ from ..core.models import Idea
7
+ from ..db import get_connection
8
+ from ..const import DEFAULT_LIMIT, PRIORITIES, STATUSES
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+ from rich.panel import Panel
12
+
13
+ console = Console()
14
+
15
+
16
+ def list_ideas(
17
+ limit: int = typer.Option(DEFAULT_LIMIT, "--limit", "-l", help="显示数量"),
18
+ today: bool = typer.Option(False, "--today", help="只显示今天的"),
19
+ week: bool = typer.Option(False, "--week", help="只显示本周的"),
20
+ tag: str = typer.Option(None, "--tag", help="按标签筛选"),
21
+ priority: str = typer.Option(None, "--priority", help="按优先级筛选: low/normal/high"),
22
+ status: str = typer.Option(None, "--status", help="按状态筛选"),
23
+ json_output: bool = typer.Option(False, "--json", help="JSON 输出"),
24
+ ):
25
+ """列出想法
26
+
27
+ Args:
28
+ limit: 显示数量
29
+ today: 只显示今天的
30
+ week: 只显示本周的
31
+ tag: 按标签筛选
32
+ priority: 按优先级筛选
33
+ status: 按状态筛选
34
+ json_output: JSON 输出
35
+ """
36
+ conn = get_connection()
37
+ cursor = conn.cursor()
38
+
39
+ # 构建查询条件
40
+ conditions = []
41
+ params = []
42
+
43
+ if today:
44
+ today_str = datetime.now().strftime("%Y-%m-%d")
45
+ conditions.append("date(created_at) = ?")
46
+ params.append(today_str)
47
+
48
+ if week:
49
+ from datetime import timedelta
50
+ week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
51
+ conditions.append("date(created_at) >= ?")
52
+ params.append(week_ago)
53
+
54
+ if tag:
55
+ conditions.append("tags LIKE ?")
56
+ params.append(f"%{tag}%")
57
+
58
+ if priority:
59
+ if priority not in PRIORITIES:
60
+ console.print(f"[yellow]⚠️ 无效的优先级: {priority}[/yellow]")
61
+ priority = None
62
+ else:
63
+ conditions.append("priority = ?")
64
+ params.append(priority)
65
+
66
+ if status:
67
+ if status not in STATUSES:
68
+ console.print(f"[yellow]⚠️ 无效的状态: {status}[/yellow]")
69
+ status = None
70
+ else:
71
+ conditions.append("status = ?")
72
+ params.append(status)
73
+
74
+ where_clause = " AND ".join(conditions) if conditions else "1=1"
75
+ params.extend([limit, 0]) # limit 和 offset
76
+
77
+ query = f"""
78
+ SELECT * FROM ideas
79
+ WHERE {where_clause}
80
+ ORDER BY created_at DESC
81
+ LIMIT ? OFFSET ?
82
+ """
83
+
84
+ cursor.execute(query, params)
85
+ rows = cursor.fetchall()
86
+ conn.close()
87
+
88
+ if json_output:
89
+ ideas = [Idea.from_row(row).to_dict() for row in rows]
90
+ console.print(json.dumps(ideas, ensure_ascii=False, indent=2))
91
+ return
92
+
93
+ if not rows:
94
+ console.print("[yellow]没有想法记录[/yellow]")
95
+ return
96
+
97
+ # 创建表格
98
+ table = Table(show_header=True, header_style="bold magenta")
99
+ table.add_column("ID", style="dim", width=6)
100
+ table.add_column("内容", width=40)
101
+ table.add_column("标签", width=20)
102
+ table.add_column("优先级", width=10)
103
+ table.add_column("状态", width=10)
104
+ table.add_column("时间", style="dim")
105
+
106
+ for row in rows:
107
+ idea = Idea.from_row(row)
108
+ tags_str = ", ".join(idea.tags) if idea.tags else ""
109
+ time_str = idea.created_at.split("T")[0] if idea.created_at else ""
110
+
111
+ # 根据优先级设置颜色
112
+ priority_style = {
113
+ "high": "bold red",
114
+ "normal": "bold white",
115
+ "low": "yellow",
116
+ }.get(idea.priority, "")
117
+
118
+ # 根据状态设置颜色
119
+ status_style = {
120
+ "done": "green",
121
+ "doing": "cyan",
122
+ "todo": "yellow",
123
+ "idea": "dim",
124
+ }.get(idea.status, "")
125
+
126
+ table.add_row(
127
+ str(idea.id),
128
+ idea.content[:40] + "..." if len(idea.content) > 40 else idea.content,
129
+ tags_str[:20],
130
+ f"[{priority_style}]{idea.priority}[/{priority_style}]" if priority_style else idea.priority,
131
+ f"[{status_style}]{idea.status}[/{status_style}]" if status_style else idea.status,
132
+ time_str,
133
+ )
134
+
135
+ console.print(table)
136
+
137
+ # 统计信息(重新查询,因为连接已关闭)
138
+ total_conn = get_connection()
139
+ total = total_conn.execute("SELECT COUNT(*) FROM ideas").fetchone()[0]
140
+ total_conn.close()
141
+ console.print(f"\n总计: {total} 条想法")
142
+
143
+
144
+ if __name__ == "__main__":
145
+ list_ideas()
@@ -0,0 +1,72 @@
1
+ """review 命令"""
2
+
3
+ import typer
4
+ import json
5
+ from datetime import datetime, timedelta
6
+ from ..core.models import Idea
7
+ from ..db import get_connection
8
+ from ..const import STATUSES, PRIORITIES, APP_NAME
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+
12
+ console = Console()
13
+
14
+
15
+ def review(
16
+ today: bool = typer.Option(False, "--today", help="只显示今天的想法"),
17
+ week: bool = typer.Option(False, "--week", help="只显示本周的想法"),
18
+ random: bool = typer.Option(False, "--random", help="随机显示一条想法"),
19
+ ):
20
+ """回顾想法
21
+
22
+ Args:
23
+ today: 只显示今天的想法
24
+ week: 只显示本周的想法
25
+ random: 随机显示一条想法
26
+ """
27
+ conn = get_connection()
28
+ cursor = conn.cursor()
29
+
30
+ query = "SELECT * FROM ideas ORDER BY RANDOM() LIMIT 1"
31
+ params = []
32
+
33
+ if today:
34
+ today_str = datetime.now().strftime("%Y-%m-%d")
35
+ query = "SELECT * FROM ideas WHERE date(created_at) = ? ORDER BY RANDOM() LIMIT 1"
36
+ params = [today_str]
37
+ elif week:
38
+ week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
39
+ query = "SELECT * FROM ideas WHERE date(created_at) >= ? ORDER BY RANDOM() LIMIT 1"
40
+ params = [week_ago]
41
+
42
+ cursor.execute(query, params)
43
+ row = cursor.fetchone()
44
+ conn.close()
45
+
46
+ if not row:
47
+ if today:
48
+ console.print(f"[yellow]{APP_NAME} 今天还没有想法记录[/yellow]")
49
+ elif week:
50
+ console.print(f"[yellow]{APP_NAME} 本周还没有想法记录[/yellow]")
51
+ else:
52
+ console.print(f"[yellow]{APP_NAME} 还没有想法记录,快去记一条吧!💡[/yellow]")
53
+ return
54
+
55
+ idea = Idea.from_row(row)
56
+
57
+ # 使用 Rich 显示
58
+ console.print(Panel(
59
+ f"{idea.content}\n\n[italic]#{idea.id} | {idea.created_at.split('T')[0]}[/italic]",
60
+ title="💡 思考时间",
61
+ title_align="left",
62
+ ))
63
+
64
+ if idea.tags:
65
+ tags_str = ", ".join(idea.tags)
66
+ console.print(f"[dim]标签: {tags_str}[/dim]")
67
+
68
+ console.print(f"[dim]上下文: {idea.context or '无'}[/dim]")
69
+
70
+
71
+ if __name__ == "__main__":
72
+ review()