@limecloud/cli 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -2
- package/dist/identity.json +826 -57
- package/dist/knowledge.json +1 -0
- package/dist/management.json +8531 -3480
- package/dist/skills.json +1 -0
- package/package.json +2 -2
- package/src/action-contracts.mjs +63 -0
- package/src/cli.mjs +115 -31
- package/src/commands.mjs +6 -3
- package/src/compatibility.mjs +89 -0
- package/src/contract.mjs +12 -4
- package/src/diagnose.mjs +99 -0
- package/src/group-task.mjs +137 -0
- package/src/knowledge.mjs +171 -0
- package/src/locales/en.json +68 -4
- package/src/locales/zh-CN.json +68 -4
- package/src/management.mjs +7 -0
- package/src/output.mjs +8 -5
- package/src/product-skills.mjs +146 -0
- package/src/protocol.mjs +2 -1
- package/src/public-json.mjs +17 -0
- package/src/storage.mjs +19 -2
package/src/diagnose.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { commandOperations, inspectSchema } from "./commands.mjs";
|
|
2
|
+
import { attachContractCompatibility } from "./compatibility.mjs";
|
|
3
|
+
import { prepareManagement, validateJSON } from "./management.mjs";
|
|
4
|
+
import { CLIError } from "./protocol.mjs";
|
|
5
|
+
import { currentLocalizer, message, translate } from "./i18n.mjs";
|
|
6
|
+
import { displayText, outputOptions, success } from "./output.mjs";
|
|
7
|
+
|
|
8
|
+
// 任务适配只组织现有公开GET,不创建另一份应用、客户端或授权事实。
|
|
9
|
+
export async function prepareLoginDiagnosis(contract, parsed, env) {
|
|
10
|
+
const environment = parsed.get("environment");
|
|
11
|
+
const limit = parsed.get("limit") ?? "20";
|
|
12
|
+
const cursor = parsed.get("cursor");
|
|
13
|
+
if (!["sandbox", "production"].includes(environment) || !/^(?:[1-9]|[1-4][0-9]|50)$/.test(limit) ||
|
|
14
|
+
cursor && (cursor.length > 1024 || /[\x00-\x20\x7f]/.test(cursor))) {
|
|
15
|
+
throw new CLIError("invalid_arguments", message("diagnosis.usage"));
|
|
16
|
+
}
|
|
17
|
+
const scope = { space_id: parsed.get("space-id"), application_id: parsed.get("app-id"), environment };
|
|
18
|
+
const catalogue = commandOperations(contract);
|
|
19
|
+
const operations = ["open.login_diagnosis.get"].map(command => {
|
|
20
|
+
const operation = catalogue.find(item => item.command === command);
|
|
21
|
+
if (!operation || operation.method !== "GET" || operation["x-lime-cli-origin"] !== "api") {
|
|
22
|
+
throw new CLIError("invalid_contract", message("diagnosis.contractUnavailable"));
|
|
23
|
+
}
|
|
24
|
+
return operation;
|
|
25
|
+
});
|
|
26
|
+
const flags = new Map(parsed.flags);
|
|
27
|
+
flags.set("limit", [limit]);
|
|
28
|
+
const inputs = [];
|
|
29
|
+
for (const operation of operations) inputs.push(await prepareManagement(contract, operation,
|
|
30
|
+
{ flags, get: name => flags.get(name)?.[0] }, env));
|
|
31
|
+
return { scope, operation: operations[0], input: inputs[0], limit: Number(limit), cursor: cursor ?? null };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function diagnoseLogin(plan, { contract, context, read, fetchCatalog, now = Date.now, signal } = {}) {
|
|
35
|
+
const started = new Date(now()).toISOString();
|
|
36
|
+
const report = { schema_version: 2, task: "application-login-diagnosis", status: "blocked", login_verified: false,
|
|
37
|
+
scope: { issuer: context.issuer, api_origin: context.apiOrigin, ...plan.scope },
|
|
38
|
+
started_at: started, observed_at: started,
|
|
39
|
+
coverage: { complete: false, common_snapshot: false, first_page: !plan.cursor, limit: plan.limit, returned: 0, next_cursor: null },
|
|
40
|
+
application: null, clients: [], checks: [], evidence: [], next_steps: [] };
|
|
41
|
+
const finish = () => { report.observed_at = new Date(now()).toISOString(); return report; };
|
|
42
|
+
const [method] = await attachContractCompatibility([inspectSchema(contract, [plan.operation.command])], contract,
|
|
43
|
+
{ online: true, issuer: context.issuer, apiOrigin: context.apiOrigin, fetchCatalog, now, signal });
|
|
44
|
+
const compatibility = { operation_id: method._meta.operationId, contract_revision: method._meta.contract_revision, ...method._meta.compatibility };
|
|
45
|
+
const contractsMatch = compatibility.status === "matched";
|
|
46
|
+
report.contract_check = { check: "contracts", status: contractsMatch ? "observed" : "blocked",
|
|
47
|
+
message: translate(contractsMatch ? "diagnosis.contractsObserved" : "diagnosis.contractsBlocked"), details: [compatibility] };
|
|
48
|
+
if (!contractsMatch) return finish();
|
|
49
|
+
const transport = { operation_id: plan.operation.operationId, method: plan.operation.method,
|
|
50
|
+
url: context.apiOrigin + plan.input.path + (plan.input.query.size ? "?" + plan.input.query : ""),
|
|
51
|
+
contract_revision: compatibility.contract_revision, started_at: new Date(now()).toISOString() };
|
|
52
|
+
try {
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
const response = await read(plan.operation, plan.input);
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
try {
|
|
57
|
+
validateJSON(contract, plan.operation.responses["200"].content["application/json"].schema, response, "response");
|
|
58
|
+
const value = response.data;
|
|
59
|
+
if (value.scope.space_id !== plan.scope.space_id || value.scope.application_id !== plan.scope.application_id || value.scope.environment !== plan.scope.environment ||
|
|
60
|
+
value.application.id !== plan.scope.application_id || value.application.space_id !== plan.scope.space_id ||
|
|
61
|
+
value.clients.length > plan.limit || value.coverage.returned !== value.clients.length || value.coverage.limit !== plan.limit ||
|
|
62
|
+
value.coverage.first_page !== !plan.cursor || value.coverage.complete && (plan.cursor || value.coverage.next_cursor !== null) ||
|
|
63
|
+
value.coverage.next_cursor !== null && (!value.coverage.next_cursor || /[\x00-\x20\x7f]/.test(value.coverage.next_cursor))) throw new Error("invalid diagnosis scope or coverage");
|
|
64
|
+
} catch { throw new CLIError("invalid_response", message("diagnosis.invalidResponse")); }
|
|
65
|
+
transport.observed_at = new Date(now()).toISOString(); transport.status = "observed";
|
|
66
|
+
Object.assign(report, response.data, { scope: { ...report.scope, ...response.data.scope }, transport });
|
|
67
|
+
if (report.coverage.next_cursor) report.next_steps.push({ reason: "next_page", executable: "limecloud-cli", arguments: ["diagnose", "login",
|
|
68
|
+
"--issuer", context.issuer, "--api-origin", context.apiOrigin, ...(context.clientID ? ["--auth-client-id", context.clientID] : []),
|
|
69
|
+
"--space-id", plan.scope.space_id, "--app-id", plan.scope.application_id, "--environment", plan.scope.environment,
|
|
70
|
+
"--limit", String(plan.limit), "--cursor", report.coverage.next_cursor, "--locale", currentLocalizer().locale] });
|
|
71
|
+
report.next_steps.push({ reason: "login_guide", executable: "limecloud-cli", arguments: ["docs", "read", "oauth-clients", "--locale", currentLocalizer().locale] });
|
|
72
|
+
return report;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (signal?.aborted) throw error;
|
|
75
|
+
const code = error instanceof CLIError && /^[A-Za-z0-9_.:-]{1,80}$/.test(error.code) ? error.code : "source_unavailable";
|
|
76
|
+
report.transport = { ...transport, observed_at: new Date(now()).toISOString(), status: "unavailable", error_code: code,
|
|
77
|
+
...(Number.isInteger(error.status) ? { http_status: error.status } : {}) };
|
|
78
|
+
report.checks.push({ check: "diagnosis", status: "unavailable", error_code: code, message: translate("diagnosis.sourceUnavailable", { code }) });
|
|
79
|
+
return finish();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function writeDiagnosis(io, report, parsed) {
|
|
84
|
+
const options = outputOptions(parsed);
|
|
85
|
+
const checks = [report.contract_check, ...report.checks].filter(Boolean);
|
|
86
|
+
if (options.format === "json") { success(io, report, { ...options, identity: "user" }); return; }
|
|
87
|
+
if (options.format === "table") {
|
|
88
|
+
for (const line of [translate("diagnosis.context", { ...report.scope }), translate("diagnosis.targets", { ...report.scope }),
|
|
89
|
+
translate("diagnosis.conclusion", { status: report.status, observed: report.observed_at }), translate("diagnosis.observationOnly")]) {
|
|
90
|
+
io.stdout.write(displayText(line) + "\n");
|
|
91
|
+
}
|
|
92
|
+
success(io, checks, { ...options, fields: options.fields ?? ["check", "status", "message"], identity: "user" });
|
|
93
|
+
// 下一页仅提供结构化参数供人或宿主选择,不自动遍历或执行。
|
|
94
|
+
if (report.coverage.next_cursor) io.stdout.write(translate("diagnosis.nextCursor") + "\n" + displayText(report.coverage.next_cursor) + "\n");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
success(io, checks.map(check => ({ ...check, task: report.task, task_status: report.status, scope: report.scope,
|
|
98
|
+
observed_at: report.observed_at, login_verified: false, coverage: report.coverage, evidence: report.evidence, next_steps: report.next_steps })), { ...options, identity: "user" });
|
|
99
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { actionContract } from "./action-contracts.mjs";
|
|
3
|
+
import { commandOperations } from "./commands.mjs";
|
|
4
|
+
import { message, translate } from "./i18n.mjs";
|
|
5
|
+
import { prepareManagement, validateJSON } from "./management.mjs";
|
|
6
|
+
import { CLIError } from "./protocol.mjs";
|
|
7
|
+
import { PrivateJSONStore } from "./storage.mjs";
|
|
8
|
+
import { displayText, outputOptions, success } from "./output.mjs";
|
|
9
|
+
|
|
10
|
+
const validID = value => typeof value === "string" && /^[A-Za-z0-9_-]{1,48}$/.test(value);
|
|
11
|
+
const digest = value => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
12
|
+
const actions = new Set(["prepare", "get", "execute", "receipt", "resolve"]);
|
|
13
|
+
const referenceKeys = new Set(["schema_version", "issuer", "api_origin", "client_id", "application_id", "audience", "environment", "actor_id", "space_id", "preparation_id", "target_id", "execution_contract_revision", "confirmation_digest", "expires_at", "execute_attempted_at"]);
|
|
14
|
+
const fail = key => { throw new CLIError("invalid_arguments", message(key)); };
|
|
15
|
+
|
|
16
|
+
export async function prepareGroupTask(contract, parsed, env, readInput) {
|
|
17
|
+
const [task, resource, action, ...extra] = parsed.positionals;
|
|
18
|
+
const id = parsed.get("preparation-id"), space = parsed.get("space-id");
|
|
19
|
+
if (task !== "task" || resource !== "group" || !actions.has(action) || extra.length || !validID(space) ||
|
|
20
|
+
(action === "prepare" ? id !== undefined : !validID(id)) ||
|
|
21
|
+
(action === "execute" ? !digest(parsed.get("confirm")) : parsed.get("confirm") !== undefined) ||
|
|
22
|
+
(action !== "prepare" && parsed.get("data") !== undefined)) fail("groupTask.usage");
|
|
23
|
+
const operation = commandOperations(contract).find(item => item.command === `open.group_change.${action}`);
|
|
24
|
+
if (!operation) throw new CLIError("invalid_contract", message("groupTask.contractUnavailable"));
|
|
25
|
+
const flags = new Map([["space-id", [space]], ...(id ? [["preparation-id", [id]]] : [])]);
|
|
26
|
+
if (action === "prepare") flags.set("data", [parsed.get("data")]);
|
|
27
|
+
if (action === "execute") flags.set("data", [JSON.stringify({ preview_token: parsed.get("confirm") })]);
|
|
28
|
+
if (action === "resolve") flags.set("data", ["{}"]);
|
|
29
|
+
const input = await prepareManagement(contract, operation, { flags, get: name => flags.get(name)?.[0] }, env, readInput);
|
|
30
|
+
return { action, id, space, operation, input, confirmation: parsed.get("confirm") };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function reference(prepared, context) {
|
|
34
|
+
return { schema_version: 1, issuer: context.issuer, api_origin: context.apiOrigin,
|
|
35
|
+
client_id: prepared.invocation.client_id, application_id: prepared.invocation.application_id,
|
|
36
|
+
audience: prepared.invocation.audience, environment: prepared.invocation.environment,
|
|
37
|
+
actor_id: prepared.actor_id, space_id: prepared.space_id, preparation_id: prepared.id,
|
|
38
|
+
target_id: prepared.command.group.id, execution_contract_revision: prepared.invocation.contract_revision,
|
|
39
|
+
confirmation_digest: prepared.preview.token, expires_at: prepared.expires_at };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function recovery(ref) {
|
|
43
|
+
const base = ["task", "group"];
|
|
44
|
+
const scope = ["--space-id", ref.space_id, "--preparation-id", ref.preparation_id];
|
|
45
|
+
return { preparation_id: ref.preparation_id, operation_key: ref.preparation_id,
|
|
46
|
+
issuer: ref.issuer, api_origin: ref.api_origin, client_id: ref.client_id, actor_id: ref.actor_id,
|
|
47
|
+
space_id: ref.space_id, execution_contract_revision: ref.execution_contract_revision,
|
|
48
|
+
commands: Object.fromEntries(["get", "receipt", "resolve"].map(action => [action, ["limecloud-cli", ...base, action, ...scope,
|
|
49
|
+
"--issuer", ref.issuer, "--api-origin", ref.api_origin, "--auth-client-id", ref.client_id]])),
|
|
50
|
+
message: translate("groupTask.recoveryHint") };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function present(value) {
|
|
54
|
+
if (Array.isArray(value)) return value.map(present);
|
|
55
|
+
if (!value || typeof value !== "object") return value;
|
|
56
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key === "token" ? "confirmation_digest" : key, present(item)]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 本地只保存恢复定位和发送标记,不缓存命令、影响、组状态、回执或任何认证凭据。
|
|
60
|
+
// 每次执行先重新读取原准备;发送前持久标记,一次尝试后只允许查询/终结,不自动重发。
|
|
61
|
+
export async function executeGroupTask(plan, { contract, context, call, referenceDirectory,
|
|
62
|
+
referenceStore = id => new PrivateJSONStore(join(referenceDirectory, `${id}.json`), { durable: true }), now = Date.now } = {}) {
|
|
63
|
+
if (!context.apiOrigin || !context.clientID) throw new CLIError("group_context_required", message("groupTask.contextRequired"));
|
|
64
|
+
const catalogue = commandOperations(contract);
|
|
65
|
+
async function invoke(operation, input, beforeSend) {
|
|
66
|
+
const response = await call(operation, input, beforeSend);
|
|
67
|
+
try { validateJSON(contract, operation.responses["200"].content["application/json"].schema, response, "response"); }
|
|
68
|
+
catch { throw new CLIError("invalid_response", message("groupTask.invalidResponse")); }
|
|
69
|
+
return response.data;
|
|
70
|
+
}
|
|
71
|
+
function verifyPrepared(value, id) {
|
|
72
|
+
if (value.id !== id || value.space_id !== plan.space || value.invocation.client_id !== context.clientID || value.invocation.audience !== context.apiOrigin ||
|
|
73
|
+
value.preview.preparation_id !== id || value.preview.target_id !== value.command.group.id || value.command.kind !== "group" ||
|
|
74
|
+
Date.parse(value.expires_at) - Date.parse(value.prepared_at) !== 300000) {
|
|
75
|
+
throw new CLIError("invalid_response", message("groupTask.invalidResponse"));
|
|
76
|
+
}
|
|
77
|
+
return reference(value, context);
|
|
78
|
+
}
|
|
79
|
+
function result(ref, key, value) {
|
|
80
|
+
return { task: "member-group-change", [key]: present(value), recovery: recovery(ref),
|
|
81
|
+
message: translate(key === "preparation" ? "groupTask.reviewHint" : "groupTask.resultHint") };
|
|
82
|
+
}
|
|
83
|
+
if (plan.action === "prepare") {
|
|
84
|
+
const prepared = await invoke(plan.operation, plan.input);
|
|
85
|
+
if (!validID(prepared.id)) throw new CLIError("invalid_response", message("groupTask.invalidResponse"));
|
|
86
|
+
const ref = verifyPrepared(prepared, prepared.id), store = referenceStore(prepared.id);
|
|
87
|
+
try { await store.withLock(async () => {
|
|
88
|
+
if (await store.read()) throw new CLIError("group_reference_conflict", message("groupTask.referenceConflict"));
|
|
89
|
+
await store.write(ref);
|
|
90
|
+
}); } catch (error) { error.recovery = recovery(ref); throw error; }
|
|
91
|
+
return result(ref, "preparation", prepared);
|
|
92
|
+
}
|
|
93
|
+
const store = referenceStore(plan.id);
|
|
94
|
+
return store.withLock(async () => {
|
|
95
|
+
let ref = await store.read();
|
|
96
|
+
if (ref && (Object.keys(ref).some(key => !referenceKeys.has(key)) || ["client_id", "application_id", "actor_id", "space_id", "preparation_id", "target_id"].some(key => !validID(ref[key])) ||
|
|
97
|
+
!digest(ref.confirmation_digest) || !/^sha256:[a-f0-9]{64}$/.test(ref.execution_contract_revision) || !Number.isFinite(Date.parse(ref.expires_at)) ||
|
|
98
|
+
ref.execute_attempted_at !== undefined && !Number.isFinite(Date.parse(ref.execute_attempted_at)) ||
|
|
99
|
+
ref.schema_version !== 1 || ref.issuer !== context.issuer || ref.api_origin !== context.apiOrigin || ref.client_id !== context.clientID ||
|
|
100
|
+
ref.space_id !== plan.space || ref.preparation_id !== plan.id)) throw new CLIError("group_reference_conflict", message("groupTask.referenceConflict"));
|
|
101
|
+
if (plan.action === "execute" && !ref) throw new CLIError("group_reference_required", message("groupTask.referenceRequired"));
|
|
102
|
+
if (plan.action === "execute" && ref.execute_attempted_at) {
|
|
103
|
+
const error = new CLIError("group_execution_already_attempted", message("groupTask.alreadyAttempted"));
|
|
104
|
+
error.recovery = recovery(ref); throw error;
|
|
105
|
+
}
|
|
106
|
+
const readOperation = catalogue.find(item => item.command === "open.group_change.get");
|
|
107
|
+
if (!readOperation) throw new CLIError("invalid_contract", message("groupTask.contractUnavailable"));
|
|
108
|
+
const prepared = await invoke(readOperation, { path: readOperation.path.replace("{spaceID}", plan.space).replace("{preparationID}", plan.id), query: new URLSearchParams() });
|
|
109
|
+
const current = verifyPrepared(prepared, plan.id);
|
|
110
|
+
if (ref && Object.keys(current).some(key => current[key] !== ref[key])) throw new CLIError("group_reference_conflict", message("groupTask.referenceConflict"));
|
|
111
|
+
if (!ref) { ref = current; await store.write(ref); }
|
|
112
|
+
if (plan.action === "get") return result(ref, "preparation", prepared);
|
|
113
|
+
if (plan.action === "execute" && (plan.confirmation !== ref.confirmation_digest ||
|
|
114
|
+
ref.execution_contract_revision !== actionContract(contract, plan.operation).contract_revision)) {
|
|
115
|
+
const error = new CLIError("group_confirmation_changed", message("groupTask.confirmationChanged"));
|
|
116
|
+
error.recovery = recovery(ref); throw error;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const value = await invoke(plan.operation, plan.input, plan.action === "execute" ? async () => {
|
|
120
|
+
ref = { ...ref, execute_attempted_at: new Date(now()).toISOString() };
|
|
121
|
+
await store.write(ref);
|
|
122
|
+
} : undefined);
|
|
123
|
+
const receipt = plan.action === "resolve" ? value.receipt : value;
|
|
124
|
+
if (receipt && (receipt.operation_key !== plan.id || receipt.preview.preparation_id !== plan.id || receipt.preview.token !== ref.confirmation_digest || receipt.result.id !== ref.target_id) ||
|
|
125
|
+
plan.action === "resolve" && value.cancellation && value.cancellation.operation_key !== plan.id) throw new CLIError("invalid_response", message("groupTask.invalidResponse"));
|
|
126
|
+
return result(ref, plan.action === "resolve" ? "resolution" : "receipt", value);
|
|
127
|
+
} catch (error) { error.recovery = recovery(ref); throw error; }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function writeGroupTask(io, value, parsed) {
|
|
132
|
+
const options = outputOptions(parsed);
|
|
133
|
+
if (options.format !== "table") { success(io, value, { ...options, identity: "user" }); return; }
|
|
134
|
+
// 核对表不截断完整命令、影响或恢复引用,避免通用80列裁剪隐藏风险。
|
|
135
|
+
const rows = Object.entries(value).map(([key, item]) => `${displayText(key)} ${displayText(item)}`);
|
|
136
|
+
io.stdout.write(`${translate("groupTask.tableHeading")}\n${rows.join("\n")}\n`);
|
|
137
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { currentLocalizer, defaultLocale, message, translate } from "./i18n.mjs";
|
|
3
|
+
import { CLIError, issuerOrigin } from "./protocol.mjs";
|
|
4
|
+
import { outputOptions, success } from "./output.mjs";
|
|
5
|
+
import { fetchPublicJSON } from "./public-json.mjs";
|
|
6
|
+
|
|
7
|
+
const articleID = /^[a-z0-9-]{1,80}$/;
|
|
8
|
+
const revisionID = /^sha256:[a-f0-9]{64}$/;
|
|
9
|
+
const publicOrigin = "https://open.limeai.run";
|
|
10
|
+
const fail = key => { throw new CLIError("invalid_arguments", message(key)); };
|
|
11
|
+
const loadPublicBundle = async () => JSON.parse(await readFile(new URL("../dist/knowledge.json", import.meta.url), "utf8"));
|
|
12
|
+
|
|
13
|
+
// 仅读取公开部署,无认证头、Cookie、重定向、自动登录或持久缓存。
|
|
14
|
+
export async function fetchPublicKnowledge(url, { signal } = {}) {
|
|
15
|
+
try {
|
|
16
|
+
return await fetchPublicJSON(url, { signal });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (signal?.aborted) throw error;
|
|
19
|
+
throw new CLIError("knowledge_unavailable", message("knowledge.unavailable"));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validateArticle(article, requestedLocale, body) {
|
|
24
|
+
if (!article || !articleID.test(article.id) || typeof article.title !== "string" || typeof article.summary !== "string" ||
|
|
25
|
+
article.visibility !== "public" || article.lifecycle !== "published" || !revisionID.test(article.revision) ||
|
|
26
|
+
article.requested_locale !== requestedLocale || ![requestedLocale, defaultLocale].includes(article.locale) ||
|
|
27
|
+
article.fallback !== (article.locale !== requestedLocale) || !["guide", "tutorial", "api", "client", "cli"].includes(article.group) || !Array.isArray(article.sections) ||
|
|
28
|
+
!Array.isArray(article.related_operations) || article.related_operations.length > 256 ||
|
|
29
|
+
new Set(article.related_operations).size !== article.related_operations.length || !article.related_operations.every(id => typeof id === "string" && /^[a-z][a-z0-9_]{0,199}$/.test(id)) ||
|
|
30
|
+
!article.sections.every(section => /^[a-z0-9-]{1,100}$/.test(section.id) && typeof section.title === "string") ||
|
|
31
|
+
body && typeof article.markdown !== "string") throw new CLIError("knowledge_invalid", message("knowledge.invalid"));
|
|
32
|
+
try {
|
|
33
|
+
const source = new URL(article.source_url);
|
|
34
|
+
if (source.href !== `${publicOrigin}/docs/${article.id}?locale=${article.locale}`) throw new Error("source");
|
|
35
|
+
} catch { throw new CLIError("knowledge_invalid", message("knowledge.invalid")); }
|
|
36
|
+
return article;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function snapshotMetadata(snapshot, locale, body = false) {
|
|
40
|
+
if (!snapshot || snapshot.schema_version !== 1 || snapshot.visibility !== "public" || snapshot.locale !== locale ||
|
|
41
|
+
typeof snapshot.software_version !== "string" || !revisionID.test(snapshot.revision) || !Array.isArray(snapshot.articles) || snapshot.articles.length > 5000) {
|
|
42
|
+
throw new CLIError("knowledge_invalid", message("knowledge.invalid"));
|
|
43
|
+
}
|
|
44
|
+
const ids = new Set();
|
|
45
|
+
for (const article of snapshot.articles) {
|
|
46
|
+
validateArticle(article, locale, body);
|
|
47
|
+
if (ids.has(article.id)) throw new CLIError("knowledge_invalid", message("knowledge.invalid"));
|
|
48
|
+
ids.add(article.id);
|
|
49
|
+
}
|
|
50
|
+
return snapshot;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function loadBundledKnowledge({ loadBundle = loadPublicBundle, locale = currentLocalizer().locale } = {}) {
|
|
54
|
+
let bundle;
|
|
55
|
+
try { bundle = await loadBundle(); } catch { throw new CLIError("knowledge_unavailable", message("knowledge.unavailable")); }
|
|
56
|
+
return snapshotMetadata(bundle?.snapshots?.find(item => item.locale === locale), locale, true);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function attachSchemaKnowledge(schema, options = {}) {
|
|
60
|
+
let snapshot;
|
|
61
|
+
try { snapshot = await loadBundledKnowledge(options); }
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!["knowledge_unavailable", "knowledge_invalid"].includes(error.code)) throw error;
|
|
64
|
+
// 知识是补充资料;缺失不改写或阻断仍然有效的本地动作合同。
|
|
65
|
+
const unavailable = method => ({ ...method, _meta: { ...method._meta, knowledge: {
|
|
66
|
+
status: "unavailable", distribution: "bundled", current_online: false, error_code: error.code, articles: [],
|
|
67
|
+
} } });
|
|
68
|
+
return Array.isArray(schema) ? schema.map(unavailable) : unavailable(schema);
|
|
69
|
+
}
|
|
70
|
+
const attach = method => ({ ...method, _meta: { ...method._meta, knowledge: {
|
|
71
|
+
status: "available",
|
|
72
|
+
distribution: "bundled", current_online: false, software_version: snapshot.software_version, snapshot_revision: snapshot.revision,
|
|
73
|
+
articles: snapshot.articles.filter(article => article.related_operations.includes(method._meta.operationId)).map(article => ({
|
|
74
|
+
id: article.id, title: article.title, revision: article.revision, source_url: article.source_url,
|
|
75
|
+
locale: article.locale, requested_locale: article.requested_locale, fallback: article.fallback,
|
|
76
|
+
})),
|
|
77
|
+
} } });
|
|
78
|
+
return Array.isArray(schema) ? schema.map(attach) : attach(schema);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function numberFlag(parsed, key, fallback, min, max) {
|
|
82
|
+
const raw = parsed.get(key);
|
|
83
|
+
if (raw === undefined) return fallback;
|
|
84
|
+
if (!/^(0|[1-9][0-9]*)$/.test(raw)) fail("knowledge.pagination");
|
|
85
|
+
const value = Number(raw);
|
|
86
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) fail("knowledge.pagination");
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function selectSection(article, id) {
|
|
91
|
+
if (!article.sections.some(section => section.id === id)) throw new CLIError("knowledge_section_not_found", message("knowledge.sectionNotFound"));
|
|
92
|
+
const index = article.sections.findIndex(section => section.id === id);
|
|
93
|
+
const start = article.markdown.indexOf(`<a id="${id}"></a>`);
|
|
94
|
+
const next = article.sections[index + 1];
|
|
95
|
+
const end = next ? article.markdown.indexOf(`<a id="${next.id}"></a>`, start + 1) : article.markdown.length;
|
|
96
|
+
if (start < 0 || end < start) throw new CLIError("knowledge_invalid", message("knowledge.invalid"));
|
|
97
|
+
return { ...article, section: id, markdown: article.markdown.slice(start, end).trim() + "\n" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function safeMarkdown(value) {
|
|
101
|
+
// 保留换行和制表符,移除终端控制序列(含OSC链接)及C0/C1字符。
|
|
102
|
+
return value.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
103
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, "");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function executeKnowledge(parsed, { io, signal, cliVersion, fetchKnowledge = fetchPublicKnowledge,
|
|
107
|
+
loadBundle = loadPublicBundle } = {}) {
|
|
108
|
+
const [, action, target, ...extra] = parsed.positionals;
|
|
109
|
+
const get = parsed.get;
|
|
110
|
+
const read = action === "read";
|
|
111
|
+
const commands = ["list", "search", "read"];
|
|
112
|
+
const knownAction = commands.includes(action);
|
|
113
|
+
const allowed = ["help", "json", "locale", ...(knownAction ? ["online", "offline", "docs-origin", "format", ...(read ? ["section", "revision"] : ["limit", "offset", "group", "fields"])] : [])];
|
|
114
|
+
for (const flag of parsed.flags.keys()) if (!allowed.includes(flag)) fail("knowledge.usage");
|
|
115
|
+
if (!knownAction) {
|
|
116
|
+
if (target || extra.length || action && !articleID.test(action)) fail("knowledge.usage");
|
|
117
|
+
success(io, { url: `${publicOrigin}/docs${action ? `/${action}` : ""}` }); return;
|
|
118
|
+
}
|
|
119
|
+
if (extra.length || read && !articleID.test(target ?? "") || action === "search" && (!target?.trim() || target.length > 256) || action === "list" && target ||
|
|
120
|
+
get("online") && get("offline") || get("docs-origin") && !get("online") || get("section") && !/^[a-z0-9-]{1,100}$/.test(get("section")) ||
|
|
121
|
+
get("revision") && !revisionID.test(get("revision"))) fail("knowledge.usage");
|
|
122
|
+
const markdown = read && (!get("format") && !get("json") || get("format") === "markdown");
|
|
123
|
+
if (markdown && get("json")) fail("knowledge.usage");
|
|
124
|
+
const output = markdown ? null : outputOptions(parsed);
|
|
125
|
+
if (read && output?.format !== "json" && !markdown) fail("knowledge.usage");
|
|
126
|
+
const limit = numberFlag(parsed, "limit", 10, 1, 50);
|
|
127
|
+
const offset = numberFlag(parsed, "offset", 0, 0, 5000);
|
|
128
|
+
if (get("group") && !["guide", "tutorial", "api", "client", "cli"].includes(get("group"))) fail("knowledge.usage");
|
|
129
|
+
const locale = currentLocalizer().locale;
|
|
130
|
+
const online = Boolean(get("online"));
|
|
131
|
+
const origin = online ? issuerOrigin(get("docs-origin") ?? publicOrigin) : publicOrigin;
|
|
132
|
+
let snapshot;
|
|
133
|
+
if (online) snapshot = snapshotMetadata(await fetchKnowledge(`${origin}/knowledge/v1/${locale}/index.json`, { signal }), locale);
|
|
134
|
+
else snapshot = await loadBundledKnowledge({ loadBundle, locale });
|
|
135
|
+
const provenance = { schema_version: 1, cli_version: cliVersion, software_version: snapshot.software_version,
|
|
136
|
+
snapshot_revision: snapshot.revision, distribution: online ? "online" : "bundled", current_online: online,
|
|
137
|
+
source_origin: origin, locale, visibility: "public", search_scope: online ? "metadata" : "article-body" };
|
|
138
|
+
if (!read) {
|
|
139
|
+
const terms = (target ?? "").trim().toLocaleLowerCase(locale).split(/\s+/u).filter(Boolean);
|
|
140
|
+
const matches = snapshot.articles.map(article => {
|
|
141
|
+
const title = `${article.id} ${article.title}`.toLocaleLowerCase(locale);
|
|
142
|
+
const summary = article.summary.toLocaleLowerCase(locale);
|
|
143
|
+
const headings = article.sections.map(section => section.title).join(" ").toLocaleLowerCase(locale);
|
|
144
|
+
const body = online ? "" : article.markdown?.toLocaleLowerCase(locale) ?? "";
|
|
145
|
+
const score = terms.reduce((sum, term) => sum + (title.includes(term) ? 100 : summary.includes(term) ? 20 : headings.includes(term) ? 10 : body.includes(term) ? 1 : 0), 0);
|
|
146
|
+
return { article, score, matches: (!get("group") || article.group === get("group")) && terms.every(term => `${title} ${summary} ${headings} ${body}`.includes(term)) };
|
|
147
|
+
}).filter(value => value.matches).sort((a, b) => b.score - a.score).map(value => value.article);
|
|
148
|
+
const rows = matches.slice(offset, offset + limit).map(({ markdown: body, sections, ...article }) => article);
|
|
149
|
+
if (output.format !== "json") io.stderr?.write(translate("knowledge.snapshotNotice", { version: snapshot.software_version, revision: snapshot.revision,
|
|
150
|
+
distribution: translate(online ? "knowledge.distributionOnline" : "knowledge.distributionBundled") }) + "\n");
|
|
151
|
+
success(io, rows, { ...output, meta: { ...provenance, total: matches.length, offset, limit,
|
|
152
|
+
next_offset: offset + rows.length < matches.length ? offset + rows.length : null, truncated: offset > 0 || offset + rows.length < matches.length } }); return;
|
|
153
|
+
}
|
|
154
|
+
const indexed = snapshot.articles.find(article => article.id === target);
|
|
155
|
+
if (!indexed) throw new CLIError("knowledge_not_found", message("knowledge.notFound"));
|
|
156
|
+
let article = online ? await fetchKnowledge(`${origin}/knowledge/v1/${locale}/articles/${target}.json`, { signal }) : indexed;
|
|
157
|
+
if (online && article?.schema_version !== 1) throw new CLIError("knowledge_invalid", message("knowledge.invalid"));
|
|
158
|
+
validateArticle(article, locale, true);
|
|
159
|
+
if (article.id !== target || article.revision !== indexed.revision || get("revision") && article.revision !== get("revision")) {
|
|
160
|
+
throw new CLIError("knowledge_revision_changed", message("knowledge.revisionChanged"));
|
|
161
|
+
}
|
|
162
|
+
if (get("section")) article = selectSection(article, get("section"));
|
|
163
|
+
if (markdown) {
|
|
164
|
+
const metadata = { id: article.id, revision: article.revision, locale: article.locale, requested_locale: locale,
|
|
165
|
+
fallback: article.fallback, source_url: article.source_url, updated_at: article.updated_at, visibility: article.visibility,
|
|
166
|
+
lifecycle: article.lifecycle, applicability: article.applicability, related_operations: article.related_operations, software_version: snapshot.software_version,
|
|
167
|
+
distribution: provenance.distribution, current_online: online, snapshot_revision: snapshot.revision, cli_version: cliVersion,
|
|
168
|
+
source_origin: origin, ...(article.section ? { section: article.section } : {}) };
|
|
169
|
+
io.stdout.write(safeMarkdown("---\n" + Object.entries(metadata).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join("\n") + "\n---\n\n" + article.markdown));
|
|
170
|
+
} else success(io, article, { ...output, meta: provenance });
|
|
171
|
+
}
|
package/src/locales/en.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"protocol.contractRequired": "The server requires an exact action contract revision (ACTION_CONTRACT_REQUIRED). This request did not invoke the domain action. Review the current contract and update the caller from a trusted source; no retry was performed.",
|
|
3
|
+
"protocol.contractChanged": "The server rejected a changed action contract (ACTION_CONTRACT_CHANGED). This request did not invoke the domain action. Preserve any earlier operation with an unknown result; review the contract and trusted updates without automatically replaying it.",
|
|
4
|
+
"protocol.contractUnavailable": "The server could not determine this action contract (ACTION_CONTRACT_UNAVAILABLE). This request did not invoke the domain action. Check public discovery before an explicit new request; no retry was performed.",
|
|
5
|
+
"arguments.managedHeader": "Lime-Action-Contract is supplied by the CLI from its own action contract and cannot be overridden.",
|
|
6
|
+
"compatibility.writeDifferent": "The current contract for {{command}} differs. No write was sent. Review schema {{command}} --online and trusted CLI/Skill updates. Do not automatically replay an earlier operation with an unknown result.",
|
|
7
|
+
"compatibility.writeAbsent": "The target does not advertise {{command}}. No write was sent. Check the selected deployment and current schema; this does not establish whether you have permission.",
|
|
8
|
+
"compatibility.writeUnknown": "The current contract for {{command}} could not be verified. No write was sent. Check public contract discovery; no update, sign-in or retry was performed.",
|
|
3
9
|
"arguments.publicClientRequired": "Provide the already registered public client with --client-id",
|
|
4
10
|
"arguments.configUsage": "Use config init, config show or config set <issuer|api_origin|client_id|scope> <value>",
|
|
5
11
|
"arguments.tooMany": "Too many arguments; use --help",
|
|
@@ -116,12 +122,12 @@
|
|
|
116
122
|
"arguments.requiredPathParameter": "Required path parameter: {{name}}",
|
|
117
123
|
"arguments.unknownSchema": "Unknown schema path: {{target}}; start with schema",
|
|
118
124
|
"protocol.apiFailed": "API request failed: {{code}} (HTTP {{status}})",
|
|
119
|
-
"help.overview": "limecloud-cli — Lime Cloud CLI (@limecloud/cli)\n\nUsage:\n limecloud-cli <command> [resource] [method] [flags]\n limecloud-cli identity +login | +whoami\n limecloud-cli identity token exchange --data <json>\n limecloud-cli api <METHOD> <contract-path> [--params <json>] [--data <json>]\n limecloud-cli schema [service.resource.method | service resource method]\n\nIdentity:\n identity Public OAuth / OpenID Connect operations and +shortcuts\n\nCLI management:\n install Install this CLI and guide first-time setup\n config init | show | set: local public-client configuration\n auth login | status | whoami | refresh | logout | scopes | check\n whoami Show the local effective identity and token state\n doctor Check local configuration and contract (--offline; no network)\n docs
|
|
125
|
+
"help.overview": "limecloud-cli — Lime Cloud CLI (@limecloud/cli)\n\nUsage:\n limecloud-cli <command> [resource] [method] [flags]\n limecloud-cli identity +login | +whoami\n limecloud-cli identity token exchange --data <json>\n limecloud-cli api <METHOD> <contract-path> [--params <json>] [--data <json>]\n limecloud-cli schema [service.resource.method | service resource method]\n\nIdentity:\n identity Public OAuth / OpenID Connect operations and +shortcuts\n\nCLI management:\n install Install this CLI and guide first-time setup\n config init | show | set: local public-client configuration\n auth login | status | whoami | refresh | logout | scopes | check\n whoami Show the local effective identity and token state\n doctor Check local configuration and contract (--offline; no network)\n diagnose Read-only application login configuration diagnosis\n task Prepare, precisely confirm and recover one member-group change\n skills Inspect and export optional public method candidates; never install automatically\n docs List, search and read public knowledge; print Web URLs\n\nAPI tools:\n api Call a contract HTTP path; list/schema/call also accept operationId\n schema Inspect public methods, parameters, responses and authentication\n\n",
|
|
120
126
|
"help.apiFlags": "API flags:\n --params JSON Query JSON; supports @file or - for stdin\n --data JSON OAuth form or API JSON body; supports @file or - for stdin\n --dry-run Print a redacted request without executing or changing state\n --format FORMAT json (default), ndjson, table or csv\n --fields PATHS Comma-separated data field paths\n --json Shorthand for --format json\n --param name=value Repeat a non-sensitive protocol parameter\n --param-env name=ENV Read a sensitive protocol parameter from an environment variable\n --header name=value Set a contract-declared HTTP header\n --file PATH Binary body for image/png methods; - reads stdin\n --show-secrets Explicitly include API response credentials in stdout\n\n",
|
|
121
127
|
"help.connectionFlags": "Connection flags:\n --issuer ORIGIN Auth origin (HTTPS; HTTP only on loopback)\n --api-origin ORIGIN Explicit business API origin, bound to the saved login\n --client-id ID Already registered sign-in client ID\n --auth-client-id ID Override the OAuth client context for a business API call\n\n --locale TAG Output language (also LIMECLOUD_LOCALE, LC_ALL, LC_MESSAGES, LANG)\n\nRun limecloud-cli <command> --help or schema identity before calling a method.\n",
|
|
122
128
|
"help.auth": "Usage: auth <login|status|whoami|refresh|logout|scopes|check> [flags]\n\nlogin --client-id ID [--scope \"openid profile\"] [--installation-id ID --space-id ID]\n --no-wait: return a verification URL and save private pending state\n --resume: complete the saved authorization without exposing its device code\nstatus: local state; whoami: remote UserInfo; refresh: rotate stored credentials\nlogout [--local-only]: revoke, then forget; --local-only forgets without revocation\nscopes: read supported OIDC scopes and report locally granted scopes\ncheck --scope SCOPES: check unexpired local granted scopes (no network)\n\nAuth commands use an already registered public client. --dry-run previews login, refresh, logout or scopes without execution.\n",
|
|
123
129
|
"help.config": "Usage:\n config init\n config init --issuer ORIGIN --client-id ID [--api-origin ORIGIN] [--scope SCOPES]\n config show\n config set <issuer|api_origin|client_id|scope> <value>\n\nConfiguration stores public client settings only. Business API origin must be explicit; changing it requires a new login binding.\n",
|
|
124
|
-
"help.schema": "Usage: schema [
|
|
130
|
+
"help.schema": "Usage: schema [service[.resource[.method]] | service resource method]\nReturns inputSchema, outputSchema, local contract revision and related public guides.\n\nDefault / --offline: local contract only; no network or credentials.\n--online: check the selected issuer and API deployment with public metadata.\n --issuer ORIGIN Canonical issuer; otherwise current CLI settings\n --api-origin ORIGIN Required API target for management methods\n\n_meta.compatibility.status is not-checked / matched / different / absent / unknown. matched compares an exact action contract; it grants no permission and is not an execution gate. Knowledge links remain bundled. No update, browser launch or business call is performed.\n",
|
|
125
131
|
"help.identityShortcuts": "Identity shortcuts:\n identity +login [--scope SCOPES] [--no-wait | --resume]\n identity +whoami\n\n",
|
|
126
132
|
"help.typedMethods": "Typed API commands:\n{{methods}}\n\nUse schema {{service}}.<resource>.<method> for parameters. --dry-run executes nothing.\n",
|
|
127
133
|
"help.services": "Available services: {{services}}\n",
|
|
@@ -149,5 +155,63 @@
|
|
|
149
155
|
"install.promptAPIOrigin": "Business API origin (leave empty for identity-only use) [{{value}}]: ",
|
|
150
156
|
"install.promptClientID": "Already registered public CLI Client ID [{{value}}]: ",
|
|
151
157
|
"install.promptLogin": "Start device authorization now? You must confirm it in your browser (y/N) [{{value}}]: ",
|
|
152
|
-
"install.pathHint": "Add {{directory}} to PATH to run limecloud-cli from any terminal."
|
|
158
|
+
"install.pathHint": "Add {{directory}} to PATH to run limecloud-cli from any terminal.",
|
|
159
|
+
"knowledge.usage": "Use docs [article-slug], docs list, docs search <query>, or docs read <article-id>. Use --help for supported options.",
|
|
160
|
+
"knowledge.pagination": "--limit must be 1–50 and --offset 0–5000.",
|
|
161
|
+
"knowledge.unavailable": "Public knowledge is unavailable. Build or reinstall the CLI for its bundled snapshot, or explicitly use --online with a trusted documentation deployment. No fallback was executed.",
|
|
162
|
+
"knowledge.invalid": "The public knowledge format is unsupported or invalid. Update from the trusted source; no content was executed.",
|
|
163
|
+
"knowledge.notFound": "Article not found in this public knowledge snapshot.",
|
|
164
|
+
"knowledge.sectionNotFound": "Section not found in this article.",
|
|
165
|
+
"knowledge.revisionChanged": "The article revision changed. Read the current index and review the source before continuing; no task was replayed.",
|
|
166
|
+
"help.docs": "Usage:\n limecloud-cli docs [article-slug] Print a Web URL\n limecloud-cli docs list [--group GROUP] List public guides\n limecloud-cli docs search \"QUERY\" Bounded keyword search\n limecloud-cli docs read ARTICLE [--section ID] Read Markdown in this host\n\nOptions:\n --locale LOCALE Registered language; English fallback\n --online Explicitly read the documentation deployment\n --docs-origin URL Trusted HTTPS origin (only with --online)\n --offline Use the bundled public snapshot (the default)\n --limit N 1–50 results, default 10 (list/search)\n --offset N 0–5000 (list/search)\n --revision SHA256 Require an exact article revision (read)\n --json Structured data with provenance\n --format FORMAT json/ndjson/table/csv for list/search; markdown/json for read\n\nNo login, Skills installation, browser launch or business operation is performed. Bundled knowledge does not prove current online capability. Online search matches metadata; bundled search also matches body text.\n",
|
|
167
|
+
"knowledge.snapshotNotice": "Public knowledge: {{distribution}}, software {{version}}, {{revision}}. Guidance does not prove current online capability.",
|
|
168
|
+
"compatibility.apiOriginRequired": "Provide the target API origin with --api-origin or current CLI settings before checking management contracts. No request was sent.",
|
|
169
|
+
"compatibility.usage": "Use schema [method] --online [--issuer ORIGIN] [--api-origin ORIGIN], or --offline. Target options require --online.",
|
|
170
|
+
"knowledge.distributionOnline": "online documentation deployment",
|
|
171
|
+
"knowledge.distributionBundled": "bundled snapshot (offline)",
|
|
172
|
+
"help.relatedKnowledge": "Related public guides (bundled snapshot, offline; not an online capability check):\n{{articles}}\n",
|
|
173
|
+
"help.knowledgeUnavailable": "The bundled guide snapshot is unavailable. This is still the local action contract; online compatibility has not been checked. Build or reinstall from a trusted source for related guides.\n",
|
|
174
|
+
"help.diagnose": "Usage: diagnose login --space-id ID --app-id ID --environment sandbox|production [--limit 1..50] [--cursor CURSOR]\n\nRead-only application login configuration diagnosis. Uses the exact configured issuer and API origin. Compares public contracts, then reads the application and one client page through the delegated API. No login, update, browser, writes or automatic pagination.\nUse --format json|table|csv|ndjson. ok:true means a report was produced; inspect status and coverage. Configuration observations do not verify a real login.\n",
|
|
175
|
+
"diagnosis.usage": "Use diagnose login --space-id ID --app-id ID --environment sandbox|production [--limit 1..50] [--cursor CURSOR].",
|
|
176
|
+
"diagnosis.contractUnavailable": "This local contract does not provide the shared read-only login diagnosis action.",
|
|
177
|
+
"diagnosis.contractsObserved": "The public diagnosis action contract matches; the original owner still checks each source against current access.",
|
|
178
|
+
"diagnosis.contractsBlocked": "A current action contract could not be matched. No business data was requested.",
|
|
179
|
+
"diagnosis.invalidResponse": "The source did not return the declared response for this scope.",
|
|
180
|
+
"diagnosis.sourceUnavailable": "Source unavailable ({{code}}); its result remains unchecked.",
|
|
181
|
+
"diagnosis.context": "Space: {{space_id}} Application: {{application_id}} Environment: {{environment}}",
|
|
182
|
+
"diagnosis.conclusion": "Report: {{status}} Observed: {{observed}}",
|
|
183
|
+
"diagnosis.observationOnly": "Read-only configuration observations; separate source reads do not share a snapshot. Real login is unverified.",
|
|
184
|
+
"diagnosis.nextCursor": "Next cursor (continue explicitly with the same scope):",
|
|
185
|
+
"columns.check": "Check",
|
|
186
|
+
"columns.status": "Status",
|
|
187
|
+
"columns.message": "Message",
|
|
188
|
+
"diagnosis.targets": "Issuer: {{issuer}} API: {{api_origin}}",
|
|
189
|
+
"help.task": "Usage: limecloud-cli task group prepare --space-id ID --data @change.json\n limecloud-cli task group get|receipt|resolve --space-id ID --preparation-id ID\n limecloud-cli task group execute --space-id ID --preparation-id ID --confirm DIGEST\nReview the complete original preparation before confirming. One execution attempt is recorded locally before sending; recover by the original ID instead of repeating execution.\n",
|
|
190
|
+
"groupTask.usage": "Use task group prepare --space-id ID --data JSON, get|receipt|resolve --space-id ID --preparation-id ID, or execute with those IDs and --confirm followed by the exact confirmation digest. No replacement command, --yes, projection or implicit confirmation is supported.",
|
|
191
|
+
"groupTask.contractUnavailable": "The bundled member-group task contract is unavailable.",
|
|
192
|
+
"groupTask.contextRequired": "Configure an explicit API origin and controlled authentication Client ID before running this task.",
|
|
193
|
+
"groupTask.invalidResponse": "The member-group response does not match the original task contract or invocation binding.",
|
|
194
|
+
"groupTask.referenceConflict": "The saved recovery reference does not match this invocation or the original preparation. Preserve it and verify the original issuer, API, client, actor and organization.",
|
|
195
|
+
"groupTask.referenceRequired": "Read the original preparation with task group get to save its recovery reference before confirming execution.",
|
|
196
|
+
"groupTask.alreadyAttempted": "An execution attempt is already recorded for this original key. Read its receipt or explicitly resolve the original operation; this task will not repeat execution.",
|
|
197
|
+
"groupTask.confirmationChanged": "The confirmation digest or execution contract differs from the saved original preparation. Check or resolve the original operation before preparing a new change.",
|
|
198
|
+
"groupTask.reviewHint": "Review the actor, organization, invocation, original values, frozen command, full impact and expiry. Only after explicit review, pass this preparation ID and confirmation_digest to task group execute --confirm. The digest does not prove human consent or grant authority.",
|
|
199
|
+
"groupTask.recoveryHint": "Use the original ID to read the preparation or receipt, or explicitly resolve it. A missing receipt is not proof of non-commit. Never replace the key or automatically repeat execution, login, update or open a browser.",
|
|
200
|
+
"groupTask.resultHint": "This is the original Owner result. Recheck current authority for later actions; the local reference is not a permission or business-state cache.",
|
|
201
|
+
"groupTask.tableHeading": "Field Complete task value (values wrap; no truncation)",
|
|
202
|
+
"help.skills": "Usage: limecloud-cli skills list | inspect ID [--online --issuer ISSUER --api-origin API_ORIGIN] | export ID --host codex|skill --output-dir NEW_DIRECTORY --digest EXACT_DIGEST\nInspection is offline by default; --online compares every required action with the selected deployment without credentials. Authorization, provenance, withdrawal and host loading remain separate. The default wrapper is codex. Export does not install, enable, update or load a host plugin.\n",
|
|
203
|
+
"skills.loginTitle": "Application login configuration diagnosis",
|
|
204
|
+
"skills.loginDescription": "Guide one read-only Lime application login configuration diagnosis inside an existing Agent host. Actual login and repairs are separate tasks.",
|
|
205
|
+
"skills.groupTitle": "One organization member group change",
|
|
206
|
+
"skills.groupDescription": "Guide one group creation, rename or status change with full preparation review, explicit confirmation and original-result recovery. Membership, roles and bulk changes are separate tasks.",
|
|
207
|
+
"skills.usage": "Use skills list, skills inspect ID [--online --issuer ISSUER --api-origin API_ORIGIN], or skills export ID --output-dir NEW_DIRECTORY --digest EXACT_DIGEST; optional --host codex|skill selects a wrapper. No automatic installation or host changes.",
|
|
208
|
+
"skills.invalidArtifact": "The bundled public Skill artifact is incomplete or its integrity does not match. Obtain the CLI from a trusted source; no artifact was exported.",
|
|
209
|
+
"skills.candidateNotice": "Bundled public candidate only. Integrity matches this installed CLI; current online compatibility, provenance, withdrawal and host loading have not been verified. Inspect the complete files before separately selecting native installation or import.",
|
|
210
|
+
"skills.onlineNotice": "Only the candidate's required action contracts were compared with the selected deployment at the recorded time. Review every action and its expiry; matching is not authorization, provenance, withdrawal clearance or evidence that this method is installed or loaded. No installation, update or business action was performed.",
|
|
211
|
+
"skills.exportedNotice": "Complete candidate exported. This did not install, enable, update or load a method, change host settings or execute artifact scripts. Native plugin installation/update belongs to the selected host manager; current session loading is unknown.",
|
|
212
|
+
"skills.notInstallation": "Export to a new ordinary directory outside Agent discovery/configuration directories. Use the actual host manager for an explicitly selected installation or update.",
|
|
213
|
+
"skills.unsafeDirectory": "The export parent must be an existing physical directory without symbolic-link ancestors.",
|
|
214
|
+
"skills.confirmationChanged": "The selected artifact digest does not match. Inspect this exact wrapper and candidate before exporting.",
|
|
215
|
+
"skills.destinationExists": "The export destination already exists. Select a new directory; existing files and local changes are never overwritten.",
|
|
216
|
+
"skills.notFound": "This public method and wrapper are not included in the installed CLI. No download, installation or update was attempted."
|
|
153
217
|
}
|