@qmuse/qmuse-cli 1.0.9 → 1.0.10
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 +14 -0
- package/dist/agent-skill.js +257 -0
- package/dist/app/account.js +23 -0
- package/dist/app/api.js +51 -0
- package/dist/app/binding.js +48 -0
- package/dist/app/chat.js +291 -0
- package/dist/app/cloud.js +28 -0
- package/dist/app/create.js +358 -0
- package/dist/app/data.js +104 -0
- package/dist/app/fn.js +25 -0
- package/dist/app/release.js +105 -0
- package/dist/app/sandbox.js +32 -0
- package/dist/app/secret.js +30 -0
- package/dist/app/sse.js +80 -0
- package/dist/app/version.js +12 -0
- package/dist/cli.js +2210 -139
- package/dist/client.js +79 -7
- package/dist/errors.js +143 -0
- package/dist/login.js +2 -0
- package/dist/telemetry.js +229 -0
- package/dist/tracker.js +130 -0
- package/dist/version-gate.js +202 -0
- package/package.json +4 -2
- package/skills/qmuse-cli/SKILL.md +30 -0
- package/skills/qmuse-cli/references/cloud.md +32 -0
- package/skills/qmuse-cli/references/core-flows.md +93 -0
package/dist/app/data.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据域(C13):AppwriteDataController/NocodeController 现有契约。
|
|
3
|
+
* formId 即清单中的 tableId;permission 为表单鉴权。
|
|
4
|
+
*/
|
|
5
|
+
import { apiCall } from '../client.js';
|
|
6
|
+
export function queryAppMetadata(appId, devAppVersionId, cfg) {
|
|
7
|
+
return apiCall('POST', '/api/appwrite/queryAppMetadataMap', { appId, devAppVersionId }, cfg);
|
|
8
|
+
}
|
|
9
|
+
export function listRecords(appId, formId, query = {}, cfg) {
|
|
10
|
+
return apiCall('POST', '/api/appwrite/queryFormRecordList', { appId, formId, ...query }, cfg);
|
|
11
|
+
}
|
|
12
|
+
export function getRecord(appId, formId, code, cfg) {
|
|
13
|
+
return apiCall('POST', '/api/appwrite/queryFormRecordByCode', { appId, formId, code }, cfg);
|
|
14
|
+
}
|
|
15
|
+
export function createRecord(appId, formId, parameters, cfg) {
|
|
16
|
+
return apiCall('POST', '/api/appwrite/createFormRecord', { appId, formId, parameters }, cfg);
|
|
17
|
+
}
|
|
18
|
+
export function updateRecord(appId, formId, parameters, cfg) {
|
|
19
|
+
return apiCall('POST', '/api/appwrite/updateFormRecord', { appId, formId, parameters }, cfg);
|
|
20
|
+
}
|
|
21
|
+
export function deleteRecord(appId, formId, code, cfg) {
|
|
22
|
+
return apiCall('POST', '/api/appwrite/deleteFormRecordByCode', { appId, formId, code }, cfg);
|
|
23
|
+
}
|
|
24
|
+
export function queryFormAuth(appId, formId, cfg) {
|
|
25
|
+
return apiCall('POST', '/api/appwrite/queryFormAuthByFormId', { appId, formId }, cfg);
|
|
26
|
+
}
|
|
27
|
+
export function syncFormAuth(appId, formId, permissions, cfg) {
|
|
28
|
+
return apiCall('POST', '/api/appwrite/syncFormAuthList', { appId, formId, permissions }, cfg);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* C13.08/09:记录写失败的参数级诊断(仅失败后调用,成功路径零开销)。
|
|
32
|
+
* 依据平台现有表元数据比对 parameters:缺失必填、只读/系统字段、未知字段。
|
|
33
|
+
* 服务器对缺必填只回“系统异常”,这里把可操作的字段级原因补出来。
|
|
34
|
+
*/
|
|
35
|
+
export async function diagnoseRecordParams(appId, formId, parameters, cfg) {
|
|
36
|
+
const { listAppVersions } = await import('./version.js');
|
|
37
|
+
const versions = await listAppVersions(appId, cfg);
|
|
38
|
+
const versionId = versions.data?.list?.[0]?.id;
|
|
39
|
+
if (!versionId)
|
|
40
|
+
return [];
|
|
41
|
+
const meta = await queryAppMetadata(appId, versionId, cfg);
|
|
42
|
+
if (!meta.ok || !meta.data)
|
|
43
|
+
return [];
|
|
44
|
+
const entities = [];
|
|
45
|
+
const walk = (node) => {
|
|
46
|
+
if (Array.isArray(node)) {
|
|
47
|
+
node.forEach(walk);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!node || typeof node !== 'object')
|
|
51
|
+
return;
|
|
52
|
+
const record = node;
|
|
53
|
+
if (typeof record.name === 'string' && Array.isArray(record.properties))
|
|
54
|
+
entities.push(record);
|
|
55
|
+
Object.values(record).forEach(walk);
|
|
56
|
+
};
|
|
57
|
+
walk(meta.data);
|
|
58
|
+
// 元数据可能同时包含 dev/prod 两侧同名实体:按字段名去重(取首个定义)。
|
|
59
|
+
const fieldsByName = new Map();
|
|
60
|
+
for (const entity of entities.filter(item => item.name === formId)) {
|
|
61
|
+
for (const property of entity.properties) {
|
|
62
|
+
const name = property.name;
|
|
63
|
+
if (typeof name !== 'string' || fieldsByName.has(name))
|
|
64
|
+
continue;
|
|
65
|
+
fieldsByName.set(name, property);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const fields = [...fieldsByName.values()];
|
|
69
|
+
if (fields.length === 0)
|
|
70
|
+
return [];
|
|
71
|
+
const hints = [];
|
|
72
|
+
const missing = fields
|
|
73
|
+
.filter(property => property.required === true && property.readonly !== true)
|
|
74
|
+
.map(property => property.name)
|
|
75
|
+
.filter(name => !(name in parameters) || parameters[name] === null || parameters[name] === undefined || parameters[name] === '');
|
|
76
|
+
if (missing.length > 0)
|
|
77
|
+
hints.push(`缺少必填字段: ${missing.join(', ')}`);
|
|
78
|
+
const readonlyFields = fields
|
|
79
|
+
.filter(property => property.readonly === true)
|
|
80
|
+
.map(property => property.name)
|
|
81
|
+
.filter(name => name in parameters);
|
|
82
|
+
if (readonlyFields.length > 0)
|
|
83
|
+
hints.push(`系统/只读字段不可写(服务端将拒绝): ${readonlyFields.join(', ')}`);
|
|
84
|
+
const known = new Set(fields.map(property => property.name));
|
|
85
|
+
const unknown = Object.keys(parameters).filter(key => !known.has(key));
|
|
86
|
+
if (unknown.length > 0)
|
|
87
|
+
hints.push(`表中不存在的字段: ${unknown.join(', ')}`);
|
|
88
|
+
const typeMismatch = [];
|
|
89
|
+
for (const property of fields) {
|
|
90
|
+
const name = property.name;
|
|
91
|
+
const type = String(property.type ?? '').toLowerCase();
|
|
92
|
+
const value = parameters[name];
|
|
93
|
+
if (value === undefined || value === null || value === '')
|
|
94
|
+
continue;
|
|
95
|
+
const isNumber = typeof value === 'number' || (typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value)));
|
|
96
|
+
if (type === 'number' && !isNumber)
|
|
97
|
+
typeMismatch.push(`${name}应为数字类型`);
|
|
98
|
+
if ((type === 'boolean' || type === 'bool' || type === 'switch') && typeof value !== 'boolean' && !['true', 'false'].includes(String(value).toLowerCase()))
|
|
99
|
+
typeMismatch.push(`${name}应为布尔类型`);
|
|
100
|
+
}
|
|
101
|
+
if (typeMismatch.length > 0)
|
|
102
|
+
hints.push(`字段类型偏差: ${typeMismatch.join('; ')}`);
|
|
103
|
+
return hints;
|
|
104
|
+
}
|
package/dist/app/fn.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 云函数域(C15):POST /api/appwrite/functions/*(AppwriteFunctionController)。
|
|
3
|
+
*/
|
|
4
|
+
import { apiCall } from '../client.js';
|
|
5
|
+
export function listFunctions(appId, cfg) {
|
|
6
|
+
return apiCall('POST', '/api/appwrite/functions/list', { appId }, cfg);
|
|
7
|
+
}
|
|
8
|
+
export function functionDetail(appId, functionId, cfg) {
|
|
9
|
+
return apiCall('POST', '/api/appwrite/functions/detail', { appId, functionId }, cfg);
|
|
10
|
+
}
|
|
11
|
+
export function retryFunctionPublish(appId, functionId, cfg) {
|
|
12
|
+
return apiCall('POST', '/api/appwrite/functions/retry-publish', { appId, functionId }, cfg);
|
|
13
|
+
}
|
|
14
|
+
export function listFunctionDeployments(appId, functionId, page = {}, cfg) {
|
|
15
|
+
return apiCall('POST', '/api/appwrite/functions/deployments/list', { appId, functionId, ...page }, cfg);
|
|
16
|
+
}
|
|
17
|
+
export function deploymentDetail(appId, functionId, deploymentId, cfg) {
|
|
18
|
+
return apiCall('POST', '/api/appwrite/functions/deployments/detail', { appId, functionId, deploymentId }, cfg);
|
|
19
|
+
}
|
|
20
|
+
export function listFunctionExecutions(appId, functionId, page = {}, cfg) {
|
|
21
|
+
return apiCall('POST', '/api/appwrite/functions/executions/list', { appId, functionId, ...page }, cfg);
|
|
22
|
+
}
|
|
23
|
+
export function executionDetail(appId, functionId, executionId, cfg) {
|
|
24
|
+
return apiCall('POST', '/api/appwrite/functions/executions/detail', { appId, functionId, executionId }, cfg);
|
|
25
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发布域(C10):POST /api/deploy/create + update(触发执行),GET /api/deploy/query | list | last。
|
|
3
|
+
* 部署单状态机:INIT → INITED(安全检查通过)→ DEPLOYING → DEPLOYED / DEPLOY_FAILED / CANCEL。
|
|
4
|
+
* query 返回 content(infosec/mosec/域名发布状态/发布地址),供 release status 汇总。
|
|
5
|
+
*/
|
|
6
|
+
import { apiCall } from '../client.js';
|
|
7
|
+
export function parseDeployTicketContent(ticket) {
|
|
8
|
+
if (typeof ticket.content !== 'string')
|
|
9
|
+
return ticket.content ?? {};
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(ticket.content);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** C10.01 发布指定版本:创建并受理部署单(服务端含权益/内容安全门禁)。 */
|
|
18
|
+
export function createDeployTicket(appId, versionId, cfg) {
|
|
19
|
+
return apiCall('POST', '/api/deploy/create', { appId, versionId }, cfg);
|
|
20
|
+
}
|
|
21
|
+
/** 触发已就绪(INITED)部署单执行发布;服务端异步执行 DEPLOYING → 终态。 */
|
|
22
|
+
export function triggerDeployTicket(deployId, cfg) {
|
|
23
|
+
return apiCall('POST', '/api/deploy/update', { id: deployId }, cfg);
|
|
24
|
+
}
|
|
25
|
+
/** C10.03 查询发布结果:同一查询返回任务详情、产物/安全检查、地址与失败原因。 */
|
|
26
|
+
export function queryDeployTicket(deployId, cfg) {
|
|
27
|
+
return apiCall('GET', `/api/deploy/query?id=${encodeURIComponent(deployId)}`, undefined, cfg);
|
|
28
|
+
}
|
|
29
|
+
/** C10.05 查询发布记录:有权访问的发布记录及固定版本。 */
|
|
30
|
+
export function listDeployTickets(appId, cfg) {
|
|
31
|
+
return apiCall('GET', `/api/deploy/list?appId=${encodeURIComponent(appId)}`, undefined, cfg);
|
|
32
|
+
}
|
|
33
|
+
export function lastDeployTicket(appId, cfg) {
|
|
34
|
+
return apiCall('GET', `/api/deploy/last?appId=${encodeURIComponent(appId)}`, undefined, cfg);
|
|
35
|
+
}
|
|
36
|
+
export const APP_DEPLOY_TERMINAL_STATUSES = new Set(['DEPLOYED', 'DEPLOY_FAILED', 'CANCEL']);
|
|
37
|
+
export function sleep(ms) {
|
|
38
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 发布等待循环:CREATE 后轮询 query;INITED 时触发 update(复用现有发布流程);
|
|
42
|
+
* 终态、--no-wait 受理(DEPLOYING)或超时返回最近一次 ticket。
|
|
43
|
+
* 温和退避(5s→15s 封顶)降低轮询请求量;连续网络错误快速失败。
|
|
44
|
+
*/
|
|
45
|
+
export async function waitDeployResult(deployId, cfg, options = {}) {
|
|
46
|
+
const noWait = options.noWait === true;
|
|
47
|
+
const timeoutMs = options.timeoutMs ?? (noWait ? 5 * 60 * 1000 : 8 * 60 * 1000);
|
|
48
|
+
const baseIntervalMs = options.intervalMs ?? 5000;
|
|
49
|
+
const startedAt = Date.now();
|
|
50
|
+
let triggered = false;
|
|
51
|
+
let lastError;
|
|
52
|
+
let polls = 0;
|
|
53
|
+
let netErrors = 0;
|
|
54
|
+
for (;;) {
|
|
55
|
+
const r = await queryDeployTicket(deployId, cfg);
|
|
56
|
+
polls += 1;
|
|
57
|
+
// 网络故障快速失败:断网/网关不可达时空转到数分钟超时没有价值。
|
|
58
|
+
if (r.status === 0) {
|
|
59
|
+
netErrors += 1;
|
|
60
|
+
if (netErrors >= 3) {
|
|
61
|
+
lastError = `连续 ${netErrors} 次网络错误:${r.errorMessage ?? 'network'}`;
|
|
62
|
+
return { ticket: undefined, triggered, timedOut: false, lastError };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
netErrors = 0;
|
|
67
|
+
}
|
|
68
|
+
const ticket = r.ok ? r.data : undefined;
|
|
69
|
+
const status = typeof ticket?.['status'] === 'string' ? ticket['status'] : undefined;
|
|
70
|
+
if (!r.ok) {
|
|
71
|
+
lastError = r.errorMessage ?? r.errorCode ?? `HTTP ${r.status}`;
|
|
72
|
+
// 快速失败:单据不存在/无权限时继续轮询没有意义(避免长达数分钟的空转)。
|
|
73
|
+
const notFound = r.status === 404
|
|
74
|
+
|| r.errorCode === 'NOT_FOUND'
|
|
75
|
+
|| /not found|不存在|无权限|forbidden/iu.test(String(lastError));
|
|
76
|
+
if (notFound) {
|
|
77
|
+
return { ticket: ticket ?? undefined, triggered, timedOut: false, lastError };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (status && APP_DEPLOY_TERMINAL_STATUSES.has(status)) {
|
|
81
|
+
return { ticket: ticket ?? undefined, triggered, timedOut: false, lastError };
|
|
82
|
+
}
|
|
83
|
+
if (status === 'INITED' && !triggered) {
|
|
84
|
+
// 安全门禁通过:触发现有发布流程(INITED → DEPLOYING,服务端异步执行)。
|
|
85
|
+
const up = await triggerDeployTicket(deployId, cfg);
|
|
86
|
+
triggered = true;
|
|
87
|
+
if (!up.ok)
|
|
88
|
+
lastError = up.errorMessage ?? up.errorCode ?? `HTTP ${up.status}`; // 触发失败语义保留(不重触发)
|
|
89
|
+
const updated = up.ok ? up.data : undefined;
|
|
90
|
+
const next = typeof updated?.['status'] === 'string' ? updated['status'] : undefined;
|
|
91
|
+
if (next && APP_DEPLOY_TERMINAL_STATUSES.has(next))
|
|
92
|
+
return { ticket: updated, triggered, timedOut: false };
|
|
93
|
+
if (updated && noWait)
|
|
94
|
+
return { ticket: updated, triggered, timedOut: false };
|
|
95
|
+
}
|
|
96
|
+
if (noWait && (triggered || (status && status !== 'INIT'))) {
|
|
97
|
+
return { ticket: ticket ?? undefined, triggered, timedOut: false, lastError };
|
|
98
|
+
}
|
|
99
|
+
if (Date.now() - startedAt > timeoutMs)
|
|
100
|
+
return { ticket: ticket ?? undefined, triggered, timedOut: true, lastError };
|
|
101
|
+
// 温和退避:每 4 轮 ×1.6,封顶 15s(8 分钟窗口轮询请求减半以上)。
|
|
102
|
+
const intervalMs = Math.min(baseIntervalMs * Math.pow(1.6, Math.floor(polls / 4)), 15000);
|
|
103
|
+
await sleep(Math.round(intervalMs));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 沙箱与预览域(C07):POST /api/v1/sandbox/*(SandboxController)。
|
|
3
|
+
* 本轮只接只读查询与显式重启;ensure 依赖云环境,随联调窗口接入。
|
|
4
|
+
*/
|
|
5
|
+
import { apiCall } from '../client.js';
|
|
6
|
+
export function ensureSandbox(appId, sprintId, cfg) {
|
|
7
|
+
return apiCall('POST', '/api/v1/sandbox/ensureSandbox', { appId, sprintId }, cfg);
|
|
8
|
+
}
|
|
9
|
+
/** 需要 sandboxId(来自 dev 会话/socketInfo),静态查询无法凭空获得。 */
|
|
10
|
+
export function previewInfo(appId, sandboxId, cfg) {
|
|
11
|
+
return apiCall('POST', '/api/v1/sandbox/previewInfo', { appId, sandboxId }, cfg);
|
|
12
|
+
}
|
|
13
|
+
export function previewHealth(appId, sandboxId, cfg) {
|
|
14
|
+
return apiCall('POST', '/api/v1/sandbox/previewHealth', { appId, sandboxId }, cfg);
|
|
15
|
+
}
|
|
16
|
+
export function querySandboxSetting(appId, cfg) {
|
|
17
|
+
return apiCall('POST', '/api/v1/sandbox/queryAppSandboxSetting', { appId }, cfg);
|
|
18
|
+
}
|
|
19
|
+
/** restart 契约为 {chatId}(服务端按 CHAT WRITE 授权从上下文解析)。 */
|
|
20
|
+
export function restartPreview(chatId, cfg) {
|
|
21
|
+
return apiCall('POST', '/api/v1/sandbox/restartPreview', { chatId }, cfg);
|
|
22
|
+
}
|
|
23
|
+
/** 构建/预览产物状态(构建日志入口,C07.14 诊断日志来源之一)。 */
|
|
24
|
+
export function buildArtifactsStatus(appId, options = {}, cfg) {
|
|
25
|
+
return apiCall('POST', '/api/v1/sandbox/buildArtifactsStatus', {
|
|
26
|
+
appId,
|
|
27
|
+
...(options.sprintId ? { sprintId: options.sprintId } : {}),
|
|
28
|
+
...(options.versionId ? { versionId: options.versionId } : {}),
|
|
29
|
+
...(options.buildId ? { buildId: options.buildId } : {}),
|
|
30
|
+
...(options.artifactTarget ? { artifactTarget: options.artifactTarget } : {}),
|
|
31
|
+
}, cfg);
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 秘密域(C16):POST /api/appwrite/secrets/*(AppwriteSecretController)。
|
|
3
|
+
* 列表包含同步状态字段;删除/重试同步为显式写操作。
|
|
4
|
+
*/
|
|
5
|
+
import { apiCall } from '../client.js';
|
|
6
|
+
export function listSecrets(appId, cfg, env = 'dev') {
|
|
7
|
+
// env 透传:prod 视角查看生产侧密钥与同步状态(服务端按 env 解析绑定)。
|
|
8
|
+
return apiCall('POST', '/api/appwrite/secrets/list', { appId, env }, cfg);
|
|
9
|
+
}
|
|
10
|
+
/** 停用/启用密钥(删除前必须先停用且同步完成;停用会同步删除云上变量)。 */
|
|
11
|
+
export function disableSecret(appId, secretId, cfg, env = 'dev') {
|
|
12
|
+
return apiCall('POST', '/api/appwrite/secrets/disable', { appId, env, secretId }, cfg);
|
|
13
|
+
}
|
|
14
|
+
export function enableSecret(appId, secretId, cfg, env = 'dev') {
|
|
15
|
+
return apiCall('POST', '/api/appwrite/secrets/enable', { appId, env, secretId }, cfg);
|
|
16
|
+
}
|
|
17
|
+
export function deleteSecret(appId, secretId, cfg, env = 'dev') {
|
|
18
|
+
return apiCall('POST', '/api/appwrite/secrets/delete', { appId, env, secretId }, cfg);
|
|
19
|
+
}
|
|
20
|
+
export function retrySecretSync(appId, secretId, cfg, env = 'dev') {
|
|
21
|
+
return apiCall('POST', '/api/appwrite/secrets/retry-sync', { appId, env, secretId }, cfg);
|
|
22
|
+
}
|
|
23
|
+
export function createSecret(appId, key, value, cfg, description) {
|
|
24
|
+
return apiCall('POST', '/api/appwrite/secrets/create', {
|
|
25
|
+
appId,
|
|
26
|
+
key,
|
|
27
|
+
value,
|
|
28
|
+
...(description ? { description } : {}),
|
|
29
|
+
}, cfg);
|
|
30
|
+
}
|
package/dist/app/sse.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 服务端 SSE 文本帧解析。
|
|
3
|
+
*
|
|
4
|
+
* 只做与业务无关的帧拆分:
|
|
5
|
+
* - `: comment`(例如心跳)跳过且不计入事件
|
|
6
|
+
* - `data: [DONE]` 通知流结束
|
|
7
|
+
* - `data: <json>` 逐帧回调,JSON 解析失败按文本帧回调
|
|
8
|
+
* - 兼容 CRLF 与一个 data 字段拆多行的情况(多行按 SSE 规范用 \n 连接)
|
|
9
|
+
*/
|
|
10
|
+
/** 按逐字符增量解析 SSE 文本并回调完整帧;返回是否已被消费方要求停止。 */
|
|
11
|
+
export class SseParser {
|
|
12
|
+
onFrame;
|
|
13
|
+
buffer = '';
|
|
14
|
+
/** 已完整回调的数据帧数量(不含心跳与 DONE)。 */
|
|
15
|
+
frameCount = 0;
|
|
16
|
+
constructor(onFrame) {
|
|
17
|
+
this.onFrame = onFrame;
|
|
18
|
+
}
|
|
19
|
+
/** 追加一段增量文本;返回 true 表示消费方要求停止读取。 */
|
|
20
|
+
push(chunk) {
|
|
21
|
+
this.buffer += chunk;
|
|
22
|
+
let index;
|
|
23
|
+
while ((index = this.buffer.indexOf('\n')) >= 0) {
|
|
24
|
+
let line = this.buffer.slice(0, index);
|
|
25
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
26
|
+
if (line.endsWith('\r'))
|
|
27
|
+
line = line.slice(0, -1);
|
|
28
|
+
const stopped = this.handleLine(line);
|
|
29
|
+
if (stopped)
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/** 流结束时冲刷剩余未换行的内容。 */
|
|
35
|
+
end() {
|
|
36
|
+
const line = this.buffer.replace(/\r$/u, '');
|
|
37
|
+
this.buffer = '';
|
|
38
|
+
if (line)
|
|
39
|
+
this.handleLine(line);
|
|
40
|
+
}
|
|
41
|
+
/** 返回 true 表示消费方要求停止读取。 */
|
|
42
|
+
handleLine(line) {
|
|
43
|
+
if (!line || line.startsWith(':'))
|
|
44
|
+
return false;
|
|
45
|
+
if (line.startsWith('event:'))
|
|
46
|
+
return false;
|
|
47
|
+
if (!line.startsWith('data:'))
|
|
48
|
+
return false;
|
|
49
|
+
let data = line.slice(5);
|
|
50
|
+
if (data.startsWith(' '))
|
|
51
|
+
data = data.slice(1);
|
|
52
|
+
if (data === '[DONE]') {
|
|
53
|
+
this.onFrame({ event: '', data: null });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
this.frameCount += 1;
|
|
57
|
+
return this.onFrame({ event: '', data }) === true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function parseChatEvent(data) {
|
|
61
|
+
const trimmed = data.trim();
|
|
62
|
+
if (!trimmed)
|
|
63
|
+
return '';
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(trimmed);
|
|
66
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
return trimmed;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return trimmed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function chatEventType(event) {
|
|
76
|
+
if (typeof event === 'string')
|
|
77
|
+
return '';
|
|
78
|
+
const value = event.__type ?? event.type ?? event.stage ?? '';
|
|
79
|
+
return typeof value === 'string' ? value : String(value ?? '');
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 版本与源码导出域(C08):POST /api/appVersion/findAllByAppIdDesc、GET /api/app/code。
|
|
3
|
+
* 服务端仅提供“导出最新已保存版本源码”的能力(AppController.getCode),CLI 侧做对应限界。
|
|
4
|
+
*/
|
|
5
|
+
import { apiCall } from '../client.js';
|
|
6
|
+
export function listAppVersions(appId, cfg) {
|
|
7
|
+
return apiCall('POST', '/api/appVersion/findAllByAppIdDesc', { appId }, cfg);
|
|
8
|
+
}
|
|
9
|
+
export function fetchSourceFiles(appId, cfg) {
|
|
10
|
+
const base = cfg.baseUrl.replace(/\/+$/, '') + (cfg.apiPrefix || '');
|
|
11
|
+
return apiCall('GET', `/api/app/code?appId=${encodeURIComponent(appId)}`, undefined, cfg);
|
|
12
|
+
}
|