personal-ai 0.1.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.md +227 -0
- package/SKILL.md +310 -0
- package/dist/auth-Dtx8Wc3l.mjs +2 -0
- package/dist/calendar-BHcM4wfQ.mjs +91 -0
- package/dist/calendar-BHcM4wfQ.mjs.map +1 -0
- package/dist/entry.mjs +3891 -0
- package/dist/entry.mjs.map +1 -0
- package/dist/gmail-B9ja9sKN.mjs +92 -0
- package/dist/gmail-B9ja9sKN.mjs.map +1 -0
- package/dist/index.mjs +1761 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mac-C9SDXZGK.mjs +55 -0
- package/dist/mac-C9SDXZGK.mjs.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# pai — Personal AI Identity Provider
|
|
2
|
+
|
|
3
|
+
本地优先的 AI Agent 身份画像系统。一条命令扫描本机,编译你的 profile,部署到任何 Agent。
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pai init → Scan → Profile → Deploy to Agents
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Profile ≠ Skill**: Profile 描述你是谁(身份、环境、偏好、项目),Skill 是 Agent 使用 pai 的说明书。
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 安装 (需要 Node.js >= 22)
|
|
15
|
+
npm install -g personal-ai
|
|
16
|
+
|
|
17
|
+
# 一条命令完成: 初始化 + 扫描本机 + 编译 profile (~12s, 零 LLM)
|
|
18
|
+
pai init
|
|
19
|
+
|
|
20
|
+
# 部署到 Cursor (Agent 自动认识你)
|
|
21
|
+
pai distribute
|
|
22
|
+
|
|
23
|
+
# 查看你的 profile
|
|
24
|
+
pai profile
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
三步搞定,不需要 LLM,不需要 API Key。Agent 立刻认识你。
|
|
28
|
+
|
|
29
|
+
### 从源码安装 (开发者)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/piai/personal-ai-skills-new.git
|
|
33
|
+
cd personal-ai-skills-new
|
|
34
|
+
pnpm install && pnpm build && npm link
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 核心概念
|
|
38
|
+
|
|
39
|
+
### Profile (核心产物)
|
|
40
|
+
|
|
41
|
+
`~/.pai/profile.md` — 从本机扫描数据直接编译,零 LLM 依赖。
|
|
42
|
+
|
|
43
|
+
包含 7 个维度:
|
|
44
|
+
- **Identity**: 用户名、Apple ID、Git 身份、语言、时区
|
|
45
|
+
- **Environment & Tools**: 运行时、包管理器、Shell、IDE 扩展
|
|
46
|
+
- **Work Style & Habits**: 命令习惯、编码规则、AI agent 配置
|
|
47
|
+
- **Active Projects & Recent Focus**: 活跃仓库、近期 commit、工作目录
|
|
48
|
+
- **Digital Footprint**: 书签、浏览域名、安装的应用
|
|
49
|
+
- **Registry & Cloud Accounts**: npm/Docker/AWS/GCP/Vercel 等账户
|
|
50
|
+
- **Context**: 日历、文件组织结构
|
|
51
|
+
|
|
52
|
+
### 14 个数据收集器
|
|
53
|
+
|
|
54
|
+
| # | Collector | 数据来源 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| 1 | identity-profile | 用户名、Apple ID、Git 配置、语言、时区 |
|
|
57
|
+
| 2 | calendar-context | Calendar.app 订阅 |
|
|
58
|
+
| 3 | file-organization | Documents/Desktop/Downloads 结构 |
|
|
59
|
+
| 4 | dev-environment | 运行时版本、包管理器、全局包 |
|
|
60
|
+
| 5 | dev-preferences | Shell aliases、Git config、Cursor 扩展 |
|
|
61
|
+
| 6 | shell-habits | 最常用命令 Top 30 |
|
|
62
|
+
| 7 | coding-rules | CLAUDE.md、Claude commands、项目规则 |
|
|
63
|
+
| 8 | active-projects | 活跃 Git 仓库、SSH hosts |
|
|
64
|
+
| 9 | productivity-setup | 已装 App、Dock 应用、浏览器 |
|
|
65
|
+
| 10 | browser-bookmarks | 书签文件夹结构 |
|
|
66
|
+
| 11 | browser-domains | 近 30 天高频域名 Top 30 |
|
|
67
|
+
| 12 | github-profile | GitHub 用户信息、仓库、stars |
|
|
68
|
+
| 13 | recent-focus | 近期 commit 分析、工作目录、工具使用 |
|
|
69
|
+
| 14 | social-profiles | npm/Docker/AWS/GCP/Vercel 账户 |
|
|
70
|
+
|
|
71
|
+
### Skill (Agent 说明书)
|
|
72
|
+
|
|
73
|
+
`pai distribute` 部署到 Agent 的文件包含两部分:
|
|
74
|
+
1. 你的 Profile (谁、环境、偏好)
|
|
75
|
+
2. Agent Skill 指令 (如何调用 `pai context/search/add`)
|
|
76
|
+
|
|
77
|
+
## CLI 命令
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Profile (核心)
|
|
81
|
+
pai init # 初始化 + 扫描 + 编译 profile (一条龙)
|
|
82
|
+
pai init --skip-scan # 仅初始化,跳过扫描 (CI/测试用)
|
|
83
|
+
pai profile # 查看当前 profile
|
|
84
|
+
pai profile --rebuild # 重新扫描 + 编译
|
|
85
|
+
pai profile --export # 输出可复制粘贴的 profile
|
|
86
|
+
pai profile --json # JSON 元数据
|
|
87
|
+
pai distribute # 部署 profile + skill 到 Cursor
|
|
88
|
+
|
|
89
|
+
# 数据管理 (进阶)
|
|
90
|
+
pai reset [--force] # 清空所有数据并重新初始化
|
|
91
|
+
pai add <text> # 手动添加文本到 raw/local/
|
|
92
|
+
pai add --url <url> # 抓取 URL 到 raw/web/
|
|
93
|
+
pai add <file> # 添加文件内容
|
|
94
|
+
pai auth google # Google 授权 (gmail/calendar 首次或重授权)
|
|
95
|
+
pai import --source mac # 手动触发 Mac 扫描 (写入 raw)
|
|
96
|
+
pai import --source gmail [--days N] [--query "..."] # Gmail 导入 (未授权时自动弹窗)
|
|
97
|
+
pai import --source calendar [--days N] # 日历导入
|
|
98
|
+
pai import --source <src> --path # 批量导入其他数据源 (需 --path)
|
|
99
|
+
|
|
100
|
+
# 深度知识 (可选,需要 LLM)
|
|
101
|
+
pai distill [--dry-run] [--file] # 蒸馏 raw → vault (需 OPENAI_API_KEY)
|
|
102
|
+
pai generate [--profile <name>] # LLM 生成 SKILL.md
|
|
103
|
+
pai index # 更新 QMD 索引
|
|
104
|
+
|
|
105
|
+
# 搜索 & 检索
|
|
106
|
+
pai ask <question> # 智能问答,端到端答案 (agent 首选,需 LLM)
|
|
107
|
+
pai ask <question> --json # JSON: answer, sources, steps
|
|
108
|
+
pai search <query> # 混合搜索 vault
|
|
109
|
+
pai search <query> --fast # 快速关键词搜索
|
|
110
|
+
pai search <query> --json # JSON 格式
|
|
111
|
+
pai context --task <desc> # 身份 + 任务相关记忆 (agent 用)
|
|
112
|
+
|
|
113
|
+
# 状态
|
|
114
|
+
pai status [--json] # 数据状态概览
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 技术栈
|
|
118
|
+
|
|
119
|
+
- **语言**: TypeScript (ESM, strict mode)
|
|
120
|
+
- **运行时**: Node.js ≥ 22
|
|
121
|
+
- **CLI 框架**: Commander.js
|
|
122
|
+
- **配置验证**: Zod + JSON5
|
|
123
|
+
- **LLM 调用**: openai SDK (可选,深度知识功能需要)
|
|
124
|
+
- **本地搜索**: QMD (可选,搜索功能需要)
|
|
125
|
+
- **终端输出**: chalk + ora
|
|
126
|
+
- **构建**: tsdown
|
|
127
|
+
- **测试**: Vitest
|
|
128
|
+
- **Lint**: Oxlint
|
|
129
|
+
- **包管理**: pnpm
|
|
130
|
+
|
|
131
|
+
## 前置依赖
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# QMD (本地搜索引擎,搜索功能需要,profile 不需要)
|
|
135
|
+
npm install -g https://github.com/tobi/qmd
|
|
136
|
+
|
|
137
|
+
# GitHub CLI (可选,GitHub profile 收集)
|
|
138
|
+
brew install gh && gh auth login
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 项目结构
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
src/
|
|
145
|
+
├── cli/ # CLI 命令注册 (Commander.js)
|
|
146
|
+
│ ├── build-program.ts
|
|
147
|
+
│ ├── command-registry.ts
|
|
148
|
+
│ └── register.*.ts
|
|
149
|
+
├── profile/ # Profile 编译器 (scan → profile.md, 零 LLM)
|
|
150
|
+
│ ├── compile.ts
|
|
151
|
+
│ └── index.ts
|
|
152
|
+
├── config/ # 配置管理 (Zod + JSON5)
|
|
153
|
+
├── auth/ # Google OAuth (encryption + google-oauth)
|
|
154
|
+
├── connectors/ # 数据收集器
|
|
155
|
+
│ ├── mac/ # 14 个 Mac collectors
|
|
156
|
+
│ ├── google/ # gmail.ts, calendar.ts
|
|
157
|
+
│ └── sanitize.ts
|
|
158
|
+
├── raw/ # Raw 层
|
|
159
|
+
├── scraper/ # 网页抓取
|
|
160
|
+
├── distill/ # 蒸馏 Pipeline (可选)
|
|
161
|
+
├── generate/ # SKILL.md 生成 (可选)
|
|
162
|
+
├── search/ # QMD 搜索封装
|
|
163
|
+
├── llm/ # OpenAI client
|
|
164
|
+
├── prompts/ # Prompt 模板
|
|
165
|
+
├── utils/ # 工具函数
|
|
166
|
+
├── types.ts # 全局类型
|
|
167
|
+
├── index.ts # Public API
|
|
168
|
+
└── entry.ts # CLI 入口
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 数据目录
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
~/.pai/
|
|
175
|
+
├── profile.md # 核心产物 — 编译后的用户画像
|
|
176
|
+
├── raw/ # 原始数据 (扫描/添加)
|
|
177
|
+
│ ├── local/
|
|
178
|
+
│ ├── web/
|
|
179
|
+
│ └── connector/ # mac/, gmail/, calendar/
|
|
180
|
+
├── credentials/ # Google OAuth: client_secret.json, google-oauth.json.enc
|
|
181
|
+
├── vault/ # 蒸馏后知识 (可选进阶)
|
|
182
|
+
├── skills/profiles/ # LLM 生成的 SKILL.md (可选)
|
|
183
|
+
└── config/
|
|
184
|
+
├── pai.json5
|
|
185
|
+
├── profiles.json5
|
|
186
|
+
└── preferences.md
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Agent 集成
|
|
190
|
+
|
|
191
|
+
### 两层记忆架构
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
Layer 1 (常驻): pai distribute → ~/.cursor/rules/pai-context.mdc
|
|
195
|
+
→ Profile (谁) + Skill (怎么用 pai), 每次对话自动注入
|
|
196
|
+
|
|
197
|
+
Layer 2 (情境): pai ask "问题" → 直接答案 (agentic,需 LLM)
|
|
198
|
+
或 pai context --task "当前任务" → 身份 + 相关记忆
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 快速部署
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
pai init && pai distribute # 两步搞定
|
|
205
|
+
|
|
206
|
+
# Agent 在工作时调用
|
|
207
|
+
pai ask "What's the user's deployment preference?" # 推荐:直接拿答案
|
|
208
|
+
pai context --task "configure PostgreSQL connection pooling"
|
|
209
|
+
pai search "database performance" --json
|
|
210
|
+
pai add "learned: always set pool_size=20 for production"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 开发
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pnpm install # 安装依赖
|
|
217
|
+
pnpm dev # 开发模式 (tsx)
|
|
218
|
+
pnpm build # 构建 (tsdown)
|
|
219
|
+
pnpm lint # Lint (oxlint)
|
|
220
|
+
pnpm typecheck # 类型检查
|
|
221
|
+
pnpm test # 测试 (vitest)
|
|
222
|
+
pnpm check # 完整门禁
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# pai — Personal AI Identity Provider
|
|
2
|
+
|
|
3
|
+
> One command to scan your machine, compile your profile, and deploy to any AI agent.
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
metadata:
|
|
7
|
+
{
|
|
8
|
+
"name": "pai",
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"description": "Local-first AI agent identity system. Scan, compile profile, deploy to agents.",
|
|
11
|
+
"requires": {
|
|
12
|
+
"bins": ["pai"],
|
|
13
|
+
"optional_bins": ["qmd"],
|
|
14
|
+
"env": [],
|
|
15
|
+
"optional_env": ["OPENAI_API_KEY"]
|
|
16
|
+
},
|
|
17
|
+
"permissions": ["read", "write", "search"],
|
|
18
|
+
"homepage": "https://github.com/piai/personal-ai-skills-new"
|
|
19
|
+
}
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
## What This Skill Does
|
|
23
|
+
|
|
24
|
+
pai is a local-first AI agent identity provider. It scans your machine, compiles a personal profile, and deploys it to AI agents. When you have access to this skill, you can:
|
|
25
|
+
|
|
26
|
+
- **Know** who the user is — identity, environment, tools, projects, preferences
|
|
27
|
+
- **Search** the user's personal knowledge base (vault + raw)
|
|
28
|
+
- **Ask** questions about the user and get direct answers (agentic)
|
|
29
|
+
- **Remember** new lessons, preferences, and discoveries for the user
|
|
30
|
+
|
|
31
|
+
## Quick Setup (1 minute)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g personal-ai # Install pai CLI
|
|
35
|
+
pai init # Scan machine + compile profile (~12s, no LLM needed)
|
|
36
|
+
pai distribute # Deploy to Cursor / Claude Code / agents
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That's it. Your agent now knows who you are.
|
|
40
|
+
|
|
41
|
+
## Authorization
|
|
42
|
+
|
|
43
|
+
### Required Environment
|
|
44
|
+
|
|
45
|
+
| Requirement | How to Check | How to Get |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `pai` CLI | `pai --version` | `npm install -g personal-ai` |
|
|
48
|
+
| Node.js ≥ 22 | `node -v` | `fnm install 22` or [nodejs.org](https://nodejs.org) |
|
|
49
|
+
|
|
50
|
+
### Optional (for advanced features)
|
|
51
|
+
|
|
52
|
+
| Requirement | Feature | How to Get |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `qmd` CLI | Search (`pai search`) | `npm install -g https://github.com/tobi/qmd` |
|
|
55
|
+
| `OPENAI_API_KEY` | AI features (`pai ask`, `pai distill`) | Set in shell profile |
|
|
56
|
+
| Google OAuth | Gmail/Calendar import | `pai auth google` (built-in flow) |
|
|
57
|
+
|
|
58
|
+
### Data Location
|
|
59
|
+
|
|
60
|
+
All data is stored locally at `~/.pai/` (override with `PAI_HOME` env var):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
~/.pai/
|
|
64
|
+
├── profile.md # Core output — compiled user profile (no LLM needed)
|
|
65
|
+
├── raw/ # Original input (immutable after write)
|
|
66
|
+
│ ├── local/ # Text & file input
|
|
67
|
+
│ ├── web/ # Scraped URLs
|
|
68
|
+
│ └── connector/# Imported data (mac scan, gmail, calendar)
|
|
69
|
+
├── vault/ # Distilled knowledge (living documents, optional)
|
|
70
|
+
├── credentials/ # Google OAuth tokens (encrypted)
|
|
71
|
+
├── skills/profiles/ # LLM-generated SKILL.md files (optional)
|
|
72
|
+
└── config/ # pai.json5 + profiles.json5 + preferences.md
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Verifying Access
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pai --version # Check pai is installed
|
|
79
|
+
pai status # Check data directory and counts
|
|
80
|
+
pai profile # View your profile
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If `pai` is not found:
|
|
84
|
+
```bash
|
|
85
|
+
npm install -g personal-ai
|
|
86
|
+
pai init
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Available Commands
|
|
90
|
+
|
|
91
|
+
### Adding Knowledge
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Add a text experience/lesson
|
|
95
|
+
pai add "Always use connection pooling with PostgreSQL in production"
|
|
96
|
+
|
|
97
|
+
# Add content from a URL (scrapes and saves)
|
|
98
|
+
pai add --url "https://docs.example.com/best-practices"
|
|
99
|
+
|
|
100
|
+
# Add a local file
|
|
101
|
+
pai add ./meeting-notes.txt
|
|
102
|
+
|
|
103
|
+
# Import from Mac system scan (no --path; auto-scans 11 dimensions)
|
|
104
|
+
pai import --source mac
|
|
105
|
+
pai import --source mac --dry-run
|
|
106
|
+
|
|
107
|
+
# Import from a data connector directory
|
|
108
|
+
pai import --source gmail --path ~/data/gmail-export/
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Ask (RECOMMENDED for Agents — get direct answers)
|
|
112
|
+
|
|
113
|
+
Ask a question; an agentic secretary uses tools to find the answer and returns a direct reply. No need to interpret raw search results.
|
|
114
|
+
|
|
115
|
+
**Requires:** `OPENAI_API_KEY` (agentic loop uses LLM + tools).
|
|
116
|
+
|
|
117
|
+
**Tools available to the agent:** search vault/raw, read profile, read file, grep (ripgrep), glob, ls, bash. The agent decides which to call and when to stop.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Ask anything about the user
|
|
121
|
+
pai ask "What deployment method does this user prefer?"
|
|
122
|
+
pai ask "Does the user have Kubernetes experience?" --json
|
|
123
|
+
|
|
124
|
+
# Options
|
|
125
|
+
pai ask "用户的编码规范是什么?" --steps 15 # max tool-call steps (default: 10)
|
|
126
|
+
pai ask "..." --model gpt-4o # override model
|
|
127
|
+
pai ask "..." --verbose # show each tool step
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Output:** plain answer; or with `--json`: `{ "answer", "sources", "steps" }`.
|
|
131
|
+
|
|
132
|
+
### Retrieving Context (fast, no LLM)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Get identity + task-relevant vault memories (hybrid search)
|
|
136
|
+
pai context --task "current task description"
|
|
137
|
+
|
|
138
|
+
# Get identity only (no search, instant)
|
|
139
|
+
pai context
|
|
140
|
+
|
|
141
|
+
# Machine-readable output
|
|
142
|
+
pai context --task "deploy React app" --json
|
|
143
|
+
|
|
144
|
+
# Use a specific profile
|
|
145
|
+
pai context --profile coding-assistant --task "..."
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Searching Knowledge
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Search distilled knowledge (vault)
|
|
152
|
+
pai search "PostgreSQL performance"
|
|
153
|
+
|
|
154
|
+
# Search raw original data (for tracing back)
|
|
155
|
+
pai search "PostgreSQL" --raw
|
|
156
|
+
|
|
157
|
+
# Search everything
|
|
158
|
+
pai search "PostgreSQL" --all
|
|
159
|
+
|
|
160
|
+
# Control result count
|
|
161
|
+
pai search "React hooks" -n 10
|
|
162
|
+
|
|
163
|
+
# Machine-readable output (for agents)
|
|
164
|
+
pai search "PostgreSQL" --json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Processing & Distilling
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Preview what would be distilled (safe, no writes)
|
|
171
|
+
pai distill --dry-run
|
|
172
|
+
|
|
173
|
+
# Process all pending raw files → extract to vault
|
|
174
|
+
pai distill
|
|
175
|
+
|
|
176
|
+
# Process a specific file
|
|
177
|
+
pai distill --file ~/.pai/raw/local/2026-02-06T15-17-xxx.md
|
|
178
|
+
|
|
179
|
+
# Update QMD search index after changes
|
|
180
|
+
pai index
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Generating SKILL.md
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Generate all configured profiles
|
|
187
|
+
pai generate
|
|
188
|
+
|
|
189
|
+
# Generate a specific profile
|
|
190
|
+
pai generate --profile coding-assistant
|
|
191
|
+
|
|
192
|
+
# View generated profile
|
|
193
|
+
cat ~/.pai/skills/profiles/coding-assistant.md
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Agent Deployment
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Deploy identity + agent instructions to Cursor rules and other agent configs
|
|
200
|
+
pai distribute # Default: all targets
|
|
201
|
+
pai distribute --target cursor # Cursor only
|
|
202
|
+
pai distribute --target claude # Claude Code only
|
|
203
|
+
pai distribute --profile coding-assistant # Use a specific generated profile
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Status & Reset
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Overview of all data
|
|
210
|
+
pai status
|
|
211
|
+
pai status --json # Machine-readable
|
|
212
|
+
|
|
213
|
+
# Remove all data and re-initialize (clean slate for testing)
|
|
214
|
+
pai reset # Prompts for confirmation
|
|
215
|
+
pai reset --force # No prompt, immediate wipe + init
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## How to Use This Skill as an Agent
|
|
219
|
+
|
|
220
|
+
### 1. Ask questions (recommended)
|
|
221
|
+
|
|
222
|
+
For any question about the user, use `pai ask` to get a direct answer. The agent uses tools (vault search, profile, grep, bash) and returns a concise reply.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pai ask "What does this user prefer for deployment?"
|
|
226
|
+
pai ask "What's the user's current project?" --json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. Retrieve context before starting work
|
|
230
|
+
|
|
231
|
+
For a quick identity + task-relevant snippets (no LLM, or light):
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pai context --task "brief description of what you're about to do"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
When you need raw search results (chunks) instead of a synthesized answer:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pai search "editor preferences"
|
|
241
|
+
pai search "React deployment" --json -n 3
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
When you discover something the user should remember (a lesson, preference, or tip):
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
pai add "The user prefers dark mode and monospace fonts in all editors"
|
|
248
|
+
pai add --url "https://the-useful-article.com"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 5. Periodic maintenance
|
|
252
|
+
|
|
253
|
+
After adding multiple items, process and regenerate:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pai distill # Process pending raw files into vault
|
|
257
|
+
pai generate # Regenerate SKILL.md profiles
|
|
258
|
+
pai distribute # Update deployed agent configs
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The generated profiles at `~/.pai/skills/profiles/` can be used by other agents.
|
|
262
|
+
|
|
263
|
+
## Data Flow
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
User Input ──→ pai add ──→ raw/ (original, immutable)
|
|
267
|
+
│
|
|
268
|
+
▼
|
|
269
|
+
pai distill ──→ vault/ (structured knowledge, 1:N routing)
|
|
270
|
+
│
|
|
271
|
+
▼
|
|
272
|
+
pai generate ──→ skills/profiles/*.md
|
|
273
|
+
│
|
|
274
|
+
▼
|
|
275
|
+
pai distribute ──→ ~/.cursor/rules/ (auto-injected)
|
|
276
|
+
│
|
|
277
|
+
▼
|
|
278
|
+
Agent starts ──→ reads identity from rules (Layer 1: passive)
|
|
279
|
+
Agent works ──→ pai ask "question" (Layer 2: direct answer) or pai context --task "..." (quick retrieval)
|
|
280
|
+
Agent learns ──→ pai add "new lesson" (Layer 3: write-back)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Security Notes
|
|
284
|
+
|
|
285
|
+
- All data stays on the user's local machine (`~/.pai/`)
|
|
286
|
+
- No data is sent anywhere except to the configured LLM API for processing
|
|
287
|
+
- `OPENAI_API_KEY` is read from environment, never stored in files
|
|
288
|
+
- Raw files are immutable after creation (append-only log)
|
|
289
|
+
- The user controls what goes in and what gets distilled
|
|
290
|
+
|
|
291
|
+
## Profiles Configuration
|
|
292
|
+
|
|
293
|
+
Profiles are defined in `~/.pai/config/profiles.json5`. Each profile specifies which vault directories to include and the max output size:
|
|
294
|
+
|
|
295
|
+
```json5
|
|
296
|
+
{
|
|
297
|
+
profiles: {
|
|
298
|
+
"coding-assistant": {
|
|
299
|
+
scope: ["vault/coding/**", "vault/preferences/coding-style.md"],
|
|
300
|
+
maxLines: 30,
|
|
301
|
+
},
|
|
302
|
+
"full-context": {
|
|
303
|
+
scope: ["vault/**"],
|
|
304
|
+
maxLines: 50,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
To add a new profile, edit `profiles.json5` and run `pai generate`.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as info, c as warn, r as googleOAuth } from "./entry.mjs";
|
|
3
|
+
import "./auth-Dtx8Wc3l.mjs";
|
|
4
|
+
import { google } from "googleapis";
|
|
5
|
+
|
|
6
|
+
//#region src/connectors/google/calendar.ts
|
|
7
|
+
/**
|
|
8
|
+
* Google Calendar connector — fetches events and returns CollectorResult[] for raw layer.
|
|
9
|
+
*
|
|
10
|
+
* Default behavior: enumerate ALL user-visible calendars via calendarList.list(),
|
|
11
|
+
* tag each event with the calendar display name so the ask agent can distinguish
|
|
12
|
+
* "Work" meetings from "Holiday" markers.
|
|
13
|
+
*/
|
|
14
|
+
function formatEventTime(start) {
|
|
15
|
+
const dt = start.dateTime ?? start.date;
|
|
16
|
+
const tz = start.timeZone ?? "UTC";
|
|
17
|
+
if (!dt) return "";
|
|
18
|
+
return `${dt} (${tz})`;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve calendar IDs + display names. If explicit list given, use as-is; otherwise enumerate all. */
|
|
21
|
+
async function resolveCalendars(calendarApi, explicitIds) {
|
|
22
|
+
if (explicitIds) return explicitIds.map((id) => ({
|
|
23
|
+
id,
|
|
24
|
+
name: id
|
|
25
|
+
}));
|
|
26
|
+
return ((await calendarApi.calendarList.list({ showHidden: false })).data.items ?? []).filter((c) => c.id).map((c) => ({
|
|
27
|
+
id: c.id,
|
|
28
|
+
name: c.summary ?? c.id
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sync Google Calendar events and return one CollectorResult per event.
|
|
33
|
+
* Each event includes a **Calendar** field with the display name of its source calendar.
|
|
34
|
+
*/
|
|
35
|
+
async function syncCalendar(opts = {}) {
|
|
36
|
+
await googleOAuth.ensureAuthenticated();
|
|
37
|
+
const calendarApi = google.calendar({
|
|
38
|
+
version: "v3",
|
|
39
|
+
auth: googleOAuth.getClient()
|
|
40
|
+
});
|
|
41
|
+
const calendars = await resolveCalendars(calendarApi, opts.calendars);
|
|
42
|
+
const lookbackDays = opts.lookbackDays ?? 30;
|
|
43
|
+
const lookforwardDays = opts.lookforwardDays ?? 90;
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const timeMin = (/* @__PURE__ */ new Date(now.getTime() - lookbackDays * 24 * 60 * 60 * 1e3)).toISOString();
|
|
46
|
+
const timeMax = new Date(now.getTime() + lookforwardDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
47
|
+
info(`Fetching events from ${calendars.length} calendar(s): ${calendars.map((c) => c.name).join(", ")}`);
|
|
48
|
+
const results = [];
|
|
49
|
+
let failed = 0;
|
|
50
|
+
for (const cal of calendars) try {
|
|
51
|
+
const items = (await calendarApi.events.list({
|
|
52
|
+
calendarId: cal.id,
|
|
53
|
+
timeMin,
|
|
54
|
+
timeMax,
|
|
55
|
+
singleEvents: true,
|
|
56
|
+
orderBy: "startTime",
|
|
57
|
+
maxResults: 250
|
|
58
|
+
})).data.items ?? [];
|
|
59
|
+
for (const event of items) {
|
|
60
|
+
const id = event.id ?? "";
|
|
61
|
+
const summary = event.summary ?? "(no title)";
|
|
62
|
+
const startStr = event.start ? formatEventTime(event.start) : "";
|
|
63
|
+
const endStr = event.end ? formatEventTime(event.end) : "";
|
|
64
|
+
const location = event.location ?? "";
|
|
65
|
+
const attendees = (event.attendees ?? []).map((a) => a.email ?? a.displayName ?? "").filter(Boolean).join(", ");
|
|
66
|
+
const status = event.status ?? "confirmed";
|
|
67
|
+
const lines = [
|
|
68
|
+
`- **Calendar**: ${cal.name}`,
|
|
69
|
+
startStr ? `- **Time**: ${startStr}${endStr ? ` - ${endStr}` : ""}` : "",
|
|
70
|
+
location ? `- **Location**: ${location}` : "",
|
|
71
|
+
attendees ? `- **Attendees**: ${attendees}` : "",
|
|
72
|
+
`- **Status**: ${status}`
|
|
73
|
+
].filter(Boolean);
|
|
74
|
+
results.push({
|
|
75
|
+
id,
|
|
76
|
+
title: summary,
|
|
77
|
+
content: lines.join("\n") || "(no details)"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
failed++;
|
|
82
|
+
const msgText = err instanceof Error ? err.message : String(err);
|
|
83
|
+
warn(`Calendar fetch failed for ${cal.name} (${cal.id}): ${msgText}`);
|
|
84
|
+
}
|
|
85
|
+
if (failed > 0 && calendars.length > failed) warn(`Calendar fetch failed for ${failed} of ${calendars.length} calendar(s).`);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { syncCalendar };
|
|
91
|
+
//# sourceMappingURL=calendar-BHcM4wfQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-BHcM4wfQ.mjs","names":[],"sources":["../src/connectors/google/calendar.ts"],"sourcesContent":["/**\n * Google Calendar connector — fetches events and returns CollectorResult[] for raw layer.\n *\n * Default behavior: enumerate ALL user-visible calendars via calendarList.list(),\n * tag each event with the calendar display name so the ask agent can distinguish\n * \"Work\" meetings from \"Holiday\" markers.\n */\n\nimport { google } from \"googleapis\";\nimport type { calendar_v3 } from \"googleapis\";\nimport type { CollectorResult } from \"../../types.js\";\nimport { googleOAuth } from \"../../auth/index.js\";\nimport * as console from \"../../utils/console.js\";\n\nexport interface SyncCalendarOptions {\n /** Calendar IDs to fetch. If omitted, fetches ALL user-visible calendars. */\n calendars?: string[];\n /** Days to look back (default 30). */\n lookbackDays?: number;\n /** Days to look forward (default 90). */\n lookforwardDays?: number;\n}\n\nfunction formatEventTime(start: calendar_v3.Schema$EventDateTime): string {\n const dt = start.dateTime ?? start.date;\n const tz = start.timeZone ?? \"UTC\";\n if (!dt) return \"\";\n return `${dt} (${tz})`;\n}\n\n/** Resolve calendar IDs + display names. If explicit list given, use as-is; otherwise enumerate all. */\nasync function resolveCalendars(\n calendarApi: calendar_v3.Calendar,\n explicitIds?: string[],\n): Promise<{ id: string; name: string }[]> {\n if (explicitIds) {\n return explicitIds.map((id) => ({ id, name: id }));\n }\n\n // Enumerate all user-visible calendars\n const res = await calendarApi.calendarList.list({ showHidden: false });\n const items = res.data.items ?? [];\n return items\n .filter((c) => c.id)\n .map((c) => ({\n id: c.id!,\n name: c.summary ?? c.id!,\n }));\n}\n\n/**\n * Sync Google Calendar events and return one CollectorResult per event.\n * Each event includes a **Calendar** field with the display name of its source calendar.\n */\nexport async function syncCalendar(\n opts: SyncCalendarOptions = {},\n): Promise<CollectorResult[]> {\n await googleOAuth.ensureAuthenticated();\n const calendarApi: calendar_v3.Calendar = google.calendar({\n version: \"v3\",\n auth: googleOAuth.getClient(),\n });\n\n const calendars = await resolveCalendars(calendarApi, opts.calendars);\n const lookbackDays = opts.lookbackDays ?? 30;\n const lookforwardDays = opts.lookforwardDays ?? 90;\n\n const now = new Date();\n const timeMin = new Date(\n now.getTime() - lookbackDays * 24 * 60 * 60 * 1000,\n ).toISOString();\n const timeMax = new Date(\n now.getTime() + lookforwardDays * 24 * 60 * 60 * 1000,\n ).toISOString();\n\n console.info(\n `Fetching events from ${calendars.length} calendar(s): ${calendars.map((c) => c.name).join(\", \")}`,\n );\n\n const results: CollectorResult[] = [];\n let failed = 0;\n\n for (const cal of calendars) {\n try {\n const res = await calendarApi.events.list({\n calendarId: cal.id,\n timeMin,\n timeMax,\n singleEvents: true,\n orderBy: \"startTime\",\n maxResults: 250,\n });\n\n const items = res.data.items ?? [];\n for (const event of items) {\n const id = event.id ?? \"\";\n const summary = event.summary ?? \"(no title)\";\n const startStr = event.start\n ? formatEventTime(event.start)\n : \"\";\n const endStr = event.end ? formatEventTime(event.end) : \"\";\n const location = event.location ?? \"\";\n const attendees = (event.attendees ?? [])\n .map((a) => a.email ?? a.displayName ?? \"\")\n .filter(Boolean)\n .join(\", \");\n const status = event.status ?? \"confirmed\";\n\n const lines = [\n `- **Calendar**: ${cal.name}`,\n startStr ? `- **Time**: ${startStr}${endStr ? ` - ${endStr}` : \"\"}` : \"\",\n location ? `- **Location**: ${location}` : \"\",\n attendees ? `- **Attendees**: ${attendees}` : \"\",\n `- **Status**: ${status}`,\n ].filter(Boolean);\n\n results.push({\n id,\n title: summary,\n content: lines.join(\"\\n\") || \"(no details)\",\n });\n }\n } catch (err) {\n failed++;\n const msgText = err instanceof Error ? err.message : String(err);\n console.warn(`Calendar fetch failed for ${cal.name} (${cal.id}): ${msgText}`);\n }\n }\n\n if (failed > 0 && calendars.length > failed) {\n console.warn(`Calendar fetch failed for ${failed} of ${calendars.length} calendar(s).`);\n }\n return results;\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,SAAS,gBAAgB,OAAiD;CACxE,MAAM,KAAK,MAAM,YAAY,MAAM;CACnC,MAAM,KAAK,MAAM,YAAY;AAC7B,KAAI,CAAC,GAAI,QAAO;AAChB,QAAO,GAAG,GAAG,IAAI,GAAG;;;AAItB,eAAe,iBACb,aACA,aACyC;AACzC,KAAI,YACF,QAAO,YAAY,KAAK,QAAQ;EAAE;EAAI,MAAM;EAAI,EAAE;AAMpD,UAFY,MAAM,YAAY,aAAa,KAAK,EAAE,YAAY,OAAO,CAAC,EACpD,KAAK,SAAS,EAAE,EAE/B,QAAQ,MAAM,EAAE,GAAG,CACnB,KAAK,OAAO;EACX,IAAI,EAAE;EACN,MAAM,EAAE,WAAW,EAAE;EACtB,EAAE;;;;;;AAOP,eAAsB,aACpB,OAA4B,EAAE,EACF;AAC5B,OAAM,YAAY,qBAAqB;CACvC,MAAM,cAAoC,OAAO,SAAS;EACxD,SAAS;EACT,MAAM,YAAY,WAAW;EAC9B,CAAC;CAEF,MAAM,YAAY,MAAM,iBAAiB,aAAa,KAAK,UAAU;CACrE,MAAM,eAAe,KAAK,gBAAgB;CAC1C,MAAM,kBAAkB,KAAK,mBAAmB;CAEhD,MAAM,sBAAM,IAAI,MAAM;CACtB,MAAM,2BAAU,IAAI,KAClB,IAAI,SAAS,GAAG,eAAe,KAAK,KAAK,KAAK,IAC/C,EAAC,aAAa;CACf,MAAM,UAAU,IAAI,KAClB,IAAI,SAAS,GAAG,kBAAkB,KAAK,KAAK,KAAK,IAClD,CAAC,aAAa;AAEf,MACE,wBAAwB,UAAU,OAAO,gBAAgB,UAAU,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,GACjG;CAED,MAAM,UAA6B,EAAE;CACrC,IAAI,SAAS;AAEb,MAAK,MAAM,OAAO,UAChB,KAAI;EAUF,MAAM,SATM,MAAM,YAAY,OAAO,KAAK;GACxC,YAAY,IAAI;GAChB;GACA;GACA,cAAc;GACd,SAAS;GACT,YAAY;GACb,CAAC,EAEgB,KAAK,SAAS,EAAE;AAClC,OAAK,MAAM,SAAS,OAAO;GACzB,MAAM,KAAK,MAAM,MAAM;GACvB,MAAM,UAAU,MAAM,WAAW;GACjC,MAAM,WAAW,MAAM,QACnB,gBAAgB,MAAM,MAAM,GAC5B;GACJ,MAAM,SAAS,MAAM,MAAM,gBAAgB,MAAM,IAAI,GAAG;GACxD,MAAM,WAAW,MAAM,YAAY;GACnC,MAAM,aAAa,MAAM,aAAa,EAAE,EACrC,KAAK,MAAM,EAAE,SAAS,EAAE,eAAe,GAAG,CAC1C,OAAO,QAAQ,CACf,KAAK,KAAK;GACb,MAAM,SAAS,MAAM,UAAU;GAE/B,MAAM,QAAQ;IACZ,mBAAmB,IAAI;IACvB,WAAW,eAAe,WAAW,SAAS,MAAM,WAAW,OAAO;IACtE,WAAW,mBAAmB,aAAa;IAC3C,YAAY,oBAAoB,cAAc;IAC9C,iBAAiB;IAClB,CAAC,OAAO,QAAQ;AAEjB,WAAQ,KAAK;IACX;IACA,OAAO;IACP,SAAS,MAAM,KAAK,KAAK,IAAI;IAC9B,CAAC;;UAEG,KAAK;AACZ;EACA,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,OAAa,6BAA6B,IAAI,KAAK,IAAI,IAAI,GAAG,KAAK,UAAU;;AAIjF,KAAI,SAAS,KAAK,UAAU,SAAS,OACnC,MAAa,6BAA6B,OAAO,MAAM,UAAU,OAAO,eAAe;AAEzF,QAAO"}
|