@luckypiggy/zhihu-ai-cli 0.1.0-test.1 → 0.2.1-test.4
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 +32 -20
- package/assets/codex-skills/zhihu-aiops/SKILL.md +32 -16
- package/assets/codex-skills/zhihu-aiops/references/commands.md +28 -14
- package/assets/codex-skills/zhihu-aiops/references/errors.md +1 -1
- package/assets/codex-skills/zhihu-aiops/references/safety.md +6 -4
- package/dist/auth-service.js +71 -83
- package/dist/cli.js +226 -22
- package/dist/codex-skill.js +3 -3
- package/dist/config.js +14 -3
- package/dist/contracts.js +3 -1
- package/dist/credentials.js +152 -29
- package/dist/http.js +413 -67
- package/dist/install.js +310 -59
- package/dist/profile-lock.js +32 -3
- package/dist/release.js +24 -0
- package/dist/terminal-prompt.js +191 -0
- package/dist/token-manager.js +36 -15
- package/package.json +4 -2
- package/dist/browser.js +0 -49
- package/dist/pkce.js +0 -15
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@ import { existsSync, lstatSync } from "node:fs";
|
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { CLI_VERSION, MIN_NODE_MAJOR, } from "./contracts.js";
|
|
5
|
+
import { CLI_VERSION, CLI_PACKAGE_NAME, MIN_NODE_MAJOR, } from "./contracts.js";
|
|
6
|
+
import { CLI_DOCTOR_COMMAND, CLI_RUNNER } from "./release.js";
|
|
6
7
|
import { ConfigError, DEFAULT_PROFILE, DEFAULT_PROFILE_NAME, checkProfile, configRoot, guardProfileConnectionChange, initializeProfile, ProfileConnectionGuardError, readConfigSnapshot, readProfileRaw, validateProfile, } from "./config.js";
|
|
7
8
|
import { withProfileLock } from "./profile-lock.js";
|
|
8
9
|
import { emitError, emitSuccess } from "./output.js";
|
|
@@ -13,6 +14,7 @@ import { IntegrationHttpClient, HttpError, validateIntegrationInfo, } from "./ht
|
|
|
13
14
|
import { CliAuthError, TokenManager, mapFailure } from "./token-manager.js";
|
|
14
15
|
import { CodexSkillError, inspectCodexSkill, installCodexSkill, uninstallCodexSkill, } from "./codex-skill.js";
|
|
15
16
|
import { InstallError, installWizard } from "./install.js";
|
|
17
|
+
import { promptTerminalCredentials, TerminalPromptCancelledError } from "./terminal-prompt.js";
|
|
16
18
|
class CliFailure extends Error {
|
|
17
19
|
shape;
|
|
18
20
|
constructor(shape) {
|
|
@@ -120,7 +122,7 @@ function versionData() {
|
|
|
120
122
|
cli_version: CLI_VERSION,
|
|
121
123
|
node_version: process.versions.node,
|
|
122
124
|
node_supported: major >= MIN_NODE_MAJOR,
|
|
123
|
-
package:
|
|
125
|
+
package: CLI_PACKAGE_NAME,
|
|
124
126
|
stage: "p1-readonly",
|
|
125
127
|
};
|
|
126
128
|
}
|
|
@@ -208,16 +210,20 @@ export async function doctorData(environment = process.env, options = {}) {
|
|
|
208
210
|
try {
|
|
209
211
|
client = options.clientFactory ? options.clientFactory(profile) : new IntegrationHttpClient(profile);
|
|
210
212
|
const info = validateIntegrationInfo(await client.info(), false);
|
|
211
|
-
const
|
|
213
|
+
const legacyAuth = info.auth?.mode === "pilot_oauth_bff";
|
|
214
|
+
const ready = info.status === "ready"
|
|
215
|
+
&& info.auth?.configured === true
|
|
216
|
+
&& info.auth?.mode === "pilot_terminal_password";
|
|
212
217
|
serviceValidatedReady = ready;
|
|
213
218
|
service = {
|
|
214
219
|
ok: ready,
|
|
215
|
-
state: ready ? "ready" : "configuration_required",
|
|
220
|
+
state: ready ? "ready" : legacyAuth ? "terminal_login_required" : "configuration_required",
|
|
216
221
|
service: info.service,
|
|
217
222
|
protocol_version: info.protocol_version,
|
|
218
223
|
minimum_cli_version: info.minimum_cli_version,
|
|
219
224
|
compatible: true,
|
|
220
225
|
status: info.status,
|
|
226
|
+
...(legacyAuth ? { detail: "This Pilot exposes legacy browser OAuth; terminal login is required for this CLI" } : {}),
|
|
221
227
|
capabilities: info.capabilities,
|
|
222
228
|
};
|
|
223
229
|
network = { ok: true, state: "reachable" };
|
|
@@ -326,21 +332,50 @@ function authService(flags) {
|
|
|
326
332
|
throw mapFailure(error);
|
|
327
333
|
}
|
|
328
334
|
const credentials = new SecureCredentialStore(selected.name);
|
|
329
|
-
return new AuthService(client, credentials, selected.name,
|
|
330
|
-
onBrowserFailure: (authorizationUrl) => {
|
|
331
|
-
process.stderr.write(`Browser launch failed. Open this URL manually: ${authorizationUrl}\n`);
|
|
332
|
-
},
|
|
333
|
-
});
|
|
335
|
+
return { service: new AuthService(client, credentials, selected.name), profile: selected.profile, profileName: selected.name };
|
|
334
336
|
}
|
|
335
|
-
async function authCommand(action, flags) {
|
|
337
|
+
async function authCommand(action, flags, json = false) {
|
|
336
338
|
if (action !== "login" && action !== "status" && action !== "logout")
|
|
337
339
|
invalid("Use auth login, auth status, or auth logout");
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
for (const key of flags.keys())
|
|
341
|
+
if (key !== "profile")
|
|
342
|
+
invalid(`Unknown auth option: --${key}`);
|
|
343
|
+
const selected = authService(flags);
|
|
344
|
+
if (action === "login") {
|
|
345
|
+
if (json || !(process.stdin.isTTY
|
|
346
|
+
&& process.stderr.isTTY)) {
|
|
347
|
+
throw new CliFailure({ code: "INTERACTIVE_REQUIRED", message: "Interactive terminal input is required for login; do not pass credentials through flags or environment variables", retryable: false, exit_code: 2 });
|
|
348
|
+
}
|
|
349
|
+
const controller = new AbortController();
|
|
350
|
+
const onSignal = () => controller.abort();
|
|
351
|
+
process.once("SIGINT", onSignal);
|
|
352
|
+
process.once("SIGTERM", onSignal);
|
|
353
|
+
try {
|
|
354
|
+
const credentials = await promptTerminalCredentials({
|
|
355
|
+
usernameLabel: selected.profile.language === "zh" ? "智护用户名" : "Zhihu username",
|
|
356
|
+
tenantLabel: selected.profile.language === "zh" ? "租户 ID(可留空)" : "Tenant id (optional)",
|
|
357
|
+
captchaLabel: selected.profile.language === "zh" ? "验证码(可留空)" : "Captcha verification (optional)",
|
|
358
|
+
passwordLabel: selected.profile.language === "zh" ? "智护密码:" : "Zhihu password: ",
|
|
359
|
+
defaultUsername: selected.profile.username,
|
|
360
|
+
defaultTenantId: selected.profile.tenant_id,
|
|
361
|
+
signal: controller.signal,
|
|
362
|
+
});
|
|
363
|
+
return await selected.service.login(credentials, { signal: controller.signal });
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (error instanceof TerminalPromptCancelledError) {
|
|
367
|
+
throw new CliFailure({ code: "USER_CANCELLED", message: "Login cancelled", retryable: false, exit_code: 2 });
|
|
368
|
+
}
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
process.removeListener("SIGINT", onSignal);
|
|
373
|
+
process.removeListener("SIGTERM", onSignal);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
341
376
|
if (action === "status")
|
|
342
|
-
return service.status();
|
|
343
|
-
return service.logout();
|
|
377
|
+
return selected.service.status();
|
|
378
|
+
return selected.service.logout();
|
|
344
379
|
}
|
|
345
380
|
async function capabilitiesCommand(action, flags) {
|
|
346
381
|
if (action !== "list")
|
|
@@ -357,6 +392,108 @@ async function capabilitiesCommand(action, flags) {
|
|
|
357
392
|
const tokenManager = new TokenManager(client, credentials, selected.name);
|
|
358
393
|
return tokenManager.callReadOnly((accessToken) => client.capabilities(accessToken));
|
|
359
394
|
}
|
|
395
|
+
const A2A_CAPABILITIES = new Set([
|
|
396
|
+
"resource_query",
|
|
397
|
+
"incident_analysis",
|
|
398
|
+
"inspection_summary",
|
|
399
|
+
"knowledge_qa",
|
|
400
|
+
]);
|
|
401
|
+
function a2aTextOption(flags, key, maxChars, maxBytes) {
|
|
402
|
+
const value = flags.get(key);
|
|
403
|
+
if (typeof value !== "string")
|
|
404
|
+
invalid(`--${key} requires a value`);
|
|
405
|
+
const normalized = value.trim();
|
|
406
|
+
if (!normalized || Array.from(value).length > maxChars || Buffer.byteLength(value, "utf8") > maxBytes
|
|
407
|
+
|| /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value)) {
|
|
408
|
+
invalid(`--${key} must be non-empty text up to ${maxChars} characters and ${maxBytes} UTF-8 bytes`);
|
|
409
|
+
}
|
|
410
|
+
return normalized;
|
|
411
|
+
}
|
|
412
|
+
function a2aTaskIdOption(flags) {
|
|
413
|
+
const value = flags.get("id");
|
|
414
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9:_-]{1,128}$/.test(value)) {
|
|
415
|
+
invalid("--id must be an opaque A2A task id");
|
|
416
|
+
}
|
|
417
|
+
return value;
|
|
418
|
+
}
|
|
419
|
+
function a2aHumanSummary(data, language) {
|
|
420
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
421
|
+
return undefined;
|
|
422
|
+
const value = data;
|
|
423
|
+
if (typeof value.task !== "string" || typeof value.target !== "string" || typeof value.result !== "string")
|
|
424
|
+
return undefined;
|
|
425
|
+
if (language === "zh") {
|
|
426
|
+
return [`目标员工:${value.target}`, `任务:${value.task}`, `结果:${value.result}`].join("\n");
|
|
427
|
+
}
|
|
428
|
+
return [`Target employee: ${value.target}`, `Task: ${value.task}`, `Result: ${value.result}`].join("\n");
|
|
429
|
+
}
|
|
430
|
+
async function a2aCommand(command, flags) {
|
|
431
|
+
const [_, action, subaction] = command;
|
|
432
|
+
const allowed = new Set(["profile", "capability", "message", "id"]);
|
|
433
|
+
for (const key of flags.keys())
|
|
434
|
+
if (!allowed.has(key))
|
|
435
|
+
invalid(`Unknown a2a option: --${key}`);
|
|
436
|
+
let operation;
|
|
437
|
+
let capability;
|
|
438
|
+
let message;
|
|
439
|
+
let taskId;
|
|
440
|
+
if (action === "capabilities" && command.length === 2) {
|
|
441
|
+
if (["capability", "message", "id"].some((key) => flags.has(key))) {
|
|
442
|
+
invalid("a2a capabilities does not accept --capability, --message, or --id");
|
|
443
|
+
}
|
|
444
|
+
operation = "capabilities";
|
|
445
|
+
}
|
|
446
|
+
else if (action === "delegate" && command.length === 2) {
|
|
447
|
+
if (flags.has("id"))
|
|
448
|
+
invalid("a2a delegate does not accept --id");
|
|
449
|
+
const rawCapability = a2aTextOption(flags, "capability", 128, 128);
|
|
450
|
+
if (!A2A_CAPABILITIES.has(rawCapability)) {
|
|
451
|
+
invalid("--capability must be resource_query, incident_analysis, inspection_summary, or knowledge_qa");
|
|
452
|
+
}
|
|
453
|
+
capability = rawCapability;
|
|
454
|
+
message = a2aTextOption(flags, "message", 8000, 16 * 1024);
|
|
455
|
+
operation = "delegate";
|
|
456
|
+
}
|
|
457
|
+
else if (action === "task" && subaction === "get" && command.length === 3) {
|
|
458
|
+
if (flags.has("capability") || flags.has("message"))
|
|
459
|
+
invalid("a2a task get does not accept --capability or --message");
|
|
460
|
+
taskId = a2aTaskIdOption(flags);
|
|
461
|
+
operation = "task";
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
invalid("Use a2a capabilities, a2a delegate --capability <id> --message <text>, or a2a task get --id <id>");
|
|
465
|
+
}
|
|
466
|
+
const selected = selectedProfile(flags);
|
|
467
|
+
let client;
|
|
468
|
+
try {
|
|
469
|
+
client = new IntegrationHttpClient(selected.profile);
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
throw mapFailure(error);
|
|
473
|
+
}
|
|
474
|
+
const credentials = new SecureCredentialStore(selected.name);
|
|
475
|
+
const tokenManager = new TokenManager(client, credentials, selected.name);
|
|
476
|
+
const controller = new AbortController();
|
|
477
|
+
const onSignal = () => controller.abort();
|
|
478
|
+
process.once("SIGINT", onSignal);
|
|
479
|
+
process.once("SIGTERM", onSignal);
|
|
480
|
+
try {
|
|
481
|
+
if (operation === "capabilities") {
|
|
482
|
+
const data = await tokenManager.callReadOnly((accessToken) => client.a2aCapabilities(accessToken, { signal: controller.signal }));
|
|
483
|
+
return { data };
|
|
484
|
+
}
|
|
485
|
+
if (operation === "delegate") {
|
|
486
|
+
const data = await tokenManager.callReadOnly((accessToken) => client.a2aDelegate(capability, message, accessToken, { signal: controller.signal }));
|
|
487
|
+
return { data, human: a2aHumanSummary(data, selected.profile.language) };
|
|
488
|
+
}
|
|
489
|
+
const data = await tokenManager.callReadOnly((accessToken) => client.a2aTask(taskId, accessToken, { signal: controller.signal }));
|
|
490
|
+
return { data, human: a2aHumanSummary(data, selected.profile.language) };
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
process.removeListener("SIGINT", onSignal);
|
|
494
|
+
process.removeListener("SIGTERM", onSignal);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
360
497
|
async function employeesCommand(action, flags) {
|
|
361
498
|
if (action !== "list")
|
|
362
499
|
invalid("Use employees list");
|
|
@@ -861,7 +998,7 @@ function codexSkillCommand(action, flags) {
|
|
|
861
998
|
}
|
|
862
999
|
async function installCommand(flags, json) {
|
|
863
1000
|
const allowed = new Set([
|
|
864
|
-
"profile", "base-url", "
|
|
1001
|
+
"profile", "base-url", "timeout", "language", "ca-file", "codex-skill-dir",
|
|
865
1002
|
"skip-login", "skip-codex", "force",
|
|
866
1003
|
]);
|
|
867
1004
|
for (const key of flags.keys()) {
|
|
@@ -881,11 +1018,14 @@ async function installCommand(flags, json) {
|
|
|
881
1018
|
invalid(`--${key} requires a value`);
|
|
882
1019
|
return value;
|
|
883
1020
|
};
|
|
1021
|
+
const controller = new AbortController();
|
|
1022
|
+
const onSignal = () => controller.abort();
|
|
1023
|
+
process.on("SIGINT", onSignal);
|
|
1024
|
+
process.on("SIGTERM", onSignal);
|
|
884
1025
|
try {
|
|
885
1026
|
const result = await installWizard({
|
|
886
1027
|
profile: stringOption("profile"),
|
|
887
1028
|
baseUrl: stringOption("base-url"),
|
|
888
|
-
authMode: stringOption("auth-mode"),
|
|
889
1029
|
timeout: stringOption("timeout"),
|
|
890
1030
|
language: stringOption("language"),
|
|
891
1031
|
caFile: stringOption("ca-file"),
|
|
@@ -893,13 +1033,19 @@ async function installCommand(flags, json) {
|
|
|
893
1033
|
skipLogin: flags.get("skip-login") === true,
|
|
894
1034
|
skipCodex: flags.get("skip-codex") === true,
|
|
895
1035
|
force: flags.get("force") === true,
|
|
1036
|
+
signal: controller.signal,
|
|
896
1037
|
json,
|
|
897
1038
|
doctor: (environment, profileName, profile) => doctorData(environment, {
|
|
898
1039
|
...(profileName ? { profile: profileName } : {}),
|
|
899
1040
|
...(profile ? { profileConfig: profile } : {}),
|
|
900
1041
|
}),
|
|
901
1042
|
});
|
|
902
|
-
|
|
1043
|
+
const data = { ...result };
|
|
1044
|
+
// Keep the existing JSON install envelope stable; the locale is used only
|
|
1045
|
+
// for the human summary and is intentionally not a new automation field.
|
|
1046
|
+
if (json)
|
|
1047
|
+
delete data.language;
|
|
1048
|
+
return data;
|
|
903
1049
|
}
|
|
904
1050
|
catch (error) {
|
|
905
1051
|
if (error instanceof InstallError) {
|
|
@@ -912,6 +1058,56 @@ async function installCommand(flags, json) {
|
|
|
912
1058
|
}
|
|
913
1059
|
throw error;
|
|
914
1060
|
}
|
|
1061
|
+
finally {
|
|
1062
|
+
process.removeListener("SIGINT", onSignal);
|
|
1063
|
+
process.removeListener("SIGTERM", onSignal);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
export function installHumanSummary(data) {
|
|
1067
|
+
const zh = data.language === "zh";
|
|
1068
|
+
const profile = typeof data.profile === "string" ? data.profile : "-";
|
|
1069
|
+
const baseUrl = typeof data.base_url === "string" ? data.base_url : "-";
|
|
1070
|
+
const login = data.login?.status === "logged_in"
|
|
1071
|
+
? (zh ? "已登录" : "logged in")
|
|
1072
|
+
: (zh ? "已跳过" : "skipped");
|
|
1073
|
+
const skillStatus = data.codex?.status;
|
|
1074
|
+
const skill = skillStatus === "unchanged"
|
|
1075
|
+
? (zh ? "已安装(可重复运行相同命令)" : "already installed (safe to rerun)")
|
|
1076
|
+
: skillStatus === "updated"
|
|
1077
|
+
? (zh ? "已更新" : "updated")
|
|
1078
|
+
: skillStatus === "skipped"
|
|
1079
|
+
? (zh ? "已跳过" : "skipped")
|
|
1080
|
+
: (zh ? "已安装" : "installed");
|
|
1081
|
+
if (zh) {
|
|
1082
|
+
return [
|
|
1083
|
+
"安装完成",
|
|
1084
|
+
`Profile:${profile}`,
|
|
1085
|
+
`智护地址:${baseUrl}`,
|
|
1086
|
+
`登录:${login}`,
|
|
1087
|
+
`Codex Skill:${skill}`,
|
|
1088
|
+
"请新开一个 Codex 会话,让它重新发现智护 Skill。",
|
|
1089
|
+
"可以试试:",
|
|
1090
|
+
"- 用智护列出当前活动告警。",
|
|
1091
|
+
"- 用智护查看资产 123 最近一小时 CPU 趋势。",
|
|
1092
|
+
"- 用智护分析告警 456 的根因。",
|
|
1093
|
+
`排查安装:${CLI_DOCTOR_COMMAND}`,
|
|
1094
|
+
`Skill runner:${CLI_RUNNER}`,
|
|
1095
|
+
].join("\n");
|
|
1096
|
+
}
|
|
1097
|
+
return [
|
|
1098
|
+
"Installation complete",
|
|
1099
|
+
`Profile: ${profile}`,
|
|
1100
|
+
`Pilot: ${baseUrl}`,
|
|
1101
|
+
`Login: ${login}`,
|
|
1102
|
+
`Codex Skill: ${skill}`,
|
|
1103
|
+
"Open a new Codex session so it can discover the Zhihu Skill.",
|
|
1104
|
+
"Try natural-language requests:",
|
|
1105
|
+
"- List the current firing alarms.",
|
|
1106
|
+
"- Show the last hour of CPU trend for asset 123.",
|
|
1107
|
+
"- Diagnose the root cause of alarm 456.",
|
|
1108
|
+
`Troubleshoot installation: ${CLI_DOCTOR_COMMAND}`,
|
|
1109
|
+
`Skill runner: ${CLI_RUNNER}`,
|
|
1110
|
+
].join("\n");
|
|
915
1111
|
}
|
|
916
1112
|
function helpData() {
|
|
917
1113
|
return {
|
|
@@ -924,6 +1120,9 @@ function helpData() {
|
|
|
924
1120
|
"auth status",
|
|
925
1121
|
"auth logout",
|
|
926
1122
|
"capabilities list",
|
|
1123
|
+
"a2a capabilities",
|
|
1124
|
+
"a2a delegate --capability <id> --message <text>",
|
|
1125
|
+
"a2a task get --id <opaque-task-id>",
|
|
927
1126
|
"employees list",
|
|
928
1127
|
"employees chat <employee-id> --message <text>",
|
|
929
1128
|
"assets list [--keyword <text>] [--model <name|code|id>] [--page N] [--page-size N]",
|
|
@@ -933,12 +1132,12 @@ function helpData() {
|
|
|
933
1132
|
"alarms get --id <id>",
|
|
934
1133
|
"incidents diagnose --alarm-id <id>",
|
|
935
1134
|
"inspections run [--model <name|code|id>] [--window 5m..24h]",
|
|
936
|
-
"install [--profile <name>] [--base-url <https-url>] [--
|
|
1135
|
+
"install [--profile <name>] [--base-url <https-url>] [--timeout N] [--language zh-CN|en] [--ca-file <absolute-file>] [--codex-skill-dir <absolute-path>] [--skip-login] [--skip-codex] [--force]",
|
|
937
1136
|
"install codex [--profile <name>] [--codex-skill-dir <absolute-path>] [--force]",
|
|
938
1137
|
"uninstall codex [--profile <name>] [--codex-skill-dir <absolute-path>] [--force]",
|
|
939
1138
|
],
|
|
940
1139
|
json: "Append --json for the stable success/error envelope",
|
|
941
|
-
note: "Authentication plus read-only employee, asset, metric, alarm, incident diagnosis, inspection queries, local installation, and the Codex Skill installer are available;
|
|
1140
|
+
note: "Authentication plus read-only employee, asset, metric, alarm, incident diagnosis, inspection queries, local installation, and the Codex Skill installer are available; login is terminal-only and passwords are never accepted through flags or environment variables; formal GA, write operations, and MCP are not available",
|
|
942
1141
|
};
|
|
943
1142
|
}
|
|
944
1143
|
async function run(parsed) {
|
|
@@ -966,7 +1165,8 @@ async function run(parsed) {
|
|
|
966
1165
|
return { data: await configInit(parsed.flags), exit_code: 0 };
|
|
967
1166
|
if (first === "install") {
|
|
968
1167
|
if (!second && parsed.command.length === 1) {
|
|
969
|
-
|
|
1168
|
+
const data = await installCommand(parsed.flags, parsed.json);
|
|
1169
|
+
return { data, exit_code: 0, ...(parsed.json ? {} : { human: installHumanSummary(data) }) };
|
|
970
1170
|
}
|
|
971
1171
|
if (second !== "codex" || parsed.command.length !== 2)
|
|
972
1172
|
invalid("Use install or install codex");
|
|
@@ -978,7 +1178,11 @@ async function run(parsed) {
|
|
|
978
1178
|
return { data: codexSkillCommand("uninstall", parsed.flags), exit_code: 0 };
|
|
979
1179
|
}
|
|
980
1180
|
if (first === "auth")
|
|
981
|
-
return { data: await authCommand(second, parsed.flags), exit_code: 0 };
|
|
1181
|
+
return { data: await authCommand(second, parsed.flags, parsed.json), exit_code: 0 };
|
|
1182
|
+
if (first === "a2a") {
|
|
1183
|
+
const result = await a2aCommand(parsed.command, parsed.flags);
|
|
1184
|
+
return { data: result.data, exit_code: 0, ...(parsed.json || !result.human ? {} : { human: result.human }) };
|
|
1185
|
+
}
|
|
982
1186
|
if (first === "capabilities") {
|
|
983
1187
|
if (parsed.command.length !== 2)
|
|
984
1188
|
invalid("Use capabilities list");
|
package/dist/codex-skill.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { CLI_VERSION, } from "./contracts.js";
|
|
6
|
+
import { CLI_PACKAGE_NAME, CLI_VERSION, } from "./contracts.js";
|
|
7
7
|
import { ConfigError, readConfigSnapshot, validateProfile, } from "./config.js";
|
|
8
8
|
export const CODEX_SKILL_NAME = "zhihu-aiops";
|
|
9
9
|
export const CODEX_SKILL_VERSION = CLI_VERSION;
|
|
@@ -118,7 +118,7 @@ function markerFor(files) {
|
|
|
118
118
|
hashes[file] = sha256(files.get(file));
|
|
119
119
|
return {
|
|
120
120
|
owner: "zhihu-ai-cli",
|
|
121
|
-
package:
|
|
121
|
+
package: CLI_PACKAGE_NAME,
|
|
122
122
|
skill: CODEX_SKILL_NAME,
|
|
123
123
|
schema: CODEX_SKILL_SCHEMA,
|
|
124
124
|
schema_version: CODEX_SKILL_SCHEMA,
|
|
@@ -219,7 +219,7 @@ function validMarker(value) {
|
|
|
219
219
|
if (Object.keys(object).some((key) => !allowedKeys.has(key)))
|
|
220
220
|
return false;
|
|
221
221
|
if (object.owner !== "zhihu-ai-cli"
|
|
222
|
-
|| object.package !==
|
|
222
|
+
|| object.package !== CLI_PACKAGE_NAME
|
|
223
223
|
|| object.skill !== CODEX_SKILL_NAME
|
|
224
224
|
|| object.schema !== CODEX_SKILL_SCHEMA
|
|
225
225
|
|| object.schema_version !== CODEX_SKILL_SCHEMA
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { chmodSync, lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { CONFIG_SCHEMA_VERSION } from "./contracts.js";
|
|
5
|
+
import { cliCommand } from "./release.js";
|
|
5
6
|
export const DEFAULT_PROFILE_NAME = "default";
|
|
6
7
|
export const DEFAULT_PROFILE = {
|
|
7
8
|
base_url: "",
|
|
@@ -110,7 +111,7 @@ function validateBaseUrl(value) {
|
|
|
110
111
|
}
|
|
111
112
|
export function validateProfile(value) {
|
|
112
113
|
const object = objectOrEmpty(value ?? {}, "Profile configuration");
|
|
113
|
-
const allowed = new Set(["base_url", "timeout_seconds", "language", "ca_bundle", "codex_skill_dir"]);
|
|
114
|
+
const allowed = new Set(["base_url", "timeout_seconds", "language", "ca_bundle", "codex_skill_dir", "username", "tenant_id"]);
|
|
114
115
|
for (const key of Object.keys(object)) {
|
|
115
116
|
if (!allowed.has(key))
|
|
116
117
|
throw new ConfigError(`Profile field ${key} is not supported`);
|
|
@@ -124,12 +125,22 @@ export function validateProfile(value) {
|
|
|
124
125
|
throw new ConfigError("Profile language must be zh or en");
|
|
125
126
|
const caBundle = optionalString(object, "ca_bundle");
|
|
126
127
|
const codexSkillDir = optionalString(object, "codex_skill_dir");
|
|
128
|
+
const username = optionalString(object, "username");
|
|
129
|
+
const tenantId = optionalString(object, "tenant_id");
|
|
130
|
+
if (username !== undefined && (username.length === 0 || /[\u0000-\u001f\u007f]/.test(username))) {
|
|
131
|
+
throw new ConfigError("Profile field username is invalid");
|
|
132
|
+
}
|
|
133
|
+
if (tenantId !== undefined && (tenantId.length === 0 || !/^[A-Za-z0-9_-]{1,128}$/.test(tenantId))) {
|
|
134
|
+
throw new ConfigError("Profile field tenant_id is invalid");
|
|
135
|
+
}
|
|
127
136
|
return {
|
|
128
137
|
base_url: validateBaseUrl(object.base_url),
|
|
129
138
|
timeout_seconds: timeout,
|
|
130
139
|
language,
|
|
131
140
|
...(caBundle ? { ca_bundle: caBundle } : {}),
|
|
132
141
|
...(codexSkillDir ? { codex_skill_dir: codexSkillDir } : {}),
|
|
142
|
+
...(username ? { username } : {}),
|
|
143
|
+
...(tenantId ? { tenant_id: tenantId } : {}),
|
|
133
144
|
};
|
|
134
145
|
}
|
|
135
146
|
/**
|
|
@@ -158,13 +169,13 @@ export async function guardProfileConnectionChange(profileName, previous, next,
|
|
|
158
169
|
}
|
|
159
170
|
const credential = await store.get();
|
|
160
171
|
if (credential) {
|
|
161
|
-
throw new ProfileConnectionGuardError("AUTH_ALREADY_LOGGED_IN", `Profile ${profileName} has an active login on another Pilot origin; run
|
|
172
|
+
throw new ProfileConnectionGuardError("AUTH_ALREADY_LOGGED_IN", `Profile ${profileName} has an active login on another Pilot origin; run ${cliCommand("auth", "logout", "--profile", profileName)} or use a new profile`);
|
|
162
173
|
}
|
|
163
174
|
}
|
|
164
175
|
catch (error) {
|
|
165
176
|
if (error instanceof ProfileConnectionGuardError)
|
|
166
177
|
throw error;
|
|
167
|
-
throw new ProfileConnectionGuardError("CREDENTIAL_STORE_UNAVAILABLE", `Unable to verify existing credentials for profile ${profileName}; run
|
|
178
|
+
throw new ProfileConnectionGuardError("CREDENTIAL_STORE_UNAVAILABLE", `Unable to verify existing credentials for profile ${profileName}; run ${cliCommand("auth", "logout", "--profile", profileName)} before changing the Pilot origin`);
|
|
168
179
|
}
|
|
169
180
|
}
|
|
170
181
|
export function writeJsonAtomic(path, value) {
|
package/dist/contracts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { CLI_PACKAGE_NAME, CLI_VERSION, CLI_RUNNER, CLI_DOCTOR_COMMAND, cliCommand, } from "./release.js";
|
|
2
2
|
export const OUTPUT_SCHEMA_VERSION = "1.0";
|
|
3
3
|
export const CONFIG_SCHEMA_VERSION = "1.0";
|
|
4
4
|
export const MIN_NODE_MAJOR = 22;
|
|
@@ -11,5 +11,7 @@ export const P1_CAPABILITY_SCOPES = [
|
|
|
11
11
|
"metrics:read",
|
|
12
12
|
"alarms:read",
|
|
13
13
|
"incidents:analyze",
|
|
14
|
+
"a2a:delegate:read",
|
|
15
|
+
"a2a:task:read",
|
|
14
16
|
"inspections:run-readonly",
|
|
15
17
|
];
|