@tplog/pi-zendy 0.2.17 → 0.3.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.
@@ -0,0 +1,190 @@
1
+ // Zendy tools — LLM-callable wrappers over direct API clients.
2
+ // Loaded by jiti as part of the zendy extension.
3
+
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import * as zendesk from "../dist/clients/zendesk.js";
7
+ import * as helm from "../dist/clients/helm-watchdog.js";
8
+ import * as kg from "../dist/clients/zendesk-kg.js";
9
+
10
+ function textResult(text: string, details: Record<string, unknown> = {}) {
11
+ return { content: [{ type: "text" as const, text }], details };
12
+ }
13
+
14
+ function registerZendeskTools(pi: ExtensionAPI): void {
15
+ pi.registerTool({
16
+ name: "zendy_ticket_get",
17
+ label: "Zendy Ticket",
18
+ description: "Fetch a Zendesk ticket with comments and user info via direct API. Prefer this over any CLI command for ticket analysis.",
19
+ promptSnippet: "Fetch Zendesk ticket, comments, and user details with zendy_ticket_get.",
20
+ promptGuidelines: [
21
+ "Use zendy_ticket_get when the user provides a Zendesk ticket ID. It returns ticket metadata, comments, requester, and assignee.",
22
+ "Base conclusions on the returned ticket and comments; do not rely on external commands.",
23
+ ],
24
+ parameters: Type.Object({
25
+ ticketId: Type.Number({ description: "Zendesk ticket ID" }),
26
+ }),
27
+ async execute(_toolCallId: string, params: { ticketId: number }, signal?: AbortSignal) {
28
+ const result = await zendesk.getTicketFull(params.ticketId, signal);
29
+ return textResult(
30
+ `Fetched Zendesk ticket #${result.ticket.id}: "${result.ticket.subject}" (${result.ticket.status}), ${result.comments?.length ?? 0} comments.`,
31
+ {
32
+ ticket: result.ticket,
33
+ comments: result.comments,
34
+ requester: result.requester,
35
+ assignee: result.assignee,
36
+ },
37
+ );
38
+ },
39
+ });
40
+
41
+ pi.registerTool({
42
+ name: "zendy_ticket_search",
43
+ label: "Zendy Search",
44
+ description: "Search Zendesk tickets via direct API. For fresh/live Zendesk lookups, not historical semantic retrieval.",
45
+ promptSnippet: "Search live Zendesk tickets with zendy_ticket_search.",
46
+ parameters: Type.Object({
47
+ query: Type.String({ description: "Zendesk search query string" }),
48
+ }),
49
+ async execute(_toolCallId: string, params: { query: string }, signal?: AbortSignal) {
50
+ const result = await zendesk.searchTickets(params.query, signal);
51
+ return textResult(`Zendesk search returned ${result.count} results.`, { results: result.results, count: result.count });
52
+ },
53
+ });
54
+ }
55
+
56
+ function registerHelmTools(pi: ExtensionAPI): void {
57
+ pi.registerTool({
58
+ name: "zendy_helm_get",
59
+ label: "Zendy Helm",
60
+ description: "Query Dify Helm Watchdog API for chart metadata, values.yaml, images, and validation. Always provide the exact chart version.",
61
+ promptSnippet: "Query Helm chart data with zendy_helm_get.",
62
+ promptGuidelines: [
63
+ "Use zendy_helm_get to query Dify Helm chart metadata. Always pass the customer's exact version.",
64
+ "Defaults change between versions — never assume a config value without checking the right version.",
65
+ ],
66
+ parameters: Type.Object({
67
+ resource: Type.Union([
68
+ Type.Literal("version"),
69
+ Type.Literal("values"),
70
+ Type.Literal("images"),
71
+ Type.Literal("validation"),
72
+ Type.Literal("latest"),
73
+ Type.Literal("versions"),
74
+ Type.Literal("cache"),
75
+ ], { description: "Resource to fetch" }),
76
+ version: Type.Optional(Type.String({ description: "Chart version. Required for: version, values, images, validation" })),
77
+ validateImages: Type.Optional(Type.Boolean({ description: "For images: include pull-status validation", default: false })),
78
+ status: Type.Optional(Type.String({ description: "For validation: filter by MISSING etc." })),
79
+ versionOnly: Type.Optional(Type.Boolean({ description: "For latest: return plain version string only", default: false })),
80
+ }),
81
+ async execute(_toolCallId: string, params: {
82
+ resource: string;
83
+ version?: string;
84
+ validateImages?: boolean;
85
+ status?: string;
86
+ versionOnly?: boolean;
87
+ }, signal?: AbortSignal) {
88
+ const needsVersion = ["version", "values", "images", "validation"].includes(params.resource);
89
+ if (needsVersion && !params.version) {
90
+ throw new Error(`version is required when resource=${params.resource}`);
91
+ }
92
+
93
+ switch (params.resource) {
94
+ case "version": {
95
+ const data = await helm.getVersion(params.version!, signal);
96
+ return textResult(`Helm Watchdog version metadata for ${params.version}.`, { version: params.version, data });
97
+ }
98
+ case "values": {
99
+ const data = await helm.getValues(params.version!, signal);
100
+ return textResult(`Helm Watchdog values.yaml for ${params.version}.`, { version: params.version, data });
101
+ }
102
+ case "images": {
103
+ const data = await helm.getImages(params.version!, params.validateImages, signal);
104
+ return textResult(`Helm Watchdog images for ${params.version} (${data.length} images).`, { version: params.version, images: data });
105
+ }
106
+ case "validation": {
107
+ const data = await helm.getValidation(params.version!, params.status, signal);
108
+ return textResult(`Helm Watchdog validation for ${params.version}.`, { version: params.version, results: data });
109
+ }
110
+ case "latest": {
111
+ const data = await helm.getLatest(params.versionOnly, signal);
112
+ return textResult("Helm Watchdog latest version.", { data });
113
+ }
114
+ case "versions": {
115
+ const data = await helm.listVersions(signal);
116
+ return textResult(`Helm Watchdog cached versions (${data.length} entries).`, { versions: data });
117
+ }
118
+ case "cache": {
119
+ const data = await helm.getCache(signal);
120
+ return textResult("Helm Watchdog cache metadata.", { data });
121
+ }
122
+ }
123
+ },
124
+ });
125
+ }
126
+
127
+ function registerKnowledgeGraphTools(pi: ExtensionAPI): void {
128
+ pi.registerTool({
129
+ name: "zendy_kg_search",
130
+ label: "Zendy KG",
131
+ description: "Search the Zendesk Knowledge Graph for historically similar tickets via direct API. Always cite ticketId values from results.",
132
+ promptSnippet: "Search historical similar tickets with zendy_kg_search.",
133
+ promptGuidelines: [
134
+ "Use zendy_kg_search for historical similar-ticket retrieval. Cite ticketId values when summarizing.",
135
+ "Empty results do not prove no similar issue exists — the KG is a snapshot, not live Zendesk.",
136
+ ],
137
+ parameters: Type.Object({
138
+ query: Type.String({ description: "Natural-language description of the issue" }),
139
+ limit: Type.Optional(Type.Number({ description: "Max results 1-20", default: 5 })),
140
+ version: Type.Optional(Type.String({ description: "Filter by Dify version mentioned in tickets" })),
141
+ priority: Type.Optional(Type.String({ description: "Filter: low, normal, high, urgent" })),
142
+ status: Type.Optional(Type.String({ description: "Filter by ticket status: open, closed, pending, etc." })),
143
+ }),
144
+ async execute(_toolCallId: string, params: {
145
+ query: string;
146
+ limit?: number;
147
+ version?: string;
148
+ priority?: string;
149
+ status?: string;
150
+ }, signal?: AbortSignal) {
151
+ const limit = Math.min(Math.max(Math.trunc(params.limit ?? 5), 1), 20);
152
+ const result = await kg.search({
153
+ query: params.query,
154
+ topK: limit,
155
+ filter: {
156
+ ...(params.version ? { versions: [params.version] } : {}),
157
+ ...(params.priority ? { priority: params.priority } : {}),
158
+ ...(params.status ? { status: params.status } : {}),
159
+ },
160
+ }, signal);
161
+ return textResult(
162
+ `KG search returned ${result.results.length} results for "${result.queryText}". Cite ticketId values from details.results.`,
163
+ { results: result.results, queryText: result.queryText },
164
+ );
165
+ },
166
+ });
167
+ }
168
+
169
+ function registerSourceTools(pi: ExtensionAPI): void {
170
+ pi.registerTool({
171
+ name: "zendy_source_status",
172
+ label: "Zendy Source Status",
173
+ description: "Report the zendy source-analysis workspace path and whether source cloning is authorized.",
174
+ promptSnippet: "Check source workspace status with zendy_source_status.",
175
+ parameters: Type.Object({}),
176
+ async execute() {
177
+ return textResult("Source workspace status.", {
178
+ workspace: process.env["ZENDY_SRC_DIR"] ?? null,
179
+ note: "Source cloning/search requires explicit user permission per zendy workflow rules. Ask before cloning private source.",
180
+ });
181
+ },
182
+ });
183
+ }
184
+
185
+ export function registerAllTools(pi: ExtensionAPI): void {
186
+ registerZendeskTools(pi);
187
+ registerHelmTools(pi);
188
+ registerKnowledgeGraphTools(pi);
189
+ registerSourceTools(pi);
190
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Zendy — unified pi extension entry point.
3
+ *
4
+ * Provides:
5
+ * - Tools (LLM-callable): zendy_ticket_get, zendy_ticket_search,
6
+ * zendy_helm_get, zendy_kg_search, zendy_source_status
7
+ * - Slash commands: /zendy-config, /zendy-status, /zendy-cleanup
8
+ * - Session lifecycle: workspace dir + cleanup + orphan sweep
9
+ * - Custom header on session start
10
+ *
11
+ * No external CLI dependencies (zcli, zendesk-kg) required.
12
+ * All data access is through direct REST APIs.
13
+ */
14
+
15
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
+ import { registerAllTools } from "./tools.js";
17
+ import { registerAllCommands } from "./commands.js";
18
+ import { mkdirSync, rmSync, readdirSync } from "node:fs";
19
+ import { join } from "node:path";
20
+
21
+ // ── Constants ──────────────────────────────────────────────────────────
22
+
23
+ const BASE_DIR = "/tmp";
24
+ const SESSION_PREFIX = "zendy-session-";
25
+ const WIPE_PREFIXES = ["dify-", SESSION_PREFIX];
26
+
27
+ // ── Session workspace helpers ──────────────────────────────────────────
28
+
29
+ function parseSessionDir(name: string): { pid: number } | null {
30
+ const m = /^zendy-session-(\d+)-(\d+)$/.exec(name);
31
+ return m ? { pid: parseInt(m[1]!, 10) } : null;
32
+ }
33
+
34
+ function isProcessAlive(pid: number): boolean {
35
+ try {
36
+ process.kill(pid, 0);
37
+ return true;
38
+ } catch (e) {
39
+ return (e as NodeJS.ErrnoException).code === "EPERM";
40
+ }
41
+ }
42
+
43
+ function safeRmrf(path: string): boolean {
44
+ try {
45
+ rmSync(path, { recursive: true, force: true });
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function sweepOrphans(base: string, exclude: string): string[] {
53
+ let entries;
54
+ try {
55
+ entries = readdirSync(base, { withFileTypes: true });
56
+ } catch {
57
+ return [];
58
+ }
59
+ const removed: string[] = [];
60
+ for (const e of entries) {
61
+ if (!e.isDirectory()) continue;
62
+ const info = parseSessionDir(e.name);
63
+ if (!info) continue;
64
+ const full = join(base, e.name);
65
+ if (full === exclude) continue;
66
+ if (isProcessAlive(info.pid)) continue;
67
+ if (safeRmrf(full)) removed.push(full);
68
+ }
69
+ return removed;
70
+ }
71
+
72
+ // ── ASCII header ───────────────────────────────────────────────────────
73
+
74
+ const HEADER = `
75
+ ███████╗███████╗███╗ ██╗██████╗ ██╗ ██╗
76
+ ╚══███╔╝██╔════╝████╗ ██║██╔══██╗╚██╗ ██╔╝
77
+ ███╔╝ █████╗ ██╔██╗ ██║██║ ██║ ╚████╔╝
78
+ ███╔╝ ██╔══╝ ██║╚██╗██║██║ ██║ ╚██╔╝
79
+ ███████╗███████╗██║ ╚████║██████╔╝ ██║
80
+ ╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═╝
81
+ Dify Enterprise Support Harness
82
+ `;
83
+
84
+ // ── Extension entry ────────────────────────────────────────────────────
85
+
86
+ export default function (pi: ExtensionAPI) {
87
+ // ── Tools & Commands ────────────────────────────────────────────────
88
+
89
+ registerAllTools(pi);
90
+ registerAllCommands(pi);
91
+
92
+ // ── Session lifecycle ───────────────────────────────────────────────
93
+
94
+ const ts = Date.now();
95
+ const sessionDir = join(BASE_DIR, `${SESSION_PREFIX}${process.pid}-${ts}`);
96
+ let cleanedUp = false;
97
+
98
+ function ensureDir(): void {
99
+ try {
100
+ mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
101
+ } catch {
102
+ // non-fatal
103
+ }
104
+ }
105
+
106
+ function cleanupSession(): void {
107
+ if (cleanedUp) return;
108
+ cleanedUp = true;
109
+ safeRmrf(sessionDir);
110
+ }
111
+
112
+ process.on("exit", cleanupSession);
113
+ process.on("SIGINT", () => { cleanupSession(); process.exit(130); });
114
+ process.on("SIGTERM", () => { cleanupSession(); process.exit(143); });
115
+ process.on("uncaughtException", (err) => {
116
+ cleanupSession();
117
+ console.error(err);
118
+ process.exit(1);
119
+ });
120
+
121
+ pi.on("session_start", async (_event, ctx) => {
122
+ ensureDir();
123
+ process.env["ZENDY_SRC_DIR"] = sessionDir;
124
+
125
+ // Custom header
126
+ if (ctx.hasUI) {
127
+ ctx.ui.notify(HEADER, "info");
128
+ }
129
+
130
+ // Sweep orphan session dirs
131
+ const removed = sweepOrphans(BASE_DIR, sessionDir);
132
+ if (ctx.hasUI && removed.length > 0) {
133
+ ctx.ui.notify(`[zendy] swept ${removed.length} orphan session dir(s)`, "info");
134
+ }
135
+ });
136
+
137
+ pi.on("session_shutdown", async () => {
138
+ cleanupSession();
139
+ });
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tplog/pi-zendy",
3
- "version": "0.2.17",
3
+ "version": "0.3.0",
4
4
  "description": "Pi package for Dify Enterprise support ticket analysis",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,16 +18,11 @@
18
18
  ],
19
19
  "pi": {
20
20
  "extensions": [
21
- "./extensions"
22
- ],
23
- "skills": [
24
- "./skills"
21
+ "./extensions/zendy.ts"
25
22
  ]
26
23
  },
27
24
  "files": [
28
25
  "dist",
29
- "agents.md",
30
- "skills",
31
26
  "extensions"
32
27
  ],
33
28
  "publishConfig": {
@@ -37,8 +32,7 @@
37
32
  "build": "tsc",
38
33
  "test": "tsc && node --test test/*.test.mjs",
39
34
  "prepare": "tsc",
40
- "prepublishOnly": "tsc",
41
- "postinstall": "command -v zendesk-kg >/dev/null 2>&1 || (curl -fsSL https://raw.githubusercontent.com/sorphwer/zendesk-kg-cli-release/main/install.sh | sh || true)"
35
+ "prepublishOnly": "tsc"
42
36
  },
43
37
  "peerDependencies": {
44
38
  "@earendil-works/pi-coding-agent": "*"
package/agents.md DELETED
@@ -1,118 +0,0 @@
1
- # zendy
2
-
3
- Internal support engineering harness for Dify Enterprise. Powered by pi.
4
-
5
- ## Identity
6
-
7
- zendy is a Pi package that loads a fixed set of skills + extensions on top of [pi](https://pi.dev) so a support engineer can analyze a Zendesk ticket end-to-end without leaving the terminal. The published npm package is `@tplog/pi-zendy`; the source is the private repo `tplog/pi-zendy`.
8
-
9
- ## What zendy ships with
10
-
11
- Three skills, loaded by default:
12
-
13
- - `zendesk-cli` — Pull Zendesk ticket metadata and comment threads via `zcli`
14
- - `helm-watchdog` — Query Dify Helm chart values, images, and validation by version
15
- - `source-check` — Clone and analyze Dify source code (only when the user explicitly asks)
16
-
17
- One additional opt-in skill:
18
-
19
- - `zendesk-kg` — Hybrid retrieval over a Zendesk Knowledge Graph for cross-ticket / similar-issue lookups
20
-
21
- Two extensions:
22
-
23
- - `custom-header` — ZENDY ASCII art on session start
24
- - `source-cleanup` — Per-session `/tmp/zendy-session-<pid>-<ts>/` directory + automatic wipe on exit; orphan sweep on next start
25
-
26
- One slash command beyond what pi gives you: `/status` runs `zendy preflight --json` and renders a per-source connectivity check (pi auth / Zendesk / GitHub / zendesk-kg).
27
-
28
- ## Typical workflow
29
-
30
- 1. User gives a ticket number → use `zcli` to pull ticket + comments
31
- 2. Identify the customer's Dify version from ticket fields
32
- 3. Pull the corresponding Helm chart values via the helm-watchdog API
33
- 4. If the issue requires code-level analysis, the user will say so → use the `source-check` skill
34
- 5. (optional) Use `zendesk-kg search` to find similar past tickets if cross-ticket evidence helps
35
- 6. Synthesize findings and draft a reply in the ticket's language
36
-
37
- ## Behavior rules
38
-
39
- - **Never guess.** Base conclusions on actual data — ticket fields, values.yaml, source code, ticket-graph results. State clearly what is fact vs inference.
40
- - **Don't recommend solutions you haven't verified.** If you haven't confirmed something in the code or docs, say so.
41
- - **Version matters.** Always check the customer's exact Dify version — defaults and behavior change between releases.
42
- - **Reply drafts match the ticket language.** Detect the language from the ticket thread (Japanese, Chinese, or English) and draft in the same language with appropriate business tone.
43
- - **Suggest source-check when confidence is low.** If your analysis cannot be confirmed from config alone, proactively ask the user: "需要我去源码确认吗?" Don't clone without permission, but suggest it instead of leaving uncertainty unresolved.
44
- - **Inspect before filtering.** When calling an unfamiliar API for the first time, look at the response structure (`| jq 'type'`, `| jq '.[0] | keys'`, or `| head`) before writing filter expressions. Never assume field names.
45
- - **Skills are APIs, not CLIs (mostly).** `helm-watchdog` is an HTTP API (use `curl`); `zcli` is a CLI; `zendesk-kg` is a CLI. Read the skill's Quick Reference before your first call. Do not guess command names.
46
- - **Auto-install missing dependencies.** If `zcli` or `zendesk-kg` is not found, run their installer automatically and continue. Don't ask — this is an internal tool, all dependencies are expected. (`zendy` v0.2.13+ also auto-installs `zendesk-kg` via postinstall.)
47
- - **Cite sources when you summarize.** When using `zendesk-kg search` results, surface the original `ticketId`s — the engineer wants to read the source ticket, not just your synthesis.
48
- - **`zendesk-kg health` is currently unreliable.** The retriever's `/health` endpoint sits behind a 302 → login page even though `/search` is API-key-authenticated and works. Don't gate retries on `health`. Only `health: ok` is a real signal; `health: unreachable` is inconclusive.
49
-
50
- ## Development flow
51
-
52
- The development of zendy itself follows three rules. These are non-negotiable.
53
-
54
- 1. **`main` is always the latest source of truth.** All accepted changes land on `main`; no long-lived divergent branches.
55
- 2. **Tags drive releases.** Pushing a `vX.Y.Z` tag triggers `.github/workflows/publish.yml`, which verifies `tag == package.json.version`, runs tests, and `npm publish --access public`. The npm registry is downstream of the tag, never the other way around.
56
- 3. **Every push to `main` goes through a Pull Request.** No direct pushes to `main`, no exceptions. This includes `chore: release X.Y.Z` version-bump commits — they too open a branch + PR before being merged.
57
-
58
- ### Concrete steps for a code change
59
-
60
- ```
61
- 1. Create a branch: git checkout -b <type>/<short-name>
62
- 2. Make the change locally
63
- 3. Verify locally: npm test (or relevant manual checks)
64
- 4. Commit + push the branch: git push -u origin <branch>
65
- 5. Open a PR: gh pr create --title "..." --body "..."
66
- 6. Wait for the human owner's review/merge
67
- 7. After merge, locally: git checkout main && git pull
68
- ```
69
-
70
- Do not bump `package.json.version` in the same PR as a feature/fix unless the human owner asked for it. Version bumps belong in their own PR.
71
-
72
- ### Concrete steps for a release
73
-
74
- ```
75
- 1. main has the merged feature/fix commits you want to ship
76
- 2. Branch: git checkout -b chore/release-X.Y.Z
77
- 3. Bump: edit package.json (version)
78
- npm install --package-lock-only
79
- 4. Commit + push branch: git push -u origin chore/release-X.Y.Z
80
- 5. Open the release PR: gh pr create --title "chore: release X.Y.Z" ...
81
- 6. Wait for merge
82
- 7. After merge, on local main: git checkout main && git pull
83
- git tag vX.Y.Z
84
- git push origin vX.Y.Z
85
- 8. Watch the workflow: gh run watch <id> --exit-status
86
- 9. Verify on npm: npm view @tplog/pi-zendy version
87
- 10. Cut a GitHub Release: gh release create vX.Y.Z --title "..." --notes "..."
88
- 11. Announce in #zendy with the GH Release URL.
89
- ```
90
-
91
- Release timing (whether to tag now vs accumulate more PRs first) is a **human decision**. Agents should not unilaterally tag — ask the human owner before step 7.
92
-
93
- ### Test machine handoff
94
-
95
- The local sandbox where an agent develops is **not** the same as the human's test machine. To let the human verify a not-yet-merged change without going through GitHub auth:
96
-
97
- ```
98
- 1. On the agent machine, after `npm test` passes locally:
99
- npm pack
100
- slock attachment upload --path tplog-zendy-<version>.tgz --channel "#zendy"
101
- 2. Tell the human: `npm install -g <downloaded-tarball-path>`
102
- ```
103
-
104
- This bypasses `npm install -g git+ssh://...#main`, which currently fails because npm 10/11 doesn't install devDependencies during git-dep `prepare`.
105
-
106
- ## Release pitfalls — do not repeat
107
-
108
- - **`--provenance` does NOT work for this package.** npm rejects provenance attestations for private source repos ("Only public source repositories are supported when publishing with provenance"). The workflow intentionally omits `--provenance` and `id-token: write`. Do not add them back.
109
- - **Tags are immutable.** If a publish fails, bump the version and push a new tag — never delete and re-push the same tag. Gaps in the npm version sequence are fine.
110
- - **Tag version must equal `package.json.version`.** The workflow fails fast if they disagree.
111
- - **Do not run `npm publish` locally.** The workflow is the canonical path.
112
- - **Never push `chore: release X.Y.Z` directly to main.** It still goes through a PR (rule 3).
113
-
114
- ## Security note for contributors
115
-
116
- This package is published to npm as-is. Anything written into `skills/**`, `extensions/**`, or `agents.md` becomes public once published. **Never** embed tokens, API keys, passwords, internal-only URLs, customer names, or any non-public information in these files. Runtime configuration (credentials, per-user endpoints) must come from environment variables or user config — never hard-coded.
117
-
118
- The source repo `tplog/pi-zendy` is private by design. The `files` whitelist in `package.json` controls what ships to the public npm tarball. Do not propose making the repo public.
@@ -1,49 +0,0 @@
1
- /**
2
- * Custom Header Extension
3
- *
4
- * Displays "zendy" in ASCII art on startup.
5
- */
6
-
7
- import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
8
-
9
- function getZendyArt(theme: Theme): string[] {
10
- const c = (text: string) => theme.fg("accent", text);
11
- const d = (text: string) => theme.fg("dim", text);
12
- const m = (text: string) => theme.fg("muted", text);
13
-
14
- return [
15
- "",
16
- c("███████╗███████╗███╗ ██╗██████╗ ██╗ ██╗"),
17
- c("╚══███╔╝██╔════╝████╗ ██║██╔══██╗╚██╗ ██╔╝"),
18
- c(" ███╔╝ █████╗ ██╔██╗ ██║██║ ██║ ╚████╔╝"),
19
- c(" ███╔╝ ██╔══╝ ██║╚██╗██║██║ ██║ ╚██╔╝"),
20
- c("███████╗███████╗██║ ╚████║██████╔╝ ██║"),
21
- c("╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═╝"),
22
- "",
23
- m("─── ") + d("Dify Enterprise Copilot") + m(" ───"),
24
- "",
25
- ];
26
- }
27
-
28
- export default function (pi: ExtensionAPI) {
29
- pi.on("session_start", async (_event, ctx) => {
30
- if (ctx.hasUI) {
31
- ctx.ui.setHeader((_tui, theme) => {
32
- return {
33
- render(_width: number): string[] {
34
- return getZendyArt(theme);
35
- },
36
- invalidate() {},
37
- };
38
- });
39
- }
40
- });
41
-
42
- pi.registerCommand("restore-header", {
43
- description: "Restore built-in header",
44
- handler: async (_args, ctx) => {
45
- ctx.ui.setHeader(undefined);
46
- ctx.ui.notify("Built-in header restored", "info");
47
- },
48
- });
49
- }