@yhong91/cpac 0.1.2 → 0.1.4

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 CHANGED
@@ -101,16 +101,20 @@ cpac claude --model claude-sonnet-4-5
101
101
  cpac claude -- -p "检查当前项目"
102
102
  ```
103
103
 
104
- `cpac claude` 会为 Claude Code 子进程设置:
104
+ `cpac claude` 会在本地起一个临时 loopback 代理,并为 Claude Code 子进程设置:
105
105
 
106
106
  ```text
107
- ANTHROPIC_BASE_URL=<CPA 地址>
107
+ ANTHROPIC_BASE_URL=http://127.0.0.1:<临时端口>
108
108
  ANTHROPIC_AUTH_TOKEN=<CPA_API_KEY>
109
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
110
+ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1
109
111
  ```
110
112
 
111
113
  同时清除可能覆盖网关选择的 `ANTHROPIC_API_KEY` 和 Claude Code 云 provider 环境变量。它不会修改 `~/.claude/`,并原样返回 Claude Code 的退出码。
112
114
 
113
- > CPA 服务端必须支持 Claude Code 使用的 Anthropic Messages API(`/v1/messages`)。Claude 路径直接连接远端 CPA;CPAC 不负责协议转换。
115
+ 代理开启 Claude Code gateway model discovery:`/model` 选择器会列出 CPA 目录里的全部模型。claude 系模型直接使用原 id;其它模型以 `claude-cpac--<模型名>` 别名出现(例如 `claude-cpac--gpt-5.6-sol`),请求发出时由代理还原为真实模型名。
116
+
117
+ > CPA 服务端必须支持 Claude Code 使用的 Anthropic Messages API(`/v1/messages`)。
114
118
 
115
119
  ### Codex
116
120
 
@@ -175,12 +179,30 @@ CPAC state:~/.cpac
175
179
 
176
180
  为防止误删或覆盖,`state_dir` 不能是文件系统根目录、用户 home、系统临时目录,也不能包含 Codex 配置文件。
177
181
 
182
+ ### Pi
183
+
184
+ 安装 CPA provider 扩展到 Pi:
185
+
186
+ ```bash
187
+ cpac pi install
188
+ ```
189
+
190
+ 它将生成 `cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
191
+
192
+ ```bash
193
+ cpac pi uninstall
194
+ cpac pi status
195
+ ```
196
+
197
+ Pi 扩展尊重 `PI_CODING_AGENT_DIR` 环境变量。
198
+
178
199
  ## 环境变量
179
200
 
180
201
  | 变量 | 默认值 | 作用 |
181
202
  | --- | --- | --- |
182
203
  | `CPA_API_KEY` | 无 | CPA Bearer key;可由 `cpac` 首次引导写入 shell 启动文件 |
183
204
  | `CPA_BASE_URL` | `https://cpa.vibetime.cc` | 覆盖内置 CPA 地址 |
205
+ | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | 覆盖 Pi agent 目录(影响 `cpac pi install` 写入位置) |
184
206
  | `CPAC_CONFIG` | `~/.config/cpac/config.json` | 指定可选 JSON 配置路径 |
185
207
  | `CODEX_HOME` | `~/.codex` | Codex home 目录 |
186
208
 
package/dist/cpac.js CHANGED
@@ -507,6 +507,134 @@ export async function createLoopbackProxy(cpaUrl, apiKey, proxyId, port) {
507
507
  }
508
508
  return { server, port: address.port };
509
509
  }
