@docyrus/docyrus 0.0.63 → 0.0.65

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.
Files changed (36) hide show
  1. package/README.md +19 -0
  2. package/agent-loader.js +165 -21
  3. package/agent-loader.js.map +2 -2
  4. package/main.js +363 -113
  5. package/main.js.map +4 -4
  6. package/package.json +5 -4
  7. package/resources/pi-agent/extensions/browser-tools.ts +1 -1
  8. package/resources/pi-agent/extensions/context.ts +12 -73
  9. package/resources/pi-agent/extensions/control.ts +1 -1
  10. package/resources/pi-agent/extensions/loop.ts +4 -1
  11. package/resources/pi-agent/extensions/pi-bash-live-view/index.ts +1 -1
  12. package/resources/pi-agent/extensions/pi-bash-live-view/package.json +3 -3
  13. package/resources/pi-agent/extensions/pi-fff/README.md +152 -0
  14. package/resources/pi-agent/extensions/pi-fff/VENDORED_FROM.md +7 -0
  15. package/resources/pi-agent/extensions/pi-fff/package.json +53 -0
  16. package/resources/pi-agent/extensions/pi-fff/src/index.ts +820 -0
  17. package/resources/pi-agent/extensions/pi-mcp-adapter/index.ts +1 -1
  18. package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +1 -1
  19. package/resources/pi-agent/extensions/prompt-editor.ts +26 -7
  20. package/resources/pi-agent/extensions/soul.ts +183 -0
  21. package/resources/pi-agent/extensions/todos.ts +1 -1
  22. package/resources/pi-agent/skills/grill-me/SKILL.md +11 -8
  23. package/resources/pi-agent/skills/release-manager/SKILL.md +469 -30
  24. package/resources/pi-agent/souls/boomer-parent.md +23 -0
  25. package/resources/pi-agent/souls/bro.md +22 -0
  26. package/resources/pi-agent/souls/catalog.ts +79 -0
  27. package/resources/pi-agent/souls/caveman.md +21 -0
  28. package/resources/pi-agent/souls/linkedin-influencer.md +60 -0
  29. package/resources/pi-agent/souls/master-yoda.md +22 -0
  30. package/resources/pi-agent/souls/noir-detective.md +24 -0
  31. package/resources/pi-agent/souls/nonsense-engineer.md +23 -0
  32. package/resources/pi-agent/souls/pirate.md +24 -0
  33. package/resources/pi-agent/souls/shakespeare.md +24 -0
  34. package/server-loader.js +803 -93
  35. package/server-loader.js.map +4 -4
  36. package/resources/pi-agent/skills/changelog-generator/SKILL.md +0 -461
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionContext, ToolInfo } from "@mariozechner/pi-coding-agent";
2
2
  import type { McpExtensionState } from "./state.js";
3
- import { Type } from "@sinclair/typebox";
3
+ import { Type } from "typebox";
4
4
  import { showStatus, showTools, reconnectServers, authenticateServer, openMcpPanel } from "./commands.js";
5
5
  import { loadMcpConfig } from "./config.js";
6
6
  import { buildProxyDescription, createDirectToolExecutor, resolveDirectTools } from "./direct-tools.js";
@@ -65,7 +65,7 @@
65
65
  "dependencies": {
66
66
  "@modelcontextprotocol/ext-apps": "^1.2.2",
67
67
  "@modelcontextprotocol/sdk": "^1.25.1",
68
- "@sinclair/typebox": "^0.32.0",
68
+ "typebox": "^1.1.24",
69
69
  "zod": "^3.25.0 || ^4.0.0"
70
70
  },
