@henryqw/pi-herdr-rename 0.3.1 → 1.0.1
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 +24 -23
- package/extensions/rename.ts +168 -77
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
1
|
# `@henryqw/pi-herdr-rename`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give each conversation a short semantic title: Pi session name, current Herdr pane, and a Git branch when the checkout is still generated.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
pi install npm:@henryqw/pi-task-models
|
|
8
9
|
pi install npm:@henryqw/pi-herdr-rename
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
`pi-task-models` provides `/task-models` for the rename routes. Requires Pi Coding Agent 0.84.2+.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
pi remove npm:@henryqw/pi-herdr-rename
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Behavior
|
|
18
|
-
|
|
19
|
-
- On the first real, non-empty text prompt in a new session, title generation starts in the background and does not delay the main Pi response. Extension-injected prompts, empty prompts, and image-only input are ignored.
|
|
20
|
-
- Successful titles are lowercase and, by default, at most four words and 40 characters. The first 1,000 characters of user text are sent to the rename model; prompt content is never logged.
|
|
21
|
-
- A successful title updates the Pi session name and current Herdr pane. The enclosing Herdr tab is updated only when the current tab has one pane. Outside Herdr, only the Pi session name changes.
|
|
22
|
-
- Resuming a named session reapplies its saved title without another rename-model request. Automatic rename-model errors show a warning; other automatic failures stay quiet. Failures do not change labels, fall back locally, or retry.
|
|
14
|
+
## Use
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
| Surface | Purpose |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| first real user prompt | Generate a title in the background. Does not delay the main reply. |
|
|
19
|
+
| `/rename` | Generate a title from up to three recent user/assistant rounds. |
|
|
20
|
+
| `/task-models` | Configure the `pi-herdr-rename/rename` profile (default `fast`). |
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
Successful titles use `type: subject`, lowercase words, and by default at most four words and 40 characters. Example: `fix: extension name` → Git branch `fix/extension-name`.
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
In a linked worktree, a detached checkout or Herdr `worktree/...` branch is renamed; an existing non-generated branch stays. A generated workspace label such as `worktree-brave-meadow-4aa8` becomes the semantic branch; custom workspace names stay. The enclosing Herdr tab updates only when this pane is the tab's only pane. Outside Herdr, only the Pi session name changes.
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Tries the assigned profile primary, then fallback. Never substitutes the current session model. No viable route leaves titles unchanged. Resuming a named session reapplies the saved title without another model request.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
## Config
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
`~/.pi/agent/config/pi-herdr-rename.json`
|
|
35
31
|
|
|
36
32
|
```json
|
|
37
33
|
{
|
|
38
|
-
"model": "provider/model",
|
|
39
34
|
"maxWords": 4,
|
|
40
35
|
"maxChars": 40
|
|
41
36
|
}
|
|
42
37
|
```
|
|
43
38
|
|
|
44
|
-
`maxWords` and `maxChars`
|
|
39
|
+
`maxWords` must be at least 2 and `maxChars` at least 6; defaults are 4 and 40. Invalid limits use defaults. Model routes live in `~/.pi/agent/config/pi-task-models.json`. Malformed shared task-model config is reported, left unchanged, and never changes a title.
|
|
40
|
+
|
|
41
|
+
## Remove
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pi remove npm:@henryqw/pi-herdr-rename
|
|
45
|
+
```
|
|
45
46
|
|
|
46
47
|
## Development
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
|
-
npm test
|
|
50
|
-
npm run typecheck
|
|
51
|
-
npm run pack:check
|
|
50
|
+
npm test --workspace @henryqw/pi-herdr-rename
|
|
51
|
+
npm run typecheck --workspace @henryqw/pi-herdr-rename
|
|
52
|
+
npm run pack:check --workspace @henryqw/pi-herdr-rename
|
|
52
53
|
```
|
package/extensions/rename.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
BorderedLoader,
|
|
5
5
|
getAgentDir,
|
|
@@ -7,44 +7,90 @@ import {
|
|
|
7
7
|
type ExtensionContext,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { createHerdrClient } from "@henryqw/pi-herdr";
|
|
10
|
+
import {
|
|
11
|
+
orderedProfileRoutes,
|
|
12
|
+
readTaskModelsConfig,
|
|
13
|
+
resolveTaskModelRoute,
|
|
14
|
+
type ResolvedTaskRoute,
|
|
15
|
+
} from "@henryqw/pi-task-models";
|
|
10
16
|
|
|
11
17
|
const WIDGET_KEY = "pi-herdr-rename";
|
|
12
18
|
const WIDGET_RESULT_MS = 2_000;
|
|
13
19
|
const MAX_MESSAGE_CHARS = 1_000;
|
|
14
|
-
const MAX_CONTEXT_CHARS =
|
|
20
|
+
const MAX_CONTEXT_CHARS = 2_000;
|
|
15
21
|
const DEFAULT_MAX_WORDS = 4;
|
|
16
22
|
const DEFAULT_MAX_CHARS = 40;
|
|
23
|
+
const RENAME_TASK = "pi-herdr-rename/rename";
|
|
24
|
+
const DEFAULT_RENAME_PROFILE = "fast" as const;
|
|
25
|
+
const HERDR_DEFAULT_WORKTREE_NAME = /^(?:worktree[-/])?(?:brave|calm|clear|green|lucky|quiet|rapid|silver)-(?:river|cloud|field|forest|harbor|meadow|stone|valley)-[0-9a-f]{4}$/;
|
|
17
26
|
const configPath = () => join(getAgentDir(), "config", "pi-herdr-rename.json");
|
|
18
27
|
|
|
19
|
-
type RenameConfig = {
|
|
28
|
+
type RenameConfig = { maxWords: number; maxChars: number };
|
|
20
29
|
|
|
21
30
|
class RenameModelError extends Error {}
|
|
22
31
|
|
|
23
|
-
const positiveInteger = (value: unknown, fallback: number) =>
|
|
24
|
-
typeof value === "number" && Number.isInteger(value) && value
|
|
32
|
+
const positiveInteger = (value: unknown, fallback: number, minimum = 1) =>
|
|
33
|
+
typeof value === "number" && Number.isInteger(value) && value >= minimum ? value : fallback;
|
|
34
|
+
|
|
35
|
+
function configuredRenameRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
|
|
36
|
+
let config;
|
|
37
|
+
try {
|
|
38
|
+
config = readTaskModelsConfig();
|
|
39
|
+
} catch {
|
|
40
|
+
throw new RenameModelError("Couldn't read task model config. Run /task-models.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const profileName = config.tasks[RENAME_TASK] ?? DEFAULT_RENAME_PROFILE;
|
|
44
|
+
const profile = config.profiles[profileName];
|
|
45
|
+
if (!profile) {
|
|
46
|
+
throw new RenameModelError(`Rename task profile ${profileName} is not configured. Run /task-models.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const routes = orderedProfileRoutes(profile)
|
|
50
|
+
.map((route) => resolveTaskModelRoute(ctx, route))
|
|
51
|
+
.filter((route): route is ResolvedTaskRoute => route !== undefined);
|
|
52
|
+
if (!routes.length) {
|
|
53
|
+
throw new RenameModelError(`Rename task profile ${profileName} has no available route. Run /task-models.`);
|
|
54
|
+
}
|
|
55
|
+
return routes;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function branchFromTitle(title: string): string | undefined {
|
|
59
|
+
const match = /^([a-z][a-z0-9-]*): ([a-z0-9]+(?: [a-z0-9]+)*)$/.exec(title);
|
|
60
|
+
return match ? `${match[1]}/${match[2].replaceAll(" ", "-")}` : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function branchAvailable(candidate: string, branches: string[]): boolean {
|
|
64
|
+
return branches.every((branch) => branch !== candidate && !branch.startsWith(`${candidate}/`) && !candidate.startsWith(`${branch}/`));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function availableBranch(branch: string, branches: string[]): string {
|
|
68
|
+
const [type, subject] = branch.split("/");
|
|
69
|
+
for (let suffix = 1; suffix <= branches.length + 1; suffix++) {
|
|
70
|
+
const candidate = suffix === 1 ? branch : `${type}/${subject}-${suffix}`;
|
|
71
|
+
if (branchAvailable(candidate, branches)) return candidate;
|
|
72
|
+
}
|
|
73
|
+
for (let suffix = 2; suffix <= branches.length + 2; suffix++) {
|
|
74
|
+
const candidate = `${type}-${suffix}/${subject}`;
|
|
75
|
+
if (branchAvailable(candidate, branches)) return candidate;
|
|
76
|
+
}
|
|
77
|
+
throw new Error("Could not choose an available semantic branch.");
|
|
78
|
+
}
|
|
25
79
|
|
|
26
80
|
async function configured(): Promise<RenameConfig> {
|
|
27
81
|
try {
|
|
28
82
|
const config: unknown = JSON.parse(await readFile(configPath(), "utf8"));
|
|
29
83
|
if (config && typeof config === "object" && !Array.isArray(config)) {
|
|
30
|
-
const values = config as {
|
|
84
|
+
const values = config as { maxWords?: unknown; maxChars?: unknown };
|
|
31
85
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
maxChars: positiveInteger(values.maxChars, DEFAULT_MAX_CHARS),
|
|
86
|
+
maxWords: positiveInteger(values.maxWords, DEFAULT_MAX_WORDS, 2),
|
|
87
|
+
maxChars: positiveInteger(values.maxChars, DEFAULT_MAX_CHARS, 6),
|
|
35
88
|
};
|
|
36
89
|
}
|
|
37
90
|
} catch {}
|
|
38
91
|
return { maxWords: DEFAULT_MAX_WORDS, maxChars: DEFAULT_MAX_CHARS };
|
|
39
92
|
}
|
|
40
93
|
|
|
41
|
-
async function saveModel(model: string): Promise<void> {
|
|
42
|
-
const path = configPath();
|
|
43
|
-
const { maxWords, maxChars } = await configured();
|
|
44
|
-
await mkdir(dirname(path), { recursive: true });
|
|
45
|
-
await writeFile(path, `${JSON.stringify({ model, maxWords, maxChars }, null, 2)}\n`, "utf8");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
94
|
function messageText(content: unknown): string {
|
|
49
95
|
if (typeof content === "string") return content;
|
|
50
96
|
if (!Array.isArray(content)) return "";
|
|
@@ -99,40 +145,67 @@ function recentConversation(ctx: ExtensionContext, fallback?: string): string |
|
|
|
99
145
|
}
|
|
100
146
|
|
|
101
147
|
async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortSignal): Promise<string> {
|
|
102
|
-
const {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
const { maxWords, maxChars } = await configured();
|
|
149
|
+
const completionContext = {
|
|
150
|
+
systemPrompt: `Return only a semantic title for latest user intent. Format: type: subject. Use lowercase type and lowercase alphanumeric subject words separated by spaces. No other punctuation; at most ${maxWords} words and at most ${maxChars} characters.`,
|
|
151
|
+
messages: [{ role: "user" as const, content: text.slice(0, MAX_CONTEXT_CHARS), timestamp: Date.now() }],
|
|
152
|
+
};
|
|
153
|
+
const complete = async (route: ResolvedTaskRoute) => {
|
|
154
|
+
let auth;
|
|
155
|
+
try {
|
|
156
|
+
auth = await ctx.modelRegistry.getApiKeyAndHeaders(route.model);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (signal.aborted) throw error;
|
|
159
|
+
throw new RenameModelError("Couldn't authenticate rename task model.");
|
|
160
|
+
}
|
|
161
|
+
if (!auth.ok) throw new RenameModelError("Couldn't authenticate rename task model.");
|
|
162
|
+
|
|
163
|
+
const provider = ctx.modelRegistry.getProvider(route.model.provider);
|
|
164
|
+
if (!provider) throw new RenameModelError("Rename task model provider is unavailable.");
|
|
165
|
+
const model = auth.baseUrl ? { ...route.model, baseUrl: auth.baseUrl } : route.model;
|
|
166
|
+
|
|
167
|
+
let response;
|
|
168
|
+
try {
|
|
169
|
+
// streamSimple maps the shared thinking level through this registered model's metadata.
|
|
170
|
+
response = await provider.streamSimple(model, completionContext, {
|
|
171
|
+
apiKey: auth.apiKey,
|
|
172
|
+
headers: auth.headers,
|
|
173
|
+
env: auth.env,
|
|
174
|
+
signal,
|
|
175
|
+
maxRetries: 0,
|
|
176
|
+
maxTokens: 64,
|
|
177
|
+
...(route.thinkingLevel === "off" ? {} : { reasoning: route.thinkingLevel }),
|
|
178
|
+
}).result();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (signal.aborted) throw error;
|
|
181
|
+
throw new RenameModelError(error instanceof Error ? error.message : "Rename task model failed.");
|
|
182
|
+
}
|
|
183
|
+
if (response.stopReason === "error") throw new RenameModelError(response.errorMessage || "Rename task model failed.");
|
|
184
|
+
if (response.stopReason !== "stop") throw new RenameModelError("Rename task model did not return a complete title.");
|
|
185
|
+
return response;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
let failure: RenameModelError | undefined;
|
|
189
|
+
for (const route of configuredRenameRoutes(ctx)) {
|
|
190
|
+
try {
|
|
191
|
+
const response = await complete(route);
|
|
192
|
+
const title = response.content
|
|
193
|
+
.filter((part) => part.type === "text")
|
|
194
|
+
.map((part) => part.text)
|
|
195
|
+
.join(" ")
|
|
196
|
+
.trim()
|
|
197
|
+
.toLowerCase()
|
|
198
|
+
.replace(/\s+/g, " ");
|
|
199
|
+
if (!title || title.length > maxChars || title.split(" ").length > maxWords || !branchFromTitle(title)) {
|
|
200
|
+
throw new RenameModelError("Rename task model returned an invalid title.");
|
|
201
|
+
}
|
|
202
|
+
return title;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (signal.aborted || !(error instanceof RenameModelError)) throw error;
|
|
205
|
+
failure = error;
|
|
206
|
+
}
|
|
134
207
|
}
|
|
135
|
-
|
|
208
|
+
throw failure ?? new RenameModelError("Rename task model routes failed.");
|
|
136
209
|
}
|
|
137
210
|
|
|
138
211
|
export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
@@ -157,7 +230,8 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
157
230
|
if (!isCurrent(request, controller)) return;
|
|
158
231
|
|
|
159
232
|
const paneResponse: unknown = await herdr.json(["pane", "get", paneId], { signal: controller.signal });
|
|
160
|
-
const
|
|
233
|
+
const pane = (paneResponse as { result?: { pane?: { tab_id?: unknown; workspace_id?: unknown } } }).result?.pane;
|
|
234
|
+
const tabId = pane?.tab_id;
|
|
161
235
|
if (typeof tabId !== "string" || !tabId) throw new Error("Herdr pane response omitted tab_id.");
|
|
162
236
|
if (!isCurrent(request, controller)) return;
|
|
163
237
|
|
|
@@ -167,6 +241,40 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
167
241
|
if (paneCount === 1 && isCurrent(request, controller)) {
|
|
168
242
|
await herdr.run(["tab", "rename", tabId, title], { signal: controller.signal });
|
|
169
243
|
}
|
|
244
|
+
if (!isCurrent(request, controller)) return;
|
|
245
|
+
|
|
246
|
+
const workspaceId = pane?.workspace_id;
|
|
247
|
+
if (typeof workspaceId !== "string" || !workspaceId) throw new Error("Herdr pane response omitted workspace_id.");
|
|
248
|
+
const workspaceResponse: unknown = await herdr.json(["workspace", "get", workspaceId], { signal: controller.signal });
|
|
249
|
+
const workspace = (workspaceResponse as { result?: { workspace?: { label?: unknown; worktree?: { checkout_path?: unknown; is_linked_worktree?: unknown } } } }).result?.workspace;
|
|
250
|
+
const workspaceName = workspace?.label;
|
|
251
|
+
if (typeof workspaceName !== "string") throw new Error("Herdr workspace response omitted label.");
|
|
252
|
+
const worktree = workspace?.worktree;
|
|
253
|
+
const checkoutPath = worktree?.checkout_path;
|
|
254
|
+
if (worktree?.is_linked_worktree !== true || typeof checkoutPath !== "string" || !checkoutPath) return;
|
|
255
|
+
|
|
256
|
+
const runGit = async (args: string[]) => {
|
|
257
|
+
const result = await pi.exec("git", args, { cwd: checkoutPath, signal: controller.signal });
|
|
258
|
+
if (result.code !== 0 || result.killed) {
|
|
259
|
+
throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim() || (result.killed ? "killed" : `exit ${result.code}`)}`);
|
|
260
|
+
}
|
|
261
|
+
return result.stdout.trim();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let branch = await runGit(["branch", "--show-current"]);
|
|
265
|
+
if (!branch || branch.startsWith("worktree/")) {
|
|
266
|
+
const generatedBranch = branchFromTitle(title);
|
|
267
|
+
if (!generatedBranch || !isCurrent(request, controller)) return;
|
|
268
|
+
const branches = (await runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"]))
|
|
269
|
+
.split("\n")
|
|
270
|
+
.filter(Boolean);
|
|
271
|
+
const semanticBranch = availableBranch(generatedBranch, branches);
|
|
272
|
+
await runGit(branch ? ["branch", "-m", semanticBranch] : ["switch", "-c", semanticBranch]);
|
|
273
|
+
branch = semanticBranch;
|
|
274
|
+
}
|
|
275
|
+
if (HERDR_DEFAULT_WORKTREE_NAME.test(workspaceName) && isCurrent(request, controller)) {
|
|
276
|
+
await herdr.run(["workspace", "rename", workspaceId, branch], { signal: controller.signal });
|
|
277
|
+
}
|
|
170
278
|
};
|
|
171
279
|
|
|
172
280
|
const begin = () => {
|
|
@@ -205,14 +313,20 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
205
313
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
206
314
|
};
|
|
207
315
|
|
|
208
|
-
pi.on("session_start",
|
|
316
|
+
pi.on("session_start", (_event, ctx) => {
|
|
209
317
|
clearWidget(ctx);
|
|
210
318
|
active?.abort();
|
|
211
319
|
active = undefined;
|
|
212
320
|
sequence++;
|
|
213
321
|
latestUserText = latestSessionUserText(ctx);
|
|
214
|
-
|
|
215
|
-
|
|
322
|
+
try {
|
|
323
|
+
const taskModels = readTaskModelsConfig();
|
|
324
|
+
const profileName = taskModels.tasks[RENAME_TASK] ?? DEFAULT_RENAME_PROFILE;
|
|
325
|
+
if (!taskModels.profiles[profileName]) {
|
|
326
|
+
ctx.ui.notify(`Configure rename task profile ${profileName} with /task-models.`, "warning");
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
ctx.ui.notify("Couldn't read task model config. Run /task-models.", "warning");
|
|
216
330
|
}
|
|
217
331
|
const title = pi.getSessionName();
|
|
218
332
|
automaticStarted = Boolean(title || latestUserText);
|
|
@@ -271,27 +385,4 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
271
385
|
widgetTimer.unref?.();
|
|
272
386
|
},
|
|
273
387
|
});
|
|
274
|
-
|
|
275
|
-
pi.registerCommand("rename-model", {
|
|
276
|
-
description: "Choose the model used to generate chat titles",
|
|
277
|
-
handler: async (_args, ctx) => {
|
|
278
|
-
const models = ctx.modelRegistry
|
|
279
|
-
.getAvailable()
|
|
280
|
-
.filter((model) => model.input.includes("text"))
|
|
281
|
-
.map((model) => `${model.provider}/${model.id}`)
|
|
282
|
-
.sort();
|
|
283
|
-
if (!models.length) {
|
|
284
|
-
ctx.ui.notify("No authenticated text models are available.", "warning");
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const selected = await ctx.ui.select("Rename model", models);
|
|
288
|
-
if (!selected) return;
|
|
289
|
-
try {
|
|
290
|
-
await saveModel(selected);
|
|
291
|
-
ctx.ui.notify(`Rename model saved: ${selected}`, "info");
|
|
292
|
-
} catch {
|
|
293
|
-
ctx.ui.notify("Couldn't save rename model config.", "warning");
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
388
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-herdr-rename",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Generate short Pi chat titles and rename the current Herdr location.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"pack:check": "npm pack --dry-run"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@earendil-works/pi-
|
|
28
|
+
"@earendil-works/pi-ai": "^0.84.2",
|
|
29
|
+
"@earendil-works/pi-coding-agent": "^0.84.2"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
]
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"@henryqw/pi-herdr": "^0.1.0"
|
|
48
|
+
"@henryqw/pi-herdr": "^0.1.0",
|
|
49
|
+
"@henryqw/pi-task-models": "^0.1.0"
|
|
48
50
|
}
|
|
49
51
|
}
|