@tplog/pi-zendy 0.3.5 → 0.3.7
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 -0
- package/dist/clients/zendesk-kg.js +4 -19
- package/dist/clients/zendesk.js +4 -18
- package/dist/config/resolve.d.ts +3 -0
- package/dist/config/resolve.js +40 -0
- package/dist/config/store.d.ts +1 -3
- package/dist/config/store.js +0 -12
- package/dist/index.js +5 -34
- package/dist/preflight.js +2 -39
- package/dist/source-cleanup.js +3 -3
- package/extensions/commands.ts +2 -1
- package/package.json +3 -2
- package/prompts/zme.md +52 -0
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ The agent can call these tools directly:
|
|
|
69
69
|
|------|-------------|
|
|
70
70
|
| `zendy_ticket_get` | Fetch ticket metadata, comments, and user info |
|
|
71
71
|
| `zendy_ticket_search` | Search live Zendesk tickets |
|
|
72
|
+
| `zendy_whoami` | Check the currently authenticated Zendesk identity |
|
|
72
73
|
| `zendy_helm_get` | Query Helm chart values, images, validation by version |
|
|
73
74
|
| `zendy_kg_search` | Semantic search over historical tickets |
|
|
74
75
|
| `zendy_source_status` | Check source analysis workspace |
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
// Direct Zendesk Knowledge Graph API client. No zendesk-kg CLI dependency.
|
|
2
|
-
import {
|
|
3
|
-
import { migrateLegacyConfig, getLegacyKgEnv } from "../config/migrate.js";
|
|
2
|
+
import { resolveKgConfig } from "../config/resolve.js";
|
|
4
3
|
import { DEFAULT_KG_API_URL } from "../config/schema.js";
|
|
5
4
|
// ── Resolve config ─────────────────────────────────────────────────────
|
|
6
5
|
function resolveConfig() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return zendeskKg;
|
|
11
|
-
// Try legacy migration
|
|
12
|
-
migrateLegacyConfig();
|
|
13
|
-
const cfg2 = getConfig();
|
|
14
|
-
if (cfg2.zendeskKg?.apiKey)
|
|
15
|
-
return cfg2.zendeskKg;
|
|
16
|
-
// Try legacy .env directly
|
|
17
|
-
const legacy = getLegacyKgEnv();
|
|
18
|
-
if (legacy?.["RETRIEVER_API_KEY"]) {
|
|
19
|
-
return {
|
|
20
|
-
apiUrl: legacy["RETRIEVER_API_URL"] || undefined,
|
|
21
|
-
apiKey: legacy["RETRIEVER_API_KEY"],
|
|
22
|
-
};
|
|
23
|
-
}
|
|
6
|
+
const cfg = resolveKgConfig();
|
|
7
|
+
if (cfg)
|
|
8
|
+
return cfg;
|
|
24
9
|
throw new Error("Zendesk KG not configured. Use /zendy-config to set up credentials.\n" +
|
|
25
10
|
"Or set env: ZENDY_KG_API_KEY");
|
|
26
11
|
}
|
package/dist/clients/zendesk.js
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
// Direct Zendesk REST API client. No zcli dependency.
|
|
2
|
-
import {
|
|
3
|
-
import { migrateLegacyConfig, getLegacyZendeskConfig } from "../config/migrate.js";
|
|
2
|
+
import { resolveZendeskConfig } from "../config/resolve.js";
|
|
4
3
|
// ── Resolve config ─────────────────────────────────────────────────────
|
|
5
4
|
function resolveConfig() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return zendesk;
|
|
10
|
-
}
|
|
11
|
-
// Try legacy migration
|
|
12
|
-
migrateLegacyConfig();
|
|
13
|
-
const cfg2 = getConfig();
|
|
14
|
-
if (cfg2.zendesk?.subdomain && cfg2.zendesk?.email && cfg2.zendesk?.apiToken) {
|
|
15
|
-
return cfg2.zendesk;
|
|
16
|
-
}
|
|
17
|
-
// Try legacy zcli config directly
|
|
18
|
-
const legacy = getLegacyZendeskConfig();
|
|
19
|
-
if (legacy?.subdomain && legacy?.email && legacy?.api_token) {
|
|
20
|
-
return { subdomain: legacy.subdomain, email: legacy.email, apiToken: legacy.api_token };
|
|
21
|
-
}
|
|
5
|
+
const cfg = resolveZendeskConfig();
|
|
6
|
+
if (cfg)
|
|
7
|
+
return cfg;
|
|
22
8
|
throw new Error("Zendesk not configured. Use /zendy-config to set up credentials.\n" +
|
|
23
9
|
"Or set env: ZENDY_ZENDESK_SUBDOMAIN, ZENDY_ZENDESK_EMAIL, ZENDY_ZENDESK_API_TOKEN");
|
|
24
10
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Single source of truth for resolving effective credentials.
|
|
2
|
+
//
|
|
3
|
+
// Precedence (same for both Zendesk and KG):
|
|
4
|
+
// 1. zendy config — env vars override ~/.zendy/config.json
|
|
5
|
+
// 2. legacy migration — import legacy zcli / zendesk-kg files into zendy config
|
|
6
|
+
// 3. direct legacy files — read the legacy config without migrating
|
|
7
|
+
//
|
|
8
|
+
// Returns undefined when nothing resolves; callers decide whether that is fatal.
|
|
9
|
+
// Lives above store.ts and migrate.ts to compose them without a dependency cycle
|
|
10
|
+
// (migrate.ts already depends on store.ts).
|
|
11
|
+
import { getConfig } from "./store.js";
|
|
12
|
+
import { migrateLegacyConfig, getLegacyZendeskConfig, getLegacyKgEnv } from "./migrate.js";
|
|
13
|
+
export function resolveZendeskConfig() {
|
|
14
|
+
const cfg = getConfig().zendesk;
|
|
15
|
+
if (cfg?.subdomain && cfg?.email && cfg?.apiToken)
|
|
16
|
+
return cfg;
|
|
17
|
+
migrateLegacyConfig();
|
|
18
|
+
const migrated = getConfig().zendesk;
|
|
19
|
+
if (migrated?.subdomain && migrated?.email && migrated?.apiToken)
|
|
20
|
+
return migrated;
|
|
21
|
+
const legacy = getLegacyZendeskConfig();
|
|
22
|
+
if (legacy?.subdomain && legacy?.email && legacy?.api_token) {
|
|
23
|
+
return { subdomain: legacy.subdomain, email: legacy.email, apiToken: legacy.api_token };
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
export function resolveKgConfig() {
|
|
28
|
+
const cfg = getConfig().zendeskKg;
|
|
29
|
+
if (cfg?.apiKey)
|
|
30
|
+
return cfg;
|
|
31
|
+
migrateLegacyConfig();
|
|
32
|
+
const migrated = getConfig().zendeskKg;
|
|
33
|
+
if (migrated?.apiKey)
|
|
34
|
+
return migrated;
|
|
35
|
+
const legacy = getLegacyKgEnv();
|
|
36
|
+
if (legacy?.["RETRIEVER_API_KEY"]) {
|
|
37
|
+
return { apiUrl: legacy["RETRIEVER_API_URL"] || undefined, apiKey: legacy["RETRIEVER_API_KEY"] };
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
package/dist/config/store.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { ZendyConfig
|
|
1
|
+
import type { ZendyConfig } from "./schema.js";
|
|
2
2
|
export declare function getConfig(): ZendyConfig;
|
|
3
|
-
export declare function getZendeskConfig(): ZendeskConfig | undefined;
|
|
4
|
-
export declare function getZendeskKgConfig(): ZendeskKgConfig | undefined;
|
|
5
3
|
export declare function writeConfig(config: ZendyConfig): void;
|
|
6
4
|
export declare function configPath(): string;
|
|
7
5
|
export declare function configExists(): boolean;
|
package/dist/config/store.js
CHANGED
|
@@ -54,18 +54,6 @@ export function getConfig() {
|
|
|
54
54
|
},
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
export function getZendeskConfig() {
|
|
58
|
-
const c = getConfig().zendesk;
|
|
59
|
-
if (c?.subdomain && c?.email && c?.apiToken)
|
|
60
|
-
return c;
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
export function getZendeskKgConfig() {
|
|
64
|
-
const c = getConfig().zendeskKg;
|
|
65
|
-
if (c?.apiKey)
|
|
66
|
-
return c;
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
57
|
export function writeConfig(config) {
|
|
70
58
|
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
71
59
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
2
|
import { join, dirname } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
3
|
import { execFileSync, spawn } from "node:child_process";
|
|
6
4
|
import { fileURLToPath } from "node:url";
|
|
7
5
|
import { createRequire } from "node:module";
|
|
8
|
-
import { createHash } from "node:crypto";
|
|
9
6
|
import { runPreflight, printPreflightReport, promptCoreMissing } from "./preflight.js";
|
|
10
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
8
|
const __dirname = dirname(__filename);
|
|
@@ -14,34 +11,11 @@ const pkg = require("../package.json");
|
|
|
14
11
|
const VERSION = pkg.version;
|
|
15
12
|
// Resolve package root (one level up from dist/)
|
|
16
13
|
const PKG_ROOT = join(__dirname, "..");
|
|
17
|
-
//
|
|
14
|
+
// The extension is loaded directly from the package's own extensions/ dir,
|
|
15
|
+
// where zendy.ts and its sibling imports (tools.ts, commands.ts) live together.
|
|
16
|
+
// This is the same file pi loads via the `pi.extensions` manifest field, so the
|
|
17
|
+
// `zendy` launcher and `pi install` behave identically — no separate extraction step.
|
|
18
18
|
const EXT_ZENDY = join(PKG_ROOT, "extensions", "zendy.ts");
|
|
19
|
-
function cacheDir() {
|
|
20
|
-
return join(homedir(), ".zendy", VERSION);
|
|
21
|
-
}
|
|
22
|
-
// All source files that get extracted to cache
|
|
23
|
-
const SOURCE_FILES = [
|
|
24
|
-
EXT_ZENDY,
|
|
25
|
-
];
|
|
26
|
-
function contentHash() {
|
|
27
|
-
const hash = createHash("sha256");
|
|
28
|
-
for (const f of SOURCE_FILES) {
|
|
29
|
-
hash.update(readFileSync(f, "utf-8"));
|
|
30
|
-
}
|
|
31
|
-
return hash.digest("hex").slice(0, 16);
|
|
32
|
-
}
|
|
33
|
-
function ensureExtracted(base) {
|
|
34
|
-
const marker = join(base, ".extracted");
|
|
35
|
-
const currentHash = contentHash();
|
|
36
|
-
// Re-extract if marker is missing or hash has changed
|
|
37
|
-
if (existsSync(marker) && readFileSync(marker, "utf-8").trim() === currentHash) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const extDir = join(base, "extensions");
|
|
41
|
-
mkdirSync(extDir, { recursive: true });
|
|
42
|
-
writeFileSync(join(extDir, "zendy.ts"), readFileSync(EXT_ZENDY, "utf-8"));
|
|
43
|
-
writeFileSync(marker, currentHash);
|
|
44
|
-
}
|
|
45
19
|
function findPi() {
|
|
46
20
|
try {
|
|
47
21
|
return execFileSync("which", ["pi"], { encoding: "utf-8" }).trim();
|
|
@@ -105,13 +79,10 @@ async function main() {
|
|
|
105
79
|
}
|
|
106
80
|
// Remove --skip-preflight before passing to pi
|
|
107
81
|
const filteredArgs = userArgs.filter((a) => a !== "--skip-preflight");
|
|
108
|
-
const base = cacheDir();
|
|
109
|
-
ensureExtracted(base);
|
|
110
82
|
const pi = findPi();
|
|
111
|
-
const extensionsDir = join(base, "extensions");
|
|
112
83
|
const args = [
|
|
113
84
|
"--extension",
|
|
114
|
-
|
|
85
|
+
EXT_ZENDY,
|
|
115
86
|
];
|
|
116
87
|
// Pass through all user arguments
|
|
117
88
|
args.push(...filteredArgs);
|
package/dist/preflight.js
CHANGED
|
@@ -3,8 +3,8 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { resolveZendeskConfig, resolveKgConfig } from "./config/resolve.js";
|
|
7
|
+
import { DEFAULT_KG_API_URL } from "./config/schema.js";
|
|
8
8
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
9
9
|
function exec(cmd, args, timeoutMs = 5000) {
|
|
10
10
|
return new Promise((resolve) => {
|
|
@@ -60,25 +60,6 @@ async function checkPi() {
|
|
|
60
60
|
}
|
|
61
61
|
return { ...base, status: "ok", hint: "" };
|
|
62
62
|
}
|
|
63
|
-
function resolveZendeskConfig() {
|
|
64
|
-
// 1. zendy config (env or ~/.zendy/config.json)
|
|
65
|
-
const cfg = getConfig();
|
|
66
|
-
if (cfg.zendesk?.subdomain && cfg.zendesk?.email && cfg.zendesk?.apiToken) {
|
|
67
|
-
return { subdomain: cfg.zendesk.subdomain, email: cfg.zendesk.email, apiToken: cfg.zendesk.apiToken };
|
|
68
|
-
}
|
|
69
|
-
// 2. Try legacy migration
|
|
70
|
-
migrateLegacyConfig();
|
|
71
|
-
const cfg2 = getConfig();
|
|
72
|
-
if (cfg2.zendesk?.subdomain && cfg2.zendesk?.email && cfg2.zendesk?.apiToken) {
|
|
73
|
-
return { subdomain: cfg2.zendesk.subdomain, email: cfg2.zendesk.email, apiToken: cfg2.zendesk.apiToken };
|
|
74
|
-
}
|
|
75
|
-
// 3. Legacy zcli config
|
|
76
|
-
const legacy = getLegacyZendeskConfig();
|
|
77
|
-
if (legacy?.subdomain && legacy?.email && legacy?.api_token) {
|
|
78
|
-
return { subdomain: legacy.subdomain, email: legacy.email, apiToken: legacy.api_token };
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
63
|
async function checkZendesk() {
|
|
83
64
|
const base = {
|
|
84
65
|
name: "zendesk",
|
|
@@ -121,24 +102,6 @@ async function checkZendesk() {
|
|
|
121
102
|
};
|
|
122
103
|
}
|
|
123
104
|
}
|
|
124
|
-
function resolveKgConfig() {
|
|
125
|
-
// 1. zendy config
|
|
126
|
-
const cfg = getConfig();
|
|
127
|
-
if (cfg.zendeskKg?.apiKey)
|
|
128
|
-
return { apiUrl: cfg.zendeskKg.apiUrl, apiKey: cfg.zendeskKg.apiKey };
|
|
129
|
-
// 2. Try legacy migration
|
|
130
|
-
migrateLegacyConfig();
|
|
131
|
-
const cfg2 = getConfig();
|
|
132
|
-
if (cfg2.zendeskKg?.apiKey)
|
|
133
|
-
return { apiUrl: cfg2.zendeskKg.apiUrl, apiKey: cfg2.zendeskKg.apiKey };
|
|
134
|
-
// 3. Legacy .env
|
|
135
|
-
const legacy = getLegacyKgEnv();
|
|
136
|
-
if (legacy?.["RETRIEVER_API_KEY"]) {
|
|
137
|
-
return { apiUrl: legacy["RETRIEVER_API_URL"] || undefined, apiKey: legacy["RETRIEVER_API_KEY"] };
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
const DEFAULT_KG_API_URL = "https://zendesk-ticket-retriever.vercel.app";
|
|
142
105
|
async function checkZendeskKgApi() {
|
|
143
106
|
const base = {
|
|
144
107
|
name: "zendesk-kg",
|
package/dist/source-cleanup.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Used by:
|
|
8
8
|
// - `src/cleanup-src.ts` — the `zendy cleanup-src` CLI subcommand.
|
|
9
|
-
// - `extensions/
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// - `extensions/zendy.ts` — the pi extension inlines the tiny session-dir
|
|
10
|
+
// helpers it needs so it can be loaded as a self-contained TS file by pi
|
|
11
|
+
// at runtime.
|
|
12
12
|
import { readdir, stat, rm } from "node:fs/promises";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
async function dirSize(path) {
|
package/extensions/commands.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { getConfig, writeConfig, configPath, configExists } from "../dist/config/store.js";
|
|
6
6
|
import { migrateLegacyConfig } from "../dist/config/migrate.js";
|
|
7
7
|
import type { ZendyConfig } from "../dist/config/schema.js";
|
|
8
|
+
import { DEFAULT_KG_API_URL } from "../dist/config/schema.js";
|
|
8
9
|
import * as zendesk from "../dist/clients/zendesk.js";
|
|
9
10
|
import * as kg from "../dist/clients/zendesk-kg.js";
|
|
10
11
|
|
|
@@ -44,7 +45,7 @@ async function configCommand(_args: string, ctx: {
|
|
|
44
45
|
} else if (action === "Knowledge Graph credentials") {
|
|
45
46
|
const apiUrl = await ctx.ui.input(
|
|
46
47
|
"Knowledge Graph API URL (leave empty for default)",
|
|
47
|
-
config.zendeskKg?.apiUrl ??
|
|
48
|
+
config.zendeskKg?.apiUrl ?? DEFAULT_KG_API_URL,
|
|
48
49
|
);
|
|
49
50
|
const apiKey = await ctx.ui.input("Knowledge Graph API key", "");
|
|
50
51
|
config.zendeskKg = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tplog/pi-zendy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Pi package for Dify Enterprise support ticket analysis",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
-
"extensions"
|
|
26
|
+
"extensions",
|
|
27
|
+
"prompts"
|
|
27
28
|
],
|
|
28
29
|
"publishConfig": {
|
|
29
30
|
"access": "public"
|
package/prompts/zme.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 查看当前 Zendesk 用户相关 tickets
|
|
3
|
+
argument-hint: "[filter]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
查看和当前 Zendesk 用户有关的 Zendesk tickets。
|
|
7
|
+
|
|
8
|
+
用户附加要求:$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
请按下面流程执行:
|
|
11
|
+
|
|
12
|
+
1. 先调用 `zendy_whoami`,确认当前 Zendesk 用户。
|
|
13
|
+
- 记下 `email`
|
|
14
|
+
- 记下 `name`
|
|
15
|
+
- 后续查询必须基于实际返回的身份,不要猜用户是谁。
|
|
16
|
+
|
|
17
|
+
2. 查询 assigned 给当前用户的 tickets。
|
|
18
|
+
- 默认查询未完成 tickets:
|
|
19
|
+
`type:ticket assignee:<email> status<solved`
|
|
20
|
+
- 如果用户在参数里指定了时间、状态、关键词,请把它们合并进查询。
|
|
21
|
+
- 如果默认查询为 0,再放宽为:
|
|
22
|
+
`type:ticket assignee:<email>`
|
|
23
|
+
|
|
24
|
+
3. 查询当前用户作为 follower / CC 的 tickets。
|
|
25
|
+
- 优先尝试:
|
|
26
|
+
`type:ticket follower:<email> status<solved`
|
|
27
|
+
- 如果结果为 0 或 Zendesk 不支持该语法,请明确说明,并尝试:
|
|
28
|
+
`type:ticket cc:<email> status<solved`
|
|
29
|
+
- 如果用户指定了过滤条件,同样合并进查询。
|
|
30
|
+
|
|
31
|
+
4. 合并结果并去重。
|
|
32
|
+
- 同一个 ticket 同时出现在 assigned 和 follower/CC 时,放入 "Both"。
|
|
33
|
+
- 只 assigned 的放入 "Assigned to me"。
|
|
34
|
+
- 只 follower/CC 的放入 "Following / CC"。
|
|
35
|
+
- 不要重复列出同一个 ticket。
|
|
36
|
+
|
|
37
|
+
5. 输出结果时使用简洁表格:
|
|
38
|
+
- ID
|
|
39
|
+
- Status
|
|
40
|
+
- Subject
|
|
41
|
+
- Created / Updated date,如果搜索结果里有
|
|
42
|
+
- Relation: Assigned / Following / Both
|
|
43
|
+
|
|
44
|
+
6. 如果结果很多:
|
|
45
|
+
- 先显示最近或最相关的 20 条
|
|
46
|
+
- 说明总数
|
|
47
|
+
- 问用户是否要继续展开、按状态过滤、或逐个分析。
|
|
48
|
+
|
|
49
|
+
7. 注意事项:
|
|
50
|
+
- 只使用 zendy 工具,不要使用 zcli、curl、Zendesk 外部 CLI。
|
|
51
|
+
- 不要臆测 Zendesk search syntax。某个查询不工作时,要说明是查询语法可能不支持,并给出实际 fallback。
|
|
52
|
+
- 如果用户要求分析某个 ticket,再调用 `zendy_ticket_get` 获取完整 ticket 和 comments。
|