@77-zhike/app-sdk 0.2.0-d7b1.5 → 0.2.0-d7b1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@77-zhike/app-sdk",
3
- "version": "0.2.0-d7b1.5",
3
+ "version": "0.2.0-d7b1.6",
4
4
  "description": "\u667a\u5ba2 Integration App \u4e00\u671f SDK \u4e0e\u672c\u5730 Toolchain",
5
5
  "type": "module",
6
6
  "engines": {
@@ -10,7 +10,7 @@ import {
10
10
  import { constants } from "node:fs";
11
11
  import { basename, dirname, resolve } from "node:path";
12
12
 
13
- import { TOOLING_VERSION } from "../config.js";
13
+ import { normalizeOrigin, TOOLING_VERSION } from "../config.js";
14
14
  import { ZhikeCliError } from "../errors.js";
15
15
  import { serializeProjectBinding } from "../project-binding.js";
16
16
 
@@ -91,6 +91,18 @@ export async function assertEmptyTarget(targetDirectory) {
91
91
  }
92
92
 
93
93
  export async function scaffoldProject({ targetDirectory, binding }) {
94
+ const docsUrl = `${normalizeOrigin(binding.platformEnvironment, "platformEnvironment")}/docs/`;
95
+ const agentInstructions = `# 智客 App 开发
96
+
97
+ 开始开发或修改此 App 前,先读取当前平台环境的开发文档:
98
+
99
+ - 文档索引:${docsUrl}llms.txt
100
+ - 开发文档:${docsUrl}
101
+
102
+ 先读取文档索引,再按当前任务读取相关的 App SDK、CLI、扩展、连接和 API 文档;不要仅凭记忆猜测接口、参数或运行时能力。
103
+ 索引中的相对链接以开发文档地址为基准解析。若无法访问文档,请明确说明访问失败,请用户提供相关文档,不要把猜测当作已确认能力。
104
+ 项目绑定环境与 App 身份见 .zhike/project.json;切换平台环境后应同步更新上述文档地址。
105
+ `;
94
106
  const temporaryDirectory = `${targetDirectory}.zhike-tmp-${randomBytes(6).toString("hex")}`;
95
107
  try {
96
108
  await mkdir(resolve(temporaryDirectory, ".zhike"), { recursive: true });
@@ -107,6 +119,9 @@ export async function scaffoldProject({ targetDirectory, binding }) {
107
119
  await mkdir(resolve(temporaryDirectory, "src", "server", "functions"), {
108
120
  recursive: true,
109
121
  });
122
+ for (const filename of ["AGENTS.md", "CLAUDE.md"]) {
123
+ await writeFile(resolve(temporaryDirectory, filename), agentInstructions, { flag: "wx" });
124
+ }
110
125
  await writeFile(
111
126
  resolve(temporaryDirectory, ".zhike", "project.json"),
112
127
  serializeProjectBinding(binding),
@@ -50,3 +50,8 @@ export type { ToastOptions, ConfirmOptions } from './interactions.js';
50
50
  export function showToast(options: import('./interactions.js').ToastOptions): Promise<void>;
51
51
  /** 用户取消返回 false;所属页面关闭时拒绝,不能当作确认。 */
52
52
  export function confirm(options: import('./interactions.js').ConfirmOptions): Promise<boolean>;
53
+ /**
54
+ * 由 Web Host 在新页签打开绝对 HTTPS URL;不返回窗口或远端加载状态。
55
+ * @platform Web
56
+ */
57
+ export function openExternalUrl(url: string): Promise<void>;
@@ -20,3 +20,4 @@ export function defineFrontendExtension(definition) {
20
20
  // 受管 Guest 注入实际请求出口;脱离前端运行环境不能调用产品交互。
21
21
  export function showToast() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
22
22
  export function confirm() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
23
+ export function openExternalUrl() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
@@ -1,8 +1,10 @@
1
1
  export interface ToastOptions { message: string; tone?: 'info' | 'success' | 'error' }
2
2
  export interface ConfirmOptions { title: string; description?: string; confirmLabel?: string; cancelLabel?: string }
3
- export type HostInteraction = {operation:'showToast';input:ToastOptions} | {operation:'confirm';input:ConfirmOptions};
4
- export function assertHostInteraction(operation: unknown, input: unknown): Readonly<ToastOptions | ConfirmOptions>;
5
- export function createClientInteractions(invoke: (operation: string, input: ToastOptions | ConfirmOptions) => Promise<unknown>): {
3
+ export interface OpenExternalUrlOptions { url: string }
4
+ export type HostInteraction = {operation:'showToast';input:ToastOptions} | {operation:'confirm';input:ConfirmOptions} | {operation:'openExternalUrl';input:OpenExternalUrlOptions};
5
+ export function assertHostInteraction(operation: unknown, input: unknown): Readonly<ToastOptions | ConfirmOptions | OpenExternalUrlOptions>;
6
+ export function createClientInteractions(invoke: (operation: string, input: ToastOptions | ConfirmOptions | OpenExternalUrlOptions) => Promise<unknown>): {
6
7
  showToast(input: ToastOptions): Promise<void>;
7
8
  confirm(input: ConfirmOptions): Promise<boolean>;
9
+ openExternalUrl(url: string): Promise<void>;
8
10
  };
@@ -1,12 +1,21 @@
1
1
  const fail = () => { throw new Error('runtime_interaction_invalid'); };
2
2
  const text = (value, max, required = false) => value === undefined ? !required : typeof value === 'string' && value.length <= max && (!required || value.length > 0);
3
+ const externalUrl = value => {
4
+ if (!text(value,8192,true) || value.trim() !== value || /[\u0000-\u001f\u007f]/.test(value)) return false;
5
+ try {
6
+ const parsed = new URL(value);
7
+ return parsed.protocol === 'https:' && parsed.hostname.length > 0 && !parsed.username && !parsed.password;
8
+ } catch { return false; }
9
+ };
3
10
  export function assertHostInteraction(operation, input) {
4
11
  if (!input || typeof input !== 'object' || Array.isArray(input)) fail();
5
- const allowed = operation === 'showToast' ? ['message','tone'] : operation === 'confirm' ? ['title','description','confirmLabel','cancelLabel'] : [];
12
+ const allowed = operation === 'showToast' ? ['message','tone'] : operation === 'confirm' ? ['title','description','confirmLabel','cancelLabel'] : operation === 'openExternalUrl' ? ['url'] : [];
6
13
  if (!allowed.length || Object.keys(input).some(key=>!allowed.includes(key))) fail();
7
14
  if (operation === 'showToast') {
8
15
  if (!text(input.message,500,true) || (input.tone !== undefined && !['info','success','error'].includes(input.tone))) fail();
9
- } else if (!text(input.title,200,true) || !text(input.description,2000) || !text(input.confirmLabel,60) || !text(input.cancelLabel,60)) fail();
16
+ } else if (operation === 'confirm') {
17
+ if (!text(input.title,200,true) || !text(input.description,2000) || !text(input.confirmLabel,60) || !text(input.cancelLabel,60)) fail();
18
+ } else if (!externalUrl(input.url)) fail();
10
19
  return Object.freeze(Object.fromEntries(Object.entries(input).filter(([,value])=>value!==undefined)));
11
20
  }
12
21
  export function createClientInteractions(invoke) {
@@ -19,5 +28,9 @@ export function createClientInteractions(invoke) {
19
28
  const request = assertHostInteraction('confirm',input);
20
29
  return invoke('confirm',request).then(value=>{if(typeof value!=='boolean')fail();return value;});
21
30
  },
31
+ openExternalUrl(url) {
32
+ const request = assertHostInteraction('openExternalUrl',{url});
33
+ return invoke('openExternalUrl',request).then(value=>{if(value!==null)fail();});
34
+ },
22
35
  });
23
36
  }