@shulex/cxclawctl 0.0.0-stage → 0.1.2
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 +228 -2
- package/bin/cxclawctl.mjs +11 -0
- package/capabilities.lock.json +898 -0
- package/dist/cli.js +363 -0
- package/dist/cli.js.map +1 -0
- package/package.json +33 -4
- package/requirements-cli.txt +2 -0
- package/skills/__lib__/__init__.py +0 -0
- package/skills/__lib__/skill-runtime/bootstrap.py +134 -0
- package/skills/__lib__/solvea-api/__init__.py +0 -0
- package/skills/__lib__/solvea-api/client.py +1944 -0
- package/skills/__lib__/solvea-api/dashboard.py +100 -0
- package/skills/__lib__/solvea-api/solvea_api.py +1341 -0
- package/skills/agent-analytics/SKILL.md +74 -0
- package/skills/agent-analytics/references/chat-report-fields.md +36 -0
- package/skills/agent-analytics/references/report-fields.md +130 -0
- package/skills/agent-analytics/references/transcript.md +110 -0
- package/skills/agent-builder/SKILL.md +42 -0
- package/skills/agent-builder/interfaces/fallback-chatflow.md +47 -0
- package/skills/agent-builder/references/chatflow/graph-spec.md +259 -0
- package/skills/agent-builder/references/chatflow/node-selection.md +89 -0
- package/skills/agent-builder/references/chatflow/nodes/agent.md +350 -0
- package/skills/agent-builder/references/chatflow/nodes/business-track.md +43 -0
- package/skills/agent-builder/references/chatflow/nodes/code.md +157 -0
- package/skills/agent-builder/references/chatflow/nodes/collect-order.md +47 -0
- package/skills/agent-builder/references/chatflow/nodes/finish.md +34 -0
- package/skills/agent-builder/references/chatflow/nodes/human-review.md +60 -0
- package/skills/agent-builder/references/chatflow/nodes/if-else.md +100 -0
- package/skills/agent-builder/references/chatflow/nodes/intent.md +79 -0
- package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +43 -0
- package/skills/agent-builder/references/chatflow/nodes/llm.md +117 -0
- package/skills/agent-builder/references/chatflow/nodes/note.md +38 -0
- package/skills/agent-builder/references/chatflow/nodes/question.md +97 -0
- package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +68 -0
- package/skills/agent-builder/references/chatflow/nodes/reply.md +97 -0
- package/skills/agent-builder/references/chatflow/nodes/start.md +35 -0
- package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +48 -0
- package/skills/agent-builder/references/chatflow/nodes/tool.md +138 -0
- package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +63 -0
- package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +52 -0
- package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +50 -0
- package/skills/agent-builder/references/chatflow/nodes/variable-process.md +70 -0
- package/skills/agent-builder/references/chatflow/overview.md +53 -0
- package/skills/agent-builder/references/chatflow/visual-translation.md +64 -0
- package/skills/agent-builder/references/chatflow/workflow.md +134 -0
- package/skills/agent-builder/references/multi-agent/create-update.md +185 -0
- package/skills/agent-builder/references/multi-agent/graph-spec.md +246 -0
- package/skills/agent-builder/references/multi-agent/graph-structure.md +12 -0
- package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +13 -0
- package/skills/agent-builder/references/multi-agent/migration/large.md +554 -0
- package/skills/agent-builder/references/multi-agent/migration/principles.md +446 -0
- package/skills/agent-builder/references/multi-agent/migration/small.md +225 -0
- package/skills/agent-builder/references/multi-agent/orchestration.md +11 -0
- package/skills/agent-builder/references/multi-agent/overview.md +48 -0
- package/skills/agent-builder/references/multi-agent/update.md +128 -0
- package/skills/agent-builder/references/multi-agent/workflow.md +87 -0
- package/skills/agent-builder/references/single-agent/overview.md +48 -0
- package/skills/agent-builder/references/single-agent/prompt.md +16 -0
- package/skills/agent-builder/references/single-agent/tools.md +36 -0
- package/skills/agent-builder/references/single-agent/voice.md +46 -0
- package/skills/agent-builder/references/tools/http-tool.md +315 -0
- package/skills/agent-builder/references/tools/mock-tool.md +279 -0
- package/skills/agent-builder/references/tools/overview.md +46 -0
- package/skills/agent-builder/references/tools/tool-config.md +43 -0
- package/skills/agent-builder/references/tools/x-token.md +34 -0
- package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +3458 -0
- package/skills/agent-builder/scripts/chatflow/validate_graph.py +1697 -0
- package/skills/agent-builder/scripts/copy_marketplace_agent.py +61 -0
- package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +5974 -0
- package/skills/agent-builder/scripts/multi-agent/replay_compare.py +679 -0
- package/skills/agent-builder/scripts/patch_agent_tools.py +135 -0
- package/skills/agent-builder/scripts/tools/validate_tool_config.py +384 -0
- package/skills/agent-tester/SKILL.md +35 -0
- package/skills/agent-tester/batch-test/overview.md +86 -0
- package/skills/agent-tester/scripts/batch_test_sheet.py +213 -0
- package/skills/agent-tester/single-test/badcase-retest.md +85 -0
- package/skills/agent-tester/single-test/direct-test.md +24 -0
- package/skills/agent-tester/single-test/overview.md +142 -0
- package/skills/delivery/SKILL.md +112 -0
- package/skills/delivery/interfaces/classify.md +38 -0
- package/skills/delivery/interfaces/create-agents.md +89 -0
- package/skills/delivery/interfaces/delivery-test.md +67 -0
- package/skills/delivery/references/flow.md +40 -0
- package/skills/delivery/references/validation.md +15 -0
- package/skills/delivery/scripts/batch_test.py +704 -0
- package/skills/delivery/scripts/classify.py +22 -0
- package/skills/delivery/scripts/create_agents.py +695 -0
- package/skills/delivery/scripts/gen_delivery_report.py +335 -0
- package/skills/delivery/scripts/gen_excel.py +234 -0
- package/skills/delivery/scripts/gen_report.py +208 -0
- package/skills/delivery/scripts/plan.py +163 -0
- package/skills/delivery/scripts/render.py +713 -0
- package/skills/delivery/scripts/render_summary.py +38 -0
- package/skills/delivery/scripts/sample_history_cases.py +109 -0
- package/skills/kb-builder/SKILL.md +80 -0
- package/skills/kb-builder/interfaces/qa-upload.md +55 -0
- package/skills/kb-builder/interfaces/web-kb.md +84 -0
- package/skills/kb-builder/references/directory.md +373 -0
- package/skills/kb-builder/references/export.md +282 -0
- package/skills/kb-builder/references/isolation-keywords.md +113 -0
- package/skills/kb-builder/references/knowledge.md +153 -0
- package/skills/kb-builder/references/product.md +52 -0
- package/skills/kb-builder/references/qa-from-help-center.md +58 -0
- package/skills/kb-builder/references/qa-from-pdf.md +94 -0
- package/skills/kb-builder/references/qa-from-tickets.md +168 -0
- package/skills/kb-builder/references/qa-from-web.md +98 -0
- package/skills/kb-builder/references/qa-quality.md +73 -0
- package/skills/kb-builder/references/reply-template.md +237 -0
- package/skills/kb-builder/references/tag-isolation-migration.md +61 -0
- package/skills/kb-builder/references/tags.md +45 -0
- package/skills/kb-builder/references/upload.md +130 -0
- package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +333 -0
- package/skills/kb-builder/scripts/build_directory_plan.py +144 -0
- package/skills/kb-builder/scripts/crawl_site.py +433 -0
- package/skills/kb-builder/scripts/create_dirs.py +146 -0
- package/skills/kb-builder/scripts/export_knowledge.py +454 -0
- package/skills/kb-builder/scripts/extract_ticket_qa.py +441 -0
- package/skills/kb-builder/scripts/extract_web_qa.py +118 -0
- package/skills/kb-builder/scripts/import_qa.py +219 -0
- package/skills/kb-builder/scripts/import_source_documents.py +143 -0
- package/skills/kb-builder/scripts/import_templates.py +409 -0
- package/skills/poc/SKILL.md +125 -0
- package/skills/poc/assets/default_manual.css +279 -0
- package/skills/poc/assets/notice_escalate.png +0 -0
- package/skills/poc/assets/notice_escalate2.png +0 -0
- package/skills/poc/assets/notice_new_chat.png +0 -0
- package/skills/poc/assets/poc_test_template.html +645 -0
- package/skills/poc/assets/poc_test_template.md +104 -0
- package/skills/poc/assets/step3_preview.png +0 -0
- package/skills/poc/assets/step4_order_note.png +0 -0
- package/skills/poc/interfaces/classify.md +38 -0
- package/skills/poc/interfaces/create-agents.md +108 -0
- package/skills/poc/interfaces/poc-test.md +94 -0
- package/skills/poc/references/flow.md +47 -0
- package/skills/poc/references/kb-directory.md +22 -0
- package/skills/poc/references/validation.md +17 -0
- package/skills/poc/scripts/classify.py +22 -0
- package/skills/poc/scripts/create_agents.py +818 -0
- package/skills/poc/scripts/gen_excel.py +234 -0
- package/skills/poc/scripts/gen_poc_manual.py +249 -0
- package/skills/poc/scripts/gen_report.py +208 -0
- package/skills/poc/scripts/inspect_chatflow.py +209 -0
- package/skills/poc/scripts/plan.py +178 -0
- package/skills/poc/scripts/poc_test.py +495 -0
- package/skills/poc/scripts/render.py +713 -0
- package/skills/poc/scripts/render_summary.py +41 -0
- package/skills/runtime-inspector/SKILL.md +51 -0
- package/skills/runtime-inspector/references/frontend-rendering.md +126 -0
- package/skills/runtime-inspector/references/model.md +59 -0
- package/skills/runtime-inspector/references/sandbox.md +131 -0
- package/skills/runtime-inspector/scripts/model_inspect.py +68 -0
- package/skills/runtime-inspector/scripts/prepare.py +98 -0
- package/skills/runtime-inspector/scripts/process.py +84 -0
- package/skills/runtime-inspector/scripts/read_state.py +32 -0
- package/skills/runtime-inspector/scripts/sandbox_inspect.py +463 -0
- package/skills/runtime-inspector/scripts/test_claude_proxy.py +49 -0
- package/skills/runtime-inspector/scripts/test_openai_proxy.py +41 -0
- package/skills/runtime-inspector/scripts/verify_persistence.py +101 -0
- package/skills/runtime-inspector/scripts/write_test.py +41 -0
- package/skills/setup/SKILL.md +17 -0
- package/skills/shulex-support/SKILL.md +39 -0
- package/skills/shulex-support/references/badcase-analysis/overview.md +34 -0
- package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +105 -0
- package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +89 -0
- package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +36 -0
- package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +53 -0
- package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +259 -0
- package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +148 -0
- package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +57 -0
- package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +120 -0
- package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +126 -0
- package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +121 -0
- package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +208 -0
- package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +115 -0
- package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +72 -0
- package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +92 -0
- package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +147 -0
- package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +283 -0
- package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +77 -0
- package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +19 -0
- package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +23 -0
- package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +31 -0
- package/skills/shulex-support/references/product-docs.md +70 -0
- package/skills/shulex-support/references/tickets.md +184 -0
- package/skills/shulex-support/scripts/support.py +427 -0
- package/skills/solvea-desk/SKILL.md +39 -0
- package/skills/solvea-desk/references/report/fields.md +82 -0
- package/skills/solvea-desk/references/report/report-design.md +200 -0
- package/skills/solvea-desk/references/report/report-template.html +44 -0
- package/skills/solvea-desk/references/report/report.css +517 -0
- package/skills/solvea-desk/references/report/workflow.md +464 -0
- package/skills/solvea-desk/references/tickets/query.md +103 -0
- package/skills/solvea-desk/references/tickets/tags.md +83 -0
- package/skills/solvea-desk/scripts/report/fetch_sources.py +580 -0
- package/skills/solvea-desk/scripts/report/render_report.py +763 -0
- package/skills/solvea-desk/scripts/tickets/desk_api.py +73 -0
- package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +403 -0
- package/skills/solvea-desk/scripts/tickets/ticket_tags.py +305 -0
- package/skills/solvea-kit/SKILL.md +84 -0
- package/skills/solvea-kit/references/agent.md +145 -0
- package/skills/solvea-kit/references/chat.md +244 -0
- package/skills/solvea-kit/references/intent.md +69 -0
- package/skills/solvea-kit/references/plugin.md +53 -0
- package/skills/solvea-kit/references/quality-inspection.md +75 -0
- package/skills/solvea-kit/references/strategy.md +62 -0
- package/skills/solvea-kit/references/tagging.md +231 -0
- package/skills/solvea-kit/references/variables.md +53 -0
- package/skills/solvea-kit/scripts/generate_label_import_file.py +151 -0
- package/skills/solvea-kit/scripts/intent_classify.py +843 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
|
|
6
|
+
// src/auth.ts
|
|
7
|
+
import open from "open";
|
|
8
|
+
|
|
9
|
+
// package.json
|
|
10
|
+
var package_default = {
|
|
11
|
+
name: "@shulex/cxclawctl",
|
|
12
|
+
version: "0.1.2",
|
|
13
|
+
description: "Solvea CXClaw CLI and Codex plugin runtime",
|
|
14
|
+
type: "module",
|
|
15
|
+
bin: { cxclawctl: "./bin/cxclawctl.mjs" },
|
|
16
|
+
files: ["bin", "dist/cli.js", "dist/cli.js.map", "skills", "capabilities.lock.json", "requirements-cli.txt"],
|
|
17
|
+
scripts: {
|
|
18
|
+
build: "tsup",
|
|
19
|
+
dev: "tsx src/cli.ts",
|
|
20
|
+
typecheck: "tsc --noEmit",
|
|
21
|
+
test: "bun test --path-ignore-patterns _tmp_",
|
|
22
|
+
"check:capabilities": "python3 scripts/check_capabilities.py",
|
|
23
|
+
"check:release": "tsx scripts/check-release.ts",
|
|
24
|
+
"release:prepare": "tsx scripts/prepare-release.ts",
|
|
25
|
+
"package:agent-plugin": "node scripts/package-agent-plugin.mjs",
|
|
26
|
+
"package:codex-plugin": "node scripts/package-codex-plugin.mjs"
|
|
27
|
+
},
|
|
28
|
+
engines: { node: ">=22" },
|
|
29
|
+
dependencies: {
|
|
30
|
+
"@alicloud/log": "^1.2.6",
|
|
31
|
+
"@napi-rs/keyring": "^2.1.2",
|
|
32
|
+
"compare-versions": "^6.1.1",
|
|
33
|
+
open: "^10.2.0"
|
|
34
|
+
},
|
|
35
|
+
devDependencies: {
|
|
36
|
+
"@types/node": "^25.0.0",
|
|
37
|
+
"bun-types": "^1.3.13",
|
|
38
|
+
tsup: "^8.5.0",
|
|
39
|
+
tsx: "^4.21.0",
|
|
40
|
+
typescript: "^5.9.0"
|
|
41
|
+
},
|
|
42
|
+
repository: { type: "git", url: "git+https://github.com/SolveaCX/cx_claw_codex.git" },
|
|
43
|
+
license: "UNLICENSED"
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/version.ts
|
|
47
|
+
var VERSION = package_default.version;
|
|
48
|
+
var TOOL_SCHEMA_VERSION = "1";
|
|
49
|
+
var SERVER_API_VERSION = "1";
|
|
50
|
+
|
|
51
|
+
// src/http.ts
|
|
52
|
+
function unwrap(value) {
|
|
53
|
+
if (value && typeof value === "object" && Object.keys(value).length === 1 && "data" in value) return value.data;
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
async function request(session, path, init = {}, tenant = false) {
|
|
57
|
+
const headers = new Headers(init.headers);
|
|
58
|
+
headers.set("content-type", "application/json");
|
|
59
|
+
headers.set("x-cxclaw-client-version", VERSION);
|
|
60
|
+
if (session.accessToken) headers.set("authorization", `Bearer ${session.accessToken}`);
|
|
61
|
+
if (tenant) {
|
|
62
|
+
if (!session.tenant?.context) throw new Error("no tenant selected; run npx --yes @shulex/cxclawctl@latest tenant use");
|
|
63
|
+
headers.set("x-cxclaw-tenant-context", session.tenant.context);
|
|
64
|
+
}
|
|
65
|
+
let last;
|
|
66
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch(`${session.serverUrl.replace(/\/$/, "")}${path}`, { ...init, headers, signal: AbortSignal.timeout(3e4) });
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
const value = text ? JSON.parse(text) : void 0;
|
|
71
|
+
if (!response.ok) throw new Error(`server returned ${response.status}: ${text}`);
|
|
72
|
+
return unwrap(value);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
last = error;
|
|
75
|
+
if (attempt < 2) await new Promise((r) => setTimeout(r, 200 * 2 ** attempt));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw last;
|
|
79
|
+
}
|
|
80
|
+
var post = (session, path, body, tenant = false) => request(session, path, { method: "POST", body: JSON.stringify(body) }, tenant);
|
|
81
|
+
|
|
82
|
+
// src/store.ts
|
|
83
|
+
import { AsyncEntry } from "@napi-rs/keyring";
|
|
84
|
+
var options = process.platform === "linux" ? { linux: { store: "secret-service" } } : void 0;
|
|
85
|
+
var entry = () => new AsyncEntry("com.solvea.cxclaw", "session", options);
|
|
86
|
+
async function loadSession() {
|
|
87
|
+
const raw = await entry().getPassword();
|
|
88
|
+
if (!raw) throw new Error("\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C npx --yes @shulex/cxclawctl@latest auth login");
|
|
89
|
+
return JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
async function saveSession(session) {
|
|
92
|
+
if (!session.refreshToken) throw new Error("refusing to persist an empty session");
|
|
93
|
+
await entry().setPassword(JSON.stringify(session));
|
|
94
|
+
}
|
|
95
|
+
async function deleteSession() {
|
|
96
|
+
await entry().deleteCredential();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/auth.ts
|
|
100
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
101
|
+
async function login(serverUrl, channel) {
|
|
102
|
+
const session = { serverUrl: serverUrl.replace(/\/$/, ""), channel, accessToken: "", refreshToken: "", accessExpiry: 0, employeeId: "", email: "" };
|
|
103
|
+
const start = await post(session, "/auth/device/start", { clientVersion: VERSION });
|
|
104
|
+
console.error(`\u8BF7\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\u5B8C\u6210\u9489\u9489\u6388\u6743\uFF1A
|
|
105
|
+
${start.verificationUriComplete}
|
|
106
|
+
\u8BBE\u5907\u7801\uFF1A${start.userCode}`);
|
|
107
|
+
await open(start.verificationUriComplete).catch(() => void 0);
|
|
108
|
+
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
109
|
+
while (Date.now() < deadline) {
|
|
110
|
+
await sleep(Math.max(2, start.interval) * 1e3);
|
|
111
|
+
try {
|
|
112
|
+
const token = await post(session, "/auth/device/token", { deviceCode: start.deviceCode, clientVersion: VERSION });
|
|
113
|
+
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(Date.now() / 1e3) + token.expiresIn, employeeId: token.employee.id, email: token.employee.email });
|
|
114
|
+
await saveSession(session);
|
|
115
|
+
return session;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (!String(error).includes("428")) throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw new Error("device authorization expired");
|
|
121
|
+
}
|
|
122
|
+
async function ensureAccess(session) {
|
|
123
|
+
if (session.accessToken && Date.now() / 1e3 + 30 < session.accessExpiry) return session;
|
|
124
|
+
const token = await post(session, "/auth/refresh", { refreshToken: session.refreshToken, clientVersion: VERSION });
|
|
125
|
+
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(Date.now() / 1e3) + token.expiresIn });
|
|
126
|
+
await saveSession(session);
|
|
127
|
+
return session;
|
|
128
|
+
}
|
|
129
|
+
async function authenticated() {
|
|
130
|
+
return ensureAccess(await loadSession());
|
|
131
|
+
}
|
|
132
|
+
async function logout() {
|
|
133
|
+
let session;
|
|
134
|
+
try {
|
|
135
|
+
session = await loadSession();
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
;
|
|
139
|
+
if (session?.refreshToken) await post(session, "/auth/logout", { refreshToken: session.refreshToken }).catch(() => void 0);
|
|
140
|
+
await deleteSession();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/args.ts
|
|
144
|
+
function option(args, name, required = false) {
|
|
145
|
+
const index = args.indexOf(name);
|
|
146
|
+
const value = index >= 0 ? args[index + 1] : void 0;
|
|
147
|
+
if (required && !value) throw new Error(`${name} is required`);
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
function flag(args, name) {
|
|
151
|
+
return args.includes(name);
|
|
152
|
+
}
|
|
153
|
+
function positionals(args) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (let i = 0; i < args.length; i++) {
|
|
156
|
+
if (args[i].startsWith("--")) {
|
|
157
|
+
if (!["--pretty", "--reverse", "--scheduled", "--install-scheduler"].includes(args[i])) i++;
|
|
158
|
+
} else out.push(args[i]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/business.ts
|
|
164
|
+
import { spawn } from "child_process";
|
|
165
|
+
import { access } from "fs/promises";
|
|
166
|
+
import { dirname, join, normalize, relative, resolve } from "path";
|
|
167
|
+
import { fileURLToPath } from "url";
|
|
168
|
+
var packageRoot = () => resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
169
|
+
async function python() {
|
|
170
|
+
for (const name of ["python3.12", "python3"]) {
|
|
171
|
+
const ok = await new Promise((r) => {
|
|
172
|
+
const p = spawn(name, ["-c", "import sys;raise SystemExit(sys.version_info[:2]!=(3,12))"], { stdio: "ignore" });
|
|
173
|
+
p.on("error", () => r(false));
|
|
174
|
+
p.on("exit", (c) => r(c === 0));
|
|
175
|
+
});
|
|
176
|
+
if (ok) return name;
|
|
177
|
+
}
|
|
178
|
+
throw new Error("Python 3.12 is required for CXClaw business scripts");
|
|
179
|
+
}
|
|
180
|
+
async function runPython(script, args, session) {
|
|
181
|
+
const root = packageRoot(), target = resolve(root, normalize(script.replace(/^[/\\]+/, "")));
|
|
182
|
+
if (!relative(join(root, "skills"), target) || relative(join(root, "skills"), target).startsWith("..")) throw new Error("script path must be inside skills/");
|
|
183
|
+
await access(target);
|
|
184
|
+
const command = await python();
|
|
185
|
+
const env = { ...process.env, PYTHONPATH: join(root, "skills", "__lib__") };
|
|
186
|
+
if (session?.tenant) Object.assign(env, { SOLVEA_ENV: session.tenant.environment, SOLVEA_BASE_URL: session.tenant.businessUrl, SOLVEA_PROD_URL: session.tenant.businessUrl, SOLVEA_TOKEN: session.tenant.businessToken, SOLVEA_PROD_TOKEN: session.tenant.businessToken });
|
|
187
|
+
return await new Promise((done, reject) => {
|
|
188
|
+
const child = spawn(command, [target, ...args], { stdio: "inherit", env });
|
|
189
|
+
child.on("error", reject);
|
|
190
|
+
child.on("exit", (code) => done(code ?? 1));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function runApi(session, command, args) {
|
|
194
|
+
const code = await runPython("skills/__lib__/solvea-api/solvea_api.py", [command, ...args], session);
|
|
195
|
+
if (code) throw new Error(`business command ${command} failed with exit code ${code}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/sls.ts
|
|
199
|
+
import LogClient from "@alicloud/log";
|
|
200
|
+
async function query(session, options2) {
|
|
201
|
+
const temporary = await post(session, "/sls/session", {}, true);
|
|
202
|
+
if (options2.from <= 0 || options2.to < options2.from || options2.to - options2.from > temporary.limits.maxTimeRangeSeconds) throw new Error("SLS time range exceeds the server limit");
|
|
203
|
+
const client = new LogClient({ endpoint: temporary.endpoint, use_https: true, accessKeyId: temporary.credentials.accessKeyId, accessKeySecret: temporary.credentials.accessKeySecret, securityToken: temporary.credentials.securityToken });
|
|
204
|
+
const response = await client.getLogs(temporary.project, temporary.logstore, new Date(options2.from * 1e3), new Date(options2.to * 1e3), { query: options2.query, line: Math.min(options2.line || 50, temporary.limits.maxResults), offset: options2.offset || 0, reverse: options2.reverse });
|
|
205
|
+
const logs = (response.body ?? response).map?.((row) => Object.fromEntries(Object.entries(row).map(([k, v]) => [k, String(v).slice(0, temporary.limits.maxMessageChars || void 0)]))) ?? response.body ?? response;
|
|
206
|
+
return { logs };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/tenant.ts
|
|
210
|
+
function environment(value) {
|
|
211
|
+
const v = value.toLowerCase();
|
|
212
|
+
if (v === "test" || v === "staging") return "staging";
|
|
213
|
+
if (v === "prod" || v === "production") return "prod";
|
|
214
|
+
throw new Error(`unsupported environment ${value}; use test/staging or prod`);
|
|
215
|
+
}
|
|
216
|
+
async function search(session, query2, env) {
|
|
217
|
+
return (await post(session, "/tenants/search", { query: query2, environment: environment(env) })).items;
|
|
218
|
+
}
|
|
219
|
+
async function useTenant(session, accountId, env, purpose) {
|
|
220
|
+
if (!purpose) throw new Error("purpose is required");
|
|
221
|
+
const response = await post(session, "/tenant-contexts", { accountId, environment: environment(env), purpose });
|
|
222
|
+
if (!response.businessToken || !response.services?.businessUrl) throw new Error("authorization server did not return business credentials");
|
|
223
|
+
session.tenant = { ...response.tenant, environment: response.environment, purpose, context: response.tenantContext, businessToken: response.businessToken, businessUrl: response.services.businessUrl.replace(/\/$/, ""), slsUrl: response.services.slsUrl?.replace(/\/$/, ""), expiresAt: response.businessTokenExpiresAt || Math.floor(Date.now() / 1e3) + response.expiresIn };
|
|
224
|
+
await saveSession(session);
|
|
225
|
+
return publicTenant(session.tenant);
|
|
226
|
+
}
|
|
227
|
+
function publicTenant(t) {
|
|
228
|
+
return t ? { accountId: t.accountId, accountCode: t.accountCode, accountName: t.accountName, environment: t.environment, purpose: t.purpose, expiresAt: t.expiresAt, configured: Boolean(t.businessToken && t.businessUrl) } : { configured: false };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/update.ts
|
|
232
|
+
import { spawn as spawn2 } from "child_process";
|
|
233
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
234
|
+
import { homedir, platform } from "os";
|
|
235
|
+
import { join as join2 } from "path";
|
|
236
|
+
import { compareVersions } from "compare-versions";
|
|
237
|
+
var exec = (command, args) => new Promise((done, reject) => {
|
|
238
|
+
const p = spawn2(command, args, { stdio: "inherit" });
|
|
239
|
+
p.on("error", reject);
|
|
240
|
+
p.on("exit", (c) => c === 0 ? done() : reject(new Error(`${command} exited with ${c}`)));
|
|
241
|
+
});
|
|
242
|
+
async function update() {
|
|
243
|
+
const info = await fetch("https://registry.npmjs.org/@shulex%2Fcxclawctl/latest", { signal: AbortSignal.timeout(3e4) }).then((r) => {
|
|
244
|
+
if (!r.ok) throw new Error(`npm registry returned ${r.status}`);
|
|
245
|
+
return r.json();
|
|
246
|
+
});
|
|
247
|
+
if (compareVersions(info.version, VERSION) > 0) console.log(`\u53D1\u73B0\u65B0\u7248\u672C ${info.version}\uFF1Bnpx \u4E0B\u6B21\u8FD0\u884C\u4F1A\u81EA\u52A8\u4F7F\u7528\u6700\u65B0\u7248\u3002`);
|
|
248
|
+
await exec("codex", ["plugin", "marketplace", "upgrade", "cxclaw"]);
|
|
249
|
+
return info.version;
|
|
250
|
+
}
|
|
251
|
+
async function installScheduler() {
|
|
252
|
+
const command = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
253
|
+
const args = ["--yes", "@shulex/cxclawctl@latest", "update", "--scheduled"];
|
|
254
|
+
if (platform() === "darwin") {
|
|
255
|
+
const dir = join2(homedir(), "Library/LaunchAgents");
|
|
256
|
+
await mkdir(dir, { recursive: true });
|
|
257
|
+
await writeFile(join2(dir, "com.solvea.cxclaw.update.plist"), `<?xml version="1.0"?><plist version="1.0"><dict><key>Label</key><string>com.solvea.cxclaw.update</string><key>ProgramArguments</key><array><string>${command}</string>${args.map((x) => `<string>${x}</string>`).join("")}</array><key>StartInterval</key><integer>21600</integer><key>RunAtLoad</key><true/></dict></plist>`);
|
|
258
|
+
await exec("launchctl", ["load", join2(dir, "com.solvea.cxclaw.update.plist")]);
|
|
259
|
+
} else if (platform() === "linux") {
|
|
260
|
+
const dir = join2(homedir(), ".config/systemd/user");
|
|
261
|
+
await mkdir(dir, { recursive: true });
|
|
262
|
+
await writeFile(join2(dir, "cxclaw-update.service"), `[Service]
|
|
263
|
+
Type=oneshot
|
|
264
|
+
ExecStart=${command} ${args.join(" ")}
|
|
265
|
+
`);
|
|
266
|
+
await writeFile(join2(dir, "cxclaw-update.timer"), "[Timer]\nOnBootSec=5m\nOnUnitActiveSec=6h\nRandomizedDelaySec=30m\nPersistent=true\n[Install]\nWantedBy=timers.target\n");
|
|
267
|
+
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
268
|
+
await exec("systemctl", ["--user", "enable", "--now", "cxclaw-update.timer"]);
|
|
269
|
+
} else await exec("schtasks", ["/Create", "/F", "/SC", "HOURLY", "/MO", "6", "/TN", "Solvea CXClaw Update", "/TR", `${command} ${args.join(" ")}`]);
|
|
270
|
+
}
|
|
271
|
+
async function installMarketplace(channel) {
|
|
272
|
+
if (!["stable", "canary", "dev"].includes(channel)) throw new Error("invalid channel");
|
|
273
|
+
await exec("codex", ["plugin", "marketplace", "add", "SolveaCX/cx_claw_codex", "--ref", `marketplace/${channel}`]).catch(() => exec("codex", ["plugin", "marketplace", "upgrade", "cxclaw"]));
|
|
274
|
+
await exec("codex", ["plugin", "add", "cxclaw@cxclaw"]).catch(() => void 0);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/cli.ts
|
|
278
|
+
var print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
279
|
+
async function main(args = process.argv.slice(2)) {
|
|
280
|
+
const [command, sub, ...rest] = args;
|
|
281
|
+
if (!command) {
|
|
282
|
+
console.log("cxclawctl auth|tenant|api|sls|script|doctor|update|setup|version");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (command === "version") return print({ pluginVersion: VERSION, cliVersion: VERSION, toolSchemaVersion: TOOL_SCHEMA_VERSION, serverApiVersion: SERVER_API_VERSION });
|
|
286
|
+
if (command === "setup") {
|
|
287
|
+
const server = option(args, "--server", true);
|
|
288
|
+
const channel = option(args, "--channel") ?? "stable";
|
|
289
|
+
await installMarketplace(channel);
|
|
290
|
+
await login(server, channel);
|
|
291
|
+
await installScheduler();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (command === "auth" || command === "login" || command === "logout") {
|
|
295
|
+
const action = command === "auth" ? sub : command;
|
|
296
|
+
if (action === "login") {
|
|
297
|
+
const channel = option(rest, "--channel") ?? "stable";
|
|
298
|
+
return print(await login(option(rest, "--server", true), channel));
|
|
299
|
+
}
|
|
300
|
+
if (action === "logout") {
|
|
301
|
+
await logout();
|
|
302
|
+
return print({ ok: true, loggedOut: true });
|
|
303
|
+
}
|
|
304
|
+
if (action === "status") {
|
|
305
|
+
try {
|
|
306
|
+
const s = await loadSession();
|
|
307
|
+
return print({ ok: true, loggedIn: true, email: s.email, channel: s.channel, accessExpiresAt: s.accessExpiry });
|
|
308
|
+
} catch {
|
|
309
|
+
return print({ ok: true, loggedIn: false });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (action === "refresh") {
|
|
313
|
+
const s = await authenticated();
|
|
314
|
+
return print({ ok: true, accessExpiresAt: s.accessExpiry });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (command === "tenant" || command === "context") {
|
|
318
|
+
const session = await authenticated();
|
|
319
|
+
if (sub === "current") return print(publicTenant(session.tenant));
|
|
320
|
+
if (sub === "clear") {
|
|
321
|
+
delete session.tenant;
|
|
322
|
+
await saveSession(session);
|
|
323
|
+
return print({ ok: true, cleared: true });
|
|
324
|
+
}
|
|
325
|
+
if (sub === "search") return print(await search(session, positionals(rest)[0] ?? "", option(rest, "--env", true)));
|
|
326
|
+
if (sub === "use") return print(await useTenant(session, Number(positionals(rest)[0]), option(rest, "--env", true), option(rest, "--purpose", true)));
|
|
327
|
+
}
|
|
328
|
+
if (command === "api") {
|
|
329
|
+
const session = await authenticated();
|
|
330
|
+
if (!sub) throw new Error("api command is required");
|
|
331
|
+
await runApi(session, sub, rest);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (command === "script") {
|
|
335
|
+
if (!sub) throw new Error("script path is required");
|
|
336
|
+
process.exitCode = await runPython(sub, rest);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (command === "sls" && sub === "query") {
|
|
340
|
+
const session = await authenticated();
|
|
341
|
+
return print(await query(session, { from: Number(option(rest, "--from", true)), to: Number(option(rest, "--to", true)), query: option(rest, "--query", true), line: Number(option(rest, "--line") ?? 50), offset: Number(option(rest, "--offset") ?? 0), reverse: flag(rest, "--reverse") }));
|
|
342
|
+
}
|
|
343
|
+
if (command === "doctor") {
|
|
344
|
+
const manifest = JSON.parse(await readFile(new URL("../plugin.json", import.meta.url), "utf8"));
|
|
345
|
+
if (manifest.version !== VERSION) throw new Error(`plugin version ${manifest.version} does not match CLI ${VERSION}`);
|
|
346
|
+
return print({ ok: true, node: process.versions.node, cliVersion: VERSION, root: packageRoot() });
|
|
347
|
+
}
|
|
348
|
+
if (command === "update") {
|
|
349
|
+
if (flag(args, "--install-scheduler")) {
|
|
350
|
+
await installScheduler();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (flag(args, "--scheduled")) await new Promise((r) => setTimeout(r, Math.random() * 18e5));
|
|
354
|
+
return print({ version: await update() });
|
|
355
|
+
}
|
|
356
|
+
if (command === "commands" || command === "capabilities") return console.log(await readFile(new URL("../capabilities.lock.json", import.meta.url), "utf8"));
|
|
357
|
+
throw new Error(`unknown command: ${command}`);
|
|
358
|
+
}
|
|
359
|
+
main().catch((error) => {
|
|
360
|
+
console.error(`cxclawctl: ${error instanceof Error ? error.message : String(error)}`);
|
|
361
|
+
process.exitCode = 1;
|
|
362
|
+
});
|
|
363
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/auth.ts","../package.json","../src/version.ts","../src/http.ts","../src/store.ts","../src/args.ts","../src/business.ts","../src/sls.ts","../src/tenant.ts","../src/update.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { authenticated, login, logout } from \"./auth.js\";\nimport { flag, option, positionals } from \"./args.js\";\nimport { packageRoot, runApi, runPython } from \"./business.js\";\nimport { loadSession, saveSession } from \"./store.js\";\nimport { query as slsQuery } from \"./sls.js\";\nimport { publicTenant, search, useTenant } from \"./tenant.js\";\nimport { installMarketplace, installScheduler, update } from \"./update.js\";\nimport { SERVER_API_VERSION, TOOL_SCHEMA_VERSION, VERSION } from \"./version.js\";\n\nconst print=(value:unknown)=>console.log(JSON.stringify(value,null,2));\nasync function main(args=process.argv.slice(2)){\n const [command,sub,...rest]=args;if(!command){console.log(\"cxclawctl auth|tenant|api|sls|script|doctor|update|setup|version\");return;}\n if(command===\"version\")return print({pluginVersion:VERSION,cliVersion:VERSION,toolSchemaVersion:TOOL_SCHEMA_VERSION,serverApiVersion:SERVER_API_VERSION});\n if(command===\"setup\"){const server=option(args,\"--server\",true)!;const channel=(option(args,\"--channel\")??\"stable\") as any;await installMarketplace(channel);await login(server,channel);await installScheduler();return;}\n if(command===\"auth\"||command===\"login\"||command===\"logout\"){\n const action=command===\"auth\"?sub:command;if(action===\"login\"){const channel=(option(rest,\"--channel\")??\"stable\") as any;return print(await login(option(rest,\"--server\",true)!,channel));}if(action===\"logout\"){await logout();return print({ok:true,loggedOut:true});}if(action===\"status\"){try{const s=await loadSession();return print({ok:true,loggedIn:true,email:s.email,channel:s.channel,accessExpiresAt:s.accessExpiry});}catch{return print({ok:true,loggedIn:false});}}if(action===\"refresh\"){const s=await authenticated();return print({ok:true,accessExpiresAt:s.accessExpiry});}}\n if(command===\"tenant\"||command===\"context\"){const session=await authenticated();if(sub===\"current\")return print(publicTenant(session.tenant));if(sub===\"clear\"){delete session.tenant;await saveSession(session);return print({ok:true,cleared:true});}if(sub===\"search\")return print(await search(session,positionals(rest)[0]??\"\",option(rest,\"--env\",true)!));if(sub===\"use\")return print(await useTenant(session,Number(positionals(rest)[0]),option(rest,\"--env\",true)!,option(rest,\"--purpose\",true)!));}\n if(command===\"api\"){const session=await authenticated();if(!sub)throw new Error(\"api command is required\");await runApi(session,sub,rest);return;}\n if(command===\"script\"){if(!sub)throw new Error(\"script path is required\");process.exitCode=await runPython(sub,rest);return;}\n if(command===\"sls\"&&sub===\"query\"){const session=await authenticated();return print(await slsQuery(session,{from:Number(option(rest,\"--from\",true)),to:Number(option(rest,\"--to\",true)),query:option(rest,\"--query\",true)!,line:Number(option(rest,\"--line\")??50),offset:Number(option(rest,\"--offset\")??0),reverse:flag(rest,\"--reverse\")}));}\n if(command===\"doctor\"){const manifest=JSON.parse(await readFile(new URL(\"../plugin.json\",import.meta.url),\"utf8\"));if(manifest.version!==VERSION)throw new Error(`plugin version ${manifest.version} does not match CLI ${VERSION}`);return print({ok:true,node:process.versions.node,cliVersion:VERSION,root:packageRoot()});}\n if(command===\"update\"){if(flag(args,\"--install-scheduler\")){await installScheduler();return;}if(flag(args,\"--scheduled\"))await new Promise(r=>setTimeout(r,Math.random()*1_800_000));return print({version:await update()});}\n if(command===\"commands\"||command===\"capabilities\")return console.log(await readFile(new URL(\"../capabilities.lock.json\",import.meta.url),\"utf8\"));\n throw new Error(`unknown command: ${command}`);\n}\nmain().catch(error=>{console.error(`cxclawctl: ${error instanceof Error?error.message:String(error)}`);process.exitCode=1});\n","import open from \"open\";\nimport { post } from \"./http.js\";\nimport { deleteSession, loadSession, saveSession } from \"./store.js\";\nimport type { DeviceStart, Session, TokenResponse } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nconst sleep=(ms:number)=>new Promise(r=>setTimeout(r,ms));\nexport async function login(serverUrl:string, channel:Session[\"channel\"]):Promise<Session>{\n const session:Session={serverUrl:serverUrl.replace(/\\/$/,\"\"),channel,accessToken:\"\",refreshToken:\"\",accessExpiry:0,employeeId:\"\",email:\"\"};\n const start=await post<DeviceStart>(session,\"/auth/device/start\",{clientVersion:VERSION});\n console.error(`请在浏览器打开以下地址完成钉钉授权:\\n${start.verificationUriComplete}\\n设备码:${start.userCode}`);\n await open(start.verificationUriComplete).catch(()=>undefined);\n const deadline=Date.now()+start.expiresIn*1000;\n while(Date.now()<deadline){ await sleep(Math.max(2,start.interval)*1000); try {\n const token=await post<TokenResponse>(session,\"/auth/device/token\",{deviceCode:start.deviceCode,clientVersion:VERSION});\n Object.assign(session,{accessToken:token.accessToken,refreshToken:token.refreshToken,accessExpiry:Math.floor(Date.now()/1000)+token.expiresIn,employeeId:token.employee.id,email:token.employee.email}); await saveSession(session); return session;\n } catch(error){ if(!String(error).includes(\"428\")) throw error; } }\n throw new Error(\"device authorization expired\");\n}\nexport async function ensureAccess(session:Session):Promise<Session>{\n if(session.accessToken && Date.now()/1000+30<session.accessExpiry)return session;\n const token=await post<TokenResponse>(session,\"/auth/refresh\",{refreshToken:session.refreshToken,clientVersion:VERSION});\n Object.assign(session,{accessToken:token.accessToken,refreshToken:token.refreshToken,accessExpiry:Math.floor(Date.now()/1000)+token.expiresIn}); await saveSession(session); return session;\n}\nexport async function authenticated(){return ensureAccess(await loadSession())}\nexport async function logout(){let session:Session|undefined;try{session=await loadSession()}catch{};if(session?.refreshToken)await post(session,\"/auth/logout\",{refreshToken:session.refreshToken}).catch(()=>undefined);await deleteSession()}\n","{\n \"name\": \"@shulex/cxclawctl\",\n \"version\": \"0.1.2\",\n \"description\": \"Solvea CXClaw CLI and Codex plugin runtime\",\n \"type\": \"module\",\n \"bin\": { \"cxclawctl\": \"./bin/cxclawctl.mjs\" },\n \"files\": [\"bin\", \"dist/cli.js\", \"dist/cli.js.map\", \"skills\", \"capabilities.lock.json\", \"requirements-cli.txt\"],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/cli.ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"bun test --path-ignore-patterns _tmp_\",\n \"check:capabilities\": \"python3 scripts/check_capabilities.py\",\n \"check:release\": \"tsx scripts/check-release.ts\",\n \"release:prepare\": \"tsx scripts/prepare-release.ts\",\n \"package:agent-plugin\": \"node scripts/package-agent-plugin.mjs\",\n \"package:codex-plugin\": \"node scripts/package-codex-plugin.mjs\"\n },\n \"engines\": { \"node\": \">=22\" },\n \"dependencies\": {\n \"@alicloud/log\": \"^1.2.6\",\n \"@napi-rs/keyring\": \"^2.1.2\",\n \"compare-versions\": \"^6.1.1\",\n \"open\": \"^10.2.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.0.0\",\n \"bun-types\": \"^1.3.13\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.0\"\n },\n \"repository\": { \"type\": \"git\", \"url\": \"git+https://github.com/SolveaCX/cx_claw_codex.git\" },\n \"license\": \"UNLICENSED\"\n}\n","import packageJson from \"../package.json\" with { type: \"json\" };\nexport const VERSION = packageJson.version;\nexport const TOOL_SCHEMA_VERSION = \"1\";\nexport const SERVER_API_VERSION = \"1\";\n","import type { Session } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nexport function unwrap<T>(value: unknown): T {\n if (value && typeof value === \"object\" && Object.keys(value).length === 1 && \"data\" in value) return (value as {data:T}).data;\n return value as T;\n}\nexport async function request<T>(session: Session, path: string, init: RequestInit = {}, tenant = false): Promise<T> {\n const headers = new Headers(init.headers); headers.set(\"content-type\", \"application/json\"); headers.set(\"x-cxclaw-client-version\", VERSION);\n if (session.accessToken) headers.set(\"authorization\", `Bearer ${session.accessToken}`);\n if (tenant) { if (!session.tenant?.context) throw new Error(\"no tenant selected; run npx --yes @shulex/cxclawctl@latest tenant use\"); headers.set(\"x-cxclaw-tenant-context\", session.tenant.context); }\n let last: unknown;\n for (let attempt=0; attempt<3; attempt++) {\n try {\n const response = await fetch(`${session.serverUrl.replace(/\\/$/,\"\")}${path}`, {...init, headers, signal: AbortSignal.timeout(30_000)});\n const text = await response.text(); const value = text ? JSON.parse(text) : undefined;\n if (!response.ok) throw new Error(`server returned ${response.status}: ${text}`);\n return unwrap<T>(value);\n } catch (error) { last=error; if (attempt<2) await new Promise(r=>setTimeout(r,200*2**attempt)); }\n }\n throw last;\n}\nexport const post = <T>(session:Session,path:string,body:unknown,tenant=false) => request<T>(session,path,{method:\"POST\",body:JSON.stringify(body)},tenant);\n","import { AsyncEntry } from \"@napi-rs/keyring\";\nimport type { Session } from \"./types.js\";\n\nconst options = process.platform === \"linux\" ? { linux: { store: \"secret-service\" as const } } : undefined;\nconst entry = () => new AsyncEntry(\"com.solvea.cxclaw\", \"session\", options);\n\nexport async function loadSession(): Promise<Session> {\n const raw = await entry().getPassword();\n if (!raw) throw new Error(\"未登录,请先运行 npx --yes @shulex/cxclawctl@latest auth login\");\n return JSON.parse(raw) as Session;\n}\nexport async function saveSession(session: Session): Promise<void> {\n if (!session.refreshToken) throw new Error(\"refusing to persist an empty session\");\n await entry().setPassword(JSON.stringify(session));\n}\nexport async function deleteSession(): Promise<void> { await entry().deleteCredential(); }\n","export function option(args:string[],name:string,required=false){const index=args.indexOf(name);const value=index>=0?args[index+1]:undefined;if(required&&!value)throw new Error(`${name} is required`);return value}\nexport function flag(args:string[],name:string){return args.includes(name)}\nexport function positionals(args:string[]){const out:string[]=[];for(let i=0;i<args.length;i++){if(args[i].startsWith(\"--\")){if(![\"--pretty\",\"--reverse\",\"--scheduled\",\"--install-scheduler\"].includes(args[i]))i++;}else out.push(args[i]);}return out}\n","import { spawn } from \"node:child_process\";\nimport { access } from \"node:fs/promises\";\nimport { dirname, join, normalize, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Session } from \"./types.js\";\n\nexport const packageRoot=()=>resolve(dirname(fileURLToPath(import.meta.url)),\"..\");\nasync function python(){for(const name of [\"python3.12\",\"python3\"]){const ok=await new Promise<boolean>(r=>{const p=spawn(name,[\"-c\",\"import sys;raise SystemExit(sys.version_info[:2]!=(3,12))\"],{stdio:\"ignore\"});p.on(\"error\",()=>r(false));p.on(\"exit\",c=>r(c===0));});if(ok)return name;}throw new Error(\"Python 3.12 is required for CXClaw business scripts\");}\nexport async function runPython(script:string,args:string[],session?:Session):Promise<number>{\n const root=packageRoot(), target=resolve(root,normalize(script.replace(/^[/\\\\]+/,\"\")));if(!relative(join(root,\"skills\"),target)||relative(join(root,\"skills\"),target).startsWith(\"..\"))throw new Error(\"script path must be inside skills/\");await access(target);\n const command=await python();const env={...process.env,PYTHONPATH:join(root,\"skills\",\"__lib__\")};if(session?.tenant)Object.assign(env,{SOLVEA_ENV:session.tenant.environment,SOLVEA_BASE_URL:session.tenant.businessUrl,SOLVEA_PROD_URL:session.tenant.businessUrl,SOLVEA_TOKEN:session.tenant.businessToken,SOLVEA_PROD_TOKEN:session.tenant.businessToken});\n return await new Promise((done,reject)=>{const child=spawn(command,[target,...args],{stdio:\"inherit\",env});child.on(\"error\",reject);child.on(\"exit\",code=>done(code??1));});\n}\nexport async function runApi(session:Session,command:string,args:string[]){const code=await runPython(\"skills/__lib__/solvea-api/solvea_api.py\",[command,...args],session);if(code)throw new Error(`business command ${command} failed with exit code ${code}`)}\n","import LogClient from \"@alicloud/log\";\nimport { post } from \"./http.js\";\nimport type { Session } from \"./types.js\";\n\ninterface SlsSession{endpoint:string;project:string;logstore:string;credentials:{accessKeyId:string;accessKeySecret:string;securityToken:string};limits:{maxTimeRangeSeconds:number;maxResults:number;maxConcurrency:number;maxMessageChars:number}}\nexport async function query(session:Session,options:{from:number;to:number;query:string;line:number;offset:number;reverse:boolean}){\n const temporary=await post<SlsSession>(session,\"/sls/session\",{},true);if(options.from<=0||options.to<options.from||options.to-options.from>temporary.limits.maxTimeRangeSeconds)throw new Error(\"SLS time range exceeds the server limit\");\n const client=new (LogClient as any)({endpoint:temporary.endpoint,use_https:true,accessKeyId:temporary.credentials.accessKeyId,accessKeySecret:temporary.credentials.accessKeySecret,securityToken:temporary.credentials.securityToken});\n const response=await client.getLogs(temporary.project,temporary.logstore,new Date(options.from*1000),new Date(options.to*1000),{query:options.query,line:Math.min(options.line||50,temporary.limits.maxResults),offset:options.offset||0,reverse:options.reverse});\n const logs=(response.body??response).map?.((row:Record<string,unknown>)=>Object.fromEntries(Object.entries(row).map(([k,v])=>[k,String(v).slice(0,temporary.limits.maxMessageChars||undefined)])))??response.body??response;\n return {logs};\n}\n","import { post } from \"./http.js\";\nimport { saveSession } from \"./store.js\";\nimport type { Session, Tenant } from \"./types.js\";\n\nexport function environment(value:string){const v=value.toLowerCase();if(v===\"test\"||v===\"staging\")return \"staging\";if(v===\"prod\"||v===\"production\")return \"prod\";throw new Error(`unsupported environment ${value}; use test/staging or prod`)}\nexport async function search(session:Session,query:string,env:string){return (await post<{items:unknown[]}>(session,\"/tenants/search\",{query,environment:environment(env)})).items}\nexport async function useTenant(session:Session,accountId:number,env:string,purpose:string){\n if(!purpose)throw new Error(\"purpose is required\");\n const response=await post<any>(session,\"/tenant-contexts\",{accountId,environment:environment(env),purpose});\n if(!response.businessToken||!response.services?.businessUrl)throw new Error(\"authorization server did not return business credentials\");\n session.tenant={...response.tenant,environment:response.environment,purpose,context:response.tenantContext,businessToken:response.businessToken,businessUrl:response.services.businessUrl.replace(/\\/$/,\"\"),slsUrl:response.services.slsUrl?.replace(/\\/$/,\"\"),expiresAt:response.businessTokenExpiresAt||Math.floor(Date.now()/1000)+response.expiresIn} as Tenant;\n await saveSession(session);return publicTenant(session.tenant);\n}\nexport function publicTenant(t?:Tenant){return t?{accountId:t.accountId,accountCode:t.accountCode,accountName:t.accountName,environment:t.environment,purpose:t.purpose,expiresAt:t.expiresAt,configured:Boolean(t.businessToken&&t.businessUrl)}:{configured:false}}\nexport async function ensureTenant(session:Session){if(!session.tenant?.accountId)throw new Error(\"no tenant selected; run npx --yes @shulex/cxclawctl@latest tenant use\");if(Date.now()/1000+30>=session.tenant.expiresAt)await useTenant(session,session.tenant.accountId,session.tenant.environment,session.tenant.purpose);return session.tenant}\n","import { spawn } from \"node:child_process\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { compareVersions } from \"compare-versions\";\nimport { VERSION } from \"./version.js\";\n\nconst exec=(command:string,args:string[])=>new Promise<void>((done,reject)=>{const p=spawn(command,args,{stdio:\"inherit\"});p.on(\"error\",reject);p.on(\"exit\",c=>c===0?done():reject(new Error(`${command} exited with ${c}`)));});\nexport async function update(){const info=await fetch(\"https://registry.npmjs.org/@shulex%2Fcxclawctl/latest\",{signal:AbortSignal.timeout(30_000)}).then(r=>{if(!r.ok)throw new Error(`npm registry returned ${r.status}`);return r.json() as Promise<{version:string}>});if(compareVersions(info.version,VERSION)>0)console.log(`发现新版本 ${info.version};npx 下次运行会自动使用最新版。`);await exec(\"codex\",[\"plugin\",\"marketplace\",\"upgrade\",\"cxclaw\"]);return info.version;}\nexport async function installScheduler(){const command=process.platform===\"win32\"?\"npx.cmd\":\"npx\";const args=[\"--yes\",\"@shulex/cxclawctl@latest\",\"update\",\"--scheduled\"];\n if(platform()===\"darwin\"){const dir=join(homedir(),\"Library/LaunchAgents\");await mkdir(dir,{recursive:true});await writeFile(join(dir,\"com.solvea.cxclaw.update.plist\"),`<?xml version=\"1.0\"?><plist version=\"1.0\"><dict><key>Label</key><string>com.solvea.cxclaw.update</string><key>ProgramArguments</key><array><string>${command}</string>${args.map(x=>`<string>${x}</string>`).join(\"\")}</array><key>StartInterval</key><integer>21600</integer><key>RunAtLoad</key><true/></dict></plist>`);await exec(\"launchctl\",[\"load\",join(dir,\"com.solvea.cxclaw.update.plist\")]);}\n else if(platform()===\"linux\"){const dir=join(homedir(),\".config/systemd/user\");await mkdir(dir,{recursive:true});await writeFile(join(dir,\"cxclaw-update.service\"),`[Service]\\nType=oneshot\\nExecStart=${command} ${args.join(\" \")}\\n`);await writeFile(join(dir,\"cxclaw-update.timer\"),\"[Timer]\\nOnBootSec=5m\\nOnUnitActiveSec=6h\\nRandomizedDelaySec=30m\\nPersistent=true\\n[Install]\\nWantedBy=timers.target\\n\");await exec(\"systemctl\",[\"--user\",\"daemon-reload\"]);await exec(\"systemctl\",[\"--user\",\"enable\",\"--now\",\"cxclaw-update.timer\"]);}\n else await exec(\"schtasks\",[\"/Create\",\"/F\",\"/SC\",\"HOURLY\",\"/MO\",\"6\",\"/TN\",\"Solvea CXClaw Update\",\"/TR\",`${command} ${args.join(\" \")}`]);}\nexport async function installMarketplace(channel:string){if(![\"stable\",\"canary\",\"dev\"].includes(channel))throw new Error(\"invalid channel\");await exec(\"codex\",[\"plugin\",\"marketplace\",\"add\",\"SolveaCX/cx_claw_codex\",\"--ref\",`marketplace/${channel}`]).catch(()=>exec(\"codex\",[\"plugin\",\"marketplace\",\"upgrade\",\"cxclaw\"]));await exec(\"codex\",[\"plugin\",\"add\",\"cxclaw@cxclaw\"]).catch(()=>undefined)}\n"],"mappings":";;;AAAA,SAAS,gBAAgB;;;ACAzB,OAAO,UAAU;;;ACAjB;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,MAAQ;AAAA,EACR,KAAO,EAAE,WAAa,sBAAsB;AAAA,EAC5C,OAAS,CAAC,OAAO,eAAe,mBAAmB,UAAU,0BAA0B,sBAAsB;AAAA,EAC7G,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,sBAAsB;AAAA,IACtB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,wBAAwB;AAAA,IACxB,wBAAwB;AAAA,EAC1B;AAAA,EACA,SAAW,EAAE,MAAQ,OAAO;AAAA,EAC5B,cAAgB;AAAA,IACd,iBAAiB;AAAA,IACjB,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,IACpB,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,EAChB;AAAA,EACA,YAAc,EAAE,MAAQ,OAAO,KAAO,oDAAoD;AAAA,EAC1F,SAAW;AACb;;;ACjCO,IAAM,UAAU,gBAAY;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB;;;ACA3B,SAAS,OAAU,OAAmB;AAC3C,MAAI,SAAS,OAAO,UAAU,YAAY,OAAO,KAAK,KAAK,EAAE,WAAW,KAAK,UAAU,MAAO,QAAQ,MAAmB;AACzH,SAAO;AACT;AACA,eAAsB,QAAW,SAAkB,MAAc,OAAoB,CAAC,GAAG,SAAS,OAAmB;AACnH,QAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AAAG,UAAQ,IAAI,gBAAgB,kBAAkB;AAAG,UAAQ,IAAI,2BAA2B,OAAO;AAC1I,MAAI,QAAQ,YAAa,SAAQ,IAAI,iBAAiB,UAAU,QAAQ,WAAW,EAAE;AACrF,MAAI,QAAQ;AAAE,QAAI,CAAC,QAAQ,QAAQ,QAAS,OAAM,IAAI,MAAM,uEAAuE;AAAG,YAAQ,IAAI,2BAA2B,QAAQ,OAAO,OAAO;AAAA,EAAG;AACtM,MAAI;AACJ,WAAS,UAAQ,GAAG,UAAQ,GAAG,WAAW;AACxC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,UAAU,QAAQ,OAAM,EAAE,CAAC,GAAG,IAAI,IAAI,EAAC,GAAG,MAAM,SAAS,QAAQ,YAAY,QAAQ,GAAM,EAAC,CAAC;AACrI,YAAM,OAAO,MAAM,SAAS,KAAK;AAAG,YAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,IAAI;AAC5E,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,mBAAmB,SAAS,MAAM,KAAK,IAAI,EAAE;AAC/E,aAAO,OAAU,KAAK;AAAA,IACxB,SAAS,OAAO;AAAE,aAAK;AAAO,UAAI,UAAQ,EAAG,OAAM,IAAI,QAAQ,OAAG,WAAW,GAAE,MAAI,KAAG,OAAO,CAAC;AAAA,IAAG;AAAA,EACnG;AACA,QAAM;AACR;AACO,IAAM,OAAO,CAAI,SAAgB,MAAY,MAAa,SAAO,UAAU,QAAW,SAAQ,MAAK,EAAC,QAAO,QAAO,MAAK,KAAK,UAAU,IAAI,EAAC,GAAE,MAAM;;;ACtB1J,SAAS,kBAAkB;AAG3B,IAAM,UAAU,QAAQ,aAAa,UAAU,EAAE,OAAO,EAAE,OAAO,iBAA0B,EAAE,IAAI;AACjG,IAAM,QAAQ,MAAM,IAAI,WAAW,qBAAqB,WAAW,OAAO;AAE1E,eAAsB,cAAgC;AACpD,QAAM,MAAM,MAAM,MAAM,EAAE,YAAY;AACtC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,gGAAwD;AAClF,SAAO,KAAK,MAAM,GAAG;AACvB;AACA,eAAsB,YAAY,SAAiC;AACjE,MAAI,CAAC,QAAQ,aAAc,OAAM,IAAI,MAAM,sCAAsC;AACjF,QAAM,MAAM,EAAE,YAAY,KAAK,UAAU,OAAO,CAAC;AACnD;AACA,eAAsB,gBAA+B;AAAE,QAAM,MAAM,EAAE,iBAAiB;AAAG;;;AJTzF,IAAM,QAAM,CAAC,OAAY,IAAI,QAAQ,OAAG,WAAW,GAAE,EAAE,CAAC;AACxD,eAAsB,MAAM,WAAkB,SAA4C;AACxF,QAAM,UAAgB,EAAC,WAAU,UAAU,QAAQ,OAAM,EAAE,GAAE,SAAQ,aAAY,IAAG,cAAa,IAAG,cAAa,GAAE,YAAW,IAAG,OAAM,GAAE;AACzI,QAAM,QAAM,MAAM,KAAkB,SAAQ,sBAAqB,EAAC,eAAc,QAAO,CAAC;AACxF,UAAQ,MAAM;AAAA,EAAuB,MAAM,uBAAuB;AAAA,0BAAS,MAAM,QAAQ,EAAE;AAC3F,QAAM,KAAK,MAAM,uBAAuB,EAAE,MAAM,MAAI,MAAS;AAC7D,QAAM,WAAS,KAAK,IAAI,IAAE,MAAM,YAAU;AAC1C,SAAM,KAAK,IAAI,IAAE,UAAS;AAAE,UAAM,MAAM,KAAK,IAAI,GAAE,MAAM,QAAQ,IAAE,GAAI;AAAG,QAAI;AAC5E,YAAM,QAAM,MAAM,KAAoB,SAAQ,sBAAqB,EAAC,YAAW,MAAM,YAAW,eAAc,QAAO,CAAC;AACtH,aAAO,OAAO,SAAQ,EAAC,aAAY,MAAM,aAAY,cAAa,MAAM,cAAa,cAAa,KAAK,MAAM,KAAK,IAAI,IAAE,GAAI,IAAE,MAAM,WAAU,YAAW,MAAM,SAAS,IAAG,OAAM,MAAM,SAAS,MAAK,CAAC;AAAG,YAAM,YAAY,OAAO;AAAG,aAAO;AAAA,IAC9O,SAAQ,OAAM;AAAE,UAAG,CAAC,OAAO,KAAK,EAAE,SAAS,KAAK,EAAG,OAAM;AAAA,IAAO;AAAA,EAAE;AAClE,QAAM,IAAI,MAAM,8BAA8B;AAChD;AACA,eAAsB,aAAa,SAAiC;AAClE,MAAG,QAAQ,eAAe,KAAK,IAAI,IAAE,MAAK,KAAG,QAAQ,aAAa,QAAO;AACzE,QAAM,QAAM,MAAM,KAAoB,SAAQ,iBAAgB,EAAC,cAAa,QAAQ,cAAa,eAAc,QAAO,CAAC;AACvH,SAAO,OAAO,SAAQ,EAAC,aAAY,MAAM,aAAY,cAAa,MAAM,cAAa,cAAa,KAAK,MAAM,KAAK,IAAI,IAAE,GAAI,IAAE,MAAM,UAAS,CAAC;AAAG,QAAM,YAAY,OAAO;AAAG,SAAO;AACtL;AACA,eAAsB,gBAAe;AAAC,SAAO,aAAa,MAAM,YAAY,CAAC;AAAC;AAC9E,eAAsB,SAAQ;AAAC,MAAI;AAA0B,MAAG;AAAC,cAAQ,MAAM,YAAY;AAAA,EAAC,QAAM;AAAA,EAAC;AAAC;AAAC,MAAG,SAAS,aAAa,OAAM,KAAK,SAAQ,gBAAe,EAAC,cAAa,QAAQ,aAAY,CAAC,EAAE,MAAM,MAAI,MAAS;AAAE,QAAM,cAAc;AAAC;;;AKzBxO,SAAS,OAAO,MAAc,MAAY,WAAS,OAAM;AAAC,QAAM,QAAM,KAAK,QAAQ,IAAI;AAAE,QAAM,QAAM,SAAO,IAAE,KAAK,QAAM,CAAC,IAAE;AAAU,MAAG,YAAU,CAAC,MAAM,OAAM,IAAI,MAAM,GAAG,IAAI,cAAc;AAAE,SAAO;AAAK;AAC7M,SAAS,KAAK,MAAc,MAAY;AAAC,SAAO,KAAK,SAAS,IAAI;AAAC;AACnE,SAAS,YAAY,MAAc;AAAC,QAAM,MAAa,CAAC;AAAE,WAAQ,IAAE,GAAE,IAAE,KAAK,QAAO,KAAI;AAAC,QAAG,KAAK,CAAC,EAAE,WAAW,IAAI,GAAE;AAAC,UAAG,CAAC,CAAC,YAAW,aAAY,eAAc,qBAAqB,EAAE,SAAS,KAAK,CAAC,CAAC,EAAE;AAAA,IAAI,MAAM,KAAI,KAAK,KAAK,CAAC,CAAC;AAAA,EAAE;AAAC,SAAO;AAAG;;;ACFvP,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,SAAS,MAAM,WAAW,UAAU,eAAe;AAC5D,SAAS,qBAAqB;AAGvB,IAAM,cAAY,MAAI,QAAQ,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAE,IAAI;AACjF,eAAe,SAAQ;AAAC,aAAU,QAAQ,CAAC,cAAa,SAAS,GAAE;AAAC,UAAM,KAAG,MAAM,IAAI,QAAiB,OAAG;AAAC,YAAM,IAAE,MAAM,MAAK,CAAC,MAAK,2DAA2D,GAAE,EAAC,OAAM,SAAQ,CAAC;AAAE,QAAE,GAAG,SAAQ,MAAI,EAAE,KAAK,CAAC;AAAE,QAAE,GAAG,QAAO,OAAG,EAAE,MAAI,CAAC,CAAC;AAAA,IAAE,CAAC;AAAE,QAAG,GAAG,QAAO;AAAA,EAAK;AAAC,QAAM,IAAI,MAAM,qDAAqD;AAAE;AACrW,eAAsB,UAAU,QAAc,MAAc,SAAiC;AAC3F,QAAM,OAAK,YAAY,GAAG,SAAO,QAAQ,MAAK,UAAU,OAAO,QAAQ,WAAU,EAAE,CAAC,CAAC;AAAE,MAAG,CAAC,SAAS,KAAK,MAAK,QAAQ,GAAE,MAAM,KAAG,SAAS,KAAK,MAAK,QAAQ,GAAE,MAAM,EAAE,WAAW,IAAI,EAAE,OAAM,IAAI,MAAM,oCAAoC;AAAE,QAAM,OAAO,MAAM;AAChQ,QAAM,UAAQ,MAAM,OAAO;AAAE,QAAM,MAAI,EAAC,GAAG,QAAQ,KAAI,YAAW,KAAK,MAAK,UAAS,SAAS,EAAC;AAAE,MAAG,SAAS,OAAO,QAAO,OAAO,KAAI,EAAC,YAAW,QAAQ,OAAO,aAAY,iBAAgB,QAAQ,OAAO,aAAY,iBAAgB,QAAQ,OAAO,aAAY,cAAa,QAAQ,OAAO,eAAc,mBAAkB,QAAQ,OAAO,cAAa,CAAC;AAC5V,SAAO,MAAM,IAAI,QAAQ,CAAC,MAAK,WAAS;AAAC,UAAM,QAAM,MAAM,SAAQ,CAAC,QAAO,GAAG,IAAI,GAAE,EAAC,OAAM,WAAU,IAAG,CAAC;AAAE,UAAM,GAAG,SAAQ,MAAM;AAAE,UAAM,GAAG,QAAO,UAAM,KAAK,QAAM,CAAC,CAAC;AAAA,EAAE,CAAC;AAC5K;AACA,eAAsB,OAAO,SAAgB,SAAe,MAAc;AAAC,QAAM,OAAK,MAAM,UAAU,2CAA0C,CAAC,SAAQ,GAAG,IAAI,GAAE,OAAO;AAAE,MAAG,KAAK,OAAM,IAAI,MAAM,oBAAoB,OAAO,0BAA0B,IAAI,EAAE;AAAC;;;ACb/P,OAAO,eAAe;AAKtB,eAAsB,MAAM,SAAgBA,UAAuF;AACjI,QAAM,YAAU,MAAM,KAAiB,SAAQ,gBAAe,CAAC,GAAE,IAAI;AAAE,MAAGA,SAAQ,QAAM,KAAGA,SAAQ,KAAGA,SAAQ,QAAMA,SAAQ,KAAGA,SAAQ,OAAK,UAAU,OAAO,oBAAoB,OAAM,IAAI,MAAM,yCAAyC;AAC1O,QAAM,SAAO,IAAK,UAAkB,EAAC,UAAS,UAAU,UAAS,WAAU,MAAK,aAAY,UAAU,YAAY,aAAY,iBAAgB,UAAU,YAAY,iBAAgB,eAAc,UAAU,YAAY,cAAa,CAAC;AACtO,QAAM,WAAS,MAAM,OAAO,QAAQ,UAAU,SAAQ,UAAU,UAAS,IAAI,KAAKA,SAAQ,OAAK,GAAI,GAAE,IAAI,KAAKA,SAAQ,KAAG,GAAI,GAAE,EAAC,OAAMA,SAAQ,OAAM,MAAK,KAAK,IAAIA,SAAQ,QAAM,IAAG,UAAU,OAAO,UAAU,GAAE,QAAOA,SAAQ,UAAQ,GAAE,SAAQA,SAAQ,QAAO,CAAC;AACjQ,QAAM,QAAM,SAAS,QAAM,UAAU,MAAM,CAAC,QAA6B,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,IAAI,CAAC,CAAC,GAAE,CAAC,MAAI,CAAC,GAAE,OAAO,CAAC,EAAE,MAAM,GAAE,UAAU,OAAO,mBAAiB,MAAS,CAAC,CAAC,CAAC,CAAC,KAAG,SAAS,QAAM;AACnN,SAAO,EAAC,KAAI;AACd;;;ACPO,SAAS,YAAY,OAAa;AAAC,QAAM,IAAE,MAAM,YAAY;AAAE,MAAG,MAAI,UAAQ,MAAI,UAAU,QAAO;AAAU,MAAG,MAAI,UAAQ,MAAI,aAAa,QAAO;AAAO,QAAM,IAAI,MAAM,2BAA2B,KAAK,4BAA4B;AAAC;AAC/O,eAAsB,OAAO,SAAgBC,QAAa,KAAW;AAAC,UAAQ,MAAM,KAAwB,SAAQ,mBAAkB,EAAC,OAAAA,QAAM,aAAY,YAAY,GAAG,EAAC,CAAC,GAAG;AAAK;AAClL,eAAsB,UAAU,SAAgB,WAAiB,KAAW,SAAe;AACzF,MAAG,CAAC,QAAQ,OAAM,IAAI,MAAM,qBAAqB;AACjD,QAAM,WAAS,MAAM,KAAU,SAAQ,oBAAmB,EAAC,WAAU,aAAY,YAAY,GAAG,GAAE,QAAO,CAAC;AAC1G,MAAG,CAAC,SAAS,iBAAe,CAAC,SAAS,UAAU,YAAY,OAAM,IAAI,MAAM,0DAA0D;AACtI,UAAQ,SAAO,EAAC,GAAG,SAAS,QAAO,aAAY,SAAS,aAAY,SAAQ,SAAQ,SAAS,eAAc,eAAc,SAAS,eAAc,aAAY,SAAS,SAAS,YAAY,QAAQ,OAAM,EAAE,GAAE,QAAO,SAAS,SAAS,QAAQ,QAAQ,OAAM,EAAE,GAAE,WAAU,SAAS,0BAAwB,KAAK,MAAM,KAAK,IAAI,IAAE,GAAI,IAAE,SAAS,UAAS;AACxV,QAAM,YAAY,OAAO;AAAE,SAAO,aAAa,QAAQ,MAAM;AAC/D;AACO,SAAS,aAAa,GAAU;AAAC,SAAO,IAAE,EAAC,WAAU,EAAE,WAAU,aAAY,EAAE,aAAY,aAAY,EAAE,aAAY,aAAY,EAAE,aAAY,SAAQ,EAAE,SAAQ,WAAU,EAAE,WAAU,YAAW,QAAQ,EAAE,iBAAe,EAAE,WAAW,EAAC,IAAE,EAAC,YAAW,MAAK;AAAC;;;ACbpQ,SAAS,SAAAC,cAAa;AACtB,SAAS,OAAO,iBAAiB;AACjC,SAAS,SAAS,gBAAgB;AAClC,SAAS,QAAAC,aAAY;AACrB,SAAS,uBAAuB;AAGhC,IAAM,OAAK,CAAC,SAAe,SAAgB,IAAI,QAAc,CAAC,MAAK,WAAS;AAAC,QAAM,IAAEC,OAAM,SAAQ,MAAK,EAAC,OAAM,UAAS,CAAC;AAAE,IAAE,GAAG,SAAQ,MAAM;AAAE,IAAE,GAAG,QAAO,OAAG,MAAI,IAAE,KAAK,IAAE,OAAO,IAAI,MAAM,GAAG,OAAO,gBAAgB,CAAC,EAAE,CAAC,CAAC;AAAE,CAAC;AAC/N,eAAsB,SAAQ;AAAC,QAAM,OAAK,MAAM,MAAM,yDAAwD,EAAC,QAAO,YAAY,QAAQ,GAAM,EAAC,CAAC,EAAE,KAAK,OAAG;AAAC,QAAG,CAAC,EAAE,GAAG,OAAM,IAAI,MAAM,yBAAyB,EAAE,MAAM,EAAE;AAAE,WAAO,EAAE,KAAK;AAAA,EAA8B,CAAC;AAAE,MAAG,gBAAgB,KAAK,SAAQ,OAAO,IAAE,EAAE,SAAQ,IAAI,kCAAS,KAAK,OAAO,0FAAoB;AAAE,QAAM,KAAK,SAAQ,CAAC,UAAS,eAAc,WAAU,QAAQ,CAAC;AAAE,SAAO,KAAK;AAAQ;AAChc,eAAsB,mBAAkB;AAAC,QAAM,UAAQ,QAAQ,aAAW,UAAQ,YAAU;AAAM,QAAM,OAAK,CAAC,SAAQ,4BAA2B,UAAS,aAAa;AACrK,MAAG,SAAS,MAAI,UAAS;AAAC,UAAM,MAAIC,MAAK,QAAQ,GAAE,sBAAsB;AAAE,UAAM,MAAM,KAAI,EAAC,WAAU,KAAI,CAAC;AAAE,UAAM,UAAUA,MAAK,KAAI,gCAAgC,GAAE,sJAAsJ,OAAO,YAAY,KAAK,IAAI,OAAG,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,oGAAoG;AAAE,UAAM,KAAK,aAAY,CAAC,QAAOA,MAAK,KAAI,gCAAgC,CAAC,CAAC;AAAA,EAAE,WACxiB,SAAS,MAAI,SAAQ;AAAC,UAAM,MAAIA,MAAK,QAAQ,GAAE,sBAAsB;AAAE,UAAM,MAAM,KAAI,EAAC,WAAU,KAAI,CAAC;AAAE,UAAM,UAAUA,MAAK,KAAI,uBAAuB,GAAE;AAAA;AAAA,YAAsC,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC;AAAA,CAAI;AAAE,UAAM,UAAUA,MAAK,KAAI,qBAAqB,GAAE,yHAAyH;AAAE,UAAM,KAAK,aAAY,CAAC,UAAS,eAAe,CAAC;AAAE,UAAM,KAAK,aAAY,CAAC,UAAS,UAAS,SAAQ,qBAAqB,CAAC;AAAA,EAAE,MAC3gB,OAAM,KAAK,YAAW,CAAC,WAAU,MAAK,OAAM,UAAS,OAAM,KAAI,OAAM,wBAAuB,OAAM,GAAG,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;AAAE;AAC1I,eAAsB,mBAAmB,SAAe;AAAC,MAAG,CAAC,CAAC,UAAS,UAAS,KAAK,EAAE,SAAS,OAAO,EAAE,OAAM,IAAI,MAAM,iBAAiB;AAAE,QAAM,KAAK,SAAQ,CAAC,UAAS,eAAc,OAAM,0BAAyB,SAAQ,eAAe,OAAO,EAAE,CAAC,EAAE,MAAM,MAAI,KAAK,SAAQ,CAAC,UAAS,eAAc,WAAU,QAAQ,CAAC,CAAC;AAAE,QAAM,KAAK,SAAQ,CAAC,UAAS,OAAM,eAAe,CAAC,EAAE,MAAM,MAAI,MAAS;AAAC;;;AVHvY,IAAM,QAAM,CAAC,UAAgB,QAAQ,IAAI,KAAK,UAAU,OAAM,MAAK,CAAC,CAAC;AACrE,eAAe,KAAK,OAAK,QAAQ,KAAK,MAAM,CAAC,GAAE;AAC7C,QAAM,CAAC,SAAQ,KAAI,GAAG,IAAI,IAAE;AAAK,MAAG,CAAC,SAAQ;AAAC,YAAQ,IAAI,kEAAkE;AAAE;AAAA,EAAO;AACrI,MAAG,YAAU,UAAU,QAAO,MAAM,EAAC,eAAc,SAAQ,YAAW,SAAQ,mBAAkB,qBAAoB,kBAAiB,mBAAkB,CAAC;AACxJ,MAAG,YAAU,SAAQ;AAAC,UAAM,SAAO,OAAO,MAAK,YAAW,IAAI;AAAG,UAAM,UAAS,OAAO,MAAK,WAAW,KAAG;AAAiB,UAAM,mBAAmB,OAAO;AAAE,UAAM,MAAM,QAAO,OAAO;AAAE,UAAM,iBAAiB;AAAE;AAAA,EAAO;AACzN,MAAG,YAAU,UAAQ,YAAU,WAAS,YAAU,UAAS;AACzD,UAAM,SAAO,YAAU,SAAO,MAAI;AAAQ,QAAG,WAAS,SAAQ;AAAC,YAAM,UAAS,OAAO,MAAK,WAAW,KAAG;AAAiB,aAAO,MAAM,MAAM,MAAM,OAAO,MAAK,YAAW,IAAI,GAAG,OAAO,CAAC;AAAA,IAAE;AAAC,QAAG,WAAS,UAAS;AAAC,YAAM,OAAO;AAAE,aAAO,MAAM,EAAC,IAAG,MAAK,WAAU,KAAI,CAAC;AAAA,IAAE;AAAC,QAAG,WAAS,UAAS;AAAC,UAAG;AAAC,cAAM,IAAE,MAAM,YAAY;AAAE,eAAO,MAAM,EAAC,IAAG,MAAK,UAAS,MAAK,OAAM,EAAE,OAAM,SAAQ,EAAE,SAAQ,iBAAgB,EAAE,aAAY,CAAC;AAAA,MAAE,QAAM;AAAC,eAAO,MAAM,EAAC,IAAG,MAAK,UAAS,MAAK,CAAC;AAAA,MAAE;AAAA,IAAC;AAAC,QAAG,WAAS,WAAU;AAAC,YAAM,IAAE,MAAM,cAAc;AAAE,aAAO,MAAM,EAAC,IAAG,MAAK,iBAAgB,EAAE,aAAY,CAAC;AAAA,IAAE;AAAA,EAAC;AAClkB,MAAG,YAAU,YAAU,YAAU,WAAU;AAAC,UAAM,UAAQ,MAAM,cAAc;AAAE,QAAG,QAAM,UAAU,QAAO,MAAM,aAAa,QAAQ,MAAM,CAAC;AAAE,QAAG,QAAM,SAAQ;AAAC,aAAO,QAAQ;AAAO,YAAM,YAAY,OAAO;AAAE,aAAO,MAAM,EAAC,IAAG,MAAK,SAAQ,KAAI,CAAC;AAAA,IAAE;AAAC,QAAG,QAAM,SAAS,QAAO,MAAM,MAAM,OAAO,SAAQ,YAAY,IAAI,EAAE,CAAC,KAAG,IAAG,OAAO,MAAK,SAAQ,IAAI,CAAE,CAAC;AAAE,QAAG,QAAM,MAAM,QAAO,MAAM,MAAM,UAAU,SAAQ,OAAO,YAAY,IAAI,EAAE,CAAC,CAAC,GAAE,OAAO,MAAK,SAAQ,IAAI,GAAG,OAAO,MAAK,aAAY,IAAI,CAAE,CAAC;AAAA,EAAE;AAC9e,MAAG,YAAU,OAAM;AAAC,UAAM,UAAQ,MAAM,cAAc;AAAE,QAAG,CAAC,IAAI,OAAM,IAAI,MAAM,yBAAyB;AAAE,UAAM,OAAO,SAAQ,KAAI,IAAI;AAAE;AAAA,EAAO;AACjJ,MAAG,YAAU,UAAS;AAAC,QAAG,CAAC,IAAI,OAAM,IAAI,MAAM,yBAAyB;AAAE,YAAQ,WAAS,MAAM,UAAU,KAAI,IAAI;AAAE;AAAA,EAAO;AAC5H,MAAG,YAAU,SAAO,QAAM,SAAQ;AAAC,UAAM,UAAQ,MAAM,cAAc;AAAE,WAAO,MAAM,MAAM,MAAS,SAAQ,EAAC,MAAK,OAAO,OAAO,MAAK,UAAS,IAAI,CAAC,GAAE,IAAG,OAAO,OAAO,MAAK,QAAO,IAAI,CAAC,GAAE,OAAM,OAAO,MAAK,WAAU,IAAI,GAAG,MAAK,OAAO,OAAO,MAAK,QAAQ,KAAG,EAAE,GAAE,QAAO,OAAO,OAAO,MAAK,UAAU,KAAG,CAAC,GAAE,SAAQ,KAAK,MAAK,WAAW,EAAC,CAAC,CAAC;AAAA,EAAE;AAC9U,MAAG,YAAU,UAAS;AAAC,UAAM,WAAS,KAAK,MAAM,MAAM,SAAS,IAAI,IAAI,kBAAiB,YAAY,GAAG,GAAE,MAAM,CAAC;AAAE,QAAG,SAAS,YAAU,QAAQ,OAAM,IAAI,MAAM,kBAAkB,SAAS,OAAO,uBAAuB,OAAO,EAAE;AAAE,WAAO,MAAM,EAAC,IAAG,MAAK,MAAK,QAAQ,SAAS,MAAK,YAAW,SAAQ,MAAK,YAAY,EAAC,CAAC;AAAA,EAAE;AAC9T,MAAG,YAAU,UAAS;AAAC,QAAG,KAAK,MAAK,qBAAqB,GAAE;AAAC,YAAM,iBAAiB;AAAE;AAAA,IAAO;AAAC,QAAG,KAAK,MAAK,aAAa,EAAE,OAAM,IAAI,QAAQ,OAAG,WAAW,GAAE,KAAK,OAAO,IAAE,IAAS,CAAC;AAAE,WAAO,MAAM,EAAC,SAAQ,MAAM,OAAO,EAAC,CAAC;AAAA,EAAE;AAC5N,MAAG,YAAU,cAAY,YAAU,eAAe,QAAO,QAAQ,IAAI,MAAM,SAAS,IAAI,IAAI,6BAA4B,YAAY,GAAG,GAAE,MAAM,CAAC;AAChJ,QAAM,IAAI,MAAM,oBAAoB,OAAO,EAAE;AAC/C;AACA,KAAK,EAAE,MAAM,WAAO;AAAC,UAAQ,MAAM,cAAc,iBAAiB,QAAM,MAAM,UAAQ,OAAO,KAAK,CAAC,EAAE;AAAE,UAAQ,WAAS;AAAC,CAAC;","names":["options","query","spawn","join","spawn","join"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shulex/cxclawctl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
}
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Solvea CXClaw CLI and Codex plugin runtime",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "cxclawctl": "./bin/cxclawctl.mjs" },
|
|
7
|
+
"files": ["bin", "dist/cli.js", "dist/cli.js.map", "skills", "capabilities.lock.json", "requirements-cli.txt"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"dev": "tsx src/cli.ts",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "bun test --path-ignore-patterns _tmp_",
|
|
13
|
+
"check:capabilities": "python3 scripts/check_capabilities.py",
|
|
14
|
+
"check:release": "tsx scripts/check-release.ts",
|
|
15
|
+
"release:prepare": "tsx scripts/prepare-release.ts",
|
|
16
|
+
"package:agent-plugin": "node scripts/package-agent-plugin.mjs",
|
|
17
|
+
"package:codex-plugin": "node scripts/package-codex-plugin.mjs"
|
|
18
|
+
},
|
|
19
|
+
"engines": { "node": ">=22" },
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@alicloud/log": "^1.2.6",
|
|
22
|
+
"@napi-rs/keyring": "^2.1.2",
|
|
23
|
+
"compare-versions": "^6.1.1",
|
|
24
|
+
"open": "^10.2.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.0.0",
|
|
28
|
+
"bun-types": "^1.3.13",
|
|
29
|
+
"tsup": "^8.5.0",
|
|
30
|
+
"tsx": "^4.21.0",
|
|
31
|
+
"typescript": "^5.9.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": { "type": "git", "url": "git+https://github.com/SolveaCX/cx_claw_codex.git" },
|
|
34
|
+
"license": "UNLICENSED"
|
|
35
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill runtime bootstrap — two lines at the top of every skill script:
|
|
3
|
+
|
|
4
|
+
import os, sys
|
|
5
|
+
sys.path.insert(0, os.path.join(os.environ["SOLVEA_LIB_DIR"], "skill-runtime"))
|
|
6
|
+
from bootstrap import *
|
|
7
|
+
|
|
8
|
+
After import, the following are available:
|
|
9
|
+
SKILL_NAME — inferred from the skill directory above scripts/
|
|
10
|
+
WORKSPACE — absolute Path to workspace/ (from $WORKSPACE_DIR, or relative fallback)
|
|
11
|
+
OUTPUTS_DIR — WORKSPACE / outputs / {skill_name} # 跨轮路径稳定,下轮直接可读
|
|
12
|
+
STATE_DIR — WORKSPACE / state / {skill_name} # 跨轮路径稳定,下轮直接可读
|
|
13
|
+
TMP_DIR — WORKSPACE / tmp / {skill_name} # 仅本轮有效
|
|
14
|
+
MOUNTED_USER — .../mounted/chat-files/user # 用户上传,lazy
|
|
15
|
+
MOUNTED_TENANT — .../mounted/tenant-files # 租户级共享,lazy
|
|
16
|
+
SOLVEA_TENANT_ID — tenant ID (from $SOLVEA_TENANT_ID)
|
|
17
|
+
SOLVEA_PROJECT_ID — Solvea project ID (from $SOLVEA_PROJECT_ID)
|
|
18
|
+
SOLVEA_ENV — "prod" or "staging" (from $SOLVEA_ENV)
|
|
19
|
+
timestamp() — str e.g. "20260422_143022"
|
|
20
|
+
|
|
21
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
22
|
+
SKILL 脚本开发规范
|
|
23
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
24
|
+
|
|
25
|
+
【写入】脚本只能写 workspace/ 下的路径:
|
|
26
|
+
- 输出文件 → OUTPUTS_DIR / f"name_{timestamp()}.ext" # 每轮新建,带时间戳
|
|
27
|
+
- 临时文件 → TMP_DIR / "tmp_name.ext"
|
|
28
|
+
- 状态文件 → STATE_DIR / "progress.json" # 固定名(有语义),每轮覆盖
|
|
29
|
+
所有目录在使用前自行 mkdir:path.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
禁止写 mounted/ 或沙箱外的任何路径。
|
|
31
|
+
|
|
32
|
+
【读取 mounted/】mounted/ 下的文件(用户上传 / 租户共享)是懒加载的:
|
|
33
|
+
agent 调用 read_file 工具后文件才会物化到本地,脚本不能假设文件已存在。
|
|
34
|
+
|
|
35
|
+
正确用法:
|
|
36
|
+
if MOUNTED_USER.exists() and (MOUNTED_USER / "data.xlsx").exists():
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
【跨轮 outputs / state 惯用写法】
|
|
40
|
+
上一轮写入 OUTPUTS_DIR / STATE_DIR 的文件,本轮启动时已由运行时 eager 还原到原路径,
|
|
41
|
+
脚本直接读写即可(路径稳定,无需 read_file 物化):
|
|
42
|
+
|
|
43
|
+
读:
|
|
44
|
+
import json
|
|
45
|
+
_state_file = STATE_DIR / "progress.json" # 用有语义的名字
|
|
46
|
+
state = {}
|
|
47
|
+
if _state_file.exists():
|
|
48
|
+
try:
|
|
49
|
+
state = json.loads(_state_file.read_text(encoding="utf-8"))
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
写:
|
|
54
|
+
_state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
_state_file.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
【路径格式】WORKSPACE_DIR 由运行时注入(docker: /sandbox/workspace,本地: {run_dir}/workspace),
|
|
58
|
+
所有路径常量均为绝对 Path,不依赖 CWD。
|
|
59
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
import os as _os
|
|
63
|
+
import sys as _sys
|
|
64
|
+
from datetime import datetime as _dt
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
# ── sys.path bootstrap ────────────────────────────────────────────────────────
|
|
68
|
+
# Use __main__.__file__ when available (direct execution) so that module
|
|
69
|
+
# caching or re-import does not return a stale frame from a different caller.
|
|
70
|
+
_main_mod = _sys.modules.get("__main__")
|
|
71
|
+
_caller_file = _os.path.abspath(
|
|
72
|
+
getattr(_main_mod, "__file__", None) or _sys._getframe(1).f_code.co_filename
|
|
73
|
+
)
|
|
74
|
+
_caller_dir = _os.path.dirname(_caller_file)
|
|
75
|
+
|
|
76
|
+
# SOLVEA_LIB_DIR 由 SLG runtime(docker / local 都注入,见 env.py);
|
|
77
|
+
# 通过 SLG runtime 之外裸跑脚本时,按仓库内 skills/{skill}/scripts 结构自动回退到 skills/__lib__。
|
|
78
|
+
def _infer_lib_root_from_caller() -> str:
|
|
79
|
+
cur = Path(_caller_dir).resolve()
|
|
80
|
+
for parent in [cur, *cur.parents]:
|
|
81
|
+
candidate = parent / "__lib__"
|
|
82
|
+
if candidate.is_dir():
|
|
83
|
+
return str(candidate)
|
|
84
|
+
raise KeyError("SOLVEA_LIB_DIR")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_lib_root = _os.environ.get("SOLVEA_LIB_DIR") or _infer_lib_root_from_caller()
|
|
88
|
+
|
|
89
|
+
# caller's own scripts/ dir first (for sibling module imports like render.py)
|
|
90
|
+
if _caller_dir not in _sys.path:
|
|
91
|
+
_sys.path.insert(0, _caller_dir)
|
|
92
|
+
|
|
93
|
+
# add all subdirs of __lib__ to sys.path (each lib is its own flat package)
|
|
94
|
+
for _d in _os.listdir(_lib_root):
|
|
95
|
+
_p = _os.path.join(_lib_root, _d)
|
|
96
|
+
if _os.path.isdir(_p) and _p not in _sys.path:
|
|
97
|
+
_sys.path.insert(0, _p)
|
|
98
|
+
|
|
99
|
+
# ── skill name & standard paths ───────────────────────────────────────────────
|
|
100
|
+
# Support both scripts/foo.py and focused subdirectories such as
|
|
101
|
+
# scripts/report/foo.py. The nearest scripts/ ancestor defines the skill root.
|
|
102
|
+
_caller_path = Path(_caller_dir).resolve()
|
|
103
|
+
_scripts_dir = next(
|
|
104
|
+
(path for path in [_caller_path, *_caller_path.parents] if path.name == "scripts"),
|
|
105
|
+
None,
|
|
106
|
+
)
|
|
107
|
+
SKILL_NAME = _scripts_dir.parent.name if _scripts_dir else _caller_path.parent.name
|
|
108
|
+
|
|
109
|
+
# WORKSPACE_DIR is injected by both backends (docker: /sandbox/workspace, local: {run_dir}/workspace).
|
|
110
|
+
# Falls back to relative "workspace" when running a script outside the runtime.
|
|
111
|
+
_workspace_dir = _os.environ.get("WORKSPACE_DIR")
|
|
112
|
+
if _workspace_dir:
|
|
113
|
+
WORKSPACE = Path(_workspace_dir)
|
|
114
|
+
_root_dir = WORKSPACE.parent
|
|
115
|
+
_mounted_base = _root_dir / "mounted"
|
|
116
|
+
else:
|
|
117
|
+
WORKSPACE = Path("workspace")
|
|
118
|
+
_mounted_base = Path("mounted")
|
|
119
|
+
|
|
120
|
+
OUTPUTS_DIR = WORKSPACE / "outputs" / SKILL_NAME
|
|
121
|
+
STATE_DIR = WORKSPACE / "state" / SKILL_NAME
|
|
122
|
+
TMP_DIR = WORKSPACE / "tmp" / SKILL_NAME
|
|
123
|
+
|
|
124
|
+
MOUNTED_USER = _mounted_base / "chat-files" / "user"
|
|
125
|
+
MOUNTED_TENANT = _mounted_base / "tenant-files"
|
|
126
|
+
|
|
127
|
+
# ── Solvea context injected by the runtime ────────────────────────────────────
|
|
128
|
+
SOLVEA_TENANT_ID = _os.environ.get("SOLVEA_TENANT_ID", "")
|
|
129
|
+
SOLVEA_PROJECT_ID = _os.environ.get("SOLVEA_PROJECT_ID", "")
|
|
130
|
+
SOLVEA_ENV = _os.environ.get("SOLVEA_ENV", "prod")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def timestamp() -> str:
|
|
134
|
+
return _dt.now().strftime("%Y%m%d_%H%M%S")
|
|
File without changes
|