510
+ const CLAUDE_ALIAS_PREFIX = "claude-cpac--";
511
+ function catalogModelRows(document) {
512
+ if (!objectValue(document))
513
+ return undefined;
514
+ const source = Array.isArray(document.models)
515
+ ? document.models
516
+ : Array.isArray(document.data)
517
+ ? document.data
518
+ : undefined;
519
+ if (!source)
520
+ return undefined;
521
+ return source.filter(objectValue);
522
+ }
523
+ function catalogModelId(model) {
524
+ if (typeof model.slug === "string" && model.slug.trim())
525
+ return model.slug.trim();
526
+ if (typeof model.id === "string" && model.id.trim())
527
+ return model.id.trim();
528
+ return undefined;
529
+ }
530
+ async function claudeModelList(cpaUrl, apiKey, response) {
531
+ let rows;
532
+ try {
533
+ const catalog = await fetch(`${apiBase(cpaUrl)}/models?client_version=1`, {
534
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
535
+ signal: AbortSignal.timeout(20_000),
536
+ });
537
+ if (catalog.ok)
538
+ rows = catalogModelRows(await catalog.json());
539
+ }
540
+ catch {
541
+ rows = undefined;
542
+ }
543
+ if (!rows) {
544
+ response.writeHead(502, { "content-type": "application/json" });
545
+ response.end(JSON.stringify({ error: "CPA catalog request failed" }));
546
+ return;
547
+ }
548
+ // Claude Code's /model picker only lists ids starting with claude/anthropic;
549
+ // expose every other catalog model as claude-cpac--<id>.
550
+ const data = [];
551
+ for (const model of rows) {
552
+ const id = catalogModelId(model);
553
+ if (!id)
554
+ continue;
555
+ const alias = id.startsWith("claude") ? id : `${CLAUDE_ALIAS_PREFIX}${id}`;
556
+ data.push({
557
+ type: "model",
558
+ id: alias,
559
+ display_name: typeof model.display_name === "string" && model.display_name.trim()
560
+ ? model.display_name
561
+ : id,
562
+ });
563
+ }
564
+ response.writeHead(200, { "content-type": "application/json" });
565
+ response.end(JSON.stringify({
566
+ data,
567
+ has_more: false,
568
+ first_id: data.length > 0 ? data[0].id : null,
569
+ last_id: data.length > 0 ? data[data.length - 1].id : null,
570
+ }));
571
+ }
572
+ export async function createClaudeProxy(cpaUrl, apiKey) {
573
+ const server = createServer((request, response) => {
574
+ const requestUrl = request.url ?? "/";
575
+ if (request.method === "GET" && requestUrl.startsWith("/v1/models")) {
576
+ void claudeModelList(cpaUrl, apiKey, response);
577
+ return;
578
+ }
579
+ let target;
580
+ try {
581
+ target = upstreamUrl(cpaUrl, requestUrl);
582
+ }
583
+ catch {
584
+ response.writeHead(400).end("invalid request URL");
585
+ return;
586
+ }
587
+ const chunks = [];
588
+ request.on("data", (chunk) => chunks.push(chunk));
589
+ request.once("error", () => response.destroy());
590
+ request.once("end", () => {
591
+ let body = Buffer.concat(chunks);
592
+ if (body.length > 0) {
593
+ try {
594
+ const parsed = JSON.parse(body.toString("utf8"));
595
+ if (objectValue(parsed) &&
596
+ typeof parsed.model === "string" &&
597
+ parsed.model.startsWith(CLAUDE_ALIAS_PREFIX)) {
598
+ parsed.model = parsed.model.slice(CLAUDE_ALIAS_PREFIX.length);
599
+ body = Buffer.from(JSON.stringify(parsed));
600
+ }
601
+ }
602
+ catch {
603
+ // not JSON; forward unchanged
604
+ }
605
+ }
606
+ const headers = proxyHeaders(request.headers, apiKey);
607
+ headers["content-length"] = String(body.length);
608
+ const send = target.protocol === "https:" ? httpsRequest : httpRequest;
609
+ const upstream = send(target, { method: request.method, headers }, (upstreamResponse) => {
610
+ response.writeHead(upstreamResponse.statusCode ?? 502, responseHeaders(upstreamResponse.headers));
611
+ upstreamResponse.pipe(response);
612
+ });
613
+ upstream.once("error", () => {
614
+ if (!response.headersSent) {
615
+ response.writeHead(502, { "content-type": "application/json" });
616
+ }
617
+ if (!response.writableEnded)
618
+ response.end(JSON.stringify({ error: "CPA upstream request failed" }));
619
+ });
620
+ upstream.end(body);
621
+ });
622
+ });
623
+ server.on("clientError", (_error, socket) => socket.destroy());
624
+ await new Promise((resolveListen, rejectListen) => {
625
+ server.once("error", rejectListen);
626
+ server.listen(0, "127.0.0.1", () => {
627
+ server.off("error", rejectListen);
628
+ resolveListen();
629
+ });
630
+ });
631
+ const address = server.address();
632
+ if (!address || typeof address === "string") {
633
+ server.close();
634
+ throw new CPACError("cannot determine Claude proxy port");
635
+ }
636
+ return { server, port: address.port };
637
+ }
510
638
  async function runProxyChild(port) {
511
639
  const cpaUrl = process.env.CPAC_PROXY_UPSTREAM;
512
640
  const apiKey = process.env.CPAC_PROXY_API_KEY;
@@ -886,7 +1014,8 @@ export async function status(config) {
886
1014
  }
887
1015
  }
