@pi-unipi/unipi 0.1.1 → 0.1.3
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 +1 -1
- package/package.json +6 -3
- package/packages/memory/index.ts +62 -35
- package/packages/memory/skills/memory/SKILL.md +30 -8
- package/packages/web-api/README.md +179 -0
- package/packages/web-api/skills/web/SKILL.md +108 -0
- package/packages/workflow/index.ts +11 -2
- package/packages/workflow/skills/brainstorm/SKILL.md +1 -0
- package/packages/workflow/skills/plan/SKILL.md +33 -13
- package/packages/workflow/skills/review-work/SKILL.md +12 -4
- package/packages/workflow/skills/work/SKILL.md +31 -9
- package/packages/workflow/skills/worktree-list/SKILL.md +23 -12
- package/packages/workflow/skills/worktree-merge/SKILL.md +10 -2
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"typecheck": "npx tsc --noEmit --skipLibCheck",
|
|
27
|
+
"test": "npm test --workspaces",
|
|
27
28
|
"publish:all": "npm publish --workspaces --access public"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
@@ -39,7 +40,8 @@
|
|
|
39
40
|
"node_modules/@pi-unipi/memory/index.ts",
|
|
40
41
|
"node_modules/@pi-unipi/info-screen/index.ts",
|
|
41
42
|
"node_modules/@pi-unipi/subagents/src/index.ts",
|
|
42
|
-
"node_modules/@pi-unipi/btw/extensions/btw.ts"
|
|
43
|
+
"node_modules/@pi-unipi/btw/extensions/btw.ts",
|
|
44
|
+
"node_modules/@pi-unipi/web-api/src/index.ts"
|
|
43
45
|
],
|
|
44
46
|
"skills": [
|
|
45
47
|
"node_modules/@pi-unipi/workflow/skills",
|
|
@@ -61,7 +63,8 @@
|
|
|
61
63
|
"@pi-unipi/memory": "*",
|
|
62
64
|
"@pi-unipi/info-screen": "*",
|
|
63
65
|
"@pi-unipi/subagents": "*",
|
|
64
|
-
"@pi-unipi/btw": "*"
|
|
66
|
+
"@pi-unipi/btw": "*",
|
|
67
|
+
"@pi-unipi/web-api": "*"
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"@types/node": "^25.6.0",
|
package/packages/memory/index.ts
CHANGED
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
searchAllProjects,
|
|
27
27
|
listAllProjects,
|
|
28
28
|
} from "./storage.js";
|
|
29
|
-
import { registerMemoryTools, MEMORY_TOOLS } from "./tools.js";
|
|
29
|
+
import { registerMemoryTools, MEMORY_TOOLS, GLOBAL_SEARCH_ALIAS } from "./tools.js";
|
|
30
30
|
import { registerMemoryCommands } from "./commands.js";
|
|
31
|
+
import { isEmbeddingReady, hasModelChanged } from "./settings.js";
|
|
31
32
|
|
|
32
33
|
/** Package version */
|
|
33
34
|
const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
|
|
@@ -47,6 +48,10 @@ function getStorage(): MemoryStorage {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export default function (pi: ExtensionAPI) {
|
|
51
|
+
// Lifecycle state — tracks whether recall/store have happened this session
|
|
52
|
+
let recallDone = false;
|
|
53
|
+
let storeDone = false;
|
|
54
|
+
|
|
50
55
|
// Register skills directory
|
|
51
56
|
const skillsDir = new URL("./skills", import.meta.url).pathname;
|
|
52
57
|
pi.on("resources_discover", async (_event, _ctx) => {
|
|
@@ -56,11 +61,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
56
61
|
});
|
|
57
62
|
|
|
58
63
|
// Register tools and commands
|
|
59
|
-
registerMemoryTools(pi, getStorage);
|
|
64
|
+
registerMemoryTools(pi, getStorage, () => { recallDone = true; storeDone = true; });
|
|
60
65
|
registerMemoryCommands(pi, getStorage);
|
|
61
66
|
|
|
62
67
|
// Session lifecycle
|
|
63
68
|
pi.on("session_start", async (_event, ctx) => {
|
|
69
|
+
// Reset lifecycle flags
|
|
70
|
+
recallDone = false;
|
|
71
|
+
storeDone = false;
|
|
72
|
+
|
|
64
73
|
// Initialize project storage
|
|
65
74
|
const projectName = getProjectName(ctx.cwd);
|
|
66
75
|
projectStorage = new MemoryStorage(projectName);
|
|
@@ -78,13 +87,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
87
|
"unipi:memory-forget",
|
|
79
88
|
"unipi:global-memory-search",
|
|
80
89
|
"unipi:global-memory-list",
|
|
90
|
+
"unipi:memory-settings",
|
|
81
91
|
],
|
|
82
92
|
tools: [
|
|
83
93
|
MEMORY_TOOLS.STORE,
|
|
84
94
|
MEMORY_TOOLS.SEARCH,
|
|
85
95
|
MEMORY_TOOLS.DELETE,
|
|
86
96
|
MEMORY_TOOLS.LIST,
|
|
87
|
-
|
|
97
|
+
GLOBAL_SEARCH_ALIAS,
|
|
88
98
|
MEMORY_TOOLS.GLOBAL_LIST,
|
|
89
99
|
],
|
|
90
100
|
});
|
|
@@ -146,59 +156,76 @@ export default function (pi: ExtensionAPI) {
|
|
|
146
156
|
const projectCount = projectStorage.listAll().length;
|
|
147
157
|
const allMemories = listAllProjects();
|
|
148
158
|
const projectCountAll = allMemories.length;
|
|
159
|
+
const vecReady = isEmbeddingReady();
|
|
160
|
+
const vecIcon = vecReady ? "⚡" : "📝";
|
|
149
161
|
ctx.ui.setStatus(
|
|
150
162
|
"unipi-memory",
|
|
151
|
-
|
|
163
|
+
`${vecIcon} memory ${projectCount}p/${projectCountAll}all${hasModelChanged() ? " ⚠" : ""}`
|
|
152
164
|
);
|
|
153
165
|
}
|
|
154
166
|
});
|
|
155
167
|
|
|
156
|
-
// Inject memory
|
|
168
|
+
// Inject memory recall reminder at agent start (hidden message, not system prompt)
|
|
157
169
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
170
|
+
if (recallDone) return;
|
|
158
171
|
if (!projectStorage) return;
|
|
159
172
|
|
|
160
173
|
const projectName = getProjectName(ctx.cwd);
|
|
161
174
|
const projectMemories = projectStorage.listAll();
|
|
162
175
|
|
|
163
176
|
if (projectMemories.length === 0) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let injection = "\n\n<memory>\n";
|
|
168
|
-
injection += `Available memories for project "${projectName}":\n\n`;
|
|
169
|
-
|
|
170
|
-
// Project memories
|
|
171
|
-
for (const m of projectMemories) {
|
|
172
|
-
injection += `- ${m.title}\n`;
|
|
177
|
+
recallDone = true; // Nothing to recall, skip
|
|
178
|
+
return;
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
injection += "</memory>";
|
|
181
|
+
const titleList = projectMemories.slice(0, 20).map(m => `- ${m.title}`).join("\n");
|
|
182
|
+
const extra = projectMemories.length > 20 ? `\n... and ${projectMemories.length - 20} more` : "";
|
|
178
183
|
|
|
179
184
|
return {
|
|
180
|
-
|
|
185
|
+
message: {
|
|
186
|
+
customType: "unipi-memory-recall-reminder",
|
|
187
|
+
content: [
|
|
188
|
+
"## 🧠 Memory System Active",
|
|
189
|
+
"",
|
|
190
|
+
`You have ${projectMemories.length} memories stored for project "${projectName}".`,
|
|
191
|
+
"**BEFORE starting work**, call `memory_search` with relevant keywords to check for existing context.",
|
|
192
|
+
"",
|
|
193
|
+
"Available memories:",
|
|
194
|
+
titleList + extra,
|
|
195
|
+
"",
|
|
196
|
+
"**AFTER completing the task**, if you learned something non-obvious,",
|
|
197
|
+
"call `memory_store` to save it for future sessions.",
|
|
198
|
+
"",
|
|
199
|
+
"Guardrails: read max 10 memory results per search. Update existing memories instead of creating duplicates.",
|
|
200
|
+
].join("\n"),
|
|
201
|
+
display: false,
|
|
202
|
+
},
|
|
181
203
|
};
|
|
182
204
|
});
|
|
183
205
|
|
|
184
|
-
//
|
|
185
|
-
pi.on("
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
// After each agent response, remind LLM to save if it hasn't yet
|
|
207
|
+
pi.on("agent_end", async (_event, _ctx) => {
|
|
208
|
+
if (storeDone || !recallDone) return;
|
|
209
|
+
|
|
210
|
+
pi.sendMessage(
|
|
211
|
+
{
|
|
212
|
+
customType: "unipi-memory-retro-reminder",
|
|
213
|
+
content: [
|
|
214
|
+
"**🧠 Memory reminder:** If you learned something non-obvious in this task,",
|
|
215
|
+
"call `memory_store` to save it as a memory for future sessions.",
|
|
216
|
+
"Update existing memories instead of creating duplicates.",
|
|
217
|
+
].join(" "),
|
|
218
|
+
display: false,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
deliverAs: "nextTurn",
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
});
|
|
199
225
|
|
|
200
|
-
|
|
201
|
-
|
|
226
|
+
// After compaction, reset recall state so reminder re-injects
|
|
227
|
+
pi.on("session_compact", async (_event, _ctx) => {
|
|
228
|
+
recallDone = false;
|
|
202
229
|
});
|
|
203
230
|
|
|
204
231
|
// Cleanup on shutdown
|
|
@@ -9,7 +9,6 @@ allowed-tools:
|
|
|
9
9
|
- memory_search
|
|
10
10
|
- memory_delete
|
|
11
11
|
- memory_list
|
|
12
|
-
- global_memory_store
|
|
13
12
|
- global_memory_search
|
|
14
13
|
- global_memory_list
|
|
15
14
|
- read
|
|
@@ -87,16 +86,18 @@ memory_list()
|
|
|
87
86
|
memory_delete(title: "auth_jwt_prefer_refresh_tokens")
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
##
|
|
89
|
+
## Search Scope
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
`memory_search` searches ALL projects by default. Use `scope` param to narrow:
|
|
92
|
+
|
|
93
|
+
| Action | Scope | Tool |
|
|
94
|
+
|--------|-------|------|
|
|
94
95
|
| **Store** | Always project-scoped | `memory_store` |
|
|
95
|
-
| **Search
|
|
96
|
-
| **Search
|
|
96
|
+
| **Search all projects** | Cross-project (default) | `memory_search(query)` or `memory_search(query, scope="all")` |
|
|
97
|
+
| **Search this project** | Current project only | `memory_search(query, scope="project")` |
|
|
97
98
|
| **List all** | Cross-project | `global_memory_list` |
|
|
98
99
|
|
|
99
|
-
**All memories are project-scoped.** When you store a memory, it belongs to the current project.
|
|
100
|
+
**All memories are project-scoped.** When you store a memory, it belongs to the current project. `memory_search` searches everything by default — no need to call a separate global search.
|
|
100
101
|
|
|
101
102
|
## Update-First Principle
|
|
102
103
|
|
|
@@ -108,7 +109,27 @@ memory_delete(title: "auth_jwt_prefer_refresh_tokens")
|
|
|
108
109
|
|
|
109
110
|
This prevents memory duplication and keeps memory clean.
|
|
110
111
|
|
|
111
|
-
##
|
|
112
|
+
## Vector Search (Embeddings)
|
|
113
|
+
|
|
114
|
+
Memory supports vector similarity search via OpenRouter API.
|
|
115
|
+
|
|
116
|
+
### Setup
|
|
117
|
+
1. Run `/unipi:memory-settings`
|
|
118
|
+
2. Add your OpenRouter API key
|
|
119
|
+
3. Select embedding model (default: `openai/text-embedding-3-small`)
|
|
120
|
+
|
|
121
|
+
### How it works
|
|
122
|
+
- Embeddings are generated when storing/searching memories
|
|
123
|
+
- Search combines **vector similarity** + **fuzzy text matching** for best results
|
|
124
|
+
- Vector search finds semantically similar memories even without exact keyword matches
|
|
125
|
+
|
|
126
|
+
### Model compatibility
|
|
127
|
+
⚠ **Different embedding models produce incompatible vectors.**
|
|
128
|
+
If you switch models, existing embeddings won't match new searches.
|
|
129
|
+
Use `/unipi:memory-settings` → "Re-embed All Memories" to fix.
|
|
130
|
+
|
|
131
|
+
### No API key?
|
|
132
|
+
Falls back to fuzzy text-only search. Still works, just less semantic.
|
|
112
133
|
|
|
113
134
|
When the user runs `/unipi:memory-consolidate` or during compaction:
|
|
114
135
|
|
|
@@ -149,3 +170,4 @@ You can read these files directly with the `read` tool for full context.
|
|
|
149
170
|
| Use vague titles | Use specific `<category>_<detail>` format |
|
|
150
171
|
| Store in wrong scope | Project-specific = project scope, universal = global |
|
|
151
172
|
| Forget to update | When context changes, update the memory |
|
|
173
|
+
| Switch embedding models without re-embedding | Re-embed or accept fuzzy-only fallback |
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @pi-unipi/web-api
|
|
2
|
+
|
|
3
|
+
Web search, read, and summarize tools with provider-based backend selection for Pi coding agent.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@pi-unipi/web-api` provides agent tools for web access:
|
|
8
|
+
|
|
9
|
+
- **web_search** — Search the web using various providers
|
|
10
|
+
- **web_read** — Extract content from URLs
|
|
11
|
+
- **web_llm_summarize** — Summarize web content using LLM
|
|
12
|
+
|
|
13
|
+
Providers are ranked by capability and cost, allowing smart auto-selection.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Provider-based architecture** — Multiple search/read providers with unified interface
|
|
18
|
+
- **Smart selection** — Auto-select cheapest available provider
|
|
19
|
+
- **API key management** — Interactive TUI for key configuration
|
|
20
|
+
- **Caching** — Web content cached with configurable TTL
|
|
21
|
+
- **Subagent integration** — Tools automatically available to spawned subagents
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @pi-unipi/web-api
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Add to your pi configuration:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"pi": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"node_modules/@pi-unipi/web-api/src/index.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Providers
|
|
42
|
+
|
|
43
|
+
### Search Providers
|
|
44
|
+
|
|
45
|
+
| Provider | Rank | Cost | API Key |
|
|
46
|
+
|----------|------|------|---------|
|
|
47
|
+
| DuckDuckGo | 1 | Free | No |
|
|
48
|
+
| Jina AI Search | 2 | Freemium | Optional |
|
|
49
|
+
| SerpAPI | 3 | Paid | Required |
|
|
50
|
+
| Tavily | 4 | Paid | Required |
|
|
51
|
+
| Perplexity | 5 | Paid | Required |
|
|
52
|
+
|
|
53
|
+
### Read Providers
|
|
54
|
+
|
|
55
|
+
| Provider | Rank | Cost | API Key |
|
|
56
|
+
|----------|------|------|---------|
|
|
57
|
+
| Jina AI Reader | 1 | Freemium | Optional |
|
|
58
|
+
| Firecrawl | 2 | Paid | Required |
|
|
59
|
+
| Perplexity | 3 | Paid | Required |
|
|
60
|
+
|
|
61
|
+
### Summarize Providers
|
|
62
|
+
|
|
63
|
+
| Provider | Rank | Cost | API Key |
|
|
64
|
+
|----------|------|------|---------|
|
|
65
|
+
| Perplexity | 1 | Paid | Required |
|
|
66
|
+
| LLM Summarize | 2 | LLM tokens | No |
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
### API Keys
|
|
71
|
+
|
|
72
|
+
Configure API keys via the interactive settings command:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
/unipi:web-settings
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or set environment variables:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export SERPAPI_KEY="your-key"
|
|
82
|
+
export TAVILY_API_KEY="your-key"
|
|
83
|
+
export FIRECRAWL_API_KEY="your-key"
|
|
84
|
+
export PERPLEXITY_API_KEY="your-key"
|
|
85
|
+
export JINA_API_KEY="your-key"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Settings Files
|
|
89
|
+
|
|
90
|
+
- **Auth:** `~/.unipi/config/web-api/auth.json` (API keys, gitignored)
|
|
91
|
+
- **Config:** `~/.unipi/config/web-api/config.json` (provider settings)
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
### Web Search
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
# Auto-select cheapest provider
|
|
99
|
+
web_search(query: "TypeScript generics")
|
|
100
|
+
|
|
101
|
+
# Use specific provider
|
|
102
|
+
web_search(query: "latest AI research", source: 4) # Tavily
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Web Read
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
# Auto-select provider
|
|
109
|
+
web_read(url: "https://example.com/article")
|
|
110
|
+
|
|
111
|
+
# Use specific provider
|
|
112
|
+
web_read(url: "https://example.com/spa", source: 2) # Firecrawl
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Web Summarize
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# Auto-summarize
|
|
119
|
+
web_llm_summarize(url: "https://example.com/long-article")
|
|
120
|
+
|
|
121
|
+
# Custom prompt
|
|
122
|
+
web_llm_summarize(url: "https://example.com/research", prompt: "Extract key findings")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
### /unipi:web-settings
|
|
128
|
+
|
|
129
|
+
Interactive settings dialog for managing providers and API keys.
|
|
130
|
+
|
|
131
|
+
### /unipi:web-cache-clear
|
|
132
|
+
|
|
133
|
+
Clear all cached web content.
|
|
134
|
+
|
|
135
|
+
## Cache
|
|
136
|
+
|
|
137
|
+
- Default TTL: 1 hour
|
|
138
|
+
- Cache location: `~/.unipi/config/web-api/cache/`
|
|
139
|
+
- Automatic for web_read operations
|
|
140
|
+
|
|
141
|
+
## Troubleshooting
|
|
142
|
+
|
|
143
|
+
### No provider available
|
|
144
|
+
|
|
145
|
+
If you see "No search provider available":
|
|
146
|
+
|
|
147
|
+
1. Run `/unipi:web-settings`
|
|
148
|
+
2. Enable at least one provider
|
|
149
|
+
3. Add API keys for paid providers
|
|
150
|
+
|
|
151
|
+
### API key invalid
|
|
152
|
+
|
|
153
|
+
If API key validation fails:
|
|
154
|
+
|
|
155
|
+
1. Check the key is correct
|
|
156
|
+
2. Verify the key has sufficient permissions
|
|
157
|
+
3. Check provider status at their website
|
|
158
|
+
|
|
159
|
+
### Rate limiting
|
|
160
|
+
|
|
161
|
+
If you hit rate limits:
|
|
162
|
+
|
|
163
|
+
1. Add an API key for higher limits
|
|
164
|
+
2. Use a different provider
|
|
165
|
+
3. Wait and retry
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Type check
|
|
171
|
+
npm run typecheck
|
|
172
|
+
|
|
173
|
+
# Build
|
|
174
|
+
npm run build
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web
|
|
3
|
+
description: "Web search, read, and summarize tools with provider-based backend"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Web Tools
|
|
7
|
+
|
|
8
|
+
Use these tools to access web content. Providers are ranked by capability and cost.
|
|
9
|
+
|
|
10
|
+
## web_search
|
|
11
|
+
|
|
12
|
+
Search the web for information. Lower `source` = simpler/cheaper providers.
|
|
13
|
+
|
|
14
|
+
- **Quick facts:** source 1-2 (DuckDuckGo, Jina Search)
|
|
15
|
+
- **Research:** source 3-5 (SerpAPI, Tavily, Perplexity)
|
|
16
|
+
|
|
17
|
+
**Parameters:**
|
|
18
|
+
- `query` (required): Search query string
|
|
19
|
+
- `source` (optional): Provider selection (1-5)
|
|
20
|
+
|
|
21
|
+
**Examples:**
|
|
22
|
+
```
|
|
23
|
+
web_search(query: "TypeScript generics tutorial")
|
|
24
|
+
web_search(query: "latest AI research", source: 4) # Use Tavily
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## web_read
|
|
28
|
+
|
|
29
|
+
Read URL content. Lower `source` = simpler providers.
|
|
30
|
+
|
|
31
|
+
- **Basic extraction:** source 1 (Jina Reader)
|
|
32
|
+
- **Advanced crawling:** source 2 (Firecrawl)
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
- `url` (required): URL to read
|
|
36
|
+
- `source` (optional): Provider selection (1-3)
|
|
37
|
+
|
|
38
|
+
**Examples:**
|
|
39
|
+
```
|
|
40
|
+
web_read(url: "https://example.com/article")
|
|
41
|
+
web_read(url: "https://example.com/spa", source: 2) # Use Firecrawl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## web_llm_summarize
|
|
45
|
+
|
|
46
|
+
Summarize URL with LLM. Higher cost (LLM tokens + provider).
|
|
47
|
+
|
|
48
|
+
- Use for complex content that needs analysis
|
|
49
|
+
- Custom prompts supported for targeted summaries
|
|
50
|
+
|
|
51
|
+
**Parameters:**
|
|
52
|
+
- `url` (required): URL to summarize
|
|
53
|
+
- `prompt` (optional): Custom summarization prompt
|
|
54
|
+
- `source` (optional): Provider selection for content fetch (1-3)
|
|
55
|
+
|
|
56
|
+
**Examples:**
|
|
57
|
+
```
|
|
58
|
+
web_llm_summarize(url: "https://example.com/long-article")
|
|
59
|
+
web_llm_summarize(url: "https://example.com/research", prompt: "Extract key findings")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Provider Selection
|
|
63
|
+
|
|
64
|
+
- Omit `source` for auto-selection (cheapest available)
|
|
65
|
+
- Specify `source` number for specific provider
|
|
66
|
+
- If provider unavailable, tool throws descriptive error
|
|
67
|
+
|
|
68
|
+
### Provider Rankings
|
|
69
|
+
|
|
70
|
+
**Search providers:**
|
|
71
|
+
1. DuckDuckGo (free)
|
|
72
|
+
2. Jina AI Search (freemium)
|
|
73
|
+
3. SerpAPI (paid)
|
|
74
|
+
4. Tavily (paid)
|
|
75
|
+
5. Perplexity (paid)
|
|
76
|
+
|
|
77
|
+
**Read providers:**
|
|
78
|
+
1. Jina AI Reader (freemium)
|
|
79
|
+
2. Firecrawl (paid)
|
|
80
|
+
3. Perplexity (paid)
|
|
81
|
+
|
|
82
|
+
**Summarize providers:**
|
|
83
|
+
1. Perplexity (paid)
|
|
84
|
+
2. LLM Summarize (uses pi's LLM)
|
|
85
|
+
|
|
86
|
+
## Cost Awareness
|
|
87
|
+
|
|
88
|
+
- **DuckDuckGo:** Free (search only)
|
|
89
|
+
- **Jina:** Freemium (search + read)
|
|
90
|
+
- **SerpAPI/Tavily:** Paid (search)
|
|
91
|
+
- **Firecrawl:** Paid (read)
|
|
92
|
+
- **Perplexity:** Paid (search + summarize)
|
|
93
|
+
- **LLM Summarize:** LLM token cost
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
Configure providers via `/unipi:web-settings` command.
|
|
98
|
+
|
|
99
|
+
- Add/remove API keys
|
|
100
|
+
- Enable/disable providers
|
|
101
|
+
- View provider status
|
|
102
|
+
|
|
103
|
+
## Cache
|
|
104
|
+
|
|
105
|
+
Web content is cached for 1 hour by default.
|
|
106
|
+
|
|
107
|
+
- Clear cache: `/unipi:web-cache-clear`
|
|
108
|
+
- Cache is automatic for web_read operations
|
|
@@ -86,7 +86,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
86
86
|
systemPrompt:
|
|
87
87
|
event.systemPrompt +
|
|
88
88
|
base +
|
|
89
|
-
`\nThe write tool is available but restricted to .unipi/docs/specs/ only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
|
|
89
|
+
`\nThe write tool is available but restricted to .unipi/docs/specs/ only.\nbash is available ONLY for specific setup use case (e.g., git init, mkdir). Do NOT use bash for reading files or listing directories — use grep, find, ls instead.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (currentSandboxLevel === "write_unipi") {
|
|
94
|
+
return {
|
|
95
|
+
systemPrompt:
|
|
96
|
+
event.systemPrompt +
|
|
97
|
+
base +
|
|
98
|
+
`\nWrite tool is restricted to .unipi/docs/ only (specs and plans).\nUse grep, find, ls for file discovery — do NOT guess filenames.\nbash is blocked — use read, write, edit, grep, find, ls only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
|
|
90
99
|
};
|
|
91
100
|
}
|
|
92
101
|
|
|
@@ -94,7 +103,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
94
103
|
systemPrompt:
|
|
95
104
|
event.systemPrompt +
|
|
96
105
|
base +
|
|
97
|
-
`\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user
|
|
106
|
+
`\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requires an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
|
|
98
107
|
};
|
|
99
108
|
});
|
|
100
109
|
|
|
@@ -145,6 +145,7 @@ The `## Implementation Checklist` section uses `- [ ]` markdown checkboxes. Thes
|
|
|
145
145
|
|
|
146
146
|
- **`[ ]` = unplanned** — not yet covered by a plan
|
|
147
147
|
- **`[x]` = planned** — marked when `/unipi:plan` covers this item
|
|
148
|
+
- **`[x]` does NOT mean done** — implementation progress is tracked in the plan file (`unstarted:` / `in-progress:` / `completed:`)
|
|
148
149
|
- Agent MUST fill in checklist items based on the design
|
|
149
150
|
- Each item should be a discrete, implementable task
|
|
150
151
|
- Items should be ordered by dependency (earlier items don't depend on later ones)
|
|
@@ -70,6 +70,7 @@ Structure the plan with heart of gold style:
|
|
|
70
70
|
title: "{Topic} — Implementation Plan"
|
|
71
71
|
type: plan
|
|
72
72
|
date: YYYY-MM-DD
|
|
73
|
+
workbranch: {branch-name or empty if on main}
|
|
73
74
|
specs:
|
|
74
75
|
- path/to/spec.md
|
|
75
76
|
---
|
|
@@ -81,16 +82,19 @@ specs:
|
|
|
81
82
|
|
|
82
83
|
## Tasks
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
1. {Concrete step}
|
|
90
|
-
2. {Concrete step}
|
|
85
|
+
- unstarted: Task 1 — {Task Name}
|
|
86
|
+
- Description: {What needs to be done}
|
|
87
|
+
- Dependencies: {None, or list of tasks}
|
|
88
|
+
- Acceptance Criteria: {How to verify done}
|
|
89
|
+
- Steps:
|
|
90
|
+
1. {Concrete step}
|
|
91
|
+
2. {Concrete step}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
...
|
|
93
|
+
- unstarted: Task 2 — {Task Name}
|
|
94
|
+
- Description: ...
|
|
95
|
+
- Dependencies: ...
|
|
96
|
+
- Acceptance Criteria: ...
|
|
97
|
+
- Steps: ...
|
|
94
98
|
|
|
95
99
|
## Sequencing
|
|
96
100
|
{Order of execution, dependency graph if complex}
|
|
@@ -107,6 +111,20 @@ specs:
|
|
|
107
111
|
- **Steps** are bite-sized — agent can follow without guessing
|
|
108
112
|
- Order tasks by dependency (foundational work first)
|
|
109
113
|
|
|
114
|
+
### Task Status Lifecycle
|
|
115
|
+
|
|
116
|
+
Tasks use prefixes to track progress:
|
|
117
|
+
|
|
118
|
+
| Status | Meaning |
|
|
119
|
+
|--------|----------|
|
|
120
|
+
| `unstarted:` | Not started |
|
|
121
|
+
| `in-progress:` | Being worked on |
|
|
122
|
+
| `completed:` | Done and verified |
|
|
123
|
+
| `failed:` | Attempted but failed, needs investigation |
|
|
124
|
+
| `awaiting_user:` | Needs user action (test, approve, provide input) |
|
|
125
|
+
| `blocked:` | Waiting on dependency or external factor |
|
|
126
|
+
| `skipped:` | Intentionally not doing (deferred, out of scope) |
|
|
127
|
+
|
|
110
128
|
---
|
|
111
129
|
|
|
112
130
|
## Phase 4: Update Brainstorm Checkboxes
|
|
@@ -129,6 +147,8 @@ After plan is complete:
|
|
|
129
147
|
|
|
130
148
|
This creates the link between brainstorm and plan — plan covers some items, others remain for future plans.
|
|
131
149
|
|
|
150
|
+
**Semantics:** Spec `[x]` means "planned" (covered by a plan). It does NOT mean "done". Implementation progress is tracked in the plan file, not the spec.
|
|
151
|
+
|
|
132
152
|
---
|
|
133
153
|
|
|
134
154
|
## Phase 5: Review Plan
|
|
@@ -138,10 +158,11 @@ Self-check before presenting:
|
|
|
138
158
|
- [ ] Every task has clear acceptance criteria
|
|
139
159
|
- [ ] Dependencies are correct (no circular, no missing)
|
|
140
160
|
- [ ] Steps are bite-sized (agent can follow without guessing)
|
|
141
|
-
- [ ] Brainstorm checkboxes updated correctly
|
|
142
161
|
- [ ] String greedy scope respected (if provided)
|
|
143
162
|
- [ ] Plan is focused enough for single `/unipi:work` session
|
|
144
163
|
|
|
164
|
+
Do NOT re-read or re-edit the spec checkboxes — Phase 4 already wrote them.
|
|
165
|
+
|
|
145
166
|
---
|
|
146
167
|
|
|
147
168
|
## Phase 6: Present & Handoff
|
|
@@ -164,6 +185,5 @@ Recommend starting a **new session** for work — it will switch pi's internal w
|
|
|
164
185
|
## Resumability
|
|
165
186
|
|
|
166
187
|
If user runs `/unipi:plan` on an existing plan:
|
|
167
|
-
1. Read the plan
|
|
168
|
-
2.
|
|
169
|
-
3. Ask user if they want to add tasks, modify existing, or plan new scope
|
|
188
|
+
1. Read the plan — look for `completed:` tasks
|
|
189
|
+
2. Ask user if they want to add tasks, modify existing, or plan new scope
|
|
@@ -25,13 +25,17 @@ Review what was built, verify task completion, run codebase checks, add reviewer
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
## Phase 1: Load Plan
|
|
28
|
+
## Phase 1: Load Plan & Switch Branch
|
|
29
29
|
|
|
30
30
|
1. If `plan:` arg provided, read that plan
|
|
31
31
|
2. If not, list plans in `.unipi/docs/plans/` and ask user
|
|
32
32
|
3. Read plan fully — understand tasks, acceptance criteria, current status
|
|
33
|
+
4. **Read `workbranch:` from plan frontmatter:**
|
|
34
|
+
- If `workbranch:` exists and is not empty → switch to that branch/worktree
|
|
35
|
+
- If `workbranch:` missing or empty → review on current branch (main)
|
|
36
|
+
- To switch: `git checkout {workbranch}` or use worktree path
|
|
33
37
|
|
|
34
|
-
**Exit:**
|
|
38
|
+
**Exit:** On correct branch. Plan loaded.
|
|
35
39
|
|
|
36
40
|
---
|
|
37
41
|
|
|
@@ -42,9 +46,13 @@ For each task in plan:
|
|
|
42
46
|
1. Read acceptance criteria
|
|
43
47
|
2. Verify against actual implementation
|
|
44
48
|
3. Determine status:
|
|
45
|
-
- **Done** — all criteria met
|
|
49
|
+
- **Done** — all criteria met → `completed:`
|
|
46
50
|
- **Partially Done X/Y** — some steps complete, others not
|
|
47
|
-
- **Unstarted** — nothing done
|
|
51
|
+
- **Unstarted** — nothing done → `unstarted:`
|
|
52
|
+
- **Failed** — attempted but broken → `failed:`
|
|
53
|
+
- **Awaiting User** — needs user action → `awaiting_user:`
|
|
54
|
+
- **Blocked** — waiting on dependency → `blocked:`
|
|
55
|
+
- **Skipped** — intentionally not done → `skipped:`
|
|
48
56
|
|
|
49
57
|
If `string(greedy)` scope provided, only check matching tasks.
|
|
50
58
|
|
|
@@ -39,6 +39,7 @@ If args not provided, ask user interactively:
|
|
|
39
39
|
- "Do you want to work on current branch or create a worktree?"
|
|
40
40
|
- If worktree: "What branch name?" (suggest based on spec topic)
|
|
41
41
|
- Create worktree if not exists
|
|
42
|
+
- **After creating/confirming worktree:** write `workbranch: {branch-name}` to the plan file frontmatter
|
|
42
43
|
|
|
43
44
|
2. **Specs:**
|
|
44
45
|
- List available plans in `.unipi/docs/plans/`
|
|
@@ -58,7 +59,7 @@ If args not provided, ask user interactively:
|
|
|
58
59
|
1. Read plan file(s)
|
|
59
60
|
2. Review critically — identify questions or concerns
|
|
60
61
|
3. If concerns: raise with user before starting
|
|
61
|
-
4. Identify
|
|
62
|
+
4. Identify task status: `unstarted:` (pending), `in-progress:` (started), `completed:` (done), `failed:` (needs investigation), `awaiting_user:` (needs user action), `blocked:` (waiting on dependency), `skipped:` (deferred)
|
|
62
63
|
|
|
63
64
|
**Exit:** Plan reviewed, ready to execute.
|
|
64
65
|
|
|
@@ -66,14 +67,36 @@ If args not provided, ask user interactively:
|
|
|
66
67
|
|
|
67
68
|
## Phase 3: Execute Tasks
|
|
68
69
|
|
|
69
|
-
For each task in order:
|
|
70
|
+
For each task in order, skip `completed:`, `skipped:`, and `awaiting_user:` tasks:
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
### If `unstarted:`
|
|
73
|
+
1. Change `unstarted:` to `in-progress:` in plan
|
|
72
74
|
2. Follow each step exactly (plan has bite-sized steps)
|
|
73
75
|
3. Run verifications as specified in acceptance criteria
|
|
74
|
-
4.
|
|
76
|
+
4. Change `in-progress:` to `completed:` when complete
|
|
75
77
|
5. Update plan file with progress
|
|
76
78
|
|
|
79
|
+
### If `in-progress:`
|
|
80
|
+
1. Continue from where it left off
|
|
81
|
+
2. Follow remaining steps
|
|
82
|
+
3. Change to `completed:` when done
|
|
83
|
+
|
|
84
|
+
### If `failed:`
|
|
85
|
+
1. Read failure notes
|
|
86
|
+
2. Investigate root cause
|
|
87
|
+
3. Fix and re-verify
|
|
88
|
+
4. Change to `completed:` when fixed, or keep `failed:` if still broken
|
|
89
|
+
|
|
90
|
+
### If `awaiting_user:`
|
|
91
|
+
1. Remind user what's needed
|
|
92
|
+
2. Wait for user response
|
|
93
|
+
3. Resume when user provides input
|
|
94
|
+
|
|
95
|
+
### If `blocked:`
|
|
96
|
+
1. Check if blocker is resolved
|
|
97
|
+
2. If resolved → change to `unstarted:` and continue
|
|
98
|
+
3. If still blocked → skip and move to next task
|
|
99
|
+
|
|
77
100
|
### When to Stop and Ask
|
|
78
101
|
|
|
79
102
|
**STOP immediately when:**
|
|
@@ -107,12 +130,11 @@ Don't wait until end to commit — incremental commits are safer.
|
|
|
107
130
|
|
|
108
131
|
## Phase 5: Complete
|
|
109
132
|
|
|
110
|
-
When all tasks
|
|
133
|
+
When all tasks are `completed:`:
|
|
111
134
|
|
|
112
135
|
1. Run final verification (tests, lint, build)
|
|
113
136
|
2. Commit all remaining changes
|
|
114
|
-
3.
|
|
115
|
-
4. Inform user:
|
|
137
|
+
3. Inform user:
|
|
116
138
|
|
|
117
139
|
> "All tasks complete. Worktree: `feat/<branch>`. Recommend reviewing before merge."
|
|
118
140
|
|
|
@@ -127,9 +149,9 @@ Suggest next step:
|
|
|
127
149
|
|
|
128
150
|
## Resumability
|
|
129
151
|
|
|
130
|
-
If user runs `/unipi:work` and plan has
|
|
152
|
+
If user runs `/unipi:work` and plan has `completed:` or `in-progress:` tasks:
|
|
131
153
|
1. Read plan
|
|
132
|
-
2. Identify first `
|
|
154
|
+
2. Identify first `unstarted:` task
|
|
133
155
|
3. Ask user: "Resume from Task N: {name}?"
|
|
134
156
|
4. Continue from there
|
|
135
157
|
|
|
@@ -9,8 +9,8 @@ List all git worktrees created by unipi.
|
|
|
9
9
|
|
|
10
10
|
## Boundaries
|
|
11
11
|
|
|
12
|
-
**This skill MAY:**
|
|
13
|
-
**This skill MAY NOT:** edit anything, create worktrees, merge branches.
|
|
12
|
+
**This skill MAY:** read filesystem (ls, read), grep for git info.
|
|
13
|
+
**This skill MAY NOT:** edit anything, create worktrees, merge branches, run bash.
|
|
14
14
|
|
|
15
15
|
## Command Format
|
|
16
16
|
|
|
@@ -24,17 +24,27 @@ No arguments. Lists all unipi worktrees.
|
|
|
24
24
|
|
|
25
25
|
## Process
|
|
26
26
|
|
|
27
|
-
### Phase 1:
|
|
27
|
+
### Phase 1: Discover Worktrees
|
|
28
28
|
|
|
29
|
-
1.
|
|
30
|
-
2.
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
- HEAD commit
|
|
35
|
-
- Whether it has uncommitted changes
|
|
29
|
+
1. `ls .unipi/worktrees/` — list worktree directories
|
|
30
|
+
2. For each directory:
|
|
31
|
+
- Read `.unipi/worktrees/<branch>/.git` file to get gitdir path
|
|
32
|
+
- Check for uncommitted changes: `ls .unipi/worktrees/<branch>/` and read key files
|
|
33
|
+
3. If `.unipi/worktrees/` doesn't exist or is empty → no worktrees
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
**Exit:** List of worktrees discovered.
|
|
36
|
+
|
|
37
|
+
### Phase 2: Determine Status
|
|
38
|
+
|
|
39
|
+
For each worktree directory:
|
|
40
|
+
|
|
41
|
+
1. Check if `HEAD` file exists in gitdir
|
|
42
|
+
2. Check for modified files by reading directory listing
|
|
43
|
+
3. Determine status:
|
|
44
|
+
- **clean** — no uncommitted changes
|
|
45
|
+
- **dirty** — has uncommitted changes
|
|
46
|
+
|
|
47
|
+
### Phase 3: Present
|
|
38
48
|
|
|
39
49
|
Display in table format:
|
|
40
50
|
|
|
@@ -51,7 +61,7 @@ Worktrees:
|
|
|
51
61
|
If no worktrees:
|
|
52
62
|
> "No unipi worktrees. Create one with `/unipi:worktree-create`"
|
|
53
63
|
|
|
54
|
-
### Phase
|
|
64
|
+
### Phase 4: Suggest Actions
|
|
55
65
|
|
|
56
66
|
Based on state:
|
|
57
67
|
- If dirty worktrees exist → suggest reviewing or committing
|
|
@@ -65,3 +75,4 @@ Based on state:
|
|
|
65
75
|
- Only shows worktrees under `.unipi/worktrees/`
|
|
66
76
|
- Ignores worktrees in other locations
|
|
67
77
|
- Status: `clean` = no uncommitted changes, `dirty` = has changes
|
|
78
|
+
- No bash required — uses ls, read, grep for all discovery
|
|
@@ -46,8 +46,16 @@ For each source branch (in dependency order if specs indicate ordering):
|
|
|
46
46
|
3. **If conflict:**
|
|
47
47
|
- Report which files conflict
|
|
48
48
|
- Reference the spec/plan context: "This branch was working on: <description>"
|
|
49
|
-
-
|
|
50
|
-
|
|
49
|
+
- **Structured conflict resolution:**
|
|
50
|
+
1. **Check finalized plan** — read the plan for this branch, understand intended changes
|
|
51
|
+
2. **Brainstorm resolution** — reason about which changes align with the plan's intent
|
|
52
|
+
3. **Read codebase** — read the conflicting files to understand current state
|
|
53
|
+
4. **Ask user** — present options and ask for final decision (always ask, never force)
|
|
54
|
+
- Options to present:
|
|
55
|
+
- Accept incoming (their changes)
|
|
56
|
+
- Accept current (main branch)
|
|
57
|
+
- Manual merge (describe what each side does, ask user to decide)
|
|
58
|
+
- Skip this branch
|
|
51
59
|
4. **If success:**
|
|
52
60
|
- Remove worktree: `git worktree remove .unipi/worktrees/<branch-name>`
|
|
53
61
|
- Delete branch: `git branch -d <branch>`
|