@qmuse/qmuse-cli 1.0.9-dev.4 → 1.0.10-dev.1

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.
@@ -0,0 +1,202 @@
1
+ /**
2
+ * 版本闸门(version gate):三档判定 + 失败降级。
3
+ *
4
+ * 档位(与业界 CLI 一致):
5
+ * - ok 低于 warnBelow 之外的正常状态
6
+ * - notice 有更新:仅退出时提示一行
7
+ * - warning 低于 warnBelow:每条命令告警,但继续执行
8
+ * - blocked 低于 minRequired:拒绝执行(exit 1),只放行 upgrade 等放行清单命令
9
+ *
10
+ * 策略来源(provider 可替换,默认读 npm 包元数据,后端零改动):
11
+ * - npm/tnpm 的 `<pkg>/latest` 响应里携带 `qmuseVersionPolicy: { minRequired, warnBelow, message, upgradeCommand }`
12
+ * - 读不到(离线/旧包/字段缺失)→ 一律降级为 ok,绝不因为闸门把 CLI 锁死
13
+ */
14
+ import semver from 'semver';
15
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
16
+ import { homedir } from 'node:os';
17
+ import { dirname, join } from 'node:path';
18
+ import { QMUSE_CLI_DISTRIBUTION, QMUSE_CLI_NAME, QMUSE_CLI_VERSION } from './version.js';
19
+ /** 不需要闸门放行的命令(否则用户无法自救升级)。 */
20
+ export const GATE_BYPASS_COMMANDS = new Set([
21
+ 'upgrade',
22
+ 'help',
23
+ 'version',
24
+ 'agent-skill',
25
+ ]);
26
+ export function defaultUpgradeCommand(distribution) {
27
+ return distribution === 'PUBLIC'
28
+ ? 'npm install -g @qmuse/qmuse-cli@latest'
29
+ : 'tnpm install -g @alipay/qmuse-cli@latest';
30
+ }
31
+ function validOrUndefined(value) {
32
+ return typeof value === 'string' && semver.valid(value) ? value : undefined;
33
+ }
34
+ /** 从 registry 的 `/latest` 元数据里解析策略;字段缺失或非法一律忽略(失败降级)。 */
35
+ export function parsePolicy(metadata) {
36
+ const record = (metadata ?? {});
37
+ // 既支持 registry 元数据的嵌套形态(qmuseVersionPolicy),也支持策略对象自身的扁平形态(环境变量/未来接口)。
38
+ const nested = (record['qmuseVersionPolicy'] ?? record['versionPolicy']);
39
+ const raw = nested && typeof nested === 'object' ? nested : record;
40
+ const policy = {};
41
+ if (raw && typeof raw === 'object') {
42
+ const minRequired = validOrUndefined(raw['minRequired']);
43
+ const warnBelow = validOrUndefined(raw['warnBelow']);
44
+ if (minRequired)
45
+ policy.minRequired = minRequired;
46
+ if (warnBelow)
47
+ policy.warnBelow = warnBelow;
48
+ if (typeof raw['message'] === 'string' && raw['message'].trim())
49
+ policy.message = raw['message'].trim().slice(0, 500);
50
+ if (typeof raw['upgradeCommand'] === 'string' && raw['upgradeCommand'].trim()) {
51
+ policy.upgradeCommand = raw['upgradeCommand'].trim().slice(0, 300);
52
+ }
53
+ }
54
+ return {
55
+ latest: validOrUndefined(record['version']),
56
+ ...(Object.keys(policy).length > 0 ? { policy } : {}),
57
+ checkedAt: Date.now(),
58
+ };
59
+ }
60
+ /** 三档判定:纯函数,便于测试;任何无法比较的输入都降级为 ok。 */
61
+ export function gateLevel(current, snapshot) {
62
+ if (!snapshot || !semver.valid(current))
63
+ return 'ok';
64
+ const { policy } = snapshot;
65
+ const minRequired = policy?.minRequired;
66
+ const warnBelow = policy?.warnBelow;
67
+ try {
68
+ // 预发布版本(1.0.9-dev.3)代表领先于同名正式版,因此「有无更新」不提示;
69
+ // 但它仍然可能落后于 minRequired(例如要求 1.5.0 时 1.0.9 必须阻断),故阻断判据不豁免。
70
+ const isPrerelease = !!semver.prerelease(current);
71
+ if (minRequired && semver.valid(minRequired) && semver.compare(current, minRequired) < 0)
72
+ return 'blocked';
73
+ if (warnBelow && semver.valid(warnBelow) && semver.compare(current, warnBelow) < 0)
74
+ return 'warning';
75
+ if (isPrerelease)
76
+ return 'ok';
77
+ if (snapshot.latest && semver.valid(snapshot.latest) && semver.lt(current, snapshot.latest))
78
+ return 'notice';
79
+ return 'ok';
80
+ }
81
+ catch {
82
+ return 'ok';
83
+ }
84
+ }
85
+ /** 是否需要升级:预发布版本不判「落后」;缺少最新版本信息时返回 false(降级)。 */
86
+ export function shouldUpgrade(current, latest) {
87
+ if (!latest || !semver.valid(latest) || !semver.valid(current))
88
+ return false;
89
+ if (semver.prerelease(current))
90
+ return false;
91
+ try {
92
+ return semver.lt(current, latest);
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ /** 生成给用户看的决定(含 upgrade 命令),供 CLI 输出。 */
99
+ export function decide(current, snapshot, distribution = QMUSE_CLI_DISTRIBUTION) {
100
+ const level = gateLevel(current, snapshot);
101
+ const upgradeCommand = snapshot?.policy?.upgradeCommand ?? defaultUpgradeCommand(distribution);
102
+ return {
103
+ level,
104
+ current,
105
+ ...(snapshot?.latest ? { latest: snapshot.latest } : {}),
106
+ ...(snapshot?.policy?.minRequired ? { minRequired: snapshot.policy.minRequired } : {}),
107
+ ...(snapshot?.policy?.message ? { message: snapshot.policy.message } : {}),
108
+ upgradeCommand,
109
+ };
110
+ }
111
+ /** 元数据地址(与 update-notice 一致,按发行版切换公共/内部 registry)。 */
112
+ export function policyRegistryUrl(distribution = QMUSE_CLI_DISTRIBUTION) {
113
+ return distribution === 'PUBLIC'
114
+ ? 'https://registry.npmjs.org/@qmuse%2fqmuse-cli/latest'
115
+ : 'https://registry.antgroup-inc.cn/@alipay%2fqmuse-cli/latest';
116
+ }
117
+ /**
118
+ * 环境变量 provider(QMUSE_VERSION_POLICY='{"minRequired":"1.2.0"}'):
119
+ * 便于自测与企业内网固定策略;与服务端接口就绪前的过渡手段。
120
+ */
121
+ export function envPolicyProvider(json = process.env.QMUSE_VERSION_POLICY) {
122
+ return async () => {
123
+ if (!json)
124
+ return undefined;
125
+ try {
126
+ return parsePolicy(JSON.parse(json));
127
+ }
128
+ catch {
129
+ return undefined;
130
+ }
131
+ };
132
+ }
133
+ const POLICY_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
134
+ /** 缓存目录:QMUSE_CLI_CACHE_DIR 可覆盖(测试与多环境隔离)。 */
135
+ function policyCacheDir() {
136
+ return process.env.QMUSE_CLI_CACHE_DIR ?? join(homedir(), '.cache', 'qmuse-cli');
137
+ }
138
+ function policyCachePath(distribution) {
139
+ return join(policyCacheDir(), `version-policy-${distribution.toLowerCase()}.json`);
140
+ }
141
+ function readPolicyCache(distribution) {
142
+ try {
143
+ const raw = JSON.parse(readFileSync(policyCachePath(distribution), 'utf8'));
144
+ if (!raw || typeof raw.checkedAt !== 'number')
145
+ return undefined;
146
+ if (Date.now() - raw.checkedAt > POLICY_CACHE_TTL_MS)
147
+ return undefined;
148
+ return raw;
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ }
154
+ function writePolicyCache(distribution, snapshot) {
155
+ try {
156
+ const file = policyCachePath(distribution);
157
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
158
+ writeFileSync(file, JSON.stringify(snapshot), { mode: 0o600 });
159
+ }
160
+ catch { /* 缓存失败不影响闸门判定 */ }
161
+ }
162
+ /** 默认 provider:读 `<pkg>/latest`,任何异常都返回 undefined(失败降级放行)。 */
163
+ export function registryPolicyProvider(options = {}) {
164
+ const distribution = options.distribution ?? QMUSE_CLI_DISTRIBUTION;
165
+ const url = options.url ?? policyRegistryUrl(distribution);
166
+ const timeoutMs = options.timeoutMs ?? 2500;
167
+ return async () => {
168
+ // 缓存优先:6 小时内不重复打 registry(每条命令一次请求代价过高)。
169
+ const cached = readPolicyCache(distribution);
170
+ if (cached)
171
+ return cached;
172
+ const controller = new AbortController();
173
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
174
+ timer.unref?.();
175
+ try {
176
+ const response = await (options.fetcher ?? fetch)(url, {
177
+ signal: controller.signal,
178
+ redirect: 'error',
179
+ headers: { Accept: 'application/json', 'x-qmuse-client': QMUSE_CLI_NAME },
180
+ });
181
+ if (!response.ok)
182
+ return undefined;
183
+ const snapshot = parsePolicy(await response.json());
184
+ writePolicyCache(distribution, snapshot);
185
+ return snapshot;
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ finally {
191
+ clearTimeout(timer);
192
+ }
193
+ };
194
+ }
195
+ /** 执行闸门检查:失败降级为 ok;放行清单内命令永不阻断。 */
196
+ export async function checkVersionGate(input) {
197
+ const current = input.current ?? QMUSE_CLI_VERSION;
198
+ const snapshot = await (input.provider ?? registryPolicyProvider({ distribution: input.distribution }))();
199
+ const decision = decide(current, snapshot, input.distribution ?? QMUSE_CLI_DISTRIBUTION);
200
+ const bypass = input.command !== undefined && GATE_BYPASS_COMMANDS.has(input.command);
201
+ return { decision, blocked: decision.level === 'blocked' && !bypass };
202
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qmuse/qmuse-cli",
3
- "version": "1.0.9-dev.4",
4
- "description": "将本地 React 项目安全导入 QMuse 的命令行客户端",
3
+ "version": "1.0.10-dev.1",
4
+ "description": "QMuse 命令行客户端:在本地创建/导入 React 项目、与 QMuse 对话、拉取发布与云端调试",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "qmuse": "dist/cli.js",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "files": [
31
31
  "dist",
32
+ "skills",
32
33
  "README.md"
33
34
  ],
34
35
  "engines": {
@@ -44,6 +45,7 @@
44
45
  "vite": "7.3.6"
45
46
  },
46
47
  "license": "MIT",
48
+ "repository": "git@code.alipay.com:q-muse/qmuse-cli.git",
47
49
  "publishConfig": {
48
50
  "access": "public",
49
51
  "registry": "https://registry.npmjs.org/"
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: qmuse-cli
3
+ description: QMuse CLI 官方研发指南:本地工程初始化、导入、应用绑定、远端对话、本地开发、源码同步、云数据与发布。当用户要求使用 QMuse CLI 创建/开发/导入/发布应用到 QMuse,或排查 qmuse 命令问题时使用。
4
+ ---
5
+
6
+ # QMuse CLI 官方指南
7
+
8
+ 总原则:先 `qmuse info --json` 判定上下文(登录态、环境、绑定、云架构),再选择下方分流入口;
9
+ 每个命令都可以 `qmuse help` 查看参数,`--json` 拿结构化输出。执行失败时:
10
+ 先读取命令输出中的诊断字段(`message`、`errorCode`、`diagnostics`、`.qmuse/last-import-error.json`),
11
+ 不要凭猜测重复执行可能创建新资源的操作(重复创建请求只恢复身份,不重跑首轮生成)。
12
+
13
+ ## 工程与任务分流(先读这里)
14
+
15
+ | 用户意图 | 入口 | 关键约束 |
16
+ | --- | --- | --- |
17
+ | 初始化一个新的本地工程 | `qmuse create <dir>` | 只负责本地模板与说明;远端应用用 `qmuse import` 或 `qmuse app create` |
18
+ | 已有 React 工程导入 QMuse | `qmuse import <dir>` | 安全预检 → 打包上传 → 应用/版本生成;预发/正式环境用 `--env` 区分 |
19
+ | 修改已导入的同一应用 | `qmuse import .`(项目目录内) | 自动走 UPDATE 语义;远端基线变化时要求 pull 或 `--force` |
20
+ | 自然语言创建应用并首轮生成 | `qmuse app create <web\|h5> --file <路径\|->` | POST /api/chat mode=create 一次完成;同 requestId 重发只恢复身份(REUSED) |
21
+ | 已有应用继续对话/生成 | `qmuse chat send / resume --app-id … --chat-id …` | 需要应用绑定或显式 appId;中断恢复传 `--interrupt-id` 与 `--file` |
22
+ | 本地开发(云资源连接) | `qmuse dev [dir]` | 云绑定应用;`qmuse dev status/stop` 管理会话 |
23
+ | 拉取最新已保存源码 | `qmuse pull [dir]` / `qmuse code export` | 本地有改动会先备份再停止;不静默覆盖 |
24
+ | 发布与发布结果 | `qmuse deploy --version <id>`、`qmuse release status <releaseId>` | 受理后可 `--no-wait` 退出;结果含发布地址与安全检查 |
25
+
26
+ ## 与官方 Skill 的关系
27
+
28
+ - 本指南随 CLI 包内分发(离线可用)。升级 CLI 后执行 `qmuse agent-skill install --agent codex` 重新安装对应版本。
29
+ - `qmuse agent-skill status` 查看安装目标、版本与文件状态。
30
+ - 云服务规则不复制到本指南:以 `qmuse info --json` / `qmuse cloud status` 返回的实际架构与平台权威文档为准(见 references/cloud.md)。
@@ -0,0 +1,32 @@
1
+ # 云服务指南(S01.11:匹配现有云服务)
2
+
3
+ CLI 不复制云规则,只做两件事:识别当前应用的实际架构,然后把操作路由到现有权威入口。
4
+
5
+ ## 1. 识别当前架构
6
+
7
+ ```bash
8
+ qmuse info --json # bindings + cloud 汇总(entitlement / environment)
9
+ qmuse cloud status --app-id <appId> --json # effectiveMode、activationStatus、publishAllowed、canRetryInitialization
10
+ ```
11
+
12
+ - `SINGLE_ENV` / Legacy:应用没有开通多环境;云初始化与首版同步由导入或对话侧完成。
13
+ - `MULTI_ENV`(Hono):`qmuse cloud enable` 之后再查询状态;已初始化失败用 `qmuse cloud environment retry --wait`。
14
+
15
+ ## 2. 路由到现有入口
16
+
17
+ | 判断 | 动作 |
18
+ | --- | --- |
19
+ | 权益预检(不伪装已开通) | `qmuse cloud init -d "<功能描述>"` |
20
+ | 实际开通 | `qmuse cloud enable --app-id <appId>` |
21
+ | 状态/失败原因 | `qmuse cloud status --app-id <appId>` |
22
+ | 初始化失败重试 | `qmuse cloud environment retry --app-id <appId> --wait` |
23
+ | 公开连接与运行配置 | `qmuse cloud pull-env --app-id <appId> --output .qmuse/local-dev.json` |
24
+ | 数据库/表结构与用量 | `qmuse data-source list|info`、`qmuse db tables`、`qmuse db records …` |
25
+ | 云函数 | `qmuse fn list|info|retry|deployment-*|execution-*` |
26
+ | 秘密(安全输入) | `qmuse cloud set-secret K1,K2 --stdin`;查询 `qmuse secret list` / `secret sync status|retry` |
27
+
28
+ ## 3. 约束
29
+
30
+ - 管理凭证只保留在 CLI 会话或服务端;`pull-env` 输出仅含公开配置。
31
+ - 预检通过不等于初始化完成:以 `cloud status` 的 `activationStatus` 为准。
32
+ - 套餐与订阅购买流程在 PC 端;CLI 只读 `qmuse account` 汇总并在缺少契约时返回 `unavailable` 原因。
@@ -0,0 +1,93 @@
1
+ # 核心流程参考(S01.12)
2
+
3
+ 仅覆盖当前交付入口:身份与应用、初始化与导入、本地开发、对话恢复、源码同步、云数据、发布及错误恢复。
4
+
5
+ ## 0. 通用建议
6
+
7
+ - 结构化输出加 `--json`;日志与进度走 stderr。
8
+ - 凭证保存在系统凭证存储(1.0.9+);`--env prod|pre|local` 隔离身份,凭证不跨 Origin 发送。
9
+ - 空间选择全局保存:`qmuse space list` → `qmuse space use <spaceId>`(保存前校验可访问)。
10
+
11
+ ## 1. 身份与应用
12
+
13
+ ```bash
14
+ qmuse login [--status] # 浏览器确认登录
15
+ qmuse whoami # 验证真实登录身份
16
+ qmuse app create web --file 构思.md # 统一创建(身份头 → 首轮 SSE 终态)
17
+ qmuse app list / info / use <appId> / current / unlink / update / delete --yes
18
+ ```
19
+
20
+ 创建恢复:同 `--request-id` 重发 → `REUSED`,返回已有身份 JSON,不重跑首轮生成。
21
+ 断流恢复顺序:`chat state` → `chat history` → 显式 `chat cancel`(如需停止)。
22
+
23
+ ## 2. 初始化与导入
24
+
25
+ ```bash
26
+ qmuse create my-app # 仅本地模板与说明
27
+ qmuse import /path/to/react-app # 预检→上传→应用/版本生成
28
+ qmuse import . # 已绑定目录的持续更新(UPDATE 语义)
29
+ qmuse import status <jobId> --wait
30
+ qmuse import configure <jobId> --cloud-mode EXTERNAL --public-config '@public.json'
31
+ ```
32
+
33
+ - 云模式:`AUTO`(默认)/`EXTERNAL`(外部云,`--public-config`)/`QMUSE`(需 `--confirm-cloud-service`)。
34
+ - 外部数据不自动迁移:`--acknowledge-empty-cloud-data` 显式确认,不伪装已迁移。
35
+ - 失败诊断:`.qmuse/last-import-error.json`(含 stage/diagnostic/retry 建议)。
36
+
37
+ ## 3. 本地开发
38
+
39
+ ```bash
40
+ qmuse dev [dir] --port 5173 # 连接开发云资源 + 启动本地 Vite
41
+ qmuse dev status # 当前会话 pid/appId/localUrl
42
+ qmuse dev stop # SIGTERM→8s→SIGKILL,清理会话文件
43
+ qmuse pull [dir] # 拉取最新已保存源码(本地有改动先备份并停止)
44
+ ```
45
+
46
+ CLI 凭证不进入浏览器:开发连接由 CLI 代理,浏览器只访问本地服务。
47
+
48
+ ## 4. 对话恢复
49
+
50
+ ```bash
51
+ qmuse chat send --app-id <id> --chat-id <id> --file - \
52
+ [--model <model>] [--ndjson]
53
+ qmuse chat resume <chatId> --interrupt-id <id> --file 回答.json --app-id <id>
54
+ qmuse chat history --app-id <id> --sprint-id <id>
55
+ qmuse chat state <chatId> # 先查询再恢复
56
+ qmuse models # 模型可用性来自平台
57
+ qmuse chat attachment upload <path> --app-id <id>
58
+ ```
59
+
60
+ 中断恢复要点:同一会话与 checkpoint;`--interrupt-id` 来自 chat state/事件;
61
+ 重复创建请求只恢复身份(REUSED),不重跑首轮生成。
62
+
63
+ ## 5. 源码同步
64
+
65
+ ```bash
66
+ qmuse version list|info --app-id <id>
67
+ qmuse code export --app-id <id> --output ./exported
68
+ qmuse pull . # 最新已保存源码;覆盖前自动备份
69
+ ```
70
+
71
+ 上传走 `import`;下载用 `pull`(最新已保存版)或 `code export`(只提供最新已保存版源码)。
72
+
73
+ ## 6. 云数据
74
+
75
+ ```bash
76
+ qmuse data-source list|info --app-id <id>
77
+ qmuse db tables --app-id <id>
78
+ qmuse db records list|get|create|update|delete --app-id <id> --form-id <formId>
79
+ qmuse db permission get --app-id <id> --form-id <formId>
80
+ ```
81
+
82
+ 记录写操作受平台数据权限约束;失败以命令输出的 `errorCode`/`errorMessage` 为准。
83
+
84
+ ## 7. 发布与错误恢复
85
+
86
+ ```bash
87
+ qmuse deploy --version <versionId> [--no-wait] # 固定版本发布(平台安全门禁)
88
+ qmuse release status <releaseId> [--wait] # 同一查询返回任务详情/检查/地址/失败原因
89
+ qmuse release list --app-id <id>
90
+ ```
91
+
92
+ 失败恢复:`release status` 的 `error`/`mosecStatus`/`infosecResult` + `deploy --version` 重发(幂等受理)。
93
+ 账户与成本观测:`qmuse account`(余额/权益/消耗摘要/订阅状态只读)。