wjw-cli 0.1.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.
- package/README.md +29 -0
- package/dist/api/normalize.d.ts +14 -0
- package/dist/api/normalize.d.ts.map +1 -0
- package/dist/api/normalize.js +282 -0
- package/dist/api/normalize.js.map +1 -0
- package/dist/api/project-detail.d.ts +7 -0
- package/dist/api/project-detail.d.ts.map +1 -0
- package/dist/api/project-detail.js +200 -0
- package/dist/api/project-detail.js.map +1 -0
- package/dist/api/structure-narrow.d.ts +21 -0
- package/dist/api/structure-narrow.d.ts.map +1 -0
- package/dist/api/structure-narrow.js +259 -0
- package/dist/api/structure-narrow.js.map +1 -0
- package/dist/api/survey-api.d.ts +48 -0
- package/dist/api/survey-api.d.ts.map +1 -0
- package/dist/api/survey-api.js +551 -0
- package/dist/api/survey-api.js.map +1 -0
- package/dist/argv.d.ts +9 -0
- package/dist/argv.d.ts.map +1 -0
- package/dist/argv.js +62 -0
- package/dist/argv.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/main.d.ts +2 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +568 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +37 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials-store.d.ts +9 -0
- package/dist/credentials-store.d.ts.map +1 -0
- package/dist/credentials-store.js +38 -0
- package/dist/credentials-store.js.map +1 -0
- package/dist/handbook.d.ts +3 -0
- package/dist/handbook.d.ts.map +1 -0
- package/dist/handbook.js +43 -0
- package/dist/handbook.js.map +1 -0
- package/dist/http/request.d.ts +15 -0
- package/dist/http/request.d.ts.map +1 -0
- package/dist/http/request.js +65 -0
- package/dist/http/request.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/io/readInput.d.ts +7 -0
- package/dist/io/readInput.d.ts.map +1 -0
- package/dist/io/readInput.js +39 -0
- package/dist/io/readInput.js.map +1 -0
- package/dist/sign.d.ts +6 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +46 -0
- package/dist/sign.js.map +1 -0
- package/dist/token.d.ts +4 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +65 -0
- package/dist/token.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +40 -0
- package/references/README.md +4 -0
- package/references/project_json_structure_guide.md +910 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 问卷网 CLI 固定配置:站点根为 https://www.wenjuan.com ,与 wenjuan-survey-mcp 默认路径一致。
|
|
3
|
+
*
|
|
4
|
+
* 问卷网服务访问令牌仅通过 `wjw init --api-key` 写入 ~/.wjw-cli/api_key ,由 getApiKey() 从同一路径读取。
|
|
5
|
+
* 令牌获取:https://www.wenjuan.com/member/common/mcp/
|
|
6
|
+
*/
|
|
7
|
+
import { readStoredApiKey } from "./credentials-store.js";
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
return {
|
|
10
|
+
baseUrl: "https://www.wenjuan.com",
|
|
11
|
+
projectsApiPath: "/app_api/skills/v1/projects/simple",
|
|
12
|
+
projectStructureApiPath: "/app_api/edit/edit_project/",
|
|
13
|
+
createQuestionApiPath: "/app_api/edit/create_question/",
|
|
14
|
+
updateQuestionApiPath: "/app_api/edit/edit_question/",
|
|
15
|
+
deleteQuestionApiPath: "/app_api/edit/delete_question/",
|
|
16
|
+
publishSurveyApiPath: "/edit/api/update_project_status/?jwt=1",
|
|
17
|
+
archiveProjectApiPath: "/report/ajax/project_archive/",
|
|
18
|
+
createSurveyApiPath: "/edit/api/textproject/?jwt=1",
|
|
19
|
+
reportOverviewStatsApiPath: "/report/api/v2/overview/stats/{project_id}/",
|
|
20
|
+
reportDownloadApiPath: "/report/api/download",
|
|
21
|
+
reportDownloadInfosApiPath: "/report/api/download/infos",
|
|
22
|
+
reportDownloadFilterCountApiPath: "/report/api/download/filter_count",
|
|
23
|
+
getWenjuanJwtApiPath: "/login/access_token/get",
|
|
24
|
+
projectsPageSize: 10,
|
|
25
|
+
tlsVerify: true,
|
|
26
|
+
tlsCaBundlePath: undefined,
|
|
27
|
+
httpLogEnabled: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function getApiKey() {
|
|
31
|
+
const k = readStoredApiKey();
|
|
32
|
+
if (!k) {
|
|
33
|
+
throw new Error("尚未保存问卷网服务访问令牌。请执行:wjw init --api-key <令牌>\n令牌获取:https://www.wenjuan.com/member/common/mcp/");
|
|
34
|
+
}
|
|
35
|
+
return k;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAuB1D,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,OAAO,EAAE,yBAAyB;QAClC,eAAe,EAAE,oCAAoC;QACrD,uBAAuB,EAAE,6BAA6B;QACtD,qBAAqB,EAAE,gCAAgC;QACvD,qBAAqB,EAAE,8BAA8B;QACrD,qBAAqB,EAAE,gCAAgC;QACvD,oBAAoB,EAAE,wCAAwC;QAC9D,qBAAqB,EAAE,+BAA+B;QACtD,mBAAmB,EAAE,8BAA8B;QACnD,0BAA0B,EAAE,6CAA6C;QACzE,qBAAqB,EAAE,sBAAsB;QAC7C,0BAA0B,EAAE,4BAA4B;QACxD,gCAAgC,EAAE,mCAAmC;QACrE,oBAAoB,EAAE,yBAAyB;QAC/C,gBAAgB,EAAE,EAAE;QACpB,SAAS,EAAE,IAAI;QACf,eAAe,EAAE,SAAS;QAC1B,cAAc,EAAE,KAAK;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,CAAC,GAAG,gBAAgB,EAAE,CAAC;IAC7B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function getCredentialsDir(): string;
|
|
2
|
+
export declare function getApiKeyFilePath(): string;
|
|
3
|
+
/** 与 `wjw init` 保存位置一致:仅读取该文件。 */
|
|
4
|
+
export declare function readStoredApiKey(): string | null;
|
|
5
|
+
/** 唯一持久化入口:`wjw init --api-key` 写入本路径(权限 0600,目录 0700)。 */
|
|
6
|
+
export declare function saveApiKey(apiKey: string): void;
|
|
7
|
+
/** 删除本地保存的令牌文件(若不存在则 noop)。 */
|
|
8
|
+
export declare function clearStoredApiKey(): boolean;
|
|
9
|
+
//# sourceMappingURL=credentials-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-store.d.ts","sourceRoot":"","sources":["../src/credentials-store.ts"],"names":[],"mappings":"AAOA,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,kCAAkC;AAClC,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAKhD;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAO/C;AAED,+BAA+B;AAC/B,wBAAgB,iBAAiB,IAAI,OAAO,CAK3C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const DIR_NAME = ".wjw-cli";
|
|
5
|
+
const API_KEY_FILE = "api_key";
|
|
6
|
+
export function getCredentialsDir() {
|
|
7
|
+
return join(homedir(), DIR_NAME);
|
|
8
|
+
}
|
|
9
|
+
export function getApiKeyFilePath() {
|
|
10
|
+
return join(getCredentialsDir(), API_KEY_FILE);
|
|
11
|
+
}
|
|
12
|
+
/** 与 `wjw init` 保存位置一致:仅读取该文件。 */
|
|
13
|
+
export function readStoredApiKey() {
|
|
14
|
+
const p = getApiKeyFilePath();
|
|
15
|
+
if (!existsSync(p))
|
|
16
|
+
return null;
|
|
17
|
+
const raw = readFileSync(p, "utf8").trim();
|
|
18
|
+
return raw.length > 0 ? raw : null;
|
|
19
|
+
}
|
|
20
|
+
/** 唯一持久化入口:`wjw init --api-key` 写入本路径(权限 0600,目录 0700)。 */
|
|
21
|
+
export function saveApiKey(apiKey) {
|
|
22
|
+
const key = apiKey.trim();
|
|
23
|
+
if (!key)
|
|
24
|
+
throw new Error("令牌不能为空");
|
|
25
|
+
const dir = getCredentialsDir();
|
|
26
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
27
|
+
const p = getApiKeyFilePath();
|
|
28
|
+
writeFileSync(p, `${key}\n`, { encoding: "utf8", mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
/** 删除本地保存的令牌文件(若不存在则 noop)。 */
|
|
31
|
+
export function clearStoredApiKey() {
|
|
32
|
+
const p = getApiKeyFilePath();
|
|
33
|
+
if (!existsSync(p))
|
|
34
|
+
return false;
|
|
35
|
+
unlinkSync(p);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=credentials-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-store.js","sourceRoot":"","sources":["../src/credentials-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,QAAQ,GAAG,UAAU,CAAC;AAC5B,MAAM,YAAY,GAAG,SAAS,CAAC;AAE/B,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,iBAAiB,EAAE,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,CAAC,GAAG,iBAAiB,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,iBAAiB,EAAE,CAAC;IAC9B,aAAa,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,iBAAiB;IAC/B,MAAM,CAAC,GAAG,iBAAiB,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACjC,UAAU,CAAC,CAAC,CAAC,CAAC;IACd,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** 与 wenjuan-survey-mcp `cherry_stdio_handbook.py` 文案一致(工具名在 CLI 中等价)。 */
|
|
2
|
+
export declare const CHERRY_STDIO_HANDBOOK = "# Cherry Studio\uFF08stdio\uFF09+ \u95EE\u5377\u7F51 MCP / wjw-cli \u4F7F\u7528\u7EA6\u5B9A\n\n\u9762\u5411\u901A\u8FC7 **stdio** \u8FDE\u63A5\u672C MCP \u6216\u4F7F\u7528 **wjw-cli** \u7684\u52A9\u624B\uFF1A\u4EE5\u4E0B\u4E0E\u5DE5\u5177\u8BF4\u660E\u3001\u4ED3\u5E93\u6307\u5357\u4E00\u81F4\uFF0C**\u8BF7\u5728\u5904\u7406\u95EE\u5377\u7F51\u76F8\u5173\u4EFB\u52A1\u524D\u5BF9\u9F50**\u3002\n\n## 1. \u4E24\u79CD\u9898\u76EE JSON \u4E0D\u8981\u6DF7\u7528\n\n| \u5DE5\u5177 / \u547D\u4EE4 | \u9898\u578B\u5B57\u6BB5 | \u4E0D\u8981\u51FA\u73B0\u7684\u5B57\u6BB5 |\n|------|----------|----------------|\n| **project_create** / `wjw project create` \u2192 `question_list[]` | **`en_name`**\uFF08\u5982 `QUESTION_TYPE_SINGLE`\uFF09 | \u4E0D\u8981\u5728\u9898\u76EE\u91CC\u5199 **`question_type`**\uFF08\u670D\u52A1\u7AEF\u6309 `en_name` \u8865\u5168\uFF09 |\n| **question_create / question_update** / `wjw question create|update` \u2192 `question_struct` | **`question_type`**\uFF08\u6574\u6570\uFF09 | **\u4E0D\u8981**\u5199 **`en_name`**\uFF08\u670D\u52A1\u7AEF\u63A8\u5BFC\u540E\u518D\u8BF7\u6C42\u4E0A\u6E38\uFF09 |\n\n## 2. \u5BA2\u6237\u7AEF\u4FA7\u7D20\u6750 \u2192 \u7EC4\u88C5 \u2192 `project_create`\uFF08\u94FE\u63A5\u4E0E\u672C\u5730\u6587\u4EF6\uFF09\n\n**\u65E2\u652F\u6301\u6839\u636E\u94FE\u63A5\u5185\u5BB9\uFF0C\u4E5F\u652F\u6301\u6839\u636E\u672C\u5730\u6587\u4EF6\u5185\u5BB9** \u6765\u300C\u521B\u5EFA\u9879\u76EE\u300D\uFF1A\u4E8C\u8005\u90FD\u6307 **\u5728\u5BA2\u6237\u7AEF**\uFF08Cherry / stdio \u5BBF\u4E3B / \u4F60\u7684\u7EC8\u7AEF\u4FA7\uFF09\u5B8C\u6210 **\u8BFB\u53D6\u3001\u89E3\u6790\u3001\u9898\u578B\u6620\u5C04**\uFF0C\u6309 **`docs/references/project_json_structure_guide.md`** **\u5728\u672C\u5730\u7EC4\u88C5\u597D** **`question_list`**\uFF08\u53CA `title`\u3001`ptype` \u7B49\uFF09\uFF0C\u518D\u8C03\u7528 **`project_create` / `wjw project create --questions @file.json`**\uFF0C**\u4EC5**\u628A\u5DF2\u7EC4\u88C5\u597D\u7684 JSON \u53D1\u7ED9\u670D\u52A1\u7AEF\u3002\n\n- **\u94FE\u63A5**\uFF1A\u5728\u5BA2\u6237\u7AEF\u73AF\u5883\u53D6\u6570\uFF1B**\u4E0D\u8981\u628A URL \u5F53\u4F5C\u53D1\u7ED9\u670D\u52A1\u7AEF\u7684\u552F\u4E00\u53C2\u6570**\uFF1B\u670D\u52A1\u7AEF **\u4E0D\u4F1A**\u4EE3\u6293\u7F51\u9875\u3002\n- **\u672C\u5730\u6587\u4EF6**\uFF1A\u7531\u4F60\u5728\u672C\u673A **\u8BFB\u53D6\u6587\u4EF6** \u5F97\u5230\u6B63\u6587\u540E\u518D\u89E3\u6790\uFF1BCLI \u4F7F\u7528 **`@` \u6587\u4EF6\u8DEF\u5F84** \u8BFB\u53D6 JSON\uFF08\u4F8B\u5982 `--questions @./q.json`\uFF09\u3002\n- **\u7981\u6B62**\uFF1A\u672A\u6784\u9020 `question_list` \u5C31\u521B\u5EFA\u9879\u76EE\uFF0C\u6216\u53EA\u4F20\u94FE\u63A5\u4E0D\u4F20\u9898\u76EE JSON\u3002\n- **\u515C\u5E95**\uFF1A\u53D6\u4E0D\u5230\u7F51\u9875\u6216\u8BFB\u4E0D\u5230\u6587\u4EF6\u65F6\u8BF7\u7528\u6237 **\u7C98\u8D34\u6B63\u6587**\uFF1B\u6216\u5DF2\u6709 **`project_id`** \u65F6\u7528 **`project_structure` / `wjw project structure`** \u4E0E **`question_update` / `wjw question update`**\u3002\n\n## 3. \u6EE1\u610F\u5EA6 / \u661F\u7EA7 / \u8BC4\u4EF7 / NPS\uFF08\u9898\u578B 50\uFF09\n\n- **\u4E0D\u8981**\u7528 **`question_type: 2`**\uFF08\u666E\u901A\u5355\u9009\uFF09\u5192\u5145\u8BC4\u4EF7/\u661F\u7EA7\u9898\u3002\n- \u4F7F\u7528 **`question_type: 50`**\uFF08SCORE\uFF09\uFF0C\u7528\u9898\u76EE **`custom_attr`** \u533A\u5206\u5B50\u7C7B\u578B\uFF1A`disp_type: evaluation` / `nps_score` / `scale`\uFF1B\u666E\u901A\u6253\u5206\u9898\u8BF7\u4F18\u5148\u7528 **Q4 \u7ED3\u6784**\uFF1A`min_answer_num` + `max_answer_num` + `magnitude_scale: 1` + \u5355\u5360\u4F4D `option_list`\u3002\n- \u8BC4\u4EF7\u9898\uFF1A`score_display` \u4E3A\u5C55\u793A\u5F62\u6001\uFF08\u5982 **`star`**\uFF09\uFF0C**\u4E0D\u8981**\u5199\u6210 **`on`/`off`**\u3002`label_data` \u5EFA\u8BAE \u00A75.4 Q22 **\u6309\u5206\u503C\u5BF9\u8C61**\u3002\n- **`question_create` / `question_update`** \u5BF9\u90E8\u5206\u9898\u578B 50 \u6709\u8BEF\u4F20\u4F1A\u505A\u670D\u52A1\u7AEF\u89C4\u8303\u5316\uFF1B**`project_create` \u6574\u5377\u5BFC\u5165\u4E0D\u505A\u540C\u4E00\u5957\u7EA0\u9519**\uFF0C\u5BFC\u5165 JSON \u9700\u5C3D\u91CF\u7B26\u5408\u6307\u5357\u3002\n\n## 4. \u7F16\u8F91\u524D\u62C9\u771F\u7ED3\u6784\n\n- \u9700\u8981 **`project_id`**\u3001\u9898\u76EE\u6240\u5728 **`questionpage_id`**\u3001**`question_id`** \u65F6\uFF0C\u4F18\u5148 **`project_structure` / `wjw project structure`**\uFF0C\u518D **`question_update`**\uFF0C\u907F\u514D\u5B57\u6BB5\u81C6\u9020\u3002\n\n## 5. \u6743\u5A01\u6587\u6863\n\n- \u4ED3\u5E93 **`docs/references/project_json_structure_guide.md`**\uFF1A\u751F\u6210 `project_json` / `question_list` / \u5355\u9898\u7ED3\u6784\u7684\u5B8C\u6574\u8BF4\u660E\u3002\n\n## 6. \u5982\u4F55\u518D\u6B21\u8BFB\u53D6\u672C\u8BF4\u660E\n\n- **MCP Resource**\uFF1AURI **`resource://wenjuan/cherry-stdio-handbook`**\u3002\n- **MCP Prompt**\uFF1A\u540D\u79F0 **`wenjuan.cherry_stdio_handbook`**\u3002\n- **CLI**\uFF1A`wjw resource cherry-handbook` \u6216 `wjw prompt cherry-handbook`\u3002\n";
|
|
3
|
+
//# sourceMappingURL=handbook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handbook.d.ts","sourceRoot":"","sources":["../src/handbook.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,eAAO,MAAM,qBAAqB,24JAwCjC,CAAC"}
|
package/dist/handbook.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** 与 wenjuan-survey-mcp `cherry_stdio_handbook.py` 文案一致(工具名在 CLI 中等价)。 */
|
|
2
|
+
export const CHERRY_STDIO_HANDBOOK = `# Cherry Studio(stdio)+ 问卷网 MCP / wjw-cli 使用约定
|
|
3
|
+
|
|
4
|
+
面向通过 **stdio** 连接本 MCP 或使用 **wjw-cli** 的助手:以下与工具说明、仓库指南一致,**请在处理问卷网相关任务前对齐**。
|
|
5
|
+
|
|
6
|
+
## 1. 两种题目 JSON 不要混用
|
|
7
|
+
|
|
8
|
+
| 工具 / 命令 | 题型字段 | 不要出现的字段 |
|
|
9
|
+
|------|----------|----------------|
|
|
10
|
+
| **project_create** / \`wjw project create\` → \`question_list[]\` | **\`en_name\`**(如 \`QUESTION_TYPE_SINGLE\`) | 不要在题目里写 **\`question_type\`**(服务端按 \`en_name\` 补全) |
|
|
11
|
+
| **question_create / question_update** / \`wjw question create|update\` → \`question_struct\` | **\`question_type\`**(整数) | **不要**写 **\`en_name\`**(服务端推导后再请求上游) |
|
|
12
|
+
|
|
13
|
+
## 2. 客户端侧素材 → 组装 → \`project_create\`(链接与本地文件)
|
|
14
|
+
|
|
15
|
+
**既支持根据链接内容,也支持根据本地文件内容** 来「创建项目」:二者都指 **在客户端**(Cherry / stdio 宿主 / 你的终端侧)完成 **读取、解析、题型映射**,按 **\`docs/references/project_json_structure_guide.md\`** **在本地组装好** **\`question_list\`**(及 \`title\`、\`ptype\` 等),再调用 **\`project_create\` / \`wjw project create --questions @file.json\`**,**仅**把已组装好的 JSON 发给服务端。
|
|
16
|
+
|
|
17
|
+
- **链接**:在客户端环境取数;**不要把 URL 当作发给服务端的唯一参数**;服务端 **不会**代抓网页。
|
|
18
|
+
- **本地文件**:由你在本机 **读取文件** 得到正文后再解析;CLI 使用 **\`@\` 文件路径** 读取 JSON(例如 \`--questions @./q.json\`)。
|
|
19
|
+
- **禁止**:未构造 \`question_list\` 就创建项目,或只传链接不传题目 JSON。
|
|
20
|
+
- **兜底**:取不到网页或读不到文件时请用户 **粘贴正文**;或已有 **\`project_id\`** 时用 **\`project_structure\` / \`wjw project structure\`** 与 **\`question_update\` / \`wjw question update\`**。
|
|
21
|
+
|
|
22
|
+
## 3. 满意度 / 星级 / 评价 / NPS(题型 50)
|
|
23
|
+
|
|
24
|
+
- **不要**用 **\`question_type: 2\`**(普通单选)冒充评价/星级题。
|
|
25
|
+
- 使用 **\`question_type: 50\`**(SCORE),用题目 **\`custom_attr\`** 区分子类型:\`disp_type: evaluation\` / \`nps_score\` / \`scale\`;普通打分题请优先用 **Q4 结构**:\`min_answer_num\` + \`max_answer_num\` + \`magnitude_scale: 1\` + 单占位 \`option_list\`。
|
|
26
|
+
- 评价题:\`score_display\` 为展示形态(如 **\`star\`**),**不要**写成 **\`on\`/\`off\`**。\`label_data\` 建议 §5.4 Q22 **按分值对象**。
|
|
27
|
+
- **\`question_create\` / \`question_update\`** 对部分题型 50 有误传会做服务端规范化;**\`project_create\` 整卷导入不做同一套纠错**,导入 JSON 需尽量符合指南。
|
|
28
|
+
|
|
29
|
+
## 4. 编辑前拉真结构
|
|
30
|
+
|
|
31
|
+
- 需要 **\`project_id\`**、题目所在 **\`questionpage_id\`**、**\`question_id\`** 时,优先 **\`project_structure\` / \`wjw project structure\`**,再 **\`question_update\`**,避免字段臆造。
|
|
32
|
+
|
|
33
|
+
## 5. 权威文档
|
|
34
|
+
|
|
35
|
+
- 仓库 **\`docs/references/project_json_structure_guide.md\`**:生成 \`project_json\` / \`question_list\` / 单题结构的完整说明。
|
|
36
|
+
|
|
37
|
+
## 6. 如何再次读取本说明
|
|
38
|
+
|
|
39
|
+
- **MCP Resource**:URI **\`resource://wenjuan/cherry-stdio-handbook\`**。
|
|
40
|
+
- **MCP Prompt**:名称 **\`wenjuan.cherry_stdio_handbook\`**。
|
|
41
|
+
- **CLI**:\`wjw resource cherry-handbook\` 或 \`wjw prompt cherry-handbook\`。
|
|
42
|
+
`;
|
|
43
|
+
//# sourceMappingURL=handbook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handbook.js","sourceRoot":"","sources":["../src/handbook.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCpC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WjwConfig } from "../config.js";
|
|
2
|
+
export type HttpResult = {
|
|
3
|
+
status: number;
|
|
4
|
+
body: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function requestText(urlStr: string, init: {
|
|
7
|
+
method: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body?: string | Buffer;
|
|
10
|
+
cfg: WjwConfig;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}): Promise<HttpResult>;
|
|
13
|
+
export declare function joinUrl(base: string, path: string): string;
|
|
14
|
+
export declare function urlEncodeForm(data: Record<string, string>): string;
|
|
15
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/http/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAiB1D,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,GAAG,EAAE,SAAS,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,UAAU,CAAC,CAkCrB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAMlE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import * as https from "node:https";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { URL } from "node:url";
|
|
5
|
+
function buildAgent(cfg) {
|
|
6
|
+
if (cfg.tlsVerify && !cfg.tlsCaBundlePath)
|
|
7
|
+
return undefined;
|
|
8
|
+
const opts = {
|
|
9
|
+
rejectUnauthorized: cfg.tlsVerify,
|
|
10
|
+
};
|
|
11
|
+
if (cfg.tlsCaBundlePath) {
|
|
12
|
+
try {
|
|
13
|
+
opts.ca = readFileSync(cfg.tlsCaBundlePath, "utf8");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// ignore
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return new https.Agent(opts);
|
|
20
|
+
}
|
|
21
|
+
export async function requestText(urlStr, init) {
|
|
22
|
+
const u = new URL(urlStr);
|
|
23
|
+
const isHttps = u.protocol === "https:";
|
|
24
|
+
const agent = isHttps ? buildAgent(init.cfg) : undefined;
|
|
25
|
+
const lib = isHttps ? https : http;
|
|
26
|
+
return await new Promise((resolve, reject) => {
|
|
27
|
+
const req = lib.request({
|
|
28
|
+
protocol: u.protocol,
|
|
29
|
+
hostname: u.hostname,
|
|
30
|
+
port: u.port || (isHttps ? 443 : 80),
|
|
31
|
+
path: `${u.pathname}${u.search}`,
|
|
32
|
+
method: init.method,
|
|
33
|
+
headers: init.headers,
|
|
34
|
+
agent: agent,
|
|
35
|
+
timeout: init.timeoutMs ?? 20000,
|
|
36
|
+
}, (res) => {
|
|
37
|
+
const chunks = [];
|
|
38
|
+
res.on("data", (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
|
|
39
|
+
res.on("end", () => {
|
|
40
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
41
|
+
resolve({ status: res.statusCode ?? 0, body });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
req.on("error", reject);
|
|
45
|
+
req.on("timeout", () => {
|
|
46
|
+
req.destroy(new Error("Request timeout"));
|
|
47
|
+
});
|
|
48
|
+
if (init.body !== undefined)
|
|
49
|
+
req.write(init.body);
|
|
50
|
+
req.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function joinUrl(base, path) {
|
|
54
|
+
const b = base.replace(/\/+$/, "");
|
|
55
|
+
const p = path.replace(/^\/+/, "");
|
|
56
|
+
return `${b}/${p}`;
|
|
57
|
+
}
|
|
58
|
+
export function urlEncodeForm(data) {
|
|
59
|
+
const sp = new URLSearchParams();
|
|
60
|
+
for (const [k, v] of Object.entries(data)) {
|
|
61
|
+
sp.set(k, v);
|
|
62
|
+
}
|
|
63
|
+
return sp.toString();
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/http/request.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 问卷网 CLI 库入口:可编程调用与类型导出。
|
|
3
|
+
*/
|
|
4
|
+
export { VERSION } from "./version.js";
|
|
5
|
+
export { loadConfig, getApiKey, type WjwConfig } from "./config.js";
|
|
6
|
+
export { runMain } from "./commands/main.js";
|
|
7
|
+
export type { ApiContext, JsonObject } from "./api/survey-api.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAkB,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 参数若以 \`@\` 开头则读取该路径文件内容,否则返回原字符串。 */
|
|
2
|
+
export declare function readMaybeFile(raw: string): string;
|
|
3
|
+
export declare function readStdinUtf8(): Promise<string>;
|
|
4
|
+
export declare function parseJson(text: string, label: string): unknown;
|
|
5
|
+
/** 从 \`--flag\` 或 stdin 读取 JSON(stdin 在无 TTY 时使用)。 */
|
|
6
|
+
export declare function readJsonFromFlagOrStdin(flagValue: string | undefined, label: string): Promise<unknown>;
|
|
7
|
+
//# sourceMappingURL=readInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readInput.d.ts","sourceRoot":"","sources":["../../src/io/readInput.ts"],"names":[],"mappings":"AAEA,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAOrD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAM9D;AAED,sDAAsD;AACtD,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM5G"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/** 参数若以 \`@\` 开头则读取该路径文件内容,否则返回原字符串。 */
|
|
3
|
+
export function readMaybeFile(raw) {
|
|
4
|
+
const v = raw.trim();
|
|
5
|
+
if (!v.startsWith("@"))
|
|
6
|
+
return v;
|
|
7
|
+
const path = v.slice(1).trim();
|
|
8
|
+
if (!path)
|
|
9
|
+
throw new Error("空文件路径(@ 后需要路径)");
|
|
10
|
+
return readFileSync(path, "utf8");
|
|
11
|
+
}
|
|
12
|
+
export async function readStdinUtf8() {
|
|
13
|
+
if (process.stdin.isTTY)
|
|
14
|
+
return "";
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for await (const chunk of process.stdin) {
|
|
17
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
18
|
+
}
|
|
19
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
20
|
+
}
|
|
21
|
+
export function parseJson(text, label) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(text);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(`${label}:不是合法 JSON`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** 从 \`--flag\` 或 stdin 读取 JSON(stdin 在无 TTY 时使用)。 */
|
|
30
|
+
export async function readJsonFromFlagOrStdin(flagValue, label) {
|
|
31
|
+
let raw = (flagValue ?? "").trim();
|
|
32
|
+
if (!raw)
|
|
33
|
+
raw = await readStdinUtf8();
|
|
34
|
+
if (!raw)
|
|
35
|
+
throw new Error(`${label}:请使用标志传入(支持 @文件),或在管道中提供 JSON`);
|
|
36
|
+
const text = readMaybeFile(raw);
|
|
37
|
+
return parseJson(text, label);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=readInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readInput.js","sourceRoot":"","sources":["../../src/io/readInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,wCAAwC;AACxC,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC7C,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,SAA6B,EAAE,KAAa;IACxF,IAAI,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,GAAG;QAAE,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+BAA+B,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC"}
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type SignInput = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
+
/**
|
|
3
|
+
* 与 Python `_build_signed_query_params` 行为一致。
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildSignedQueryParams(params: SignInput): Record<string, string>;
|
|
6
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAOrF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgChF"}
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** 与 wenjuan-survey-mcp `api_client.SIGN_CREDENTIALS` 中 ai_mcp 一致 */
|
|
3
|
+
const SIGN_CREDENTIALS = {
|
|
4
|
+
ai_mcp: {
|
|
5
|
+
wj9XvLmR2pTa8sYcHnB4Dz: "xW7fQp3Lk9MvT2aZrY8uHsCjN6eB4gVdP1oR",
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
function strVal(v) {
|
|
9
|
+
if (v === null || v === undefined)
|
|
10
|
+
return "";
|
|
11
|
+
return String(v);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 与 Python `_build_signed_query_params` 行为一致。
|
|
15
|
+
*/
|
|
16
|
+
export function buildSignedQueryParams(params) {
|
|
17
|
+
const webSite = "ai_mcp";
|
|
18
|
+
const appMap = SIGN_CREDENTIALS[webSite];
|
|
19
|
+
if (!appMap)
|
|
20
|
+
throw new Error(`Missing signature credentials for web_site=${webSite}`);
|
|
21
|
+
const appkey = Object.keys(appMap)[0];
|
|
22
|
+
const secret = appMap[appkey];
|
|
23
|
+
if (!appkey || !secret)
|
|
24
|
+
throw new Error("Missing appkey/secret");
|
|
25
|
+
const signParams = { ...params };
|
|
26
|
+
delete signParams.signature;
|
|
27
|
+
const ts = String(Math.floor(Date.now() / 1000));
|
|
28
|
+
signParams.web_site = webSite;
|
|
29
|
+
signParams.appkey = appkey;
|
|
30
|
+
signParams.timestamp = signParams.timestamp != null && String(signParams.timestamp) !== "" ? signParams.timestamp : ts;
|
|
31
|
+
const sortedKeys = Object.keys(signParams).sort();
|
|
32
|
+
const valueConcat = sortedKeys.map((key) => strVal(signParams[key])).join("");
|
|
33
|
+
const signature = createHash("md5").update(valueConcat + secret, "utf8").digest("hex");
|
|
34
|
+
Object.assign(signParams, {
|
|
35
|
+
appkey,
|
|
36
|
+
web_site: webSite,
|
|
37
|
+
timestamp: String(signParams.timestamp),
|
|
38
|
+
signature,
|
|
39
|
+
});
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const k of Object.keys(signParams)) {
|
|
42
|
+
out[k] = strVal(signParams[k]);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=sign.js.map
|
package/dist/sign.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,qEAAqE;AACrE,MAAM,gBAAgB,GAA2C;IAC/D,MAAM,EAAE;QACN,sBAAsB,EAAE,sCAAsC;KAC/D;CACF,CAAC;AAIF,SAAS,MAAM,CAAC,CAAU;IACxB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAC7C,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC;IACzB,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAC;IACtF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAEjE,MAAM,UAAU,GAA4B,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1D,OAAO,UAAU,CAAC,SAAS,CAAC;IAE5B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACjD,UAAU,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC9B,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;IAC3B,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,SAAS,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvH,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEvF,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE;QACxB,MAAM;QACN,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;QACvC,SAAS;KACV,CAAC,CAAC;IAEH,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACxC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WjwConfig } from "./config.js";
|
|
2
|
+
export declare function resolveWenjuanJwtToken(tempToken: string, cfg: WjwConfig): Promise<string>;
|
|
3
|
+
export declare function resolveAuthToken(userTempToken: string, cfg: WjwConfig): Promise<string>;
|
|
4
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAkB7C,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC/F;AAED,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAe7F"}
|
package/dist/token.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { joinUrl, requestText } from "./http/request.js";
|
|
2
|
+
function extractWenjuanJwtToken(payload) {
|
|
3
|
+
const statusCode = payload.status_code;
|
|
4
|
+
if (statusCode !== 1) {
|
|
5
|
+
const errCode = payload.err_code ?? "unknown_err_code";
|
|
6
|
+
const errMsg = payload.err_msg ?? "unknown error";
|
|
7
|
+
throw new Error(`Resolve token business error: ${String(errCode)} ${String(errMsg)}`);
|
|
8
|
+
}
|
|
9
|
+
const data = payload.data;
|
|
10
|
+
if (data && typeof data === "object" && "access_token" in data) {
|
|
11
|
+
const t = data.access_token;
|
|
12
|
+
if (typeof t === "string" && t)
|
|
13
|
+
return t;
|
|
14
|
+
}
|
|
15
|
+
throw new Error("Invalid token response payload: data.access_token not found");
|
|
16
|
+
}
|
|
17
|
+
export async function resolveWenjuanJwtToken(tempToken, cfg) {
|
|
18
|
+
const base = cfg.baseUrl.replace(/\/+$/, "") + "/";
|
|
19
|
+
const path = cfg.getWenjuanJwtApiPath.replace(/^\/+/, "");
|
|
20
|
+
const fullUrl = joinUrl(base, path);
|
|
21
|
+
const res = await requestText(fullUrl, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
Accept: "application/json",
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
Authorization: `Bearer ${tempToken}`,
|
|
27
|
+
},
|
|
28
|
+
cfg,
|
|
29
|
+
timeoutMs: 10000,
|
|
30
|
+
});
|
|
31
|
+
if (res.status === 404) {
|
|
32
|
+
const err = new Error(`Resolve token HTTPError 404: ${res.body}`);
|
|
33
|
+
err.status404 = true;
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
if (res.status < 200 || res.status >= 300) {
|
|
37
|
+
throw new Error(`Resolve token HTTPError ${res.status}: ${res.body}`);
|
|
38
|
+
}
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(res.body);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error("Invalid JSON from token resolver");
|
|
45
|
+
}
|
|
46
|
+
if (!parsed || typeof parsed !== "object")
|
|
47
|
+
throw new Error("Invalid token response");
|
|
48
|
+
return extractWenjuanJwtToken(parsed);
|
|
49
|
+
}
|
|
50
|
+
export async function resolveAuthToken(userTempToken, cfg) {
|
|
51
|
+
if (!userTempToken.trim()) {
|
|
52
|
+
throw new Error("缺少问卷网服务访问令牌。请执行:wjw init --api-key <令牌>\n令牌获取:https://www.wenjuan.com/member/common/mcp/");
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return await resolveWenjuanJwtToken(userTempToken, cfg);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
59
|
+
if (msg.includes("HTTPError 404") || e?.status404) {
|
|
60
|
+
return userTempToken;
|
|
61
|
+
}
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wjw-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "问卷网(wenjuan.com)命令行工具:在终端管理问卷、收集与开放能力(TypeScript)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"wjw": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"references"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"pack": "npm run build && npm pack",
|
|
20
|
+
"publish:npm": "npm publish",
|
|
21
|
+
"wjw": "node dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"问卷网",
|
|
28
|
+
"wjw",
|
|
29
|
+
"wenjuan.com",
|
|
30
|
+
"wenjuanwang",
|
|
31
|
+
"survey",
|
|
32
|
+
"cli",
|
|
33
|
+
"typescript"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.15.3",
|
|
38
|
+
"typescript": "^5.8.3"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# references
|
|
2
|
+
|
|
3
|
+
- **`project_json_structure_guide.md`**:`wjw project create --questions` 用的题目 JSON 说明。仓库内为 **wjw-cli 精简版**:**仅保留**题目通用结构、**各题型**(第四节~第六节)、预设信息题、考试测评题、**题型速查表**、`option_list` 全局规则及线上下落对照;已去掉项目根结构、项目类型表、结束语、完整示例与校验清单等非题型主体(减少噪音)。
|
|
4
|
+
- 若需与 **wenjuan-survey** 上游全文逐字一致,可到该仓库取同路径原文件覆盖;再按需把本仓精简逻辑重做一遍或手工合并。
|