weaver-work-cli 0.1.8 → 0.1.20
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 +11 -11
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +432 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +2 -2
- package/dist/internal/e10/auth/session.js +14 -0
- package/dist/internal/e10/auth/xiaoe.js +116 -51
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +6 -3
- package/dist/shortcuts/workflow/operations/form.js +931 -589
- package/dist/shortcuts/workflow/operations/read.js +128 -205
- package/dist/shortcuts/workflow/operations/shared.js +21 -0
- package/dist/shortcuts/workflow/operations/write.js +750 -52
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +2 -2
- package/docs/hrm.md +19 -9
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +2 -2
- package/scripts/package-skill.mjs +32 -20
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +54 -6
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +7 -4
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +36 -25
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +32 -37
- package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +38 -20
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -6,7 +6,14 @@ import { CliError, ExitCode } from '../../../core/errors.js';
|
|
|
6
6
|
import { buildE10CookieHeader, E10AuthSession, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
|
|
7
7
|
export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
|
|
8
8
|
export const XIAOE_AGENT_TYPE = 'XiaoE';
|
|
9
|
+
// `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
|
|
10
|
+
// 本 CLI 自定义的宿主集成变量一律使用自有前缀 `WEAVER_XIAOE_*`;历史 `XIAOE_*`
|
|
11
|
+
// 名称仅作兼容别名排在后面,读取优先级低于新名称。
|
|
9
12
|
const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
|
|
13
|
+
'WEAVER_XIAOE_AUTH_MAP',
|
|
14
|
+
'WEAVER_XIAOE_E10_AUTH_MAP',
|
|
15
|
+
'WEAVER_XIAOE_DOMAIN_ETEAMSID_MAP',
|
|
16
|
+
'WEAVER_XIAOE_UNIFIED_LOGIN_MAP',
|
|
10
17
|
'XIAOE_E10_AUTH_MAP',
|
|
11
18
|
'XIAOE_E10_DOMAIN_ETEAMSID_MAP',
|
|
12
19
|
'XIAOE_DOMAIN_ETEAMSID_MAP',
|
|
@@ -14,25 +21,30 @@ const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
|
|
|
14
21
|
];
|
|
15
22
|
const XIAOE_SINGLE_DOMAIN_ENV_KEYS = [
|
|
16
23
|
'XIAOE_DOMAIN',
|
|
24
|
+
'WEAVER_XIAOE_E10_DOMAIN',
|
|
25
|
+
'WEAVER_XIAOE_BASE_URL',
|
|
26
|
+
'WEAVER_XIAOE_E10_BASE_URL',
|
|
17
27
|
'XIAOE_E10_DOMAIN',
|
|
18
28
|
'XIAOE_BASE_URL',
|
|
19
29
|
'XIAOE_E10_BASE_URL',
|
|
20
30
|
];
|
|
21
31
|
const XIAOE_DEFAULT_DOMAIN_ENV_KEYS = [
|
|
32
|
+
'WEAVER_XIAOE_DEFAULT_DOMAIN',
|
|
33
|
+
'WEAVER_XIAOE_DEFAULT_BASE_URL',
|
|
34
|
+
'WEAVER_XIAOE_E10_DEFAULT_DOMAIN',
|
|
35
|
+
'WEAVER_XIAOE_E10_DEFAULT_BASE_URL',
|
|
22
36
|
'XIAOE_DEFAULT_DOMAIN',
|
|
23
37
|
'XIAOE_DEFAULT_BASE_URL',
|
|
24
38
|
'XIAOE_E10_DEFAULT_DOMAIN',
|
|
25
39
|
'XIAOE_E10_DEFAULT_BASE_URL',
|
|
26
40
|
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const XIAOE_TOKEN_SCRIPT_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PATH';
|
|
35
|
-
const XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PYTHON';
|
|
41
|
+
// The XiaoE host only injects domain variables (XIAOE_DOMAIN etc.) together
|
|
42
|
+
// with XIAOE_USER_TOKEN; it never injects an ETEAMSID environment variable, so
|
|
43
|
+
// no `XIAOE_*ETEAMSID*` credential channel exists here on purpose.
|
|
44
|
+
const XIAOE_TOKEN_SCRIPT_ENV_KEY = 'WEAVER_XIAOE_TOKEN_SCRIPT_PATH';
|
|
45
|
+
const XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY = 'WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON';
|
|
46
|
+
const XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PATH';
|
|
47
|
+
const XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PYTHON';
|
|
36
48
|
const XIAOE_TOKEN_SCRIPT_TIMEOUT_MS = 15_000;
|
|
37
49
|
const XIAOE_CLIENT_TOKEN_TIMEOUT_MS = 15_000;
|
|
38
50
|
export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
|
|
@@ -50,29 +62,38 @@ function hasEnvPrefix(env, prefix) {
|
|
|
50
62
|
return Object.keys(env).some((key) => key === prefix || key.startsWith(`${prefix}_`));
|
|
51
63
|
}
|
|
52
64
|
export function hasXiaoEEnv(env = process.env) {
|
|
53
|
-
|
|
65
|
+
// `XIAOE_*` are the platform-injected variables; `WEAVER_XIAOE_*` are this
|
|
66
|
+
// CLI's own host-integration variables. Either one marks a XiaoE host.
|
|
67
|
+
return hasEnvPrefix(env, 'XIAOE') || hasEnvPrefix(env, 'WEAVER_XIAOE');
|
|
54
68
|
}
|
|
55
69
|
export function describeXiaoECredentialSource(source) {
|
|
56
|
-
|
|
70
|
+
// Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
|
|
71
|
+
// it is available in every sandbox run and needs no local desktop client.
|
|
72
|
+
if (source === XIAOE_USER_TOKEN_SOURCE) {
|
|
57
73
|
return {
|
|
58
74
|
credentialSourceStep: 1,
|
|
75
|
+
credentialSourceType: 'xiaoe_platform',
|
|
76
|
+
credentialSourceLabel: 'XiaoE platform user token',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (source === 'xiaoeUserData/tokens.py') {
|
|
80
|
+
return {
|
|
81
|
+
credentialSourceStep: 2,
|
|
59
82
|
credentialSourceType: 'token_script',
|
|
60
83
|
credentialSourceLabel: 'xiaoeUserData/tokens.py',
|
|
61
84
|
};
|
|
62
85
|
}
|
|
63
|
-
if (source === XIAOE_CLIENT_SETTINGS_SOURCE
|
|
86
|
+
if (source === XIAOE_CLIENT_SETTINGS_SOURCE) {
|
|
64
87
|
return {
|
|
65
|
-
credentialSourceStep:
|
|
88
|
+
credentialSourceStep: 3,
|
|
66
89
|
credentialSourceType: 'xiaoe_client',
|
|
67
|
-
credentialSourceLabel:
|
|
68
|
-
? 'XiaoE client user token'
|
|
69
|
-
: 'XiaoE client settings',
|
|
90
|
+
credentialSourceLabel: 'XiaoE client settings',
|
|
70
91
|
};
|
|
71
92
|
}
|
|
72
93
|
return {
|
|
73
|
-
credentialSourceStep:
|
|
94
|
+
credentialSourceStep: 4,
|
|
74
95
|
credentialSourceType: 'xiaoe_env',
|
|
75
|
-
credentialSourceLabel: '
|
|
96
|
+
credentialSourceLabel: 'WEAVER_XIAOE_* environment variables',
|
|
76
97
|
};
|
|
77
98
|
}
|
|
78
99
|
function normalizeUrl(value) {
|
|
@@ -223,25 +244,27 @@ function listXiaoEUnifiedLoginCredentials(env = process.env) {
|
|
|
223
244
|
if (raw)
|
|
224
245
|
candidates.push(...parseXiaoEUnifiedLoginMap(raw, key));
|
|
225
246
|
}
|
|
226
|
-
const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
|
|
227
|
-
const singleEteamsId = envString(env, ...XIAOE_SINGLE_ETEAMSID_ENV_KEYS);
|
|
228
|
-
if (singleDomain && singleEteamsId) {
|
|
229
|
-
candidates.push({
|
|
230
|
-
baseUrl: normalizeUrl(singleDomain),
|
|
231
|
-
eteamsId: singleEteamsId,
|
|
232
|
-
source: `${XIAOE_SINGLE_DOMAIN_ENV_KEYS[0]}+${XIAOE_SINGLE_ETEAMSID_ENV_KEYS[0]}`,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
247
|
return candidates;
|
|
236
248
|
}
|
|
237
249
|
export function resolveXiaoEUnifiedLoginCredential(baseUrl, env = process.env) {
|
|
238
250
|
const candidates = listXiaoEUnifiedLoginCredentials(env);
|
|
239
251
|
return candidates.find((candidate) => domainMatches(baseUrl, candidate.baseUrl)) || null;
|
|
240
252
|
}
|
|
253
|
+
function normalizeBaseUrlValue(value) {
|
|
254
|
+
const raw = normalizeUrl(value.trim());
|
|
255
|
+
if (!raw)
|
|
256
|
+
return '';
|
|
257
|
+
return /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
258
|
+
}
|
|
241
259
|
export function resolveXiaoEDefaultBaseUrl(env = process.env) {
|
|
242
260
|
const explicitDefault = envString(env, ...XIAOE_DEFAULT_DOMAIN_ENV_KEYS);
|
|
243
261
|
if (explicitDefault)
|
|
244
|
-
return
|
|
262
|
+
return normalizeBaseUrlValue(explicitDefault);
|
|
263
|
+
// The injected XIAOE_DOMAIN (XIAOE_USER_TOKEN's target domain) is a fact
|
|
264
|
+
// about the current host and must win over the CLI's built-in fallback.
|
|
265
|
+
const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
|
|
266
|
+
if (singleDomain)
|
|
267
|
+
return normalizeBaseUrlValue(singleDomain);
|
|
245
268
|
const firstCredential = listXiaoEUnifiedLoginCredentials(env)[0];
|
|
246
269
|
if (firstCredential?.baseUrl)
|
|
247
270
|
return normalizeUrl(firstCredential.baseUrl);
|
|
@@ -259,7 +282,7 @@ function uniqueStrings(values) {
|
|
|
259
282
|
return result;
|
|
260
283
|
}
|
|
261
284
|
function xiaoETokenScriptCandidatePaths(env = process.env) {
|
|
262
|
-
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
|
|
285
|
+
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
|
|
263
286
|
if (explicit)
|
|
264
287
|
return [explicit];
|
|
265
288
|
if (process.platform === 'win32') {
|
|
@@ -281,14 +304,14 @@ function xiaoETokenScriptPath(env = process.env) {
|
|
|
281
304
|
return xiaoETokenScriptCandidatePaths(env).find((path) => existsSync(path)) || '';
|
|
282
305
|
}
|
|
283
306
|
export function hasXiaoETokenScript(env = process.env) {
|
|
284
|
-
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
|
|
307
|
+
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
|
|
285
308
|
if (explicit)
|
|
286
309
|
return existsSync(explicit);
|
|
287
310
|
const scriptPath = xiaoETokenScriptPath(env);
|
|
288
311
|
return Boolean(scriptPath && existsSync(scriptPath));
|
|
289
312
|
}
|
|
290
313
|
function xiaoETokenScriptPythonCandidates(env = process.env) {
|
|
291
|
-
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY);
|
|
314
|
+
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY, XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY);
|
|
292
315
|
if (explicit)
|
|
293
316
|
return [explicit];
|
|
294
317
|
return uniqueStrings(process.platform === 'win32'
|
|
@@ -418,14 +441,31 @@ export function findXiaoEClientSettingsFile(env = process.env) {
|
|
|
418
441
|
* Mirror of the host `xiaoeUserData/tokens.py` credential chain, implemented in
|
|
419
442
|
* Node so the CLI does not depend on a Python runtime that has `requests`.
|
|
420
443
|
*/
|
|
421
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Platform-injected credential: XIAOE_USER_TOKEN (+ the XiaoE service address).
|
|
446
|
+
* This is the only source that works in a Linux sandbox with no desktop client
|
|
447
|
+
* and no Python runtime, so it is tried first.
|
|
448
|
+
*/
|
|
449
|
+
export function readXiaoEPlatformCredential(env = process.env) {
|
|
422
450
|
const envToken = envString(env, 'XIAOE_USER_TOKEN');
|
|
423
|
-
if (envToken)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
451
|
+
if (!envToken)
|
|
452
|
+
return null;
|
|
453
|
+
const rawErpa = envString(env, 'WEAVER_XIAOE_ERP', 'WEAVER_XIAOE_ERPA', 'WEAVER_XIAOE_ERP_URL', 'XIAOE_ERP', 'XIAOE_ERPA', 'XIAOE_ERP_URL', 'XIAOE_DOMAIN');
|
|
454
|
+
// Hosts may inject the domain without a scheme (e.g. "weapp.example.cn");
|
|
455
|
+
// default to https instead of rejecting the credential outright.
|
|
456
|
+
const erpaUrl = normalizeBaseUrlValue(rawErpa);
|
|
457
|
+
if (!erpaUrl)
|
|
458
|
+
return null;
|
|
459
|
+
return { authToken: envToken, erpaUrl, source: XIAOE_USER_TOKEN_SOURCE };
|
|
460
|
+
}
|
|
461
|
+
export function hasXiaoEPlatformCredential(env = process.env) {
|
|
462
|
+
return Boolean(readXiaoEPlatformCredential(env));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Desktop XiaoE client session: the newest XiaoE/Robots/<id>/user_settings.json.
|
|
466
|
+
* Kept for local desktop hosts only; sandboxes must rely on the platform credential.
|
|
467
|
+
*/
|
|
468
|
+
export function readXiaoEClientCredential(env = process.env) {
|
|
429
469
|
const settingsFile = findXiaoEClientSettingsFile(env);
|
|
430
470
|
if (!settingsFile)
|
|
431
471
|
return null;
|
|
@@ -442,7 +482,7 @@ export function readXiaoEClientCredential(env = process.env) {
|
|
|
442
482
|
}
|
|
443
483
|
}
|
|
444
484
|
export function hasXiaoEClientCredential(env = process.env) {
|
|
445
|
-
return Boolean(
|
|
485
|
+
return Boolean(findXiaoEClientSettingsFile(env));
|
|
446
486
|
}
|
|
447
487
|
async function fetchXiaoEClientEteamId(credential, domain) {
|
|
448
488
|
const controller = new AbortController();
|
|
@@ -468,13 +508,22 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
468
508
|
clearTimeout(timer);
|
|
469
509
|
}
|
|
470
510
|
}
|
|
511
|
+
async function resolveXiaoETokenCredential(credential, baseUrl) {
|
|
512
|
+
const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
|
|
513
|
+
const payload = await fetchXiaoEClientEteamId(credential, targetBaseUrl);
|
|
514
|
+
return readXiaoECredentialRecord(payload, credential.source, targetBaseUrl);
|
|
515
|
+
}
|
|
516
|
+
async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
|
|
517
|
+
const credential = readXiaoEPlatformCredential(env);
|
|
518
|
+
if (!credential)
|
|
519
|
+
return null;
|
|
520
|
+
return resolveXiaoETokenCredential(credential, baseUrl);
|
|
521
|
+
}
|
|
471
522
|
async function resolveXiaoEClientCredential(baseUrl, env = process.env) {
|
|
472
523
|
const clientCredential = readXiaoEClientCredential(env);
|
|
473
524
|
if (!clientCredential)
|
|
474
525
|
return null;
|
|
475
|
-
|
|
476
|
-
const payload = await fetchXiaoEClientEteamId(clientCredential, targetBaseUrl);
|
|
477
|
-
return readXiaoECredentialRecord(payload, clientCredential.source, targetBaseUrl);
|
|
526
|
+
return resolveXiaoETokenCredential(clientCredential, baseUrl);
|
|
478
527
|
}
|
|
479
528
|
export function resolveXiaoEPreferredBaseUrl(options) {
|
|
480
529
|
const env = options.env || process.env;
|
|
@@ -563,37 +612,53 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
563
612
|
if (!baseUrl && !mayUseTokenScript)
|
|
564
613
|
return null;
|
|
565
614
|
let credential = null;
|
|
566
|
-
|
|
615
|
+
// Step 1 — platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN).
|
|
616
|
+
// Works in sandboxes: no desktop client, no Python, no developer-machine paths.
|
|
617
|
+
if (hasXiaoEPlatformCredential(env)) {
|
|
618
|
+
options.logger?.info('Trying XiaoE platform credential from XIAOE_USER_TOKEN...');
|
|
619
|
+
try {
|
|
620
|
+
credential = await resolveXiaoEPlatformCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
624
|
+
options.logger?.warn('XiaoE platform credential failed. ' + message);
|
|
625
|
+
}
|
|
626
|
+
if (!credential) {
|
|
627
|
+
options.logger?.warn('XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// Step 2 — host tokens.py (desktop host only; Linux sandboxes have no such path).
|
|
631
|
+
if (!credential && mayUseTokenScript) {
|
|
567
632
|
const tokenScriptPath = xiaoETokenScriptPath(env);
|
|
568
633
|
if (!tokenScriptPath) {
|
|
569
|
-
options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the
|
|
634
|
+
options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the local XiaoE client session next.');
|
|
570
635
|
}
|
|
571
636
|
else {
|
|
572
637
|
options.logger?.info('Trying XiaoE token script credential...');
|
|
573
638
|
try {
|
|
574
|
-
credential = await resolveXiaoETokenScriptCredential(
|
|
639
|
+
credential = await resolveXiaoETokenScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
575
640
|
}
|
|
576
641
|
catch (error) {
|
|
577
642
|
const message = error instanceof Error ? error.message : String(error);
|
|
578
643
|
options.logger?.warn('XiaoE token script failed. ' + message);
|
|
579
644
|
}
|
|
580
645
|
if (!credential) {
|
|
581
|
-
options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the
|
|
646
|
+
options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the local XiaoE client session next.');
|
|
582
647
|
}
|
|
583
648
|
}
|
|
584
|
-
//
|
|
585
|
-
// session and exchanges it for an eteamId, so a Python runtime
|
|
586
|
-
// `requests` no longer blocks unified login.
|
|
649
|
+
// Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
|
|
650
|
+
// client session and exchanges it for an eteamId, so a Python runtime
|
|
651
|
+
// without `requests` no longer blocks unified login.
|
|
587
652
|
if (!credential && hasXiaoEClientCredential(env)) {
|
|
588
653
|
try {
|
|
589
|
-
credential = await resolveXiaoEClientCredential(
|
|
654
|
+
credential = await resolveXiaoEClientCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
590
655
|
}
|
|
591
656
|
catch (error) {
|
|
592
657
|
const message = error instanceof Error ? error.message : String(error);
|
|
593
658
|
options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
|
|
594
659
|
}
|
|
595
660
|
if (!credential) {
|
|
596
|
-
options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try
|
|
661
|
+
options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try WEAVER_XIAOE_* credentials next.');
|
|
597
662
|
}
|
|
598
663
|
}
|
|
599
664
|
}
|
|
@@ -131,6 +131,11 @@ export class E10RequestRuntime {
|
|
|
131
131
|
composeHeaders(request, trustedHeaders = {}) {
|
|
132
132
|
return {
|
|
133
133
|
Accept: 'application/json, text/plain, */*',
|
|
134
|
+
// E10 各端(含 eBuilder 表单运行态)依赖这两个头;缺失时 EB formdata 接口会直接返回
|
|
135
|
+
// 业务失败(CLI 侧表现为 business_error「泛微接口返回业务失败」,且响应无可读 message,
|
|
136
|
+
// 极易被误判为「EB 未配置/环境不对」)。standalone skill 的脚本一直带这两个头。
|
|
137
|
+
langType: 'zh_CN',
|
|
138
|
+
timeZoneOffset: String(new Date().getTimezoneOffset()),
|
|
134
139
|
...request.headers,
|
|
135
140
|
Cookie: this.session.cookieHeader(),
|
|
136
141
|
eteamsid: this.session.eteamsId,
|
|
@@ -1,13 +1,153 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync,
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { basename, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { basename, posix, relative, resolve, sep, win32 } from 'node:path';
|
|
4
4
|
const SKILL_PREFIX = 'weaver-e10-';
|
|
5
5
|
const LEGACY_SKILL_PREFIX = 'weaver-work-cli-';
|
|
6
6
|
const SHARED_SKILL_NAME = 'weaver-e10-shared';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function envValue(env, name) {
|
|
8
|
+
const direct = env[name];
|
|
9
|
+
if (typeof direct === 'string' && direct.trim())
|
|
10
|
+
return direct.trim();
|
|
11
|
+
const expected = name.toUpperCase();
|
|
12
|
+
for (const [key, value] of Object.entries(env)) {
|
|
13
|
+
if (key.toUpperCase() === expected && typeof value === 'string' && value.trim())
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
function hasEnvFamily(env, prefix, ignoredNames = []) {
|
|
19
|
+
const expected = `${prefix.toUpperCase()}_`;
|
|
20
|
+
const ignored = new Set(ignoredNames.map((name) => name.toUpperCase()));
|
|
21
|
+
return Object.entries(env).some(([key, value]) => (key.toUpperCase().startsWith(expected)
|
|
22
|
+
&& !ignored.has(key.toUpperCase())
|
|
23
|
+
&& typeof value === 'string'
|
|
24
|
+
&& Boolean(value.trim())));
|
|
25
|
+
}
|
|
26
|
+
function normalizedAgentType(value) {
|
|
27
|
+
const normalized = value.trim().toLowerCase();
|
|
28
|
+
if (normalized === 'codex-cli')
|
|
29
|
+
return 'codex';
|
|
30
|
+
if (normalized === 'work-buddy')
|
|
31
|
+
return 'workbuddy';
|
|
32
|
+
return normalized || 'unknown';
|
|
33
|
+
}
|
|
34
|
+
function isKnownSkillsHost(host) {
|
|
35
|
+
return host === 'codex' || host === 'workbuddy';
|
|
36
|
+
}
|
|
37
|
+
export function resolveSkillsTargetPath(value, options = {}) {
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!trimmed)
|
|
40
|
+
throw new Error('skills target path must not be empty');
|
|
41
|
+
const platform = options.platform ?? process.platform;
|
|
42
|
+
const pathApi = platform === 'win32' ? win32 : posix;
|
|
43
|
+
const userHome = options.homeDir ?? homedir();
|
|
44
|
+
if (trimmed === '~')
|
|
45
|
+
return pathApi.resolve(userHome);
|
|
46
|
+
if (/^~[\\/]/u.test(trimmed)) {
|
|
47
|
+
const segments = trimmed.slice(2).split(/[\\/]+/u).filter(Boolean);
|
|
48
|
+
return pathApi.resolve(userHome, ...segments);
|
|
49
|
+
}
|
|
50
|
+
return pathApi.resolve(trimmed);
|
|
51
|
+
}
|
|
52
|
+
function hostSkillsRoot(host, env, options) {
|
|
53
|
+
const platform = options.platform ?? process.platform;
|
|
54
|
+
const pathApi = platform === 'win32' ? win32 : posix;
|
|
55
|
+
const userHome = options.homeDir ?? homedir();
|
|
56
|
+
const envName = host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME';
|
|
57
|
+
const configuredHome = envValue(env, envName);
|
|
58
|
+
const hostHome = configuredHome
|
|
59
|
+
? resolveSkillsTargetPath(configuredHome, options)
|
|
60
|
+
: pathApi.resolve(userHome, host === 'codex' ? '.codex' : '.workbuddy');
|
|
61
|
+
return pathApi.resolve(hostHome, 'skills');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 根据当前进程的环境信号探测宿主。目录存在只代表曾安装过某个宿主,
|
|
65
|
+
* 不能证明当前命令由该宿主调用,因此不在这里参与判定。
|
|
66
|
+
*
|
|
67
|
+
* WEAVER_AGENT_TYPE 是跨宿主的显式信号;没有它时,当前进程的运行时变量优先于
|
|
68
|
+
* 可能被用户长期配置的 *_HOME。两个宿主仍然同时有信号时保持 unknown。
|
|
69
|
+
*/
|
|
70
|
+
export function detectSkillsHost(env = process.env) {
|
|
71
|
+
const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
|
|
72
|
+
if (explicitAgent)
|
|
73
|
+
return normalizedAgentType(explicitAgent);
|
|
74
|
+
const hasCodexRuntime = hasEnvFamily(env, 'CODEX', ['CODEX_HOME']);
|
|
75
|
+
const hasWorkBuddyRuntime = hasEnvFamily(env, 'WORKBUDDY', ['WORKBUDDY_HOME']);
|
|
76
|
+
if (hasCodexRuntime !== hasWorkBuddyRuntime)
|
|
77
|
+
return hasCodexRuntime ? 'codex' : 'workbuddy';
|
|
78
|
+
const hasCodexHome = Boolean(envValue(env, 'CODEX_HOME'));
|
|
79
|
+
const hasWorkBuddyHome = Boolean(envValue(env, 'WORKBUDDY_HOME'));
|
|
80
|
+
if (hasCodexHome !== hasWorkBuddyHome)
|
|
81
|
+
return hasCodexHome ? 'codex' : 'workbuddy';
|
|
82
|
+
return 'unknown';
|
|
83
|
+
}
|
|
84
|
+
export function resolveSkillsRoot(env = process.env, options = {}) {
|
|
85
|
+
const configuredRoot = envValue(env, 'WEAVER_SKILLS_ROOT');
|
|
86
|
+
if (configuredRoot) {
|
|
87
|
+
const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
|
|
88
|
+
const detectedHost = explicitAgent ? normalizedAgentType(explicitAgent) : detectSkillsHost(env);
|
|
89
|
+
return {
|
|
90
|
+
root: resolveSkillsTargetPath(configuredRoot, options),
|
|
91
|
+
host: detectedHost === 'unknown' ? 'explicit' : detectedHost,
|
|
92
|
+
source: 'explicit-env',
|
|
93
|
+
environmentVariable: 'WEAVER_SKILLS_ROOT',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const host = detectSkillsHost(env);
|
|
97
|
+
if (isKnownSkillsHost(host)) {
|
|
98
|
+
const environmentVariable = envValue(env, host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
|
|
99
|
+
? (host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
|
|
100
|
+
: undefined;
|
|
101
|
+
return {
|
|
102
|
+
root: hostSkillsRoot(host, env, options),
|
|
103
|
+
host,
|
|
104
|
+
source: environmentVariable ? 'explicit-env' : 'host-detected',
|
|
105
|
+
...(environmentVariable ? { environmentVariable } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const codexRoot = hostSkillsRoot('codex', env, options);
|
|
109
|
+
const workBuddyRoot = hostSkillsRoot('workbuddy', env, options);
|
|
110
|
+
const pathExists = options.pathExists ?? existsSync;
|
|
111
|
+
if (host === 'unknown') {
|
|
112
|
+
const existing = [
|
|
113
|
+
{ root: codexRoot, host: 'codex' },
|
|
114
|
+
{ root: workBuddyRoot, host: 'workbuddy' },
|
|
115
|
+
].filter((candidate) => pathExists(candidate.root));
|
|
116
|
+
if (existing.length === 1) {
|
|
117
|
+
return { ...existing[0], source: 'filesystem-detected' };
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
root: codexRoot,
|
|
121
|
+
host,
|
|
122
|
+
source: 'fallback',
|
|
123
|
+
requiresExplicitTarget: true,
|
|
124
|
+
reason: existing.length > 1 ? 'ambiguous-host' : 'unknown-host',
|
|
125
|
+
candidates: [codexRoot, workBuddyRoot],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
root: codexRoot,
|
|
130
|
+
host,
|
|
131
|
+
source: 'fallback',
|
|
132
|
+
requiresExplicitTarget: true,
|
|
133
|
+
reason: 'unsupported-host',
|
|
134
|
+
candidates: [codexRoot, workBuddyRoot],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function defaultCodexSkillsRoot(env = process.env, options = {}) {
|
|
138
|
+
return hostSkillsRoot('codex', env, options);
|
|
139
|
+
}
|
|
140
|
+
export function unresolvedSkillsRootMessage(resolution, targetOption = '--target-dir') {
|
|
141
|
+
const reason = resolution.reason === 'ambiguous-host'
|
|
142
|
+
? '检测到多个可能的宿主目录'
|
|
143
|
+
: resolution.reason === 'unsupported-host'
|
|
144
|
+
? `检测到当前智能体 ${resolution.host},但不知道它的 skills 目录`
|
|
145
|
+
: '未检测到当前智能体及其 skills 目录';
|
|
146
|
+
return [
|
|
147
|
+
`${reason},为避免装错位置,本次未执行安装。`,
|
|
148
|
+
`请使用 ${targetOption} <当前智能体的 skills 根目录>,或设置 WEAVER_SKILLS_ROOT 后重试。`,
|
|
149
|
+
...(resolution.candidates?.length ? [`已知候选目录:${resolution.candidates.join(',')}`] : []),
|
|
150
|
+
].join('\n');
|
|
11
151
|
}
|
|
12
152
|
export function normalizePackagedSkillName(value) {
|
|
13
153
|
const trimmed = value.trim();
|
|
@@ -65,7 +205,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
65
205
|
const skillNames = resolvePackagedSkillNames(resolvedSourceRoot, requested);
|
|
66
206
|
const productContent = productMetadataContent(resolvedSourceRoot);
|
|
67
207
|
mkdirSync(resolvedTargetRoot, { recursive: true, mode: 0o700 });
|
|
68
|
-
const installed = skillNames.
|
|
208
|
+
const installed = skillNames.map((name) => {
|
|
69
209
|
const source = resolveInside(resolvedSourceRoot, name);
|
|
70
210
|
const target = resolve(resolvedTargetRoot, basename(name));
|
|
71
211
|
statSync(resolve(source, 'SKILL.md'));
|
|
@@ -81,23 +221,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
81
221
|
target,
|
|
82
222
|
files: countFiles(target),
|
|
83
223
|
};
|
|
84
|
-
|
|
85
|
-
return [current];
|
|
86
|
-
const legacyTarget = resolve(resolvedTargetRoot, LEGACY_SHARED_SKILL_NAME);
|
|
87
|
-
rmSync(legacyTarget, { recursive: true, force: true });
|
|
88
|
-
cpSync(source, legacyTarget, {
|
|
89
|
-
recursive: true,
|
|
90
|
-
force: true,
|
|
91
|
-
filter: (entry) => shouldCopy(entry, source, Boolean(productContent)),
|
|
92
|
-
});
|
|
93
|
-
rewriteSkillName(resolve(legacyTarget, 'SKILL.md'), LEGACY_SHARED_SKILL_NAME);
|
|
94
|
-
writeProductMetadata(legacyTarget, productContent);
|
|
95
|
-
return [current, {
|
|
96
|
-
name: LEGACY_SHARED_SKILL_NAME,
|
|
97
|
-
source,
|
|
98
|
-
target: legacyTarget,
|
|
99
|
-
files: countFiles(legacyTarget),
|
|
100
|
-
}];
|
|
224
|
+
return current;
|
|
101
225
|
});
|
|
102
226
|
return {
|
|
103
227
|
sourceRoot: resolvedSourceRoot,
|
|
@@ -106,10 +230,6 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
106
230
|
installed,
|
|
107
231
|
};
|
|
108
232
|
}
|
|
109
|
-
function rewriteSkillName(skillPath, name) {
|
|
110
|
-
const content = readFileSync(skillPath, 'utf8');
|
|
111
|
-
writeFileSync(skillPath, content.replace(/^name:\s*weaver-e10-shared\s*$/mu, `name: ${name}`), 'utf8');
|
|
112
|
-
}
|
|
113
233
|
function productMetadataContent(sourceRoot) {
|
|
114
234
|
const productPath = productMetadataPath(sourceRoot);
|
|
115
235
|
if (!existsSync(productPath))
|
|
@@ -16,13 +16,14 @@ export class SkillReader {
|
|
|
16
16
|
}
|
|
17
17
|
listPath(target) {
|
|
18
18
|
const { name, subpath } = splitTarget(target);
|
|
19
|
-
const
|
|
19
|
+
const skill = this.resolveSkill(name);
|
|
20
|
+
const skillRoot = skill.root;
|
|
20
21
|
const dir = this.resolveInside(skillRoot, subpath);
|
|
21
22
|
const info = statSync(dir);
|
|
22
23
|
if (!info.isDirectory()) {
|
|
23
24
|
throw new Error(`path is a file, not a directory: ${target}`);
|
|
24
25
|
}
|
|
25
|
-
const listed = cleanOutputPath(name, subpath);
|
|
26
|
+
const listed = cleanOutputPath(skill.name, subpath);
|
|
26
27
|
const entries = readdirSync(dir, { withFileTypes: true })
|
|
27
28
|
.map((entry) => ({
|
|
28
29
|
path: cleanOutputPath(listed, entry.name),
|
|
@@ -33,16 +34,15 @@ export class SkillReader {
|
|
|
33
34
|
}
|
|
34
35
|
read(target, pathArg = '') {
|
|
35
36
|
const split = splitTarget(target);
|
|
36
|
-
const
|
|
37
|
+
const skill = this.resolveSkill(split.name);
|
|
37
38
|
const subpath = pathArg || split.subpath || 'SKILL.md';
|
|
38
|
-
const
|
|
39
|
-
const file = this.resolveInside(skillRoot, subpath);
|
|
39
|
+
const file = this.resolveInside(skill.root, subpath);
|
|
40
40
|
const info = statSync(file);
|
|
41
41
|
if (info.isDirectory()) {
|
|
42
|
-
throw new Error(`path is a directory, not a file: ${cleanOutputPath(name, subpath)}`);
|
|
42
|
+
throw new Error(`path is a directory, not a file: ${cleanOutputPath(skill.name, subpath)}`);
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
45
|
-
skill: name,
|
|
45
|
+
skill: skill.name,
|
|
46
46
|
path: cleanOutputPath(subpath),
|
|
47
47
|
content: readFileSync(file, 'utf-8'),
|
|
48
48
|
};
|
|
@@ -64,6 +64,9 @@ export class SkillReader {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
ensureSkill(name) {
|
|
67
|
+
return this.resolveSkill(name).root;
|
|
68
|
+
}
|
|
69
|
+
resolveSkill(name) {
|
|
67
70
|
if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
|
|
68
71
|
throw new Error(`unknown skill: ${name}`);
|
|
69
72
|
}
|
|
@@ -73,14 +76,14 @@ export class SkillReader {
|
|
|
73
76
|
if (normalized !== name) {
|
|
74
77
|
const normalizedRoot = this.resolveInside(this.root, normalized);
|
|
75
78
|
if (existsDirectoryWithSkill(normalizedRoot))
|
|
76
|
-
return normalizedRoot;
|
|
79
|
+
return { name: normalized, root: normalizedRoot };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
const info = statSync(skillRoot);
|
|
80
83
|
if (!info.isDirectory())
|
|
81
84
|
throw new Error(`unknown skill: ${name}`);
|
|
82
85
|
statSync(resolve(skillRoot, 'SKILL.md'));
|
|
83
|
-
return skillRoot;
|
|
86
|
+
return { name, root: skillRoot };
|
|
84
87
|
}
|
|
85
88
|
resolveInside(base, subpath = '') {
|
|
86
89
|
const cleaned = subpath.replace(/\\/g, '/');
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { blogValidationError } from './errors.js';
|
|
7
|
+
function stateRoot(ctx) {
|
|
8
|
+
const configured = ctx?.config?.path();
|
|
9
|
+
if (configured)
|
|
10
|
+
return resolve(configured, '..');
|
|
11
|
+
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
12
|
+
}
|
|
13
|
+
async function loadKey(ctx) {
|
|
14
|
+
const keysDir = resolve(stateRoot(ctx), 'keys');
|
|
15
|
+
const filename = resolve(keysDir, 'blog-continuation.key');
|
|
16
|
+
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
17
|
+
if (existsSync(filename))
|
|
18
|
+
return readFile(filename);
|
|
19
|
+
const key = randomBytes(32);
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
if (process.platform !== 'win32')
|
|
23
|
+
await chmod(filename, 0o600).catch(() => undefined);
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return readFile(filename);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function encode(value) {
|
|
31
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
32
|
+
}
|
|
33
|
+
function signature(payload, key) {
|
|
34
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* prepare -> apply 确认链的 signed continuation(HMAC-SHA256,默认 10 分钟有效)。
|
|
38
|
+
* 密钥持久化在 `<stateRoot>/keys/blog-continuation.key`,保证多次 CLI 进程调用可校验。
|
|
39
|
+
*/
|
|
40
|
+
export function createBlogContinuationSigner(ctx) {
|
|
41
|
+
return {
|
|
42
|
+
async issue(payload, ttlMs = 10 * 60_000) {
|
|
43
|
+
const issuedAt = Date.now();
|
|
44
|
+
const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
|
|
45
|
+
const sig = signature(body, await loadKey(ctx));
|
|
46
|
+
return `${body}.${sig}`;
|
|
47
|
+
},
|
|
48
|
+
async verify(token, expectedOperation) {
|
|
49
|
+
if (typeof token !== 'string' || !token.includes('.')) {
|
|
50
|
+
throw blogValidationError('continuation_invalid', 'continuation 无效');
|
|
51
|
+
}
|
|
52
|
+
const [body, provided] = token.split('.');
|
|
53
|
+
const expected = signature(body, await loadKey(ctx));
|
|
54
|
+
const valid = provided.length === expected.length
|
|
55
|
+
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
56
|
+
if (!valid)
|
|
57
|
+
throw blogValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
58
|
+
let value;
|
|
59
|
+
try {
|
|
60
|
+
value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw blogValidationError('continuation_invalid', 'continuation 内容无法解析');
|
|
64
|
+
}
|
|
65
|
+
if (value.operation !== expectedOperation) {
|
|
66
|
+
throw blogValidationError('continuation_mismatch', 'continuation 不属于当前操作');
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
|
|
69
|
+
throw blogValidationError('continuation_expired', 'continuation 已过期');
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|