@aifabrix/builder 2.54.1 → 2.55.2

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.
Files changed (253) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +172 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +272 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/start.js +310 -0
  23. package/lib/agent-kit/state-file.js +83 -0
  24. package/lib/agent-kit/status.js +50 -0
  25. package/lib/agent-kit/v1-detect.js +81 -0
  26. package/lib/agent-kit/validate-kit.js +87 -0
  27. package/lib/agent-kit/workspace-name.js +40 -0
  28. package/lib/agent-kit/workspace-skeleton.js +103 -0
  29. package/lib/api/role-assistant-test-pack.api.js +86 -0
  30. package/lib/api/work-search.api.js +31 -4
  31. package/lib/api/work.api.js +19 -12
  32. package/lib/app/deploy.js +7 -4
  33. package/lib/app/list-external-systems.js +2 -1
  34. package/lib/app/offer-create-from-template.js +184 -0
  35. package/lib/app/show-display.js +33 -6
  36. package/lib/app/show.js +25 -2
  37. package/lib/channels/add-channel.js +1 -0
  38. package/lib/channels/approval-guides/claude.js +2 -2
  39. package/lib/channels/channel-add-failure.js +1 -0
  40. package/lib/channels/channel-add-remaining.js +15 -9
  41. package/lib/channels/channel-definition-status.js +115 -0
  42. package/lib/channels/channel-description.js +2 -1
  43. package/lib/channels/channel-install-envelope.js +4 -2
  44. package/lib/channels/channel-integration-generator.js +2 -0
  45. package/lib/channels/channel-runtime-resolver.js +14 -0
  46. package/lib/channels/channel-targets.js +31 -5
  47. package/lib/channels/claude-setup-values.js +4 -1
  48. package/lib/channels/slack-channel-add.js +59 -6
  49. package/lib/channels/slack-mcp-callback.js +51 -0
  50. package/lib/channels/slack-mcp-runtime.js +156 -0
  51. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  52. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  53. package/lib/cli/index.js +4 -0
  54. package/lib/cli/setup-app.js +8 -1
  55. package/lib/cli/setup-auth.js +11 -2
  56. package/lib/cli/setup-certification-commands.js +2 -2
  57. package/lib/cli/setup-certification.help.js +3 -1
  58. package/lib/commands/agent-kit-help.js +149 -0
  59. package/lib/commands/agent-kit.js +417 -0
  60. package/lib/commands/auth-status-user.js +99 -0
  61. package/lib/commands/auth-status.js +14 -4
  62. package/lib/commands/channel-add-help.js +5 -5
  63. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  64. package/lib/commands/datasource-load-export-cli.js +7 -2
  65. package/lib/commands/datasource-test-trust-cli.js +5 -1
  66. package/lib/commands/governance-command-action.js +5 -4
  67. package/lib/commands/identity-membership-role-cache.js +6 -1
  68. package/lib/commands/lifecycle-command-action.js +16 -15
  69. package/lib/commands/login-credentials.js +8 -3
  70. package/lib/commands/login-device.js +14 -5
  71. package/lib/commands/login.js +14 -1
  72. package/lib/commands/logout.js +50 -15
  73. package/lib/commands/repair-auth-apply.js +1 -1
  74. package/lib/commands/repair-datasource-key-skip.js +39 -0
  75. package/lib/commands/repair-datasource-keys.js +24 -12
  76. package/lib/commands/repair-rbac.js +16 -10
  77. package/lib/commands/role-assistant-copy-output.js +61 -0
  78. package/lib/commands/role-assistant-copy.js +155 -0
  79. package/lib/commands/role-assistant-test-output.js +8 -9
  80. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  81. package/lib/commands/role-assistant.js +140 -59
  82. package/lib/commands/search.js +143 -0
  83. package/lib/commands/test-trust-external.js +7 -5
  84. package/lib/commands/upload-sidecar-followups.js +4 -1
  85. package/lib/commands/verify-governance-command-action.js +9 -7
  86. package/lib/commands/verify-operations-command-action.js +9 -8
  87. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  88. package/lib/commands/verify-operations-step-plan.js +6 -33
  89. package/lib/commands/verify-operations-steps.js +3 -1
  90. package/lib/commands/verify-trust-command-action.js +6 -5
  91. package/lib/datasource/capability/relate-operations.js +28 -10
  92. package/lib/datasource/capability/run-capability-relate.js +48 -25
  93. package/lib/datasource/datasource-identifier.js +13 -5
  94. package/lib/deployment/deployer-poll-run.js +117 -35
  95. package/lib/external-system/deploy-dataplane-published.js +70 -0
  96. package/lib/external-system/deploy.js +12 -2
  97. package/lib/external-system/download-role-assistant.js +41 -15
  98. package/lib/external-system/download.js +8 -3
  99. package/lib/generator/helpers.js +12 -1
  100. package/lib/generator/wizard-datasource-key.js +54 -0
  101. package/lib/generator/wizard.js +55 -54
  102. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  103. package/lib/integration-definition/apply-relationships.js +195 -0
  104. package/lib/integration-definition/apply-static-enterprise.js +232 -0
  105. package/lib/integration-definition/apply-system-file.js +71 -0
  106. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  107. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  108. package/lib/integration-definition/apply.js +78 -59
  109. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  110. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  111. package/lib/lifecycle/role-assistant-governance.js +56 -21
  112. package/lib/programmatic/capability-registry.js +1 -0
  113. package/lib/programmatic/index.js +2 -0
  114. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  115. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  116. package/lib/programmatic/openapi-component-schemas.js +1 -0
  117. package/lib/programmatic/openapi-descriptions.js +9 -3
  118. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  119. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  120. package/lib/programmatic/openapi-request-schemas.js +5 -0
  121. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  122. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  123. package/lib/programmatic/openapi-spec.js +1 -0
  124. package/lib/programmatic/request-timeout.js +3 -0
  125. package/lib/programmatic/route-handler-map.js +7 -0
  126. package/lib/programmatic/run-channel-catalog.js +50 -8
  127. package/lib/programmatic/run-channel-install-options.js +5 -1
  128. package/lib/programmatic/run-download.js +41 -0
  129. package/lib/protection/auth-context.js +3 -1
  130. package/lib/protection/protection-display.js +3 -2
  131. package/lib/protection/report-exit.js +50 -0
  132. package/lib/protection/run-commands.js +2 -0
  133. package/lib/role-assistant/knowledge-sync-settings-push.js +172 -0
  134. package/lib/role-assistant/knowledge-sync.js +9 -126
  135. package/lib/role-assistant/package-validate.js +175 -0
  136. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  137. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  138. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  139. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  140. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  141. package/lib/role-assistant/test-case-copy.js +119 -0
  142. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  143. package/lib/role-assistant/test-case-validate.js +88 -0
  144. package/lib/role-assistant/test-cases-bid.js +117 -4
  145. package/lib/role-assistant/test-cases-evaluate.js +149 -31
  146. package/lib/role-assistant/test-cases-evidence.js +44 -5
  147. package/lib/role-assistant/test-cases-honesty.js +155 -6
  148. package/lib/role-assistant/test-cases-lifecycle.js +297 -0
  149. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  150. package/lib/role-assistant/test-cases-search.js +20 -2
  151. package/lib/role-assistant/test-cases.js +34 -168
  152. package/lib/role-assistant/test-pack-map.js +180 -0
  153. package/lib/role-assistant/test-pack-sync.js +187 -0
  154. package/lib/role-assistant/test-runner-debug.js +54 -0
  155. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  156. package/lib/role-assistant/test-runner-search.js +20 -5
  157. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  158. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  159. package/lib/role-assistant/test-runner-workhub.js +45 -21
  160. package/lib/role-assistant/test-runner.js +53 -10
  161. package/lib/schema/external-datasource.schema.json +36 -5
  162. package/lib/schema/external-system.schema.json +20 -2
  163. package/lib/schema/infra-parameter.schema.json +51 -3
  164. package/lib/schema/infra.parameter.yaml +476 -40
  165. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  166. package/lib/schema/type/document-storage.json +1 -1
  167. package/lib/search/dataplane-context.js +23 -0
  168. package/lib/search/search-args.js +211 -0
  169. package/lib/search/search-display.js +238 -0
  170. package/lib/search/search-errors.js +50 -0
  171. package/lib/search/search-prompts.js +119 -0
  172. package/lib/search/search-roles.js +85 -0
  173. package/lib/search/search-run.js +241 -0
  174. package/lib/search/search-skills.js +198 -0
  175. package/lib/utils/api-required-permissions.js +271 -0
  176. package/lib/utils/api.js +16 -13
  177. package/lib/utils/authentication-slot-show.js +153 -0
  178. package/lib/utils/cli-json-exit.js +41 -0
  179. package/lib/utils/cli-utils.js +23 -5
  180. package/lib/utils/controller-url.js +78 -4
  181. package/lib/utils/credential-display.js +43 -1
  182. package/lib/utils/deployment-errors.js +15 -0
  183. package/lib/utils/deployment-validation-helpers.js +4 -1
  184. package/lib/utils/device-code.js +31 -10
  185. package/lib/utils/error-formatters/error-parser.js +16 -1
  186. package/lib/utils/error-formatters/permission-errors.js +19 -5
  187. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  188. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  189. package/lib/utils/external-system-validators.js +2 -1
  190. package/lib/utils/help-builder.js +3 -1
  191. package/lib/utils/keycloak-realm-ready.js +5 -2
  192. package/lib/utils/logger.js +1 -0
  193. package/lib/utils/paths.js +26 -5
  194. package/lib/utils/schema-loader.js +37 -0
  195. package/lib/utils/secrets-helpers.js +2 -2
  196. package/lib/utils/write-stream-and-wait.js +63 -0
  197. package/lib/validation/validate-external-file.js +5 -1
  198. package/lib/validation/validate.js +3 -2
  199. package/package.json +20 -6
  200. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  201. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  202. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  203. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  204. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  205. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  206. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  207. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  208. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  209. package/templates/agent-kit/agent-kit.yaml +45 -0
  210. package/templates/agent-kit/instructions/AGENTKIT.md +104 -0
  211. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  212. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  213. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  214. package/templates/agent-kit/parity-inventory.yaml +188 -0
  215. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +59 -0
  216. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  217. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  218. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  219. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  220. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +52 -0
  221. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  222. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  223. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  224. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +51 -0
  225. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  226. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  227. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  228. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +43 -0
  229. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  230. package/templates/agent-kit/skills/shared/hosts.md +65 -0
  231. package/templates/agent-kit/skills/shared/login.md +16 -0
  232. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  233. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  234. package/templates/agent-kit/workspace/README.md +89 -0
  235. package/templates/applications/builder-api/Dockerfile +3 -1
  236. package/templates/applications/dataplane/application.yaml +2 -3
  237. package/templates/applications/dataplane/env.template +5 -5
  238. package/templates/applications/dataplane/rbac.yaml +28 -0
  239. package/templates/applications/keycloak/env.template +3 -3
  240. package/templates/applications/miso-controller/application.yaml +21 -2
  241. package/templates/applications/miso-controller/env.template +96 -25
  242. package/templates/applications/miso-controller/rbac.yaml +5 -0
  243. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  244. package/templates/channels/claude/channel.json +1 -1
  245. package/templates/channels/claude/install.template.md.hbs +1 -0
  246. package/templates/channels/claude/integration/claude-system.json +1 -1
  247. package/templates/channels/slack/channel.json +11 -0
  248. package/templates/channels/slack/install.template.md.hbs +9 -0
  249. package/templates/channels/slack/integration/slack-system.json +11 -0
  250. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  251. package/templates/channels/teams-bot/channel.json +1 -0
  252. package/templates/python/docker-compose.hbs +14 -0
  253. package/templates/typescript/docker-compose.hbs +14 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Orchestrate aifabrix search and aifabrix search skills.
