@numa-tech/numa 1.12.8 → 1.12.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 +127 -4
- package/dist/application-candidates/client.d.ts +167 -0
- package/dist/application-candidates/client.js +103 -0
- package/dist/application-candidates/client.js.map +1 -0
- package/dist/application-candidates/commands.d.ts +13 -0
- package/dist/application-candidates/commands.js +225 -0
- package/dist/application-candidates/commands.js.map +1 -0
- package/dist/application-candidates/schemas.d.ts +171 -0
- package/dist/application-candidates/schemas.js +80 -0
- package/dist/application-candidates/schemas.js.map +1 -0
- package/dist/application-onboarding/client.d.ts +256 -0
- package/dist/application-onboarding/client.js +213 -0
- package/dist/application-onboarding/client.js.map +1 -0
- package/dist/application-onboarding/commands.d.ts +64 -0
- package/dist/application-onboarding/commands.js +694 -0
- package/dist/application-onboarding/commands.js.map +1 -0
- package/dist/application-onboarding/errors.d.ts +11 -0
- package/dist/application-onboarding/errors.js +51 -0
- package/dist/application-onboarding/errors.js.map +1 -0
- package/dist/application-onboarding/schemas.d.ts +1167 -0
- package/dist/application-onboarding/schemas.js +459 -0
- package/dist/application-onboarding/schemas.js.map +1 -0
- package/dist/application-onboarding/scm-credentials.d.ts +8 -0
- package/dist/application-onboarding/scm-credentials.js +98 -0
- package/dist/application-onboarding/scm-credentials.js.map +1 -0
- package/dist/application-onboarding/tui-model.d.ts +131 -0
- package/dist/application-onboarding/tui-model.js +554 -0
- package/dist/application-onboarding/tui-model.js.map +1 -0
- package/dist/application-onboarding/tui.d.ts +14 -0
- package/dist/application-onboarding/tui.js +554 -0
- package/dist/application-onboarding/tui.js.map +1 -0
- package/dist/cli.js +18 -4
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.js +134 -0
- package/dist/command-catalog.js.map +1 -1
- package/dist/web-console.js +4 -3
- package/dist/web-console.js.map +1 -1
- package/package.json +4 -4
- package/skills/numa-cli/SKILL.md +44 -0
- package/skills/numa-cli/agents/openai.yaml +4 -0
- package/skills/numa-cli/evals/evals.json +35 -0
- package/skills/numa-cli/references/pipeline-release.md +107 -0
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- 包含 Realm 角色、Client 角色和用户组的个人信息页;
|
|
11
11
|
- 使用服务端托管的 Codex 账号启动官方 Codex CLI,客户端不保存 refresh token;
|
|
12
12
|
- 通过平台授权代理发现、治理并安装私有 Nacos Skills 与 MCP Servers;
|
|
13
|
+
- 通过可恢复的 `app` 会话和统一 Web/API 契约创建新应用或录入存量仓库;
|
|
13
14
|
- 通过领域化 `media` 命令安全调用平台托管的 TTS、视频、资产和素材库服务;
|
|
14
15
|
- 兼容原有 stdio MCP 服务。
|
|
15
16
|
|
|
@@ -34,10 +35,10 @@ npm config set @numa-tech:registry=https://registry.npmjs.org/
|
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
脚本会先检查 Git 工作区干净、当前分支与 upstream 完全同步,再于修改版本号之前检查公共
|
|
37
|
-
npm 身份以及 `@tokensrc`、`@numa-tech` scope
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
npm 身份以及 `@tokensrc`、`@numa-tech` scope 权限。发布认证默认完全交给 npm 处理;
|
|
39
|
+
需要时 npm 会启动自己的浏览器认证或 2FA 流程,脚本不会额外询问 OTP。也可以临时通过
|
|
40
|
+
`NUMA_PUBLIC_NPM_OTP` 显式提供 6 位 OTP;自动化发布更推荐使用带 bypass 2FA 权限的
|
|
41
|
+
granular token 或 npm trusted publishing。
|
|
41
42
|
|
|
42
43
|
首次从本机发布公共包,或公共登录已经过期时,先运行:
|
|
43
44
|
|
|
@@ -192,6 +193,9 @@ numa config \
|
|
|
192
193
|
| `numa registry mcp list/inspect/install/bridge/...` | 需登录 | 通过企业 Router 安装/桥接 MCP,并维护版本、标签和工具状态 |
|
|
193
194
|
| `numa registry approvals/router/audit` | 需登录 | 两人制发布、Router 健康/对账和追加式审计 |
|
|
194
195
|
| `numa registry tui` | 需登录 | 打开 namespace、Skills、MCP、审批、Router、审计全屏工作台 |
|
|
196
|
+
| `numa app onboard/apply/resume/inspect/list/tui` | 需登录 | 创建新应用、录入存量应用并恢复服务端入驻会话 |
|
|
197
|
+
| `numa app options` | 需登录 | 查询应用归属所需的团队、系统、业务域和环境 ID |
|
|
198
|
+
| `numa app candidate list/inspect/preview/review/adopt` | 需登录 | 审查应用发现候选并确认收录,服务端执行最终管理员授权和审计 |
|
|
195
199
|
| `numa media capabilities` | 需登录 | 查询媒体 API、Provider 和版本能力 |
|
|
196
200
|
| `numa media tts submit` | 需登录 | 提交 TTS 异步任务 |
|
|
197
201
|
| `numa media video submit` | 需登录 | 提交视频生成异步任务 |
|
|
@@ -451,6 +455,125 @@ RBAC 开启 Developer mode。
|
|
|
451
455
|
Nacos token、绕过 403 或未经确认覆盖本地配置。对应场景评测在
|
|
452
456
|
`skills/numa-ai-registry/evals/evals.json`。
|
|
453
457
|
|
|
458
|
+
## 应用创建与存量录入
|
|
459
|
+
|
|
460
|
+
`numa app` 使用 platform profile 的 `platform` service 和现有 `mcp-client` Keycloak 会话调用
|
|
461
|
+
`/api/v1/application-onboarding`。Web、CLI 和 TUI 共享服务端 `sessionNo`、乐观锁 `revision`、
|
|
462
|
+
`planHash` 和单调递增的 event sequence;CLI 只展示服务端状态,不会在本地推断任务已完成。
|
|
463
|
+
|
|
464
|
+
交互终端直接运行 `numa app onboard` 会打开 Ink TUI,并可在同一服务端草稿中完成
|
|
465
|
+
`MODE → BASIC_INFORMATION → OWNERSHIP → SOURCE_CONTROL → TECHNOLOGY → STANDARDIZATION → DELIVERY → REVIEW → EXECUTION`
|
|
466
|
+
九步。`[` / `]` 切换步骤(切换时保存 `revision` 与稳定 `currentStep`),方向键选择字段和目录项,
|
|
467
|
+
`Enter` / `e` 编辑,`s` 保存;`REVIEW` 中先按 `p` 查看阻断、警告与 `planHash`,再按 `a`、`y`
|
|
468
|
+
二次确认执行。目录接口不可用时仍可手工输入实体 ID;仓库 Provider 返回 501 时会明确提示手工输入
|
|
469
|
+
稳定 `externalId` 与 `expectedHead`,不会把 URL 或凭据写入草稿。
|
|
470
|
+
|
|
471
|
+
非交互、CI 和 AI 调用必须提供 `--spec`。spec 使用 `schemaVersion: 1`,不得包含 Token、密码、
|
|
472
|
+
SCM credential 或 secret ref:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
# 只保存草稿并预检,不产生仓库、分支或 Jenkins 等外部副作用
|
|
476
|
+
numa app onboard --spec onboarding.json --plan --json
|
|
477
|
+
|
|
478
|
+
# 接续 Web/TUI 中已保存的同一草稿;使用服务端最新 revision,避免静默覆盖
|
|
479
|
+
numa app onboard --session <session-no> --spec onboarding.json --plan --json
|
|
480
|
+
|
|
481
|
+
# 一步式使用服务端最新 planHash 提交异步执行;幂等键为必填
|
|
482
|
+
numa app onboard \
|
|
483
|
+
--spec onboarding.json \
|
|
484
|
+
--apply \
|
|
485
|
+
--idempotency-key app-order-20260819 \
|
|
486
|
+
--json
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
自动化更推荐先运行 `--plan`、保存返回的 `session_no`,再明确提交该会话。这样即使网络在
|
|
490
|
+
apply 返回前中断,也可以对同一会话复用原幂等键,而不会重新创建草稿:
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
numa app apply <session-no> --idempotency-key app-order-20260819 --json
|
|
494
|
+
numa app inspect <session-no> --watch --after-sequence 42
|
|
495
|
+
numa app resume <session-no> --json
|
|
496
|
+
numa app tui --session <session-no>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`inspect --watch` 每次都从最后已确认的 sequence 补拉,断线重连不会重复展示事件;Ctrl+C
|
|
500
|
+
只停止本地观察,不取消服务端任务。取消必须显式执行 `numa app cancel <session-no> --yes`,
|
|
501
|
+
且不会回滚已经完成的外部副作用。完整 spec schema、状态机和入驻流程由 DevOps 平台的
|
|
502
|
+
Application Onboarding 契约定义。
|
|
503
|
+
|
|
504
|
+
SCM 目录和连接诊断使用同一身份。仓库查询支持服务端 search/cursor 分页,返回稳定的
|
|
505
|
+
`externalId`、`defaultBranch`、`headRevision`;向导选中仓库后会把 `headRevision` 固定为
|
|
506
|
+
`expectedHead`。HTTP 429 会保留服务端 `Retry-After` 诊断,Provider 未安装(501)时 TUI
|
|
507
|
+
明确退回手工填写稳定标识:
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
numa app scm list --provider codeup --json
|
|
511
|
+
numa app scm inspect codeup-main --repositories --search order --limit 50 --json
|
|
512
|
+
numa app scm inspect codeup-main --repositories --cursor '<next-cursor>' --limit 50 --json
|
|
513
|
+
numa app scm probe codeup-main --json
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
SCM 管理接口位于 `/api/v1/admin/scm-connections`,最终权限始终由网关和后端按 canonical
|
|
517
|
+
Client Role 校验。metadata JSON 只允许 provider、URL、组织、auth type 以及服务端托管的
|
|
518
|
+
`aliyunAccountId`/`aliyunAssumeRoleId` 引用;Numa 不接收云 AK。CODEUP 必须引用
|
|
519
|
+
`aliyunAccountId`,没有 access token 时直接使用服务端 Aliyun identity,不写入空 credential。
|
|
520
|
+
GitHub App/PAT 或可选 Codeup access token 只能从 owner-only 文件或重定向 stdin 读取,禁止放在
|
|
521
|
+
argv、环境变量和 metadata JSON 中,且不会出现在输出或错误中:
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
numa app scm create --input codeup.json --json
|
|
525
|
+
chmod 600 github-app.private.json
|
|
526
|
+
numa app scm create --input github.json --credential-file github-app.private.json --json
|
|
527
|
+
numa app scm rotate github-main --revision 4 --credential-stdin --yes --json < github-pat.private.json
|
|
528
|
+
numa app scm disable old-codeup --revision 7 --yes --json
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## 应用候选收录
|
|
532
|
+
|
|
533
|
+
Jenkins 发现任务只生成可审查的应用候选,不会直接创建正式应用。Numa 对外把后续动作称为
|
|
534
|
+
“候选收录”(`adopt`):候选可能创建新应用,也可能合并到已有应用,同时确认候选携带的
|
|
535
|
+
Jenkins Job 绑定。“转正”容易与 npm 发布晋级、环境晋级或人员状态混淆,因此不作为 CLI
|
|
536
|
+
命令和用户提示;服务端继续使用既有 `/promote` API 与审计事件,保持兼容。
|
|
537
|
+
|
|
538
|
+
先查询候选和所需目录 ID:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
numa app options team --search platform --json
|
|
542
|
+
numa app options system --json
|
|
543
|
+
numa profile --json
|
|
544
|
+
|
|
545
|
+
numa app candidate list --status new --json
|
|
546
|
+
numa app candidate inspect 123 --json
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
`candidate list` 仅返回 `APPLICATION` 类型候选;列表中的 `id` 是后续操作使用的稳定候选 ID,
|
|
550
|
+
不等同于 Jenkins Job ID 或 Build Number。Job ID、环境层及绑定证据来自候选的
|
|
551
|
+
`proposed_relationships`,CLI 不要求用户重复填写。
|
|
552
|
+
|
|
553
|
+
收录必须先通过服务端预检,并在写入时显式确认:
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
numa app candidate preview 123 --json
|
|
557
|
+
numa app candidate adopt 123 --yes --json
|
|
558
|
+
|
|
559
|
+
# 最多批量处理 200 个候选;任一预检阻断时不会提交本批收录
|
|
560
|
+
numa app candidate adopt 123 124 --yes --json
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
如果候选缺少名称、环境证据不明确,或需要合并到已有应用,先 `inspect` 获取完整候选与
|
|
564
|
+
`version`,再用完整 review JSON 和乐观锁版本提交修复:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
numa app candidate review 123 \
|
|
568
|
+
--input candidate-review.json \
|
|
569
|
+
--revision 4 \
|
|
570
|
+
--json
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
review JSON 必须包含 `operation`、`proposedFields`、`proposedRelationships` 和 `confidence`,
|
|
574
|
+
且禁止嵌入凭据或 secret 引用。最终 Scope、候选状态、Job 时效性、CREATE/MERGE 冲突、逐候选
|
|
575
|
+
事务和审计记录均由网关及 DevOps 后端校验,CLI 本地隐藏或显示命令不构成授权。
|
|
576
|
+
|
|
454
577
|
## Jenkins 流水线与生产操作
|
|
455
578
|
|
|
456
579
|
`numa pipeline` 通过平台授权代理查询和操作 Jenkins。CLI 不接收 Jenkins 用户名、Token 或
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { AuthConfig } from "../config.js";
|
|
2
|
+
import { type KeycloakTokenProvider } from "../oauth.js";
|
|
3
|
+
import { type ApplicationCandidateReviewInput, type ApplicationCandidateStatus } from "./schemas.js";
|
|
4
|
+
export interface ApplicationCandidateClientDependencies {
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
tokenProvider?: KeycloakTokenProvider;
|
|
7
|
+
}
|
|
8
|
+
export interface ListApplicationCandidateOptions {
|
|
9
|
+
status?: ApplicationCandidateStatus;
|
|
10
|
+
sourceId?: number;
|
|
11
|
+
search?: string;
|
|
12
|
+
page?: number;
|
|
13
|
+
size?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ApplicationCandidateApi {
|
|
16
|
+
listCandidates(options?: ListApplicationCandidateOptions): ReturnType<ApplicationCandidateClient["listCandidates"]>;
|
|
17
|
+
getCandidate(candidateId: number): ReturnType<ApplicationCandidateClient["getCandidate"]>;
|
|
18
|
+
updateCandidate(candidateId: number, input: ApplicationCandidateReviewInput & {
|
|
19
|
+
expectedVersion: number;
|
|
20
|
+
}): ReturnType<ApplicationCandidateClient["updateCandidate"]>;
|
|
21
|
+
previewCandidates(candidateIds: number[]): ReturnType<ApplicationCandidateClient["previewCandidates"]>;
|
|
22
|
+
adoptCandidates(candidateIds: number[]): ReturnType<ApplicationCandidateClient["adoptCandidates"]>;
|
|
23
|
+
}
|
|
24
|
+
export declare class ApplicationCandidateClient {
|
|
25
|
+
private readonly config;
|
|
26
|
+
private readonly fetchImpl;
|
|
27
|
+
private readonly tokenProvider;
|
|
28
|
+
constructor(config: AuthConfig, dependencies?: ApplicationCandidateClientDependencies);
|
|
29
|
+
private authenticatedFetch;
|
|
30
|
+
private json;
|
|
31
|
+
listCandidates(options?: ListApplicationCandidateOptions): Promise<{
|
|
32
|
+
[x: string]: unknown;
|
|
33
|
+
content: {
|
|
34
|
+
[x: string]: unknown;
|
|
35
|
+
id: number;
|
|
36
|
+
candidateKey: string;
|
|
37
|
+
sourceId: number;
|
|
38
|
+
lastRunId?: number | null | undefined;
|
|
39
|
+
entityType: string;
|
|
40
|
+
operation: "CREATE" | "MERGE" | "UPDATE";
|
|
41
|
+
targetEntityId?: number | null | undefined;
|
|
42
|
+
proposedFields: Record<string, unknown>;
|
|
43
|
+
proposedRelationships: Record<string, unknown>[];
|
|
44
|
+
confidence: number;
|
|
45
|
+
status: "CONFIRMED" | "CONFLICTED" | "IGNORED" | "MERGED" | "NEW" | "REVIEWING";
|
|
46
|
+
conflictReason?: string | null | undefined;
|
|
47
|
+
resolutionNote?: string | null | undefined;
|
|
48
|
+
reviewedBy?: string | null | undefined;
|
|
49
|
+
reviewedAt?: string | null | undefined;
|
|
50
|
+
promotedEntityType?: string | null | undefined;
|
|
51
|
+
promotedEntityId?: number | null | undefined;
|
|
52
|
+
version: number;
|
|
53
|
+
evidence: {
|
|
54
|
+
[x: string]: unknown;
|
|
55
|
+
id: number;
|
|
56
|
+
discoveredResourceId?: number | null | undefined;
|
|
57
|
+
evidenceKey?: string | undefined;
|
|
58
|
+
evidenceType?: string | undefined;
|
|
59
|
+
evidencePath?: string | null | undefined;
|
|
60
|
+
evidenceValue?: string | null | undefined;
|
|
61
|
+
weight?: number | null | undefined;
|
|
62
|
+
}[];
|
|
63
|
+
}[];
|
|
64
|
+
totalElements: number;
|
|
65
|
+
page: number;
|
|
66
|
+
size: number;
|
|
67
|
+
totalPages: number;
|
|
68
|
+
}>;
|
|
69
|
+
getCandidate(candidateId: number): Promise<{
|
|
70
|
+
[x: string]: unknown;
|
|
71
|
+
id: number;
|
|
72
|
+
candidateKey: string;
|
|
73
|
+
sourceId: number;
|
|
74
|
+
lastRunId?: number | null | undefined;
|
|
75
|
+
entityType: string;
|
|
76
|
+
operation: "CREATE" | "MERGE" | "UPDATE";
|
|
77
|
+
targetEntityId?: number | null | undefined;
|
|
78
|
+
proposedFields: Record<string, unknown>;
|
|
79
|
+
proposedRelationships: Record<string, unknown>[];
|
|
80
|
+
confidence: number;
|
|
81
|
+
status: "CONFIRMED" | "CONFLICTED" | "IGNORED" | "MERGED" | "NEW" | "REVIEWING";
|
|
82
|
+
conflictReason?: string | null | undefined;
|
|
83
|
+
resolutionNote?: string | null | undefined;
|
|
84
|
+
reviewedBy?: string | null | undefined;
|
|
85
|
+
reviewedAt?: string | null | undefined;
|
|
86
|
+
promotedEntityType?: string | null | undefined;
|
|
87
|
+
promotedEntityId?: number | null | undefined;
|
|
88
|
+
version: number;
|
|
89
|
+
evidence: {
|
|
90
|
+
[x: string]: unknown;
|
|
91
|
+
id: number;
|
|
92
|
+
discoveredResourceId?: number | null | undefined;
|
|
93
|
+
evidenceKey?: string | undefined;
|
|
94
|
+
evidenceType?: string | undefined;
|
|
95
|
+
evidencePath?: string | null | undefined;
|
|
96
|
+
evidenceValue?: string | null | undefined;
|
|
97
|
+
weight?: number | null | undefined;
|
|
98
|
+
}[];
|
|
99
|
+
}>;
|
|
100
|
+
updateCandidate(candidateId: number, input: ApplicationCandidateReviewInput & {
|
|
101
|
+
expectedVersion: number;
|
|
102
|
+
}): Promise<{
|
|
103
|
+
[x: string]: unknown;
|
|
104
|
+
id: number;
|
|
105
|
+
candidateKey: string;
|
|
106
|
+
sourceId: number;
|
|
107
|
+
lastRunId?: number | null | undefined;
|
|
108
|
+
entityType: string;
|
|
109
|
+
operation: "CREATE" | "MERGE" | "UPDATE";
|
|
110
|
+
targetEntityId?: number | null | undefined;
|
|
111
|
+
proposedFields: Record<string, unknown>;
|
|
112
|
+
proposedRelationships: Record<string, unknown>[];
|
|
113
|
+
confidence: number;
|
|
114
|
+
status: "CONFIRMED" | "CONFLICTED" | "IGNORED" | "MERGED" | "NEW" | "REVIEWING";
|
|
115
|
+
conflictReason?: string | null | undefined;
|
|
116
|
+
resolutionNote?: string | null | undefined;
|
|
117
|
+
reviewedBy?: string | null | undefined;
|
|
118
|
+
reviewedAt?: string | null | undefined;
|
|
119
|
+
promotedEntityType?: string | null | undefined;
|
|
120
|
+
promotedEntityId?: number | null | undefined;
|
|
121
|
+
version: number;
|
|
122
|
+
evidence: {
|
|
123
|
+
[x: string]: unknown;
|
|
124
|
+
id: number;
|
|
125
|
+
discoveredResourceId?: number | null | undefined;
|
|
126
|
+
evidenceKey?: string | undefined;
|
|
127
|
+
evidenceType?: string | undefined;
|
|
128
|
+
evidencePath?: string | null | undefined;
|
|
129
|
+
evidenceValue?: string | null | undefined;
|
|
130
|
+
weight?: number | null | undefined;
|
|
131
|
+
}[];
|
|
132
|
+
}>;
|
|
133
|
+
previewCandidates(candidateIds: number[]): Promise<{
|
|
134
|
+
[x: string]: unknown;
|
|
135
|
+
requested: number;
|
|
136
|
+
succeeded: number;
|
|
137
|
+
conflicted: number;
|
|
138
|
+
results: {
|
|
139
|
+
[x: string]: unknown;
|
|
140
|
+
candidateId: number;
|
|
141
|
+
valid: boolean;
|
|
142
|
+
code: string;
|
|
143
|
+
message: string;
|
|
144
|
+
missingFields: string[];
|
|
145
|
+
entityType?: string | null | undefined;
|
|
146
|
+
entityId?: number | null | undefined;
|
|
147
|
+
resultingStatus?: "CONFIRMED" | "CONFLICTED" | "IGNORED" | "MERGED" | "NEW" | "REVIEWING" | null | undefined;
|
|
148
|
+
}[];
|
|
149
|
+
}>;
|
|
150
|
+
adoptCandidates(candidateIds: number[]): Promise<{
|
|
151
|
+
[x: string]: unknown;
|
|
152
|
+
requested: number;
|
|
153
|
+
succeeded: number;
|
|
154
|
+
conflicted: number;
|
|
155
|
+
results: {
|
|
156
|
+
[x: string]: unknown;
|
|
157
|
+
candidateId: number;
|
|
158
|
+
valid: boolean;
|
|
159
|
+
code: string;
|
|
160
|
+
message: string;
|
|
161
|
+
missingFields: string[];
|
|
162
|
+
entityType?: string | null | undefined;
|
|
163
|
+
entityId?: number | null | undefined;
|
|
164
|
+
resultingStatus?: "CONFIRMED" | "CONFLICTED" | "IGNORED" | "MERGED" | "NEW" | "REVIEWING" | null | undefined;
|
|
165
|
+
}[];
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { resolveBackendUrl } from "../backend.js";
|
|
2
|
+
import { ensureAccessToken, fetchWithKeycloakIdentity } from "../oauth.js";
|
|
3
|
+
import { OnboardingError, redactOnboardingText } from "../application-onboarding/errors.js";
|
|
4
|
+
import { ApplicationCandidateApiErrorSchema, ApplicationCandidateBatchResultSchema, ApplicationCandidatePageSchema, ApplicationCandidateSchema } from "./schemas.js";
|
|
5
|
+
const API_ROOT = "/api/v1/admin/catalog-candidates";
|
|
6
|
+
async function responseBody(response) {
|
|
7
|
+
const text = await response.text();
|
|
8
|
+
if (!text)
|
|
9
|
+
return {};
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(text);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return text;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function requestIdFrom(response, body) {
|
|
18
|
+
if (body && typeof body === "object" && "requestId" in body && typeof body.requestId === "string") {
|
|
19
|
+
return body.requestId;
|
|
20
|
+
}
|
|
21
|
+
return response.headers.get("x-request-id") ?? undefined;
|
|
22
|
+
}
|
|
23
|
+
function apiFailure(response, body) {
|
|
24
|
+
const parsed = ApplicationCandidateApiErrorSchema.safeParse(body);
|
|
25
|
+
const message = parsed.success
|
|
26
|
+
? parsed.data.message ?? response.statusText ?? "Application candidate API request failed."
|
|
27
|
+
: typeof body === "string"
|
|
28
|
+
? body
|
|
29
|
+
: response.statusText || "Application candidate API request failed.";
|
|
30
|
+
return new OnboardingError(`Application candidate API HTTP ${response.status}: ${redactOnboardingText(message)}`, parsed.success ? parsed.data.code ?? "application_candidate_api_error" : "application_candidate_api_error", response.status, requestIdFrom(response, body), response.status === 429 || response.status >= 500, parsed.success ? parsed.data.details : undefined);
|
|
31
|
+
}
|
|
32
|
+
function queryPath(path, values) {
|
|
33
|
+
const query = new URLSearchParams();
|
|
34
|
+
for (const [key, value] of Object.entries(values)) {
|
|
35
|
+
if (value != null && value !== "")
|
|
36
|
+
query.set(key, String(value));
|
|
37
|
+
}
|
|
38
|
+
return query.size ? `${path}?${query.toString()}` : path;
|
|
39
|
+
}
|
|
40
|
+
export class ApplicationCandidateClient {
|
|
41
|
+
config;
|
|
42
|
+
fetchImpl;
|
|
43
|
+
tokenProvider;
|
|
44
|
+
constructor(config, dependencies = {}) {
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.fetchImpl = dependencies.fetch ?? fetch;
|
|
47
|
+
this.tokenProvider = dependencies.tokenProvider ?? ensureAccessToken;
|
|
48
|
+
}
|
|
49
|
+
async authenticatedFetch(path, init = {}) {
|
|
50
|
+
const url = resolveBackendUrl(this.config, { method: "GET", path });
|
|
51
|
+
const headers = new Headers(init.headers);
|
|
52
|
+
headers.set("Accept", "application/json");
|
|
53
|
+
if (init.body != null)
|
|
54
|
+
headers.set("Content-Type", "application/json");
|
|
55
|
+
return fetchWithKeycloakIdentity(this.config, url, () => ({ ...init, headers }), { fetch: this.fetchImpl, tokenProvider: this.tokenProvider });
|
|
56
|
+
}
|
|
57
|
+
async json(path, schema, init = {}, expectedStatuses) {
|
|
58
|
+
const response = await this.authenticatedFetch(path, init);
|
|
59
|
+
const body = await responseBody(response);
|
|
60
|
+
if (!response.ok)
|
|
61
|
+
throw apiFailure(response, body);
|
|
62
|
+
if (expectedStatuses && !expectedStatuses.includes(response.status)) {
|
|
63
|
+
throw new OnboardingError(`Application candidate API returned HTTP ${response.status}; expected ${expectedStatuses.join(" or ")}.`, "application_candidate_invalid_response", response.status, requestIdFrom(response, body));
|
|
64
|
+
}
|
|
65
|
+
const parsed = schema.safeParse(body);
|
|
66
|
+
if (!parsed.success) {
|
|
67
|
+
throw new OnboardingError(`Application candidate API returned an invalid response: ${parsed.error.message}`, "application_candidate_invalid_response", response.status, requestIdFrom(response, body));
|
|
68
|
+
}
|
|
69
|
+
return parsed.data;
|
|
70
|
+
}
|
|
71
|
+
listCandidates(options = {}) {
|
|
72
|
+
return this.json(queryPath(API_ROOT, {
|
|
73
|
+
status: options.status,
|
|
74
|
+
entityType: "APPLICATION",
|
|
75
|
+
sourceId: options.sourceId,
|
|
76
|
+
q: options.search,
|
|
77
|
+
page: options.page ?? 0,
|
|
78
|
+
size: options.size ?? 50
|
|
79
|
+
}), ApplicationCandidatePageSchema);
|
|
80
|
+
}
|
|
81
|
+
getCandidate(candidateId) {
|
|
82
|
+
return this.json(`${API_ROOT}/${candidateId}`, ApplicationCandidateSchema);
|
|
83
|
+
}
|
|
84
|
+
updateCandidate(candidateId, input) {
|
|
85
|
+
return this.json(`${API_ROOT}/${candidateId}`, ApplicationCandidateSchema, {
|
|
86
|
+
method: "PUT",
|
|
87
|
+
body: JSON.stringify(input)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
previewCandidates(candidateIds) {
|
|
91
|
+
return this.json(`${API_ROOT}/preview`, ApplicationCandidateBatchResultSchema, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
body: JSON.stringify({ candidateIds })
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
adoptCandidates(candidateIds) {
|
|
97
|
+
return this.json(`${API_ROOT}/promote`, ApplicationCandidateBatchResultSchema, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
body: JSON.stringify({ candidateIds })
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/application-candidates/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type ApplicationCandidateApi } from "./client.js";
|
|
3
|
+
import { type ApplicationCandidateBatchResult, type ApplicationCandidateStatus } from "./schemas.js";
|
|
4
|
+
export interface ApplicationCandidateCommandDependencies {
|
|
5
|
+
clientFactory?: () => ApplicationCandidateApi;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseApplicationCandidateStatus(value: string | undefined): ApplicationCandidateStatus | undefined;
|
|
8
|
+
export declare function parseApplicationCandidateIds(values: string[]): number[];
|
|
9
|
+
export declare function adoptApplicationCandidates(client: Pick<ApplicationCandidateApi, "previewCandidates" | "adoptCandidates">, candidateIds: number[]): Promise<{
|
|
10
|
+
preview: ApplicationCandidateBatchResult;
|
|
11
|
+
adoption: ApplicationCandidateBatchResult;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function registerApplicationCandidateCommands(app: Command, dependencies?: ApplicationCandidateCommandDependencies): void;
|