71
71
  "devDependencies": {
@@ -1,5 +1,9 @@
1
- import type { ExtensionAPI, ExtensionContext, ModelSelectEvent, ThinkingLevel } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import { CustomEditor, ModelSelectorComponent, SettingsManager } from "@mariozechner/pi-coding-agent";
3
+
4
+ // Mirrors @mariozechner/pi-agent-core's ThinkingLevel (including "off"), defined locally so this
5
+ // extension can be loaded by pi without pulling an additional direct dep. Keep in sync with pi-agent-core.
6
+ type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
3
7
  import path from "node:path";
4
8
  import os from "node:os";
5
9
  import fs from "node:fs/promises";
@@ -1148,13 +1152,28 @@ function setEditor(pi: ExtensionAPI, ctx: ExtensionContext, history: PromptEntry
1148
1152
  requestEditorRender = () => editor.requestRenderNow();
1149
1153
  editor.modeLabelProvider = () => runtime.currentMode;
1150
1154
  // Keep the mode label color stable (match footer/status bar).
1151
- editor.modeLabelColor = (text: string) => ctx.ui.theme.fg("dim", text);
1155
+ // ctx can go stale after session replacement or reload; swallow the stale
1156
+ // assertion in the render-path callbacks so the last TUI frame doesn't crash.
1157
+ editor.modeLabelColor = (text: string) => {
1158
+ try {
1159
+ return ctx.ui.theme.fg("dim", text);
1160
+ } catch {
1161
+ return text;
1162
+ }
1163
+ };
1152
1164
  const borderColor = (text: string) => {
1153
- const isBashMode = editor.getText().trimStart().startsWith("!");
1154
- if (isBashMode) {
1155
- return ctx.ui.theme.getBashModeBorderColor()(text);
1165
+ // ctx can go stale after session replacement or reload; a final TUI render
1166
+ // can still fire from timers and calls into ctx.ui.* throw. Fall back to
1167
+ // the raw text instead of crashing the editor on the way out.
1168
+ try {
1169
+ const isBashMode = editor.getText().trimStart().startsWith("!");
1170
+ if (isBashMode) {
1171
+ return ctx.ui.theme.getBashModeBorderColor()(text);
1172
+ }
1173
+ return getModeBorderColor(ctx, pi, runtime.currentMode)(text);
1174
+ } catch {
1175
+ return text;
1156
1176
  }
1157
- return getModeBorderColor(ctx, pi, runtime.currentMode)(text);
1158
1177
  };
1159
1178
 
1160
1179
  editor.borderColor = borderColor;
@@ -1267,7 +1286,7 @@ export default function(pi: ExtensionAPI) {
1267
1286
  applyEditor(pi, ctx);
1268
1287
  });
1269
1288
 
1270
- pi.on("model_select", async(event: ModelSelectEvent, ctx) => {
1289
+ pi.on("model_select", async(event, ctx) => {
1271
1290
  // Always track the last observed model for overlay/store correctness.
1272
1291
  lastObservedModel = { provider: event.model.provider, modelId: event.model.id };
1273
1292
 
@@ -0,0 +1,183 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import type {
5
+ ExtensionAPI,
6
+ ExtensionCommandContext,
7
+ } from "@mariozechner/pi-coding-agent";
8
+ import { DEFAULT_SOUL_ID, SOULS, findSoul } from "../souls/catalog";
9
+
10
+ type Scope = "local" | "global";
11
+
12
+ interface IStoredConfig {
13
+ version?: number;
14
+ activeEnvironmentId?: string;
15
+ environments?: unknown;
16
+ defaultClientId?: string;
17
+ activeSoulId?: string;
18
+ [extraKey: string]: unknown;
19
+ }
20
+
21
+ function resolveScope(): Scope {
22
+ const raw = process.env.DOCYRUS_CLI_SCOPE?.trim();
23
+ if (raw === "global") {
24
+ return "global";
25
+ }
26
+
27
+ return "local";
28
+ }
29
+
30
+ function resolveConfigFilePath(cwd: string): string {
31
+ const scope = resolveScope();
32
+ const root = scope === "global" ? join(homedir(), ".docyrus") : join(cwd, ".docyrus");
33
+ return join(root, "config.json");
34
+ }
35
+
36
+ async function readStoredConfig(cwd: string): Promise<IStoredConfig> {
37
+ const configPath = resolveConfigFilePath(cwd);
38
+ try {
39
+ const raw = await readFile(configPath, "utf8");
40
+ const parsed = JSON.parse(raw) as IStoredConfig;
41
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
42
+ return parsed;
43
+ }
44
+ }
45
+ catch (error) {
46
+ const code = (error as NodeJS.ErrnoException | null)?.code;
47
+ if (code !== "ENOENT") {
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ return {};
53
+ }
54
+
55
+ async function writeStoredConfig(cwd: string, config: IStoredConfig): Promise<void> {
56
+ const configPath = resolveConfigFilePath(cwd);
57
+ await mkdir(dirname(configPath), {
58
+ recursive: true,
59
+ mode: 0o700,
60
+ });
61
+
62
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, {
63
+ encoding: "utf8",
64
+ mode: 0o600,
65
+ });
66
+
67
+ await chmod(configPath, 0o600);
68
+ }
69
+
70
+ async function getPersistedSoulId(cwd: string): Promise<string> {
71
+ const config = await readStoredConfig(cwd);
72
+ const stored = typeof config.activeSoulId === "string" ? config.activeSoulId.trim() : "";
73
+ if (stored && findSoul(stored)) {
74
+ return stored;
75
+ }
76
+
77
+ return DEFAULT_SOUL_ID;
78
+ }
79
+
80
+ async function persistSoulId(cwd: string, soulId: string): Promise<void> {
81
+ const config = await readStoredConfig(cwd);
82
+ await writeStoredConfig(cwd, {
83
+ ...config,
84
+ activeSoulId: soulId,
85
+ });
86
+ }
87
+
88
+ function parseSoulArg(rawArgs: string): string | null {
89
+ const trimmed = rawArgs.trim();
90
+ return trimmed.length > 0 ? trimmed.split(/\s+/u)[0] || null : null;
91
+ }
92
+
93
+ function renderSoulList(activeSoulId: string, runtimeSoulId: string): string {
94
+ const lines: string[] = [
95
+ "# Available Souls",
96
+ "",
97
+ "Use `/soul <id>` to pick one. Changes take effect on the **next** agent session (restart the agent).",
98
+ "",
99
+ ];
100
+
101
+ for (const soul of SOULS) {
102
+ const markers: string[] = [];
103
+ if (soul.id === runtimeSoulId) {
104
+ markers.push("running");
105
+ }
106
+ if (soul.id === activeSoulId && soul.id !== runtimeSoulId) {
107
+ markers.push("pending");
108
+ }
109
+ if (soul.id === activeSoulId && soul.id === runtimeSoulId) {
110
+ markers.length = 0;
111
+ markers.push("active");
112
+ }
113
+
114
+ const suffix = markers.length > 0 ? ` _(${markers.join(", ")})_` : "";
115
+ lines.push(`- \`${soul.id}\` — **${soul.name}**: ${soul.description}${suffix}`);
116
+ }
117
+
118
+ lines.push("");
119
+ lines.push(`Current: \`${runtimeSoulId}\` (running) · \`${activeSoulId}\` (persisted for next session)`);
120
+ return lines.join("\n");
121
+ }
122
+
123
+ function renderUsage(activeSoulId: string, runtimeSoulId: string): string {
124
+ return [
125
+ "# /soul",
126
+ "",
127
+ "Switch the agent's communication style (SOUL).",
128
+ "",
129
+ "- `/soul` — show this help and current selection.",
130
+ "- `/soul <id>` — persist a new soul; takes effect on the next session.",
131
+ "- `/souls` — list every available soul with descriptions.",
132
+ "",
133
+ `Currently running: \`${runtimeSoulId}\``,
134
+ `Persisted for next session: \`${activeSoulId}\``,
135
+ ].join("\n");
136
+ }
137
+
138
+ export default function(pi: ExtensionAPI) {
139
+ pi.registerCommand("souls", {
140
+ description: "List available agent souls (communication styles)",
141
+ handler: async(_rawArgs: string, ctx: ExtensionCommandContext) => {
142
+ const activeSoulId = await getPersistedSoulId(ctx.cwd);
143
+ const runtimeSoulId = process.env.DOCYRUS_PI_SOUL_ID?.trim() || DEFAULT_SOUL_ID;
144
+ if (ctx.hasUI) {
145
+ ctx.ui.notify(renderSoulList(activeSoulId, runtimeSoulId), "info");
146
+ }
147
+ },
148
+ });
149
+
150
+ pi.registerCommand("soul", {
151
+ description: "Show or change the agent's soul (communication style)",
152
+ handler: async(rawArgs: string, ctx: ExtensionCommandContext) => {
153
+ const requested = parseSoulArg(rawArgs);
154
+ const activeSoulId = await getPersistedSoulId(ctx.cwd);
155
+ const runtimeSoulId = process.env.DOCYRUS_PI_SOUL_ID?.trim() || DEFAULT_SOUL_ID;
156
+
157
+ if (!requested) {
158
+ if (ctx.hasUI) {
159
+ ctx.ui.notify(renderUsage(activeSoulId, runtimeSoulId), "info");
160
+ }
161
+ return;
162
+ }
163
+
164
+ const candidate = findSoul(requested);
165
+ if (!candidate) {
166
+ const known = SOULS.map((soul) => soul.id).join(", ");
167
+ if (ctx.hasUI) {
168
+ ctx.ui.notify(`Unknown soul '${requested}'. Known souls: ${known}.`, "warning");
169
+ }
170
+ return;
171
+ }
172
+
173
+ await persistSoulId(ctx.cwd, candidate.id);
174
+
175
+ if (ctx.hasUI) {
176
+ const suffix = candidate.id === runtimeSoulId
177
+ ? " Already running — no restart needed."
178
+ : " Takes effect on the next agent session — restart with `/exit` and relaunch.";
179
+ ctx.ui.notify(`Soul set to \`${candidate.id}\` — ${candidate.name}.${suffix}`, "info");
180
+ }
181
+ },
182
+ });
183
+ }
@@ -31,7 +31,7 @@
31
31
  */
32
32
  import { DynamicBorder, copyToClipboard, getMarkdownTheme, keyHint, type ExtensionAPI, type ExtensionContext, type Theme } from "@mariozechner/pi-coding-agent";
33
33
  import { StringEnum } from "@mariozechner/pi-ai";
34
- import { Type } from "@sinclair/typebox";
34
+ import { Type } from "typebox";
35
35
  import path from "node:path";
36
36
  import fs from "node:fs/promises";
37
37
  import { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -1,14 +1,17 @@
1
1
  ---
2
2
  name: grill-me
3
3
  description: >
4
- Interview the user relentlessly about a plan, architecture, or data model design until reaching shared understanding, resolving each branch of the decision tree. Use when the user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
5
-
6
- **Triggers — use this skill when:**
7
- - User says "grill me", "stress test this", "challenge my design"
8
- - User asks to "poke holes", "find gaps", "review my thinking"
9
- - User wants to validate a plan before implementing
10
- - User says "interview me about this", "question my decisions"
11
- - Used after `/architect` or `/plan` when the user wants to pressure-test the output
4
+ Stress-test a plan, architecture, or data model design by interviewing the user one question at a time, walking each branch of the decision tree until every critical choice is resolved. Tailored to Docyrus projects — probes data source design, field types, enum choices, relations and dependencies, access control, edge cases, and implementation feasibility against the current platform. Tracks resolved decisions, surfaces open questions, and offers to update `PLAN.md` / `data-sources.plan.json` with the agreed changes when the interview finishes.
5
+
6
+ **Triggers — use this skill when:**
7
+ - User says "grill me", "stress test this", "challenge my design", "pressure-test this"
8
+ - User asks to "poke holes", "find gaps", "review my thinking"
9
+ - User wants to validate a plan before implementing
10
+ - User says "interview me about this", "question my decisions", "ask me hard questions"
11
+ - Used after `/architect` or `/plan` when the user wants to pressure-test the output
12
+ - User is about to commit to a schema, data model, or architectural decision and wants a second pass
13
+
14
+ **Covers:** Plans and architectures for Docyrus-backed apps — data sources, fields, enums, relations, apps, queries, and the surrounding access-control and implementation-feasibility questions. Works best when there is a plan artifact on disk (PLAN.md, data-sources.plan.json, discovery snapshot) to anchor the conversation.
12
15
  ---
13
16
 
14
17
  # Grill Me