3
+ *
4
+ * @fileoverview Search command run path (retrieve only)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('../internal/fs-real-sync');
12
+ const logger = require('../utils/logger');
13
+ const { getWorkRoles, getWorkSkills, searchWork } = require('../api/work-search.api');
14
+ const { resolveSearchDataplaneContext } = require('./dataplane-context');
15
+ const { SearchCliError, authRequiredError } = require('./search-errors');
16
+ const { buildSearchBody, roleRefFromOptions, resourceTypeFromOptions } = require('./search-args');
17
+ const {
18
+ skillsEnvelope,
19
+ resourceTypesMap,
20
+ typeSpec,
21
+ isOperationRequired,
22
+ assertTypePublished,
23
+ assertOperationPublished
24
+ } = require('./search-skills');
25
+ const {
26
+ isInteractive,
27
+ missingFlagsError,
28
+ promptRoleAssistant,
29
+ promptResourceType,
30
+ promptOperation
31
+ } = require('./search-prompts');
32
+ const { emitSearchSuccess, emitSkillsSuccess } = require('./search-display');
33
+
34
+ /**
35
+ * @param {Object} [overrides]
36
+ * @returns {Object}
37
+ */
38
+ function defaultDeps(overrides = {}) {
39
+ return {
40
+ resolveContext: resolveSearchDataplaneContext,
41
+ getWorkRoles,
42
+ getWorkSkills,
43
+ searchWork,
44
+ prompt: (qs) => require('inquirer').prompt(qs),
45
+ logger,
46
+ isTty: () => Boolean(process.stdin && process.stdin.isTTY),
47
+ fsApi: fs,
48
+ ...overrides
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @param {Object} options
54
+ * @param {Function} resolveContext
55
+ * @returns {Promise<Object>}
56
+ */
57
+ async function resolveContextOrThrow(options, resolveContext) {
58
+ try {
59
+ return await resolveContext(options);
60
+ } catch (err) {
61
+ const message = err.message || String(err);
62
+ if (/login|auth|token|controller url/i.test(message)) {
63
+ throw authRequiredError(message);
64
+ }
65
+ throw new SearchCliError(message);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * @param {Object} options
71
+ */
72
+ function assertScriptSearchFlags(options) {
73
+ const missing = [];
74
+ if (!roleRefFromOptions(options)) missing.push('--role');
75
+ if (!resourceTypeFromOptions(options)) missing.push('--resource-type');
76
+ if (missing.length) {
77
+ throw missingFlagsError(missing);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {Object} res
83
+ * @param {string} fallback
84
+ * @returns {Object}
85
+ */
86
+ function payloadOrThrow(res, fallback) {
87
+ if (res && Number(res.status) === 200) {
88
+ const payload = skillsEnvelope(res.payload);
89
+ return payload && typeof payload === 'object' ? payload : {};
90
+ }
91
+ const text = String((res && res.errorText) || '').trim() || fallback;
92
+ throw new SearchCliError(text);
93
+ }
94
+
95
+ /**
96
+ * @param {Object} options
97
+ * @param {Object} ctx
98
+ * @param {Object} deps
99
+ * @returns {Promise<string>}
100
+ */
101
+ async function resolveRoleRef(options, ctx, deps) {
102
+ const existing = roleRefFromOptions(options);
103
+ if (existing) {
104
+ return existing;
105
+ }
106
+ return promptRoleAssistant(ctx, deps);
107
+ }
108
+
109
+ /**
110
+ * @param {string} explicitType
111
+ * @param {string[]} typeKeys
112
+ * @param {Object} typeMap
113
+ * @param {boolean} interactive
114
+ * @param {Object} deps
115
+ * @returns {Promise<string>}
116
+ */
117
+ async function resolveResourceType(explicitType, typeKeys, typeMap, interactive, deps) {
118
+ if (!typeKeys.length) {
119
+ throw new SearchCliError('No searchable resource types for this Role Assistant.');
120
+ }
121
+ if (explicitType) {
122
+ assertTypePublished(explicitType, typeMap);
123
+ return explicitType;
124
+ }
125
+ if (!interactive) {
126
+ throw missingFlagsError(['--resource-type']);
127
+ }
128
+ return promptResourceType(typeKeys, deps);
129
+ }
130
+
131
+ /**
132
+ * @param {string} explicitOp
133
+ * @param {Object} spec
134
+ * @param {boolean} interactive
135
+ * @param {Object} deps
136
+ * @returns {Promise<string>}
137
+ */
138
+ async function resolveOperation(explicitOp, spec, interactive, deps) {
139
+ if (explicitOp) {
140
+ assertOperationPublished(explicitOp, spec);
141
+ return explicitOp;
142
+ }
143
+ if (isOperationRequired(spec) && !interactive) {
144
+ throw new SearchCliError(
145
+ 'Operation is required for this resource type. Pass --operation.'
146
+ );
147
+ }
148
+ if (!interactive) {
149
+ return '';
150
+ }
151
+ return promptOperation(spec, deps);
152
+ }
153
+
154
+ /**
155
+ * @param {Object} body
156
+ * @param {Object} skillsPayload
157
+ * @param {Object} options
158
+ * @param {Object} deps
159
+ * @returns {Promise<Object>}
160
+ */
161
+ async function fillTypeAndOperation(body, skillsPayload, options, deps) {
162
+ const typeMap = resourceTypesMap(skillsPayload);
163
+ const typeKeys = Object.keys(typeMap);
164
+ const interactive = isInteractive(options, deps.isTty);
165
+ const resourceType = await resolveResourceType(
166
+ body.resourceType || '',
167
+ typeKeys,
168
+ typeMap,
169
+ interactive,
170
+ deps
171
+ );
172
+ body.resourceType = resourceType;
173
+ const spec = typeSpec(typeMap, resourceType);
174
+ const operation = await resolveOperation(body.operation || '', spec, interactive, deps);
175
+ if (operation) {
176
+ body.operation = operation;
177
+ } else {
178
+ delete body.operation;
179
+ }
180
+ if (isOperationRequired(spec) && !body.operation) {
181
+ throw new SearchCliError(
182
+ 'Operation is required for this resource type. Pass --operation.'
183
+ );
184
+ }
185
+ return body;
186
+ }
187
+
188
+ /**
189
+ * @param {Object} options
190
+ * @param {Object} [depOverrides]
191
+ * @returns {Promise<number>}
192
+ */
193
+ async function runSearch(options, depOverrides) {
194
+ const deps = defaultDeps(depOverrides);
195
+ const body = buildSearchBody(options, deps.fsApi);
196
+ const interactive = isInteractive(options, deps.isTty);
197
+ if (!interactive) {
198
+ assertScriptSearchFlags(options);
199
+ }
200
+ const ctx = await resolveContextOrThrow(options, deps.resolveContext);
201
+ if (!body.roleRef) {
202
+ body.roleRef = await resolveRoleRef(options, ctx, deps);
203
+ }
204
+ const skillsRes = await deps.getWorkSkills(ctx.dataplaneUrl, ctx.authConfig, body.roleRef);
205
+ const skillsPayload = payloadOrThrow(skillsRes, 'Unable to load search skills.');
206
+ await fillTypeAndOperation(body, skillsPayload, options, deps);
207
+ const searchRes = await deps.searchWork(ctx.dataplaneUrl, ctx.authConfig, body);
208
+ const payload = payloadOrThrow(searchRes, 'Search request failed.');
209
+ emitSearchSuccess(options, body, payload, deps.logger, deps.fsApi);
210
+ return 0;
211
+ }
212
+
213
+ /**
214
+ * @param {Object} options
215
+ * @param {Object} [depOverrides]
216
+ * @returns {Promise<number>}
217
+ */
218
+ async function runSearchSkills(options, depOverrides) {
219
+ const deps = defaultDeps(depOverrides);
220
+ const interactive = isInteractive(options, deps.isTty);
221
+ if (!interactive && !roleRefFromOptions(options)) {
222
+ throw missingFlagsError(['--role']);
223
+ }
224
+ const ctx = await resolveContextOrThrow(options, deps.resolveContext);
225
+ const roleRef = await resolveRoleRef(options, ctx, deps);
226
+ const skillsRes = await deps.getWorkSkills(ctx.dataplaneUrl, ctx.authConfig, roleRef);
227
+ const payload = payloadOrThrow(skillsRes, 'Unable to load search skills.');
228
+ if (!payload.roleRef) {
229
+ payload.roleRef = roleRef;
230
+ }
231
+ emitSkillsSuccess(options, payload, deps.logger, deps.fsApi);
232
+ return 0;
233
+ }
234
+
235
+ module.exports = {
236
+ defaultDeps,
237
+ runSearch,
238
+ runSearchSkills,
239
+ fillTypeAndOperation,
240
+ resolveRoleRef
241
+ };
@@ -0,0 +1,198 @@
1
+ /**
2
+ * YAML-free copies of work.skills helpers (do not import the test runner).
3
+ *
4
+ * @fileoverview Resource type / operation keys from a skills payload
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { SearchCliError } = require('./search-errors');
12
+
13
+ /** Table retrieve keys stamped by the search grammar (list/search page, get one). */
14
+ const RETRIEVE_TABLE_KEYS = new Set(['list', 'get', 'search']);
15
+
16
+ /**
17
+ * @param {*} item
18
+ * @returns {string}
19
+ */
20
+ function operationKeyFromItem(item) {
21
+ if (typeof item === 'string' && item.trim()) {
22
+ return item.trim();
23
+ }
24
+ if (item && typeof item === 'object') {
25
+ return String(item.key || '').trim();
26
+ }
27
+ return '';
28
+ }
29
+
30
+ /**
31
+ * @param {*} item
32
+ * @returns {boolean}
33
+ */
34
+ function hasResultMode(item) {
35
+ return Boolean(item && typeof item === 'object' && String(item.resultMode || '').trim());
36
+ }
37
+
38
+ /**
39
+ * Retrieve/analyze only. Write-shaped keys stay off search.
40
+ * Keep table retrieve keys (list/get/search) and named views (source viewpoint).
41
+ *
42
+ * @param {*} item
43
+ * @param {boolean} grammarPresent
44
+ * @param {boolean} tableRetrievePresent
45
+ * @returns {boolean}
46
+ */
47
+ function isRetrieveOperation(item, grammarPresent, tableRetrievePresent) {
48
+ const key = operationKeyFromItem(item);
49
+ if (!key) {
50
+ return false;
51
+ }
52
+ if (RETRIEVE_TABLE_KEYS.has(key)) {
53
+ return true;
54
+ }
55
+ if (typeof item === 'string') {
56
+ return !tableRetrievePresent;
57
+ }
58
+ const source = String((item && item.source) || '')
59
+ .trim()
60
+ .toLowerCase();
61
+ if (source === 'viewpoint') {
62
+ return true;
63
+ }
64
+ if (!source && !grammarPresent && !tableRetrievePresent) {
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ /**
71
+ * @param {Object|null|undefined} spec
72
+ * @returns {*[]}
73
+ */
74
+ function operationItems(spec) {
75
+ return Array.isArray(spec && spec.operations) ? spec.operations : [];
76
+ }
77
+
78
+ /**
79
+ * @param {Object|null|undefined} spec
80
+ * @returns {string[]}
81
+ */
82
+ function operationKeys(spec) {
83
+ const raw = operationItems(spec);
84
+ const grammarPresent = raw.some(hasResultMode);
85
+ const tableRetrievePresent = raw.some((item) =>
86
+ RETRIEVE_TABLE_KEYS.has(operationKeyFromItem(item))
87
+ );
88
+ const keys = [];
89
+ for (const item of raw) {
90
+ if (!isRetrieveOperation(item, grammarPresent, tableRetrievePresent)) {
91
+ continue;
92
+ }
93
+ const key = operationKeyFromItem(item);
94
+ if (key) {
95
+ keys.push(key);
96
+ }
97
+ }
98
+ return keys;
99
+ }
100
+
101
+ /**
102
+ * @param {Object|null|undefined} spec
103
+ * @returns {string[]}
104
+ */
105
+ function allOperationKeys(spec) {
106
+ return operationItems(spec).map(operationKeyFromItem).filter(Boolean);
107
+ }
108
+
109
+ /**
110
+ * work.skills is an object. MCP execute may wrap it as a one-element array.
111
+ * @param {*} skillsPayload
112
+ * @returns {Object|null}
113
+ */
114
+ function skillsEnvelope(skillsPayload) {
115
+ let cur = skillsPayload;
116
+ if (Array.isArray(cur) && cur.length === 1 && cur[0] && typeof cur[0] === 'object') {
117
+ cur = cur[0];
118
+ }
119
+ if (!cur || typeof cur !== 'object' || Array.isArray(cur)) {
120
+ return null;
121
+ }
122
+ if (cur.search && typeof cur.search === 'object') {
123
+ return cur;
124
+ }
125
+ if (cur.data && typeof cur.data === 'object' && !Array.isArray(cur.data) && cur.data.search) {
126
+ return cur.data;
127
+ }
128
+ return cur;
129
+ }
130
+
131
+ /**
132
+ * @param {Object|null|undefined} skillsPayload
133
+ * @returns {Object}
134
+ */
135
+ function resourceTypesMap(skillsPayload) {
136
+ const root = skillsEnvelope(skillsPayload);
137
+ const rts = root && root.search && root.search.resourceTypes;
138
+ return rts && typeof rts === 'object' && !Array.isArray(rts) ? rts : {};
139
+ }
140
+
141
+ /**
142
+ * @param {Object} typeMap
143
+ * @param {string} resourceType
144
+ * @returns {Object}
145
+ */
146
+ function typeSpec(typeMap, resourceType) {
147
+ const spec = typeMap && typeMap[resourceType];
148
+ return spec && typeof spec === 'object' ? spec : {};
149
+ }
150
+
151
+ /**
152
+ * @param {Object} spec
153
+ * @returns {boolean}
154
+ */
155
+ function isOperationRequired(spec) {
156
+ return spec && spec.operationRequired === true;
157
+ }
158
+
159
+ /**
160
+ * @param {string} resourceType
161
+ * @param {Object} typeMap
162
+ */
163
+ function assertTypePublished(resourceType, typeMap) {
164
+ if (!Object.prototype.hasOwnProperty.call(typeMap, resourceType)) {
165
+ throw new SearchCliError(
166
+ `Resource type "${resourceType}" is not published for this Role Assistant.`
167
+ );
168
+ }
169
+ }
170
+
171
+ /**
172
+ * @param {string} operation
173
+ * @param {Object} spec
174
+ */
175
+ function assertOperationPublished(operation, spec) {
176
+ const keys = operationKeys(spec);
177
+ if (keys.includes(operation)) {
178
+ return;
179
+ }
180
+ if (allOperationKeys(spec).includes(operation)) {
181
+ throw new SearchCliError(
182
+ `Operation "${operation}" is not a retrieve operation for this resource type.`
183
+ );
184
+ }
185
+ throw new SearchCliError(
186
+ `Operation "${operation}" is not published for this resource type.`
187
+ );
188
+ }
189
+
190
+ module.exports = {
191
+ operationKeys,
192
+ skillsEnvelope,
193
+ resourceTypesMap,
194
+ typeSpec,
195
+ isOperationRequired,
196
+ assertTypePublished,
197
+ assertOperationPublished
198
+ };
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Resolve required RBAC scopes for online API calls.
3
+ *
4
+ * Catalog is built from `@requiresPermission` plus the HTTP path in `lib/api/*.api.js`.
5
+ * Used when a 403 body does not list missing/required permissions.
6
+ *
7
+ * @fileoverview Fallback required-permission lookup for 403 errors
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fsRealSync = require('../internal/fs-real-sync');
15
+ const path = require('path');
16
+
17
+ const SCOPE_IN_TEXT_RE = /\b[a-z][a-z0-9_-]*:[a-z][a-z0-9_-]*(?::[a-z][a-z0-9_-]*)*\b/gi;
18
+ const SKIP_SCOPE_SCHEMES = new Set(['http', 'https', 'mailto', 'ws', 'wss']);
19
+
20
+ /** @type {Array<{ method: string, regex: RegExp, service: string, permissions: string[] }>|null} */
21
+ let cachedCatalog = null;
22
+
23
+ /**
24
+ * CLI command name (handleCommandError second arg) → required permission fallback.
25
+ * URL catalog is preferred; this is used when the 403 has no request URL.
26
+ */
27
+ const COMMAND_REQUIRED_PERMISSIONS = Object.freeze({
28
+ deploy: { service: 'Controller', permissions: ['applications:deploy'] },
29
+ 'env deploy': { service: 'Controller', permissions: ['controller:deploy'] },
30
+ upload: { service: 'Dataplane', permissions: ['external-system:publish'] },
31
+ download: { service: 'Dataplane', permissions: ['external-system:read'] },
32
+ delete: { service: 'Dataplane', permissions: ['external-system:delete'] },
33
+ wizard: { service: 'Dataplane', permissions: ['external-system:create'] },
34
+ 'credential list': { service: 'Dataplane', permissions: ['credential:read'] },
35
+ 'deployment list': { service: 'Controller', permissions: ['deployments:read'] },
36
+ 'app register': { service: 'Controller', permissions: ['environments-applications:create'] },
37
+ 'app list': { service: 'Controller', permissions: ['environments-applications:read'] }
38
+ });
39
+
40
+ /**
41
+ * @param {string} spec
42
+ * @returns {boolean}
43
+ */
44
+ function looksLikeRbacSpec(spec) {
45
+ if (!spec || typeof spec !== 'string') return false;
46
+ const trimmed = spec.trim();
47
+ if (/^(none|public)\b/i.test(trimmed) && !trimmed.includes(':')) return false;
48
+ if (/authenticated\s*\(/i.test(trimmed)) return false;
49
+ if (/client credentials/i.test(trimmed) && !/[a-z0-9_-]+:[a-z0-9_-]+/i.test(trimmed)) return false;
50
+ return /[a-z0-9_-]+:[a-z0-9_-]+/i.test(trimmed);
51
+ }
52
+
53
+ /**
54
+ * @param {string} spec
55
+ * @returns {string[]}
56
+ */
57
+ function permissionListFromSpec(spec) {
58
+ const main = String(spec).split('(')[0].trim();
59
+ if (!main) return [];
60
+ if (/\bor\b/i.test(main)) return [main];
61
+ return main.split(/\s*\+\s*/).map((part) => part.trim()).filter(Boolean);
62
+ }
63
+
64
+ /**
65
+ * @param {string} template
66
+ * @returns {RegExp}
67
+ */
68
+ function pathTemplateToRegex(template) {
69
+ const parts = String(template).split('/');
70
+ const escaped = parts.map((seg) => {
71
+ if (/^\{[^}]+\}$/.test(seg)) return '[^/]+';
72
+ return seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
73
+ }).join('/');
74
+ return new RegExp(`^${escaped}$`);
75
+ }
76
+
77
+ /**
78
+ * @param {string} block
79
+ * @returns {{ method: string, path: string, service: string, permissions: string[] }|null}
80
+ */
81
+ function parsePermissionCommentBlock(block) {
82
+ const text = String(block).replace(/^\s*\*\s?/gm, '');
83
+ const methodPath = text.match(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/api\/[^\s*]+)/);
84
+ const perm = text.match(/@requiresPermission\s+\{([^}]+)\}\s+([^\n*]+)/);
85
+ if (!methodPath || !perm) return null;
86
+ const spec = String(perm[2] || '').trim();
87
+ if (!looksLikeRbacSpec(spec)) return null;
88
+ const permissions = permissionListFromSpec(spec);
89
+ if (permissions.length === 0) return null;
90
+ return {
91
+ method: methodPath[1].toUpperCase(),
92
+ path: methodPath[2].replace(/\/+$/, ''),
93
+ service: String(perm[1] || '').trim(),
94
+ permissions
95
+ };
96
+ }
97
+
98
+ /**
99
+ * @param {string} source
100
+ * @returns {Array<{ method: string, regex: RegExp, service: string, permissions: string[], pathLength: number }>}
101
+ */
102
+ function parsePermissionEntriesFromSource(source) {
103
+ const entries = [];
104
+ const blocks = String(source).match(/\/\*\*[\s\S]*?\*\//g) || [];
105
+ for (const block of blocks) {
106
+ const parsed = parsePermissionCommentBlock(block);
107
+ if (!parsed) continue;
108
+ entries.push({
109
+ method: parsed.method,
110
+ regex: pathTemplateToRegex(parsed.path),
111
+ service: parsed.service,
112
+ permissions: parsed.permissions,
113
+ pathLength: parsed.path.length
114
+ });
115
+ }
116
+ return entries;
117
+ }
118
+
119
+ /**
120
+ * @returns {Array<{ method: string, regex: RegExp, service: string, permissions: string[], pathLength: number }>}
121
+ */
122
+ function loadApiPermissionCatalog() {
123
+ if (cachedCatalog) return cachedCatalog;
124
+ const apiDir = path.join(__dirname, '..', 'api');
125
+ const entries = [];
126
+ let names = [];
127
+ try {
128
+ names = fsRealSync.readdirSync(apiDir);
129
+ } catch {
130
+ cachedCatalog = [];
131
+ return cachedCatalog;
132
+ }
133
+ for (const name of names) {
134
+ if (!name.endsWith('.api.js')) continue;
135
+ const source = fsRealSync.readFileSync(path.join(apiDir, name), 'utf8');
136
+ entries.push(...parsePermissionEntriesFromSource(source));
137
+ }
138
+ entries.sort((a, b) => b.pathLength - a.pathLength);
139
+ cachedCatalog = entries;
140
+ return cachedCatalog;
141
+ }
142
+
143
+ /**
144
+ * @param {string} [url]
145
+ * @returns {string}
146
+ */
147
+ function apiPathFromUrl(url) {
148
+ const raw = String(url || '');
149
+ try {
150
+ const parsed = new URL(raw);
151
+ const idx = parsed.pathname.indexOf('/api/');
152
+ return idx >= 0 ? parsed.pathname.slice(idx).replace(/\/+$/, '') : parsed.pathname;
153
+ } catch {
154
+ const idx = raw.indexOf('/api/');
155
+ const pathOnly = idx >= 0 ? raw.slice(idx).split('?')[0] : raw.split('?')[0];
156
+ return pathOnly.replace(/\/+$/, '');
157
+ }
158
+ }
159
+
160
+ /**
161
+ * @param {string} [method]
162
+ * @param {string} [url]
163
+ * @returns {{ service: string, permissions: string[] }|null}
164
+ */
165
+ function lookupRequiredPermission(method, url) {
166
+ const verb = String(method || '').toUpperCase();
167
+ const apiPath = apiPathFromUrl(url);
168
+ if (!verb || !apiPath.startsWith('/api/')) return null;
169
+ const catalog = loadApiPermissionCatalog();
170
+ for (const entry of catalog) {
171
+ if (entry.method === verb && entry.regex.test(apiPath)) {
172
+ return { service: entry.service, permissions: entry.permissions };
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /**
179
+ * @param {string} [command]
180
+ * @returns {{ service: string, permissions: string[] }|null}
181
+ */
182
+ function lookupCommandRequiredPermission(command) {
183
+ if (!command || typeof command !== 'string') return null;
184
+ return COMMAND_REQUIRED_PERMISSIONS[command] || null;
185
+ }
186
+
187
+ /**
188
+ * @param {string} text
189
+ * @returns {string[]}
190
+ */
191
+ function extractPermissionTokensFromText(text) {
192
+ if (!text || typeof text !== 'string') return [];
193
+ const found = text.match(SCOPE_IN_TEXT_RE) || [];
194
+ const unique = [];
195
+ for (const token of found) {
196
+ const scheme = token.split(':')[0].toLowerCase();
197
+ if (SKIP_SCOPE_SCHEMES.has(scheme)) continue;
198
+ if (!unique.includes(token)) unique.push(token);
199
+ }
200
+ return unique;
201
+ }
202
+
203
+ /**
204
+ * @param {Object} data
205
+ * @returns {boolean}
206
+ */
207
+ function hasBodyPermissionLists(data) {
208
+ if (!data || typeof data !== 'object') return false;
209
+ const lists = [
210
+ data.missingPermissions,
211
+ data.requiredPermissions,
212
+ data.permissions,
213
+ data.missing && data.missing.permissions,
214
+ data.required && data.required.permissions,
215
+ data.data && data.data.missing && data.data.missing.permissions,
216
+ data.data && data.data.required && data.data.required.permissions
217
+ ];
218
+ return lists.some((list) => Array.isArray(list) && list.length > 0);
219
+ }
220
+
221
+ /**
222
+ * @param {*} errorData
223
+ * @returns {Object}
224
+ */
225
+ function asErrorObject(errorData) {
226
+ if (errorData && typeof errorData === 'object' && !Array.isArray(errorData)) {
227
+ return { ...errorData };
228
+ }
229
+ const text = errorData === undefined || errorData === null ? 'Permission denied' : String(errorData);
230
+ return { detail: text, message: text };
231
+ }
232
+
233
+ /**
234
+ * When a 403 body has no permission list, fill requiredPermissions from the request catalog.
235
+ *
236
+ * @param {*} errorData
237
+ * @param {{ status?: number, method?: string, url?: string }} [ctx]
238
+ * @returns {*}
239
+ */
240
+ function enrichForbiddenErrorData(errorData, ctx = {}) {
241
+ if (ctx.status !== 403) return errorData;
242
+ const data = asErrorObject(errorData);
243
+ if (ctx.method && !data.method) data.method = ctx.method;
244
+ if (ctx.url && !data.instance && !data.url) data.url = ctx.url;
245
+ if (hasBodyPermissionLists(data)) {
246
+ return data;
247
+ }
248
+ const fromText = extractPermissionTokensFromText(
249
+ [data.detail, data.message, data.error, data.title, data.errorDescription].filter(Boolean).join(' ')
250
+ );
251
+ if (fromText.length > 0) {
252
+ data.requiredPermissions = fromText;
253
+ return data;
254
+ }
255
+ const hit = lookupRequiredPermission(ctx.method, ctx.url);
256
+ if (!hit) return data;
257
+ data.cliPermissionService = hit.service;
258
+ data.requiredPermissions = hit.permissions;
259
+ return data;
260
+ }
261
+
262
+ module.exports = {
263
+ lookupRequiredPermission,
264
+ lookupCommandRequiredPermission,
265
+ enrichForbiddenErrorData,
266
+ extractPermissionTokensFromText,
267
+ parsePermissionEntriesFromSource,
268
+ parsePermissionCommentBlock,
269
+ apiPathFromUrl,
270
+ COMMAND_REQUIRED_PERMISSIONS
271
+ };