888
1016
  console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
889
- console.log(`pi: ${keyConfigured ? "ready" : "not configured"}`);
1017
+ const piInstalled = isPiExtensionInstalled();
1018
+ console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac pi install") : "not configured"}`);
890
1019
  if (!state)
891
1020
  return 1;
892
1021
  const proxy = stateProxy(state);
@@ -894,9 +1023,6 @@ export async function status(config) {
894
1023
  return 2;
895
1024
  return 0;
896
1025
  }
897
- function claudeBaseUrl(cpaUrl) {
898
- return cpaUrl.replace(/\/v1$/, "");
899
- }
900
1026
  async function promptSecret(name) {
901
1027
  if (!process.stdin.isTTY || !process.stderr.isTTY) {
902
1028
  throw new CPACError(`environment variable ${name} is not set; run: export ${name}="..."`);
@@ -952,7 +1078,7 @@ export function saveApiKeyExport(profile, name, apiKey) {
952
1078
  atomicWrite(profile, Buffer.from(content), mode);
953
1079
  }
954
1080
  async function guide(config) {
955
- console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac claude [args...] Launch Claude Code through CPA\n cpac inject Inject CPA into Codex and start its loopback proxy\n cpac proxy Run the injected loopback proxy in the foreground\n cpac status Show agent support status (Codex, Claude, Pi)\n cpac restore Restore the original Codex config\n cpac --help Show command usage`);
1081
+ console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac claude [args...] Launch Claude Code through CPA\n cpac inject Inject CPA into Codex and start its loopback proxy\n cpac proxy Run the injected loopback proxy in the foreground\n cpac status Show agent support status (Codex, Claude, Pi)\n cpac restore Restore the original Codex config\n cpac pi install Install CPA provider extension for Pi\n cpac pi uninstall Remove the Pi CPA provider extension\n cpac pi status Show Pi extension install status\n cpac --help Show command usage`);
956
1082
  if (process.env[config.api_key_env]?.trim())
957
1083
  return 0;
958
1084
  const apiKey = await promptSecret(config.api_key_env);
@@ -964,10 +1090,17 @@ async function guide(config) {
964
1090
  }
965
1091
  export async function runClaude(config, args, executable = "claude") {
966
1092
  const apiKey = await resolveApiKey(config.api_key_env);
1093
+ const proxy = await createClaudeProxy(config.cpa_url, apiKey);
1094
+ const stopProxy = () => {
1095
+ proxy.server.closeAllConnections?.();
1096
+ proxy.server.close();
1097
+ };
967
1098
  const env = {
968
1099
  ...process.env,
969
- ANTHROPIC_BASE_URL: claudeBaseUrl(config.cpa_url),
1100
+ ANTHROPIC_BASE_URL: `http://127.0.0.1:${proxy.port}`,
970
1101
  ANTHROPIC_AUTH_TOKEN: apiKey,
1102
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
1103
+ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: "1",
971
1104
  };
972
1105
  delete env.ANTHROPIC_API_KEY;
973
1106
  delete env.CLAUDE_CODE_USE_ANTHROPIC_AWS;
@@ -977,11 +1110,15 @@ export async function runClaude(config, args, executable = "claude") {
977
1110
  return await new Promise((resolve, reject) => {
978
1111
  const child = spawn(executable, args, { env, stdio: "inherit" });
979
1112
  child.once("error", (error) => {
1113
+ stopProxy();
980
1114
  reject(new CPACError(error instanceof Error && "code" in error && error.code === "ENOENT"
981
1115
  ? `${executable} not found`
982
1116
  : `cannot start ${executable}: ${error.message}`));
983
1117
  });
984
- child.once("close", (code) => resolve(code ?? 1));
1118
+ child.once("close", (code) => {
1119
+ stopProxy();
1120
+ resolve(code ?? 1);
1121
+ });
985
1122
  });
986
1123
  }
987
1124
  export async function runProxy(config) {
@@ -1016,18 +1153,74 @@ export async function runProxy(config) {
1016
1153
  await new Promise((resolveClose) => proxy.server.once("close", resolveClose));
1017
1154
  return 0;
1018
1155
  }
1156
+ function piExtensionsDir() {
1157
+ return join(expandUserPath(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent")), "extensions");
1158
+ }
1159
+ export function isPiExtensionInstalled() {
1160
+ return existsSync(join(piExtensionsDir(), "cpac.ts"));
1161
+ }
1162
+ function piTemplatePath() {
1163
+ return join(dirname(fileURLToPath(import.meta.url)), "pi-extension.template");
1164
+ }
1165
+ function piExtensionContent(cpaUrl) {
1166
+ return readFileSync(piTemplatePath(), "utf8").replace("__CPA_URL__", cpaUrl);
1167
+ }
1168
+ export async function installPiExtension(config) {
1169
+ const dir = piExtensionsDir();
1170
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
1171
+ const target = join(dir, "cpac.ts");
1172
+ atomicWrite(target, Buffer.from(piExtensionContent(config.cpa_url)), 0o644);
1173
+ console.log(`Installed Pi extension: ${target}`);
1174
+ }
1175
+ export async function uninstallPiExtension() {
1176
+ const target = join(piExtensionsDir(), "cpac.ts");
1177
+ if (!existsSync(target))
1178
+ throw new CPACError("Pi extension is not installed");
1179
+ unlinkSync(target);
1180
+ console.log(`Removed Pi extension: ${target}`);
1181
+ }
1182
+ export async function runPi(config, action) {
1183
+ if (action === "install") {
1184
+ await installPiExtension(config);
1185
+ return 0;
1186
+ }
1187
+ if (action === "uninstall") {
1188
+ await uninstallPiExtension();
1189
+ return 0;
1190
+ }
1191
+ if (action === "status") {
1192
+ const installed = isPiExtensionInstalled();
1193
+ console.log(`Pi extension: ${installed ? "installed" : "not installed"}`);
1194
+ return installed ? 0 : 1;
1195
+ }
1196
+ throw new CPACError(`unknown pi action: ${action}; use install, uninstall, or status`);
1197
+ }
1019
1198
  function usage() {
1020
1199
  return [
1021
1200
  "Usage: cpac",
1022
1201
  " cpac <inject|status|restore> [--config PATH]",
1023
1202
  " cpac proxy [--config PATH]",
1024
1203
  " cpac claude [--config PATH] [--] [claude args...]",
1204
+ " cpac pi <install|uninstall|status> [--config PATH]",
1025
1205
  ].join("\n");
1026
1206
  }
1027
1207
  function parseArgs(args) {
1028
1208
  if (args.length === 0) {
1029
1209
  return { command: "guide", configPath: defaultConfigPath() };
1030
1210
  }
1211
+ if (args[0] === "pi") {
1212
+ let configPath = defaultConfigPath();
1213
+ let index = 1;
1214
+ if (args[index] === "--config") {
1215
+ const value = args[++index];
1216
+ if (!value)
1217
+ throw new CPACError("--config requires a path");
1218
+ configPath = resolve(expandUserPath(value));
1219
+ index += 1;
1220
+ }
1221
+ const action = args[index] || "status";
1222
+ return { command: "pi", configPath, action };
1223
+ }
1031
1224
  if (args[0] === "claude") {
1032
1225
  let configPath = defaultConfigPath();
1033
1226
  let index = 1;
@@ -1088,6 +1281,8 @@ export async function main(args = process.argv.slice(2)) {
1088
1281
  return await runClaude(config, parsed.args);
1089
1282
  if (parsed.command === "proxy")
1090
1283
  return await runProxy(config);
1284
+ if (parsed.command === "pi")
1285
+ return await runPi(config, parsed.action);
1091
1286
  if (parsed.command === "inject")
1092
1287
  await inject(config);
1093
1288
  else if (parsed.command === "restore")
@@ -0,0 +1,107 @@
1
+ // CPAC Pi extension: register CPA models as Pi providers
2
+ // Installed by: cpac pi install
3
+
4
+ const CPA = "__CPA_URL__";
5
+ const BUILTIN = new Set(["openai", "github-copilot", "xai", "deepseek", "anthropic", "google"]);
6
+ const VENDOR_PREFIXES = [
7
+ ["gpt-", "openai"], ["o1-", "openai"], ["o3-", "openai"], ["o4-", "openai"],
8
+ ["claude-", "anthropic"], ["gemini-", "google"], ["grok-", "xai"],
9
+ ["deepseek-", "deepseek"], ["glm-", "zhipu"], ["kimi-", "moonshot"],
10
+ ["mimo-", "xiaomi"], ["doubao-", "volcengine"], ["ark-", "volcengine"],
11
+ ["minimax-", "minimax"], ["step-", "stepfun"], ["qwen-", "alibaba"],
12
+ ["hunyuan-", "tencent"],
13
+ ];
14
+ const MAX_TOKENS = {
15
+ "gpt-5.6-sol": 128000, "gpt-5.6-terra": 128000, "gpt-5.6-luna": 128000,
16
+ "claude-opus-4-6-thinking": 128000, "claude-sonnet-4-6": 64000,
17
+ "gemini-3.6-flash": 65536, "gemini-3.6-flash-high": 65536,
18
+ "deepseek-v4-pro": 128000, "deepseek-v4-flash": 128000,
19
+ "glm-5.2": 128000, "kimi-k2.7-code": 128000, "minimax-m3": 128000,
20
+ "mimo-v2.5": 131072, "mimo-v2.5-pro": 131072, "grok-4.5": 128000,
21
+ "doubao-seed-2.0-lite": 128000, "doubao-seed-2.1-turbo": 128000,
22
+ };
23
+ const DEFAULT_MAX_TOKENS = 65536;
24
+
25
+ function vendorFor(slug) {
26
+ for (const [prefix, vendor] of VENDOR_PREFIXES) {
27
+ if (slug.startsWith(prefix)) return vendor;
28
+ }
29
+ return "misc";
30
+ }
31
+
32
+ function groupName(vendor) {
33
+ return BUILTIN.has(vendor) ? vendor + "-cpa" : vendor;
34
+ }
35
+
36
+ function intField(value) {
37
+ return typeof value === "number" && Number.isFinite(value) && value > 0
38
+ ? Math.floor(value) : undefined;
39
+ }
40
+
41
+ export default async function (pi) {
42
+ const apiKey = (process.env.CPA_API_KEY || "").trim();
43
+ if (!apiKey) {
44
+ console.error("[cpac] CPA_API_KEY not set; skipping CPA providers");
45
+ return;
46
+ }
47
+ let payload;
48
+ try {
49
+ const res = await fetch(CPA + "/v1/models?client_version=1", {
50
+ headers: { Authorization: "Bearer " + apiKey },
51
+ signal: AbortSignal.timeout(10000),
52
+ });
53
+ if (!res.ok) {
54
+ console.error("[cpac] CPA models request failed: HTTP " + res.status);
55
+ return;
56
+ }
57
+ payload = await res.json();
58
+ } catch (error) {
59
+ console.error("[cpac] CPA models request failed: " + (error && error.message || error));
60
+ return;
61
+ }
62
+ const source = payload && Array.isArray(payload.models)
63
+ ? payload.models
64
+ : payload && Array.isArray(payload.data) ? payload.data : null;
65
+ if (!source) {
66
+ console.error("[cpac] CPA models response has no models list");
67
+ return;
68
+ }
69
+ const rows = [];
70
+ for (const m of source) {
71
+ if (!m || typeof m !== "object") continue;
72
+ const slug = typeof m.slug === "string" && m.slug.trim()
73
+ ? m.slug.trim()
74
+ : typeof m.id === "string" && m.id.trim() ? m.id.trim() : null;
75
+ if (slug) rows.push({ slug: slug, display_name: m.display_name, supported_reasoning_levels: m.supported_reasoning_levels, context_window: m.context_window });
76
+ }
77
+ if (rows.length === 0) {
78
+ console.error("[cpac] CPA models response has no usable models");
79
+ return;
80
+ }
81
+ const groups = new Map();
82
+ for (const m of rows) {
83
+ const name = groupName(vendorFor(m.slug));
84
+ if (!groups.has(name)) groups.set(name, []);
85
+ groups.get(name).push(m);
86
+ }
87
+ for (const [provName, models] of groups) {
88
+ const vendor = provName.replace(/-cpa$/, "");
89
+ pi.registerProvider(provName, {
90
+ name: vendor + " (cpa)",
91
+ baseUrl: CPA + "/v1",
92
+ apiKey: apiKey,
93
+ api: "openai-responses",
94
+ models: models.map(function (m) {
95
+ return {
96
+ id: m.slug,
97
+ name: typeof m.display_name === "string" ? m.display_name : m.slug,
98
+ reasoning: Array.isArray(m.supported_reasoning_levels) && m.supported_reasoning_levels.length > 0,
99
+ input: ["text", "image"],
100
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
101
+ contextWindow: intField(m.context_window) || 200000,
102
+ maxTokens: MAX_TOKENS[m.slug] || DEFAULT_MAX_TOKENS,
103
+ };
104
+ }),
105
+ });
106
+ }
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "dist/cpac.js",
11
+ "dist/pi-extension.template",
11
12
  "cpac.example.json",
12
13
  "README.md"
13
14
  ],
@@ -28,10 +29,10 @@
28
29
  },
29
30
  "scripts": {
30
31
  "clean": "node -e \"for (const d of ['dist','dist-test']) require('fs').rmSync(d,{recursive:true,force:true})\"",
31
- "build": "npm run clean --if-present && tsc -p tsconfig.build.json && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
32
+ "build": "npm run clean --if-present && tsc -p tsconfig.build.json && cp src/pi-extension.template dist/ && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
32
33
  "check": "tsc -p tsconfig.json --noEmit",
33
34
  "check:pi": "tsc -p tsconfig.pi.json --noEmit",
34
- "test": "npm run clean && tsc -p tsconfig.test.json && node --test --test-reporter=spec dist-test/cpac.test.js",
35
+ "test": "npm run clean && tsc -p tsconfig.test.json && cp src/pi-extension.template dist-test/src/ && node --test --test-reporter=spec dist-test/cpac.test.js",
35
36
  "pack:check": "npm pack --dry-run"
36
37
  },
37
38
  "devDependencies": {