@xapps-platform/cli 0.1.1

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/dist/cli.cjs ADDED
@@ -0,0 +1,4806 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ CliError: () => CliError,
35
+ runCli: () => runCli
36
+ });
37
+ module.exports = __toCommonJS(cli_exports);
38
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
39
+ var import_node_crypto2 = require("node:crypto");
40
+ var import_node_http2 = __toESM(require("node:http"), 1);
41
+ var import_node_os = __toESM(require("node:os"), 1);
42
+ var import_node_path7 = __toESM(require("node:path"), 1);
43
+ var import_promises2 = __toESM(require("node:readline/promises"), 1);
44
+ var import_server_sdk3 = require("@xapps-platform/server-sdk");
45
+
46
+ // src/devStatus.ts
47
+ var import_node_fs = __toESM(require("node:fs"), 1);
48
+ var import_node_path = __toESM(require("node:path"), 1);
49
+ function buildDevRefs(repoRoot) {
50
+ const refs = [
51
+ ["pm_readme", "dev/engineering/pm/README.md"],
52
+ ["open_points", "dev/engineering/pm/OPEN_POINTS.md"],
53
+ ["open_list", "dev/engineering/pm/OPEN_LIST.md"],
54
+ ["open_list_v2", "dev/engineering/pm/OPEN_LIST_V2.md"],
55
+ ["sprint_plan", "dev/engineering/pm/SPRINT_PLAN.md"],
56
+ ["next_steps", "dev/engineering/pm/NEXT_STEPS.md"],
57
+ ["done_points", "dev/engineering/pm/DONE_POINTS.md"],
58
+ ["v1_review_audit", "dev/engineering/audits/V1_PRODUCTION_CODEBASE_REVIEW.md"],
59
+ ["open067_phase1_plan", "dev/engineering/audits/OPEN_067_PHASE1_INTERNAL_REPO_PLAN.md"],
60
+ ["sdk_cli_ai_checklist", "dev/engineering/checklists/SDK_CLI_AI_CHECKLIST.md"]
61
+ ];
62
+ return refs.map(([key, rel]) => ({
63
+ key,
64
+ path: rel,
65
+ exists: import_node_fs.default.existsSync(import_node_path.default.join(repoRoot, rel))
66
+ }));
67
+ }
68
+ function runDevStatusRefsCommand(args, deps) {
69
+ const repoRoot = deps.findRepoRoot();
70
+ if (!repoRoot) {
71
+ throw deps.makeCliError(
72
+ "CLI_REPO_NOT_FOUND",
73
+ "xapps dev status refs is repo-only and requires the xapps monorepo checkout"
74
+ );
75
+ }
76
+ const refs = buildDevRefs(repoRoot);
77
+ const sampleXplace = {
78
+ publisher: {
79
+ server: "apps/publishers/xplace/backend/server.js",
80
+ readme: "apps/publishers/xplace/backend/README.md"
81
+ },
82
+ xapp: {
83
+ manifest: "apps/publishers/xplace/xapps/xplace-certs/manifest.json",
84
+ ai_policy: "apps/publishers/xplace/xapps/xplace-certs/ai/policy.readonly.internal-v1.json"
85
+ }
86
+ };
87
+ const sampleStatus = {
88
+ xplace_certs: {
89
+ publisher: Object.fromEntries(
90
+ Object.entries(sampleXplace.publisher).map(([key, rel]) => [
91
+ key,
92
+ { path: rel, exists: import_node_fs.default.existsSync(import_node_path.default.join(repoRoot, rel)) }
93
+ ])
94
+ ),
95
+ xapp: Object.fromEntries(
96
+ Object.entries(sampleXplace.xapp).map(([key, rel]) => [
97
+ key,
98
+ { path: rel, exists: import_node_fs.default.existsSync(import_node_path.default.join(repoRoot, rel)) }
99
+ ])
100
+ ),
101
+ defaults: {
102
+ publisher_base_url: "http://localhost:3012",
103
+ gateway_base_url: "http://localhost:3000"
104
+ }
105
+ }
106
+ };
107
+ const payload = {
108
+ schema_version: "xapps.dev.status.refs.v1",
109
+ ok: refs.every((ref) => ref.exists),
110
+ repo_root: repoRoot,
111
+ refs,
112
+ samples: sampleStatus
113
+ };
114
+ if (deps.argFlag(args, "json")) {
115
+ console.log(JSON.stringify(payload, null, 2));
116
+ return;
117
+ }
118
+ console.log(
119
+ [
120
+ `Repo root: ${repoRoot}`,
121
+ ...refs.map((ref) => `${ref.exists ? "OK" : "MISS"} ${ref.key} -> ${ref.path}`),
122
+ "Sample refs:",
123
+ ...Object.entries(sampleStatus.xplace_certs.publisher).map(
124
+ ([key, item]) => `${item.exists ? "OK" : "MISS"} xplace.publisher.${key} -> ${item.path}`
125
+ ),
126
+ ...Object.entries(sampleStatus.xplace_certs.xapp).map(
127
+ ([key, item]) => `${item.exists ? "OK" : "MISS"} xplace.xapp.${key} -> ${item.path}`
128
+ )
129
+ ].join("\n")
130
+ );
131
+ }
132
+ function runDevCheckV1Command(args, deps) {
133
+ const repoRoot = deps.findRepoRoot();
134
+ if (!repoRoot) {
135
+ throw deps.makeCliError(
136
+ "CLI_REPO_NOT_FOUND",
137
+ "xapps dev check v1 is repo-only and requires the xapps monorepo checkout"
138
+ );
139
+ }
140
+ const refs = buildDevRefs(repoRoot);
141
+ const mustRead = (relPath) => import_node_fs.default.readFileSync(import_node_path.default.join(repoRoot, relPath), "utf8");
142
+ const openList = mustRead("dev/engineering/pm/OPEN_LIST.md");
143
+ const sprintPlan = mustRead("dev/engineering/pm/SPRINT_PLAN.md");
144
+ const checks = [
145
+ {
146
+ key: "refs_exist",
147
+ ok: refs.every((ref) => ref.exists),
148
+ details: { missing: refs.filter((ref) => !ref.exists).map((ref) => ref.path) }
149
+ },
150
+ {
151
+ key: "v1_list_has_must_have_section",
152
+ ok: /Must Have for First Publisher \/ xplace/i.test(openList),
153
+ details: {}
154
+ },
155
+ {
156
+ key: "sprint_plan_has_scope_rules",
157
+ ok: /Scope Proposal Discipline Rule/i.test(sprintPlan) && /Documentation Scope Rule/i.test(sprintPlan),
158
+ details: {}
159
+ },
160
+ {
161
+ key: "pm_readme_exists",
162
+ ok: import_node_fs.default.existsSync(import_node_path.default.join(repoRoot, "dev/engineering/pm/README.md")),
163
+ details: {}
164
+ }
165
+ ];
166
+ const payload = {
167
+ schema_version: "xapps.dev.check.v1",
168
+ ok: checks.every((check) => check.ok),
169
+ repo_root: repoRoot,
170
+ checks,
171
+ suggested_flows: ["pay-per-request", "guard-orchestration", "xplace-certs"]
172
+ };
173
+ if (deps.argFlag(args, "json")) {
174
+ console.log(JSON.stringify(payload, null, 2));
175
+ return;
176
+ }
177
+ console.log(
178
+ [
179
+ `V1 check: ${payload.ok ? "PASS" : "FAIL"}`,
180
+ ...checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.key}`)
181
+ ].join("\n")
182
+ );
183
+ if (!payload.ok) {
184
+ throw deps.makeCliError("CLI_DEV_CHECK_FAILED", "xapps dev check v1 failed", { checks });
185
+ }
186
+ }
187
+
188
+ // src/contextCommands.ts
189
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
190
+ var import_node_path2 = __toESM(require("node:path"), 1);
191
+ var import_node_crypto = require("node:crypto");
192
+ function isPlainObject(value) {
193
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
194
+ }
195
+ function collectContextTools(manifest) {
196
+ return manifest.tools.map((tool) => {
197
+ const inputSchema = tool.input_schema && typeof tool.input_schema === "object" ? tool.input_schema : {};
198
+ const outputSchema = tool.output_schema && typeof tool.output_schema === "object" ? tool.output_schema : {};
199
+ const requiredRaw = Array.isArray(inputSchema.required) ? inputSchema.required : [];
200
+ const outputProps = isPlainObject(outputSchema.properties) ? Object.keys(outputSchema.properties) : [];
201
+ return {
202
+ tool_name: tool.tool_name,
203
+ title: tool.title || "",
204
+ input_required: requiredRaw.filter((item) => typeof item === "string").sort(),
205
+ output_properties: outputProps.sort()
206
+ };
207
+ }).sort((a, b) => a.tool_name.localeCompare(b.tool_name));
208
+ }
209
+ function collectContextWidgets(manifest) {
210
+ return manifest.widgets.map((widget) => ({
211
+ widget_name: widget.widget_name,
212
+ type: widget.type || "",
213
+ renderer: widget.renderer || "",
214
+ ...widget.bind_tool_name ? { bind_tool_name: widget.bind_tool_name } : {}
215
+ })).sort((a, b) => a.widget_name.localeCompare(b.widget_name));
216
+ }
217
+ function buildContextExportPayload(manifest, filePath, deps) {
218
+ const manifestJson = JSON.stringify(manifest);
219
+ const manifestSha256 = (0, import_node_crypto.createHash)("sha256").update(manifestJson).digest("hex");
220
+ const tools = collectContextTools(manifest);
221
+ const widgets = collectContextWidgets(manifest);
222
+ return {
223
+ schema_version: "xapps.context.v1",
224
+ source: {
225
+ manifest_path: filePath,
226
+ manifest_sha256: manifestSha256
227
+ },
228
+ summary: {
229
+ name: manifest.name,
230
+ slug: manifest.slug,
231
+ version: manifest.version,
232
+ tools: manifest.tools.length,
233
+ widgets: manifest.widgets.length
234
+ },
235
+ indexes: {
236
+ tools,
237
+ widgets
238
+ },
239
+ manifest: deps.canonicalizeJson(manifest)
240
+ };
241
+ }
242
+ function buildInternalV1ContextPreset(repoRoot, deps) {
243
+ return {
244
+ name: "internal-v1",
245
+ refs: deps.buildDevRefs(repoRoot),
246
+ anchors: {
247
+ specs: [
248
+ "docs/specifications/01-publisher-rendered-integration.md",
249
+ "docs/specifications/02-platform-rendered-integration.md",
250
+ "docs/specifications/04-guard-xapps.md",
251
+ "docs/specifications/10-data-sharing-graph-policy-v2.md",
252
+ "docs/specifications/13-ai-agent-xapp-authoring.md"
253
+ ],
254
+ audits: [
255
+ "dev/engineering/audits/V1_PRODUCTION_CODEBASE_REVIEW.md",
256
+ "dev/engineering/audits/OPEN_067_PHASE1_INTERNAL_REPO_PLAN.md"
257
+ ],
258
+ tests: [
259
+ "src/__tests__/xappsCli.test.ts",
260
+ "src/__tests__/guardBeforeToolRun.test.ts",
261
+ "src/__tests__/openapiErrorContract.test.ts"
262
+ ]
263
+ }
264
+ };
265
+ }
266
+ function runContextExportCommand(args, deps) {
267
+ const from = deps.argString(args, "from");
268
+ if (!from) {
269
+ throw new Error("Missing required argument: --from <manifest.json>");
270
+ }
271
+ const { manifest, filePath } = deps.parseManifestFromFile(from);
272
+ const payload = buildContextExportPayload(manifest, filePath, deps);
273
+ const preset = deps.argString(args, "preset");
274
+ if (preset) {
275
+ const normalized = preset.trim().toLowerCase();
276
+ if (normalized !== "internal-v1") {
277
+ throw deps.makeCliError(
278
+ "CLI_INVALID_OPTION",
279
+ `Invalid --preset: ${preset} (expected internal-v1)`,
280
+ {
281
+ label: "--preset",
282
+ value: preset
283
+ }
284
+ );
285
+ }
286
+ const repoRoot = deps.findRepoRoot();
287
+ if (!repoRoot) {
288
+ throw deps.makeCliError(
289
+ "CLI_REPO_NOT_FOUND",
290
+ "xapps context export --preset internal-v1 is repo-only and requires the xapps monorepo checkout"
291
+ );
292
+ }
293
+ payload.preset = buildInternalV1ContextPreset(repoRoot, deps);
294
+ }
295
+ const outPath = deps.argString(args, "out");
296
+ const rendered = `${JSON.stringify(payload, null, 2)}
297
+ `;
298
+ if (outPath) {
299
+ const target = import_node_path2.default.resolve(process.cwd(), outPath);
300
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(target), { recursive: true });
301
+ import_node_fs2.default.writeFileSync(target, rendered);
302
+ console.log(`Context exported: ${target}`);
303
+ return;
304
+ }
305
+ process.stdout.write(rendered);
306
+ }
307
+
308
+ // src/commands/manifestCommands.ts
309
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
310
+ var import_node_path3 = __toESM(require("node:path"), 1);
311
+ var import_openapi_import = require("@xapps-platform/openapi-import");
312
+ function buildInitManifestTemplate(input2) {
313
+ return {
314
+ name: input2.name,
315
+ slug: input2.slug,
316
+ version: input2.version,
317
+ target_client_id: "REPLACE_CLIENT_ID",
318
+ description: "Generated by xapps init",
319
+ tools: [
320
+ {
321
+ tool_name: "example_tool",
322
+ title: "Example Tool",
323
+ input_schema: {
324
+ type: "object",
325
+ properties: {
326
+ message: { type: "string" }
327
+ },
328
+ required: ["message"]
329
+ },
330
+ output_schema: {
331
+ type: "object",
332
+ properties: {
333
+ echoed: { type: "string" }
334
+ }
335
+ }
336
+ }
337
+ ],
338
+ widgets: [
339
+ {
340
+ widget_name: "example_widget",
341
+ type: "write",
342
+ renderer: "json-forms",
343
+ bind_tool_name: "example_tool",
344
+ entry: {
345
+ kind: "platform_template",
346
+ template: "write_form"
347
+ }
348
+ }
349
+ ]
350
+ };
351
+ }
352
+ function isPlainObject2(value) {
353
+ return !!value && typeof value === "object" && !Array.isArray(value);
354
+ }
355
+ function jsonTypeMatches(type, value) {
356
+ if (type === "string") return typeof value === "string";
357
+ if (type === "number") return typeof value === "number";
358
+ if (type === "integer") return typeof value === "number" && Number.isInteger(value);
359
+ if (type === "boolean") return typeof value === "boolean";
360
+ if (type === "array") return Array.isArray(value);
361
+ if (type === "object") return isPlainObject2(value);
362
+ if (type === "null") return value === null;
363
+ return true;
364
+ }
365
+ function validatePayloadAgainstSchema(schema, payload, label) {
366
+ if (!isPlainObject2(schema)) return [];
367
+ if (!isPlainObject2(payload)) {
368
+ return [`${label} must be a JSON object`];
369
+ }
370
+ const errors = [];
371
+ const required = Array.isArray(schema.required) ? schema.required : [];
372
+ for (const key of required) {
373
+ if (typeof key === "string" && !(key in payload)) {
374
+ errors.push(`${label} missing required field: ${key}`);
375
+ }
376
+ }
377
+ const properties = isPlainObject2(schema.properties) ? schema.properties : {};
378
+ for (const [key, propSchema] of Object.entries(properties)) {
379
+ if (!(key in payload)) continue;
380
+ if (!isPlainObject2(propSchema)) continue;
381
+ const expectedType = typeof propSchema.type === "string" ? propSchema.type : "";
382
+ if (expectedType && !jsonTypeMatches(expectedType, payload[key])) {
383
+ errors.push(
384
+ `${label}.${key} type mismatch (expected ${expectedType}, got ${Array.isArray(payload[key]) ? "array" : typeof payload[key]})`
385
+ );
386
+ }
387
+ }
388
+ return errors;
389
+ }
390
+ async function runImportCommand(args, deps) {
391
+ const from = deps.argString(args, "from");
392
+ const out = deps.argString(args, "out");
393
+ const fetchTimeoutMs = deps.parsePositiveIntOrThrow(
394
+ deps.argString(args, "fetch-timeout-ms"),
395
+ 1e4,
396
+ "--fetch-timeout-ms"
397
+ );
398
+ if (!from || !out) {
399
+ throw new Error("Missing required arguments: --from and --out");
400
+ }
401
+ const openApiText = await deps.readInput(from, fetchTimeoutMs);
402
+ const fallbackInfo = (0, import_openapi_import.extractOpenApiInfoFromText)(openApiText);
403
+ const name = deps.argString(args, "name") || fallbackInfo.title || "OpenAPI Imported App";
404
+ const slug = deps.argString(args, "slug") || (0, import_openapi_import.slugifyManifestName)(name) || "openapi-imported-app";
405
+ const manifest = (0, import_openapi_import.buildManifestFromOpenApiText)(openApiText, {
406
+ name,
407
+ slug,
408
+ version: deps.argString(args, "version") || "1.0.0",
409
+ description: deps.argString(args, "description") || fallbackInfo.description,
410
+ endpointBaseUrl: deps.argString(args, "endpoint") || "https://api.example.com",
411
+ endpointEnv: deps.argString(args, "endpoint-env") || "prod"
412
+ });
413
+ const outPath = import_node_path3.default.resolve(process.cwd(), out);
414
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(outPath), { recursive: true });
415
+ import_node_fs3.default.writeFileSync(outPath, JSON.stringify(manifest, null, 2));
416
+ console.log(
417
+ `Manifest generated: ${outPath}
418
+ Tools: ${manifest.tools.length}
419
+ Widgets: ${manifest.widgets.length}`
420
+ );
421
+ }
422
+ function runValidateCommand(args, deps) {
423
+ const from = deps.argString(args, "from");
424
+ if (!from) {
425
+ throw new Error("Missing required argument: --from <manifest.json>");
426
+ }
427
+ const { manifest, filePath } = deps.parseManifestFromFile(from);
428
+ console.log(
429
+ `Manifest valid: ${filePath}
430
+ Slug: ${manifest.slug}
431
+ Tools: ${manifest.tools.length}
432
+ Widgets: ${manifest.widgets.length}`
433
+ );
434
+ }
435
+ function runInitCommand(args, deps) {
436
+ const out = deps.argString(args, "out");
437
+ if (!out) {
438
+ throw new Error("Missing required argument: --out <directory>");
439
+ }
440
+ const outDir = import_node_path3.default.resolve(process.cwd(), out);
441
+ import_node_fs3.default.mkdirSync(outDir, { recursive: true });
442
+ const name = deps.argString(args, "name") || "My Xapp";
443
+ const slug = deps.argString(args, "slug") || (0, import_openapi_import.slugifyManifestName)(name) || "my-xapp";
444
+ const version = deps.argString(args, "version") || "1.0.0";
445
+ const force = deps.argFlag(args, "force");
446
+ const manifestPath = import_node_path3.default.join(outDir, "manifest.json");
447
+ const readmePath = import_node_path3.default.join(outDir, "README.md");
448
+ if (!force) {
449
+ for (const filePath of [manifestPath, readmePath]) {
450
+ if (import_node_fs3.default.existsSync(filePath)) {
451
+ throw new Error(`File already exists: ${filePath} (use --force to overwrite)`);
452
+ }
453
+ }
454
+ }
455
+ const manifest = buildInitManifestTemplate({ name, slug, version });
456
+ import_node_fs3.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
457
+ import_node_fs3.default.writeFileSync(
458
+ readmePath,
459
+ `# ${name}
460
+
461
+ Generated by xapps init.
462
+
463
+ Next steps:
464
+
465
+ 1. Edit manifest.json (set target_client_id to the tenant client ID)
466
+ 2. Validate: xapps validate --from manifest.json
467
+ 3. Import/OpenAPI: xapps import ...
468
+ `
469
+ );
470
+ console.log(`Initialized xapp scaffold in: ${outDir}`);
471
+ }
472
+ function runTestCommand(args, deps) {
473
+ const from = deps.argString(args, "from");
474
+ const vectorsPath = deps.argString(args, "vectors");
475
+ if (!from || !vectorsPath) {
476
+ throw new Error(
477
+ "Missing required arguments: --from <manifest.json> and --vectors <vectors.json>"
478
+ );
479
+ }
480
+ const { manifest, filePath: manifestPath } = deps.parseManifestFromFile(from);
481
+ const vectorsRaw = deps.readJsonFile(vectorsPath);
482
+ if (!isPlainObject2(vectorsRaw)) {
483
+ throw new Error("Vector file must be a JSON object with a vectors[] field");
484
+ }
485
+ const vectorsContainer = vectorsRaw;
486
+ if (!Array.isArray(vectorsContainer.vectors)) {
487
+ throw new Error("Vector file must contain vectors[]");
488
+ }
489
+ const failures = [];
490
+ for (let index = 0; index < vectorsContainer.vectors.length; index += 1) {
491
+ const vector = vectorsContainer.vectors[index];
492
+ const name = typeof vector?.name === "string" ? vector.name : `vector_${index + 1}`;
493
+ const toolName = typeof vector?.tool_name === "string" ? vector.tool_name : "";
494
+ if (!toolName) {
495
+ failures.push(`${name}: missing tool_name`);
496
+ continue;
497
+ }
498
+ const tool = manifest.tools.find((item) => item.tool_name === toolName);
499
+ if (!tool) {
500
+ failures.push(`${name}: unknown tool_name '${toolName}'`);
501
+ continue;
502
+ }
503
+ const inputErrors = validatePayloadAgainstSchema(
504
+ tool.input_schema,
505
+ vector.input || {},
506
+ "input"
507
+ );
508
+ const outputErrors = validatePayloadAgainstSchema(
509
+ tool.output_schema,
510
+ vector.expected_output || {},
511
+ "expected_output"
512
+ );
513
+ for (const error of [...inputErrors, ...outputErrors]) {
514
+ failures.push(`${name}: ${error}`);
515
+ }
516
+ }
517
+ if (failures.length) {
518
+ throw new Error(
519
+ `Contract test failed (${failures.length} issues)
520
+ ${failures.map((f) => `- ${f}`).join("\n")}`
521
+ );
522
+ }
523
+ console.log(
524
+ `Contract test passed: ${vectorsContainer.vectors.length} vector(s)
525
+ Manifest: ${manifestPath}
526
+ Vectors: ${import_node_path3.default.resolve(process.cwd(), vectorsPath)}`
527
+ );
528
+ }
529
+
530
+ // src/commands/devCommands.ts
531
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
532
+ var import_node_http = __toESM(require("node:http"), 1);
533
+ var import_node_path6 = __toESM(require("node:path"), 1);
534
+
535
+ // src/devFlow.ts
536
+ function buildDevFlowInitTemplate(type) {
537
+ const normalized = String(type || "").trim().toLowerCase();
538
+ if (normalized === "ai-artifacts") {
539
+ return {
540
+ flow: "sample-ai-artifacts",
541
+ title: "Sample AI plan/check artifacts workflow",
542
+ description: "Edit paths for your app and policy. Uses {{ARTIFACTS_DIR}} for generated plan/check reports.",
543
+ commands: [
544
+ "npm run xapps -- validate --from ./manifest.json",
545
+ "npm run xapps -- ai plan --mode internal --from ./manifest.json --preset internal-v1 --json --out {{ARTIFACTS_DIR}}/xapps.plan.json",
546
+ "npm run xapps -- ai check --mode internal --plan {{ARTIFACTS_DIR}}/xapps.plan.json --policy-preset internal-readonly --json --out {{ARTIFACTS_DIR}}/xapps.check.json"
547
+ ],
548
+ refs: [
549
+ "./manifest.json",
550
+ "./ai/policy.readonly.internal-v1.json",
551
+ "dev/engineering/checklists/SDK_CLI_AI_CHECKLIST.md"
552
+ ]
553
+ };
554
+ }
555
+ if (normalized === "manual-loop") {
556
+ return {
557
+ flow: "sample-manual-loop",
558
+ title: "Sample publisher manual response loop smoke",
559
+ description: "Edit the smoke script path for your publisher sample/workspace. Intended for async manual-response callback loops.",
560
+ commands: ["node ./scripts/manual-loop-smoke.mjs"],
561
+ refs: ["./scripts/manual-loop-smoke.mjs", "./README.md"]
562
+ };
563
+ }
564
+ return null;
565
+ }
566
+ function parseDevFlowObject(raw, fallbackFlowId = "") {
567
+ const errors = [];
568
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
569
+ return {
570
+ flow: { key: "", title: "", description: "", commands: [], refs: [] },
571
+ errors: ["expected_object"]
572
+ };
573
+ }
574
+ const source = raw;
575
+ const key = String(source.flow || source.key || fallbackFlowId || "").trim().toLowerCase();
576
+ const title = String(source.title || "").trim();
577
+ const description = String(source.description || "").trim();
578
+ const commands = Array.isArray(source.commands) ? source.commands.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean) : [];
579
+ const refs = Array.isArray(source.refs) ? source.refs.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean) : [];
580
+ if (!key) errors.push("flow_id_missing");
581
+ if (!title) errors.push("title_missing");
582
+ if (!description) errors.push("description_missing");
583
+ if (!commands.length) errors.push("commands_missing");
584
+ return {
585
+ flow: { key, title, description, commands, refs },
586
+ errors
587
+ };
588
+ }
589
+ function lintDevFlowDefinition(flow) {
590
+ const commands = Array.isArray(flow.commands) ? flow.commands : [];
591
+ return [
592
+ {
593
+ key: "flow_id_present",
594
+ ok: typeof flow.key === "string" && flow.key.trim().length > 0,
595
+ details: { value: flow.key || null }
596
+ },
597
+ {
598
+ key: "title_present",
599
+ ok: typeof flow.title === "string" && flow.title.trim().length > 0,
600
+ details: { value: flow.title || null }
601
+ },
602
+ {
603
+ key: "description_present",
604
+ ok: typeof flow.description === "string" && flow.description.trim().length > 0,
605
+ details: { value: flow.description || null }
606
+ },
607
+ {
608
+ key: "commands_nonempty",
609
+ ok: commands.length > 0,
610
+ details: { command_count: commands.length }
611
+ },
612
+ {
613
+ key: "commands_prefixed",
614
+ ok: commands.every((cmd) => /^(npm|npx|node|pnpm|yarn|bun)\b/.test(String(cmd).trim())),
615
+ details: {
616
+ invalid_commands: commands.filter(
617
+ (cmd) => !/^(npm|npx|node|pnpm|yarn|bun)\b/.test(String(cmd).trim())
618
+ )
619
+ }
620
+ }
621
+ ];
622
+ }
623
+
624
+ // src/devCheckFlow.ts
625
+ var import_node_child_process = require("node:child_process");
626
+ var import_node_path4 = __toESM(require("node:path"), 1);
627
+ function shellEscapeArg(value) {
628
+ if (/^[A-Za-z0-9_./:-]+$/.test(value)) return value;
629
+ return `'${String(value).replace(/'/g, `'"'"'`)}'`;
630
+ }
631
+ function buildBuiltInDevCheckFlows(artifactsDir) {
632
+ const xplacePlanPath = import_node_path4.default.join(artifactsDir, "xapps-xplace-certs.plan.json");
633
+ const xplaceCheckPath = import_node_path4.default.join(artifactsDir, "xapps-xplace-certs.check.json");
634
+ return {
635
+ "pay-per-request": {
636
+ title: "Pay-per-request baseline flow",
637
+ description: "Verifies guarded payment orchestration baseline across widget/guards/contracts for V1.",
638
+ commands: [
639
+ "npm test -- src/__tests__/guardBeforeToolRun.test.ts",
640
+ "npm test -- src/__tests__/widgetSdk.test.ts src/__tests__/extensionsLabJsonformsPayment.e2e.test.ts",
641
+ "npm test -- src/__tests__/openapiErrorContract.test.ts"
642
+ ],
643
+ refs: [
644
+ "dev/engineering/audits/V1_PRODUCTION_CODEBASE_REVIEW.md",
645
+ "dev/engineering/pm/OPEN_LIST.md"
646
+ ]
647
+ },
648
+ "guard-orchestration": {
649
+ title: "Guard orchestration bridge flow",
650
+ description: "Verifies blocked/confirm/payment orchestration bridge behavior across host/embed guard paths.",
651
+ commands: [
652
+ "npm test -- src/__tests__/guardContractParity.test.ts src/__tests__/integrationHostGuardContracts.test.ts",
653
+ "npm test -- src/__tests__/widgetRuntime.guardBridge.test.ts",
654
+ "npx playwright test e2e/extensions-guards.spec.ts --grep orchestration|guard"
655
+ ],
656
+ refs: [
657
+ "dev/engineering/audits/V1_PRODUCTION_CODEBASE_REVIEW.md",
658
+ "dev/engineering/pm/OPEN_POINTS.md"
659
+ ]
660
+ },
661
+ "xplace-certs": {
662
+ title: "xplace certs first-xapp internal workflow",
663
+ description: "Validates and inspects the xplace-certs JSON Forms manifest using internal-repo CLI helpers.",
664
+ commands: [
665
+ "npm run xapps -- validate --from apps/publishers/xplace/xapps/xplace-certs/manifest.json",
666
+ `npm run xapps -- ai plan --mode internal --from apps/publishers/xplace/xapps/xplace-certs/manifest.json --preset internal-v1 --flow xplace-certs --json --out ${shellEscapeArg(xplacePlanPath)}`,
667
+ `npm run xapps -- ai check --mode internal --plan ${shellEscapeArg(xplacePlanPath)} --policy apps/publishers/xplace/xapps/xplace-certs/ai/policy.readonly.internal-v1.json --json --out ${shellEscapeArg(xplaceCheckPath)}`
668
+ ],
669
+ refs: [
670
+ "apps/publishers/xplace/xapps/xplace-certs/manifest.json",
671
+ "apps/publishers/xplace/xapps/xplace-certs/ai/policy.readonly.internal-v1.json",
672
+ "apps/publishers/xplace/backend/server.js",
673
+ "dev/engineering/audits/OPEN_067_PHASE1_INTERNAL_REPO_PLAN.md"
674
+ ]
675
+ }
676
+ };
677
+ }
678
+ function executeDevCheckFlowCommands(commands, repoRoot) {
679
+ return commands.map((command) => {
680
+ const parts = String(command).trim().split(/\s+/).filter(Boolean);
681
+ if (!parts.length) {
682
+ return {
683
+ command,
684
+ ok: false,
685
+ exit_code: null,
686
+ signal: null,
687
+ stdout: "",
688
+ stderr: "",
689
+ error: "empty_command"
690
+ };
691
+ }
692
+ const result = (0, import_node_child_process.spawnSync)(parts[0], parts.slice(1), {
693
+ cwd: repoRoot,
694
+ encoding: "utf8",
695
+ stdio: "pipe"
696
+ });
697
+ return {
698
+ command,
699
+ ok: (result.status ?? 1) === 0 && !result.error,
700
+ exit_code: result.status ?? null,
701
+ signal: result.signal ?? null,
702
+ stdout: String(result.stdout || ""),
703
+ stderr: String(result.stderr || ""),
704
+ error: result.error ? String(result.error.message || result.error) : null
705
+ };
706
+ });
707
+ }
708
+
709
+ // src/aiCommands.ts
710
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
711
+ var import_node_path5 = __toESM(require("node:path"), 1);
712
+ var import_promises = __toESM(require("node:readline/promises"), 1);
713
+ var import_node_process = require("node:process");
714
+ var import_server_sdk = require("@xapps-platform/server-sdk");
715
+ function isPlainObject3(value) {
716
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
717
+ }
718
+ function parseAiMode(args, deps) {
719
+ const mode = String(deps.argString(args, "mode") || "internal").trim().toLowerCase();
720
+ if (mode !== "internal") {
721
+ throw deps.makeCliError(
722
+ "CLI_AI_MODE_UNSUPPORTED",
723
+ `Unsupported --mode: ${mode} (only internal is implemented in this phase)`,
724
+ { label: "--mode", value: mode, supported: ["internal"] }
725
+ );
726
+ }
727
+ return "internal";
728
+ }
729
+ function parsePositiveIntOptionOrThrow(value, fallback, label, deps) {
730
+ if (!value) return fallback;
731
+ const parsed = Number(value);
732
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
733
+ throw deps.makeCliError(
734
+ "CLI_INVALID_OPTION",
735
+ `Invalid ${label}: ${value} (expected positive integer)`,
736
+ { label, value }
737
+ );
738
+ }
739
+ return parsed;
740
+ }
741
+ function detectPlanRendererFamilies(manifest) {
742
+ const found = /* @__PURE__ */ new Set();
743
+ for (const widget of manifest.widgets || []) {
744
+ const renderer = String(widget.renderer || "").trim().toLowerCase();
745
+ if (renderer === "publisher") {
746
+ found.add("publisher-rendered");
747
+ continue;
748
+ }
749
+ if (renderer === "json-forms") {
750
+ found.add("jsonforms");
751
+ continue;
752
+ }
753
+ if (renderer === "platform") found.add("platform");
754
+ }
755
+ return Array.from(found.values()).sort();
756
+ }
757
+ function normalizeLowerSet(values) {
758
+ return new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean));
759
+ }
760
+ function inferMockAssetKind(fileName) {
761
+ const lower = fileName.toLowerCase();
762
+ if (/\.(png|jpe?g|gif|webp|bmp|svg)$/.test(lower)) return "image";
763
+ if (/\.json$/.test(lower)) return "json";
764
+ if (/\.(txt|md|yaml|yml)$/.test(lower)) return "text";
765
+ return "other";
766
+ }
767
+ function listMockAssets(manifestFilePath, args, deps) {
768
+ const rawMockDirs = [
769
+ deps.argString(args, "mocks"),
770
+ deps.argString(args, "mocks-dir"),
771
+ import_node_path5.default.join(import_node_path5.default.dirname(manifestFilePath), "mocks")
772
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
773
+ const seenDirs = normalizeLowerSet([]);
774
+ const out = [];
775
+ for (const rawDir of rawMockDirs) {
776
+ const dirPath = import_node_path5.default.resolve(process.cwd(), rawDir);
777
+ const dedupeKey = dirPath.toLowerCase();
778
+ if (seenDirs.has(dedupeKey)) continue;
779
+ seenDirs.add(dedupeKey);
780
+ if (!import_node_fs4.default.existsSync(dirPath)) continue;
781
+ let entries = [];
782
+ try {
783
+ entries = import_node_fs4.default.readdirSync(dirPath, { withFileTypes: true });
784
+ } catch {
785
+ continue;
786
+ }
787
+ for (const entry of entries) {
788
+ if (!entry.isFile()) continue;
789
+ const absPath = import_node_path5.default.join(dirPath, entry.name);
790
+ let size;
791
+ try {
792
+ size = import_node_fs4.default.statSync(absPath).size;
793
+ } catch {
794
+ size = void 0;
795
+ }
796
+ out.push({
797
+ path: import_node_path5.default.relative(process.cwd(), absPath),
798
+ name: entry.name,
799
+ kind: inferMockAssetKind(entry.name),
800
+ ...typeof size === "number" ? { bytes: size } : {}
801
+ });
802
+ }
803
+ }
804
+ return out.sort((a, b) => a.path.localeCompare(b.path));
805
+ }
806
+ function readAiGuidance(args, deps) {
807
+ const inline = deps.argString(args, "guidance");
808
+ const file = deps.argString(args, "guidance-file");
809
+ if (inline && file) {
810
+ throw deps.makeCliError(
811
+ "CLI_INVALID_ARGS",
812
+ "Use either --guidance <text> or --guidance-file <path>, not both"
813
+ );
814
+ }
815
+ const raw = file ? import_node_fs4.default.readFileSync(import_node_path5.default.resolve(process.cwd(), file), "utf8") : typeof inline === "string" ? inline : "";
816
+ const guidance = String(raw || "").trim();
817
+ return guidance || void 0;
818
+ }
819
+ function getToolByName(manifest, toolName) {
820
+ for (const tool of manifest.tools || []) {
821
+ if (String(tool.tool_name || "") === toolName) return tool;
822
+ }
823
+ return null;
824
+ }
825
+ function collectUiElements(uiSchema) {
826
+ if (!uiSchema || typeof uiSchema !== "object") return [];
827
+ const stack = [uiSchema];
828
+ const out = [];
829
+ while (stack.length) {
830
+ const node = stack.pop();
831
+ if (!node || typeof node !== "object") continue;
832
+ out.push(node);
833
+ const elements = Array.isArray(node.elements) ? node.elements : [];
834
+ for (const child of elements) stack.push(child);
835
+ }
836
+ return out;
837
+ }
838
+ function hasStepperUiSchema(uiSchema) {
839
+ if (!uiSchema || typeof uiSchema !== "object") return false;
840
+ if (String(uiSchema.type || "") !== "Categorization") return false;
841
+ const options = uiSchema.options;
842
+ return options && typeof options === "object" && String(options.variant || "").toLowerCase() === "stepper";
843
+ }
844
+ function hasPreviewSections(uiSchema) {
845
+ return collectUiElements(uiSchema).some(
846
+ (node) => String(node.type || "") === "XAppsDisplayPanel" || String(node.component || "").toLowerCase().includes("preview")
847
+ );
848
+ }
849
+ function buildControlScope(propertyName) {
850
+ return `#/properties/${propertyName}`;
851
+ }
852
+ function buildStepperUiSchemaSuggestion(inputSchema, options) {
853
+ const props = inputSchema && typeof inputSchema === "object" && inputSchema.properties && typeof inputSchema.properties === "object" ? inputSchema.properties : {};
854
+ const propertyNames = Object.keys(props);
855
+ const nonAttachments = [];
856
+ const attachments = [];
857
+ for (const key of propertyNames) {
858
+ const prop = props[key] || {};
859
+ const isBinaryArray = String(prop.type || "") === "array" && prop.items && typeof prop.items === "object" && String(prop.items.format || "").toLowerCase() === "binary";
860
+ const isBinary = String(prop.format || "").toLowerCase() === "binary";
861
+ if (isBinary || isBinaryArray) attachments.push(key);
862
+ else nonAttachments.push(key);
863
+ }
864
+ const splitIndex = nonAttachments.length > 3 ? Math.ceil(nonAttachments.length / 2) : nonAttachments.length;
865
+ const primary = nonAttachments.slice(0, splitIndex);
866
+ const secondary = nonAttachments.slice(splitIndex);
867
+ const categories = [];
868
+ if (primary.length) {
869
+ const elements = primary.map((key) => ({ type: "Control", scope: buildControlScope(key) }));
870
+ if (options?.includePreviewPanel) {
871
+ const previewScope = buildControlScope(attachments[0] || primary[0]);
872
+ elements.push({
873
+ type: "XAppsDisplayPanel",
874
+ component: "json-preview",
875
+ label: attachments[0] ? "Attachments Preview" : "Form Preview",
876
+ scope: previewScope
877
+ });
878
+ }
879
+ categories.push({
880
+ type: "Category",
881
+ label: "Basic Info",
882
+ elements
883
+ });
884
+ }
885
+ if (secondary.length) {
886
+ categories.push({
887
+ type: "Category",
888
+ label: "Details",
889
+ elements: secondary.map((key) => ({ type: "Control", scope: buildControlScope(key) }))
890
+ });
891
+ }
892
+ if (attachments.length) {
893
+ categories.push({
894
+ type: "Category",
895
+ label: "Attachments",
896
+ elements: attachments.map((key) => ({ type: "Control", scope: buildControlScope(key) }))
897
+ });
898
+ }
899
+ if (!categories.length) {
900
+ categories.push({
901
+ type: "Category",
902
+ label: "Form",
903
+ elements: options?.includePreviewPanel ? [
904
+ {
905
+ type: "XAppsDisplayPanel",
906
+ component: "json-preview",
907
+ label: "Form Preview",
908
+ scope: "#"
909
+ }
910
+ ] : []
911
+ });
912
+ }
913
+ return {
914
+ type: "Categorization",
915
+ options: { variant: "stepper" },
916
+ elements: categories
917
+ };
918
+ }
919
+ function buildPreviewSectionSuggestion(inputSchema) {
920
+ const props = inputSchema && typeof inputSchema === "object" && inputSchema.properties && typeof inputSchema.properties === "object" ? inputSchema.properties : {};
921
+ const names = Object.keys(props);
922
+ const attachmentKey = names.find((key) => {
923
+ const prop = props[key] || {};
924
+ return String(prop.type || "") === "array" && prop.items && typeof prop.items === "object" && String(prop.items.format || "").toLowerCase() === "binary" || String(prop.format || "").toLowerCase() === "binary";
925
+ });
926
+ const fallbackKey = attachmentKey || names[0];
927
+ return [
928
+ {
929
+ type: "XAppsDisplayPanel",
930
+ component: "json-preview",
931
+ label: attachmentKey ? "Attachments Preview" : "Form Preview",
932
+ ...fallbackKey ? { scope: buildControlScope(fallbackKey) } : { scope: "#" }
933
+ }
934
+ ];
935
+ }
936
+ function buildManifestPatchHints(manifest, mockAssets, guidance) {
937
+ const hints = [];
938
+ const guidanceLower = String(guidance || "").toLowerCase();
939
+ const wantsStepper = /step(?:s|per)?|wizard/.test(guidanceLower);
940
+ const wantsPreview = /preview|widget[- ]?preview|display panel/.test(guidanceLower);
941
+ const hasImageMocks = mockAssets.some((asset) => asset.kind === "image");
942
+ for (const widget of manifest.widgets || []) {
943
+ const w = widget;
944
+ if (String(w.renderer || "").toLowerCase() !== "json-forms") continue;
945
+ const toolName = String(w.bind_tool_name || "").trim();
946
+ if (!toolName) continue;
947
+ const tool = getToolByName(manifest, toolName);
948
+ if (!tool) continue;
949
+ const uiSchema = tool.input_ui_schema;
950
+ const inputSchema = tool.input_schema;
951
+ const hasStepper = hasStepperUiSchema(uiSchema);
952
+ const hasPreview = hasPreviewSections(uiSchema);
953
+ const shouldSuggestStepper = !hasStepper && (wantsStepper || hasImageMocks);
954
+ const shouldSuggestPreview = !hasPreview && (wantsPreview || hasImageMocks);
955
+ if (!shouldSuggestStepper && !shouldSuggestPreview) continue;
956
+ const proposedInputUiSchema = shouldSuggestStepper ? buildStepperUiSchemaSuggestion(inputSchema, { includePreviewPanel: shouldSuggestPreview }) : void 0;
957
+ const appendPreviewSections = !shouldSuggestStepper && shouldSuggestPreview ? buildPreviewSectionSuggestion(inputSchema) : void 0;
958
+ hints.push({
959
+ widget_name: String(w.widget_name || ""),
960
+ bind_tool_name: toolName,
961
+ reasons: [
962
+ ...hasImageMocks ? ["mock_assets_detected"] : [],
963
+ ...shouldSuggestStepper ? ["missing_stepper_wizard_ui"] : [],
964
+ ...shouldSuggestPreview ? ["missing_preview_sections"] : []
965
+ ],
966
+ patch: {
967
+ tool_name: toolName,
968
+ field: "input_ui_schema",
969
+ mode: shouldSuggestStepper ? "replace" : "append_preview_sections",
970
+ ...proposedInputUiSchema ? { value: proposedInputUiSchema } : {},
971
+ ...appendPreviewSections ? { preview_sections: appendPreviewSections } : {}
972
+ },
973
+ review_notes: [
974
+ "Review inferred step grouping and labels before applying.",
975
+ "Preview panel suggestion is generic; customize component/scope for your UX."
976
+ ]
977
+ });
978
+ }
979
+ return hints;
980
+ }
981
+ function mimeTypeForMockAsset(fileName) {
982
+ const lower = fileName.toLowerCase();
983
+ if (lower.endsWith(".png")) return "image/png";
984
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
985
+ if (lower.endsWith(".webp")) return "image/webp";
986
+ if (lower.endsWith(".gif")) return "image/gif";
987
+ if (lower.endsWith(".svg")) return "image/svg+xml";
988
+ return "application/octet-stream";
989
+ }
990
+ function toDataUrlFromFile(filePath) {
991
+ const abs = import_node_path5.default.resolve(process.cwd(), filePath);
992
+ const data = import_node_fs4.default.readFileSync(abs);
993
+ const mime = mimeTypeForMockAsset(filePath);
994
+ return `data:${mime};base64,${data.toString("base64")}`;
995
+ }
996
+ function extractJsonObjectFromText(text) {
997
+ const trimmed = String(text || "").trim();
998
+ if (!trimmed) return null;
999
+ try {
1000
+ const parsed = JSON.parse(trimmed);
1001
+ return isPlainObject3(parsed) ? parsed : null;
1002
+ } catch {
1003
+ }
1004
+ const fenceMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
1005
+ if (fenceMatch?.[1]) {
1006
+ try {
1007
+ const parsed = JSON.parse(fenceMatch[1].trim());
1008
+ return isPlainObject3(parsed) ? parsed : null;
1009
+ } catch {
1010
+ return null;
1011
+ }
1012
+ }
1013
+ return null;
1014
+ }
1015
+ async function buildLlmManifestPatchHints(manifest, mockAssets, guidance, args, deps) {
1016
+ const apiKey = deps.argString(args, "llm-api-key") || process.env.OPENAI_API_KEY || process.env.XAPPS_AI_API_KEY || "";
1017
+ if (!apiKey) {
1018
+ throw deps.makeCliError(
1019
+ "CLI_INVALID_ARGS",
1020
+ "Missing LLM API key. Provide --llm-api-key or set OPENAI_API_KEY/XAPPS_AI_API_KEY"
1021
+ );
1022
+ }
1023
+ const baseUrl = String(
1024
+ deps.argString(args, "llm-base-url") || process.env.OPENAI_BASE_URL || "https://api.openai.com/v1"
1025
+ ).replace(/\/+$/, "");
1026
+ const model = String(
1027
+ deps.argString(args, "llm-model") || process.env.XAPPS_AI_MODEL || "gpt-5.2"
1028
+ ).trim();
1029
+ const timeoutMs = parsePositiveIntOptionOrThrow(
1030
+ deps.argString(args, "llm-timeout-ms") || process.env.XAPPS_AI_TIMEOUT_MS,
1031
+ 3e4,
1032
+ "--llm-timeout-ms",
1033
+ deps
1034
+ );
1035
+ const imageAssets = mockAssets.filter((m) => m.kind === "image").slice(0, 4);
1036
+ const userContent = [
1037
+ {
1038
+ type: "text",
1039
+ text: [
1040
+ "Analyze this xapp manifest and mock assets and propose JSON Forms manifest patch hints.",
1041
+ "Focus on json-forms widgets and infer complete form fields from mockups/guidance.",
1042
+ "When image mockups are provided, BOTH are needed: propose input_schema AND input_ui_schema (wizard + previews).",
1043
+ 'Return STRICT JSON object only with shape: {"hints": [ ... ]}.',
1044
+ "Each hint item must include: widget_name, bind_tool_name, reasons (array), patch (object), review_notes (array).",
1045
+ "Patch object must include tool_name, field, mode and target only input_schema or input_ui_schema.",
1046
+ "Supported patch modes: replace (for input_schema or input_ui_schema), append_preview_sections (input_ui_schema only).",
1047
+ "For image mockups, you MUST attempt complete input_schema inference first (best effort), then align input_ui_schema controls/previews to those fields.",
1048
+ "If confidence is low, still provide a best-effort input_schema and explain uncertainty in review_notes.",
1049
+ "For image mocks, prefer image-aware preview suggestions (e.g. attachment-scoped preview panels, data-view cards/list/table of image metadata, or publisher_preview-backed preview sections if endpoints already exist) instead of a generic form-level json-preview when possible.",
1050
+ "If you suggest preview sections, include component and any options needed (label, scope, options.view/options.dataSource, etc.).",
1051
+ "Use only supported preview/display components and views: components=[json-preview, location-map-preview, data-view]; data-view types=[key-value, list, table, badges, cards, json].",
1052
+ "Do not modify security/guards/connectivity.",
1053
+ guidance ? `User guidance: ${guidance}` : "User guidance: none",
1054
+ `Manifest JSON:
1055
+ ${JSON.stringify(manifest, null, 2)}`,
1056
+ `Mock assets summary:
1057
+ ${JSON.stringify(mockAssets, null, 2)}`
1058
+ ].join("\n\n")
1059
+ }
1060
+ ];
1061
+ for (const asset of imageAssets) {
1062
+ try {
1063
+ userContent.push({
1064
+ type: "image_url",
1065
+ image_url: { url: toDataUrlFromFile(asset.path) }
1066
+ });
1067
+ } catch {
1068
+ }
1069
+ }
1070
+ const payload = {
1071
+ model,
1072
+ response_format: { type: "json_object" },
1073
+ messages: [
1074
+ {
1075
+ role: "system",
1076
+ content: "You are an xapps manifest assistant. Propose minimal, safe JSON Forms manifest patch hints (input_schema and input_ui_schema only)."
1077
+ },
1078
+ {
1079
+ role: "user",
1080
+ content: userContent
1081
+ }
1082
+ ],
1083
+ temperature: 0.2
1084
+ };
1085
+ const controller = new AbortController();
1086
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1087
+ let response;
1088
+ try {
1089
+ response = await fetch(`${baseUrl}/chat/completions`, {
1090
+ method: "POST",
1091
+ headers: {
1092
+ "Content-Type": "application/json",
1093
+ Authorization: `Bearer ${apiKey}`
1094
+ },
1095
+ body: JSON.stringify(payload),
1096
+ signal: controller.signal
1097
+ });
1098
+ } catch (err) {
1099
+ if (err?.name === "AbortError") {
1100
+ throw deps.makeCliError("CLI_RUNTIME_ERROR", `LLM request timed out after ${timeoutMs}ms`, {
1101
+ provider: "openai-compatible",
1102
+ model,
1103
+ timeout_ms: timeoutMs
1104
+ });
1105
+ }
1106
+ throw err;
1107
+ } finally {
1108
+ clearTimeout(timer);
1109
+ }
1110
+ const rawText = await response.text();
1111
+ if (!response.ok) {
1112
+ throw deps.makeCliError("CLI_RUNTIME_ERROR", `LLM request failed: status=${response.status}`, {
1113
+ status: response.status,
1114
+ body: rawText.slice(0, 2e3),
1115
+ provider: "openai-compatible",
1116
+ model
1117
+ });
1118
+ }
1119
+ let parsedResp = null;
1120
+ try {
1121
+ parsedResp = JSON.parse(rawText);
1122
+ } catch {
1123
+ throw deps.makeCliError("CLI_RUNTIME_ERROR", "LLM returned non-JSON response envelope", {
1124
+ provider: "openai-compatible",
1125
+ model
1126
+ });
1127
+ }
1128
+ const content = String(parsedResp?.choices?.[0]?.message?.content || "");
1129
+ const llmJson = extractJsonObjectFromText(content);
1130
+ if (!llmJson || !Array.isArray(llmJson.hints)) {
1131
+ throw deps.makeCliError("CLI_RUNTIME_ERROR", "LLM response missing hints[] JSON payload", {
1132
+ provider: "openai-compatible",
1133
+ model
1134
+ });
1135
+ }
1136
+ const hints = llmJson.hints.filter(
1137
+ (item) => isPlainObject3(item)
1138
+ );
1139
+ return { hints, provider: "openai-compatible", model };
1140
+ }
1141
+ function evaluateLlmHintCompleteness(hints, options) {
1142
+ const fields = /* @__PURE__ */ new Set();
1143
+ for (const hint of hints) {
1144
+ const patch = isPlainObject3(hint.patch) ? hint.patch : null;
1145
+ const field = String(patch?.field || "").trim();
1146
+ if (field) fields.add(field);
1147
+ }
1148
+ const missing = [];
1149
+ if (options.requireInputSchema && !fields.has("input_schema")) missing.push("input_schema");
1150
+ if (options.requireInputUiSchema && !fields.has("input_ui_schema"))
1151
+ missing.push("input_ui_schema");
1152
+ return { ok: missing.length === 0, missing };
1153
+ }
1154
+ async function readAiGuidanceInteractiveIfRequested(args, deps) {
1155
+ const guidance = readAiGuidance(args, deps);
1156
+ if (guidance) return guidance;
1157
+ if (!deps.argFlag(args, "ask-guidance")) return void 0;
1158
+ if (!import_node_process.stdin.isTTY || !import_node_process.stdout.isTTY) {
1159
+ throw deps.makeCliError(
1160
+ "CLI_INVALID_ARGS",
1161
+ "--ask-guidance requires an interactive terminal (TTY) or use --guidance/--guidance-file"
1162
+ );
1163
+ }
1164
+ const rl = import_promises.default.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
1165
+ try {
1166
+ const answer = await rl.question(
1167
+ "AI guidance (optional). Example: infer complete form fields from mockups, build a step wizard, add supported preview sections, and exclude payment screens because payment is handled by guards.\n> "
1168
+ );
1169
+ const trimmed = String(answer || "").trim();
1170
+ return trimmed || void 0;
1171
+ } finally {
1172
+ rl.close();
1173
+ }
1174
+ }
1175
+ function applySingleManifestPatchHint(manifest, hint) {
1176
+ const patch = isPlainObject3(hint.patch) ? hint.patch : null;
1177
+ if (!patch) return { changed: false, summary: "skip: missing patch object" };
1178
+ const toolName = String(patch.tool_name || "").trim();
1179
+ const field = String(patch.field || "").trim();
1180
+ const mode = String(patch.mode || "").trim();
1181
+ if (!toolName || field !== "input_ui_schema" && field !== "input_schema" || !mode) {
1182
+ return {
1183
+ changed: false,
1184
+ summary: `skip: unsupported patch target (${toolName || "?"}:${field}:${mode})`
1185
+ };
1186
+ }
1187
+ const tools = Array.isArray(manifest.tools) ? manifest.tools : [];
1188
+ const tool = tools.find((t) => String(t?.tool_name || "") === toolName);
1189
+ if (!tool || typeof tool !== "object") {
1190
+ return { changed: false, summary: `skip: tool not found (${toolName})` };
1191
+ }
1192
+ if (mode === "replace") {
1193
+ if (!isPlainObject3(patch.value)) {
1194
+ return { changed: false, summary: `skip: replace patch missing object value (${toolName})` };
1195
+ }
1196
+ const before = JSON.stringify(tool[field] ?? null);
1197
+ tool[field] = patch.value;
1198
+ const after = JSON.stringify(tool[field] ?? null);
1199
+ return { changed: before !== after, summary: `replace ${toolName}.${field}` };
1200
+ }
1201
+ if (mode === "append_preview_sections") {
1202
+ if (field !== "input_ui_schema") {
1203
+ return {
1204
+ changed: false,
1205
+ summary: `skip: append_preview_sections only supports input_ui_schema (${toolName})`
1206
+ };
1207
+ }
1208
+ const previewSections = Array.isArray(patch.preview_sections) ? patch.preview_sections.filter((s) => isPlainObject3(s)) : [];
1209
+ if (!previewSections.length) {
1210
+ return { changed: false, summary: `skip: no preview_sections (${toolName})` };
1211
+ }
1212
+ const ui = isPlainObject3(tool[field]) ? tool[field] : null;
1213
+ if (!ui) {
1214
+ tool[field] = { type: "VerticalLayout", elements: [...previewSections] };
1215
+ return { changed: true, summary: `create ${toolName}.${field} with preview sections` };
1216
+ }
1217
+ const uiType = String(ui.type || "");
1218
+ if (uiType === "Categorization" && Array.isArray(ui.elements)) {
1219
+ const categories = ui.elements;
1220
+ const attachmentCategory = categories.find((cat) => {
1221
+ if (!cat || typeof cat !== "object") return false;
1222
+ const label = String(cat.label || "").toLowerCase();
1223
+ if (label.includes("attachment")) return true;
1224
+ const els = Array.isArray(cat.elements) ? cat.elements : [];
1225
+ return els.some(
1226
+ (el) => String(el?.type || "") === "Control" && String(el?.scope || "").toLowerCase().includes("/attachments")
1227
+ );
1228
+ });
1229
+ if (attachmentCategory) {
1230
+ if (!Array.isArray(attachmentCategory.elements))
1231
+ attachmentCategory.elements = [];
1232
+ const targetEls2 = attachmentCategory.elements;
1233
+ const before2 = JSON.stringify(targetEls2);
1234
+ for (const section of previewSections) targetEls2.push(section);
1235
+ return {
1236
+ changed: before2 !== JSON.stringify(targetEls2),
1237
+ summary: `append preview sections to attachment category in ${toolName}.${field}`
1238
+ };
1239
+ }
1240
+ categories.push({
1241
+ type: "Category",
1242
+ label: "Preview",
1243
+ elements: [...previewSections]
1244
+ });
1245
+ return { changed: true, summary: `append Preview category to ${toolName}.${field}` };
1246
+ }
1247
+ if (!Array.isArray(ui.elements)) ui.elements = [];
1248
+ const targetEls = ui.elements;
1249
+ const before = JSON.stringify(targetEls);
1250
+ for (const section of previewSections) targetEls.push(section);
1251
+ return {
1252
+ changed: before !== JSON.stringify(targetEls),
1253
+ summary: `append preview sections to ${toolName}.${field}`
1254
+ };
1255
+ }
1256
+ return { changed: false, summary: `skip: unsupported patch mode (${mode})` };
1257
+ }
1258
+ function renderSimpleJsonDiff(beforeText, afterText) {
1259
+ if (beforeText === afterText) return "(no changes)";
1260
+ const before = beforeText.split("\n");
1261
+ const after = afterText.split("\n");
1262
+ let prefix = 0;
1263
+ while (prefix < before.length && prefix < after.length && before[prefix] === after[prefix])
1264
+ prefix += 1;
1265
+ let suffix = 0;
1266
+ while (suffix < before.length - prefix && suffix < after.length - prefix && before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
1267
+ suffix += 1;
1268
+ }
1269
+ const beforeMid = before.slice(prefix, before.length - suffix);
1270
+ const afterMid = after.slice(prefix, after.length - suffix);
1271
+ const outLines = [];
1272
+ const startLine = prefix + 1;
1273
+ outLines.push(`@@ manifest.json:${startLine} @@`);
1274
+ for (const line of beforeMid) outLines.push(`- ${line}`);
1275
+ for (const line of afterMid) outLines.push(`+ ${line}`);
1276
+ return outLines.join("\n");
1277
+ }
1278
+ function applyManifestPatchHintsToFile(filePath, manifest, hints) {
1279
+ const beforeObj = JSON.parse(JSON.stringify(manifest));
1280
+ const beforeText = `${JSON.stringify(beforeObj, null, 2)}
1281
+ `;
1282
+ const workObj = JSON.parse(beforeText);
1283
+ const summaries = [];
1284
+ let appliedCount = 0;
1285
+ let changed = false;
1286
+ for (const hint of hints) {
1287
+ const result = applySingleManifestPatchHint(workObj, hint);
1288
+ summaries.push(result.summary);
1289
+ if (result.summary.startsWith("skip:")) continue;
1290
+ appliedCount += 1;
1291
+ changed = changed || result.changed;
1292
+ }
1293
+ const afterText = `${JSON.stringify(workObj, null, 2)}
1294
+ `;
1295
+ if (changed) {
1296
+ (0, import_server_sdk.parseXappManifest)(workObj);
1297
+ import_node_fs4.default.writeFileSync(filePath, afterText, "utf8");
1298
+ }
1299
+ return {
1300
+ applied: true,
1301
+ changed,
1302
+ file: filePath,
1303
+ applied_count: appliedCount,
1304
+ summaries,
1305
+ diff: renderSimpleJsonDiff(beforeText, afterText)
1306
+ };
1307
+ }
1308
+ function parseAiCheckPolicy(rawPolicy, policyFile, deps) {
1309
+ if (!isPlainObject3(rawPolicy)) {
1310
+ throw deps.makeCliError("CLI_AI_POLICY_INVALID", "Invalid AI policy JSON: expected object", {
1311
+ file: policyFile
1312
+ });
1313
+ }
1314
+ const policy = rawPolicy;
1315
+ const schemaVersion = typeof policy.schema_version === "string" ? String(policy.schema_version).trim() : "";
1316
+ if (schemaVersion && schemaVersion !== "xapps.ai.policy.v1") {
1317
+ throw deps.makeCliError(
1318
+ "CLI_AI_POLICY_INVALID",
1319
+ `Invalid AI policy schema_version: ${schemaVersion} (expected xapps.ai.policy.v1)`,
1320
+ { file: policyFile, schema_version: schemaVersion }
1321
+ );
1322
+ }
1323
+ if (Object.prototype.hasOwnProperty.call(policy, "require_read_only") && typeof policy.require_read_only !== "boolean") {
1324
+ throw deps.makeCliError(
1325
+ "CLI_AI_POLICY_INVALID",
1326
+ "AI policy require_read_only must be boolean",
1327
+ {
1328
+ file: policyFile
1329
+ }
1330
+ );
1331
+ }
1332
+ if (Object.prototype.hasOwnProperty.call(policy, "max_actions")) {
1333
+ const value = policy.max_actions;
1334
+ if (!Number.isInteger(value) || Number(value) < 0) {
1335
+ throw deps.makeCliError(
1336
+ "CLI_AI_POLICY_INVALID",
1337
+ "AI policy max_actions must be a non-negative integer",
1338
+ { file: policyFile }
1339
+ );
1340
+ }
1341
+ }
1342
+ if (Object.prototype.hasOwnProperty.call(policy, "allow_action_kinds")) {
1343
+ const value = policy.allow_action_kinds;
1344
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !String(item).trim())) {
1345
+ throw deps.makeCliError(
1346
+ "CLI_AI_POLICY_INVALID",
1347
+ "AI policy allow_action_kinds must be an array of non-empty strings",
1348
+ { file: policyFile }
1349
+ );
1350
+ }
1351
+ }
1352
+ return rawPolicy;
1353
+ }
1354
+ function buildAiCheckPolicyPreset(name, deps) {
1355
+ const normalized = String(name || "").trim().toLowerCase();
1356
+ if (normalized !== "internal-readonly") {
1357
+ throw deps.makeCliError(
1358
+ "CLI_INVALID_OPTION",
1359
+ `Unknown --policy-preset: ${name} (expected internal-readonly)`,
1360
+ { label: "--policy-preset", value: name, allowed: ["internal-readonly"] }
1361
+ );
1362
+ }
1363
+ return {
1364
+ schema_version: "xapps.ai.policy.v1",
1365
+ require_read_only: true,
1366
+ max_actions: 16,
1367
+ allow_action_kinds: [
1368
+ "analyze.manifest",
1369
+ "suggest.flow_checks",
1370
+ "analyze.renderers",
1371
+ "suggest.renderer_checks",
1372
+ "suggest.manifest_patch",
1373
+ "suggest.context_refresh",
1374
+ "suggest.ai_policy_check",
1375
+ "suggest.flow_run"
1376
+ ]
1377
+ };
1378
+ }
1379
+ async function runAiPlanCommand(args, deps) {
1380
+ const subcommand = deps.argString(args, "_subcommand");
1381
+ if (subcommand !== "plan") {
1382
+ throw deps.makeCliError("CLI_INVALID_ARGS", "Missing required subcommand: ai plan|check");
1383
+ }
1384
+ const mode = parseAiMode(args, deps);
1385
+ const from = deps.argString(args, "from");
1386
+ if (!from) {
1387
+ throw deps.makeCliError(
1388
+ "CLI_INVALID_ARGS",
1389
+ "Missing required argument: --from <manifest.json>"
1390
+ );
1391
+ }
1392
+ const repoRoot = deps.findRepoRoot();
1393
+ if (!repoRoot) {
1394
+ throw deps.makeCliError(
1395
+ "CLI_REPO_NOT_FOUND",
1396
+ "xapps ai plan --mode internal is repo-only and requires the xapps monorepo checkout"
1397
+ );
1398
+ }
1399
+ const { manifest, filePath } = deps.parseManifestFromFile(from);
1400
+ const mockAssets = listMockAssets(filePath, args, deps);
1401
+ const guidance = await readAiGuidanceInteractiveIfRequested(args, deps);
1402
+ const context = deps.buildContextExportPayload(manifest, filePath);
1403
+ const refs = deps.buildDevRefs(repoRoot);
1404
+ const rendererFamilies = detectPlanRendererFamilies(manifest);
1405
+ const preset = deps.argString(args, "preset");
1406
+ let presetPayload;
1407
+ if (preset) {
1408
+ const normalizedPreset = preset.trim().toLowerCase();
1409
+ if (normalizedPreset !== "internal-v1") {
1410
+ throw deps.makeCliError(
1411
+ "CLI_INVALID_OPTION",
1412
+ `Invalid --preset: ${preset} (expected internal-v1)`,
1413
+ {
1414
+ label: "--preset",
1415
+ value: preset
1416
+ }
1417
+ );
1418
+ }
1419
+ presetPayload = deps.buildInternalV1ContextPreset(repoRoot);
1420
+ }
1421
+ const flow = deps.argString(args, "flow");
1422
+ const normalizedFlow = flow ? flow.trim().toLowerCase() : void 0;
1423
+ const supportedFlows = /* @__PURE__ */ new Set(["pay-per-request", "guard-orchestration", "xplace-certs"]);
1424
+ if (normalizedFlow && !supportedFlows.has(normalizedFlow)) {
1425
+ throw deps.makeCliError("CLI_INVALID_OPTION", `Unknown --flow: ${flow}`, {
1426
+ label: "--flow",
1427
+ value: flow,
1428
+ allowed: Array.from(supportedFlows)
1429
+ });
1430
+ }
1431
+ const actions = [
1432
+ {
1433
+ kind: "analyze.manifest",
1434
+ status: "proposed",
1435
+ description: "Review manifest/tools/widgets against V1 baseline contracts"
1436
+ },
1437
+ {
1438
+ kind: "suggest.flow_checks",
1439
+ status: "proposed",
1440
+ flows: ["pay-per-request", "guard-orchestration", "xplace-certs"]
1441
+ },
1442
+ {
1443
+ kind: "analyze.renderers",
1444
+ status: "proposed",
1445
+ renderers: rendererFamilies,
1446
+ description: "Classify widget renderer families and map to V1 checks (publisher-rendered, jsonforms, platform)"
1447
+ }
1448
+ ];
1449
+ if (rendererFamilies.includes("jsonforms")) {
1450
+ actions.push({
1451
+ kind: "suggest.renderer_checks",
1452
+ status: "proposed",
1453
+ renderer: "jsonforms",
1454
+ command: "npm test -- src/__tests__/jsonformsEmbed.test.ts src/__tests__/jsonformsStepDispatch.test.ts src/__tests__/jsonformsFiles.test.ts"
1455
+ });
1456
+ }
1457
+ if (rendererFamilies.includes("publisher-rendered")) {
1458
+ actions.push({
1459
+ kind: "suggest.renderer_checks",
1460
+ status: "proposed",
1461
+ renderer: "publisher-rendered",
1462
+ command: "npm test -- src/__tests__/embedWidgets.test.ts src/__tests__/guardContractParity.test.ts src/__tests__/integrationHostGuardContracts.test.ts"
1463
+ });
1464
+ }
1465
+ if (presetPayload) {
1466
+ actions.push({
1467
+ kind: "suggest.context_refresh",
1468
+ status: "proposed",
1469
+ command: `xapps context export --from ${filePath} --preset internal-v1`
1470
+ });
1471
+ actions.push({
1472
+ kind: "suggest.ai_policy_check",
1473
+ status: "proposed",
1474
+ command: "xapps ai check --mode internal --plan <plan.json> --policy <policy.json> --json"
1475
+ });
1476
+ }
1477
+ if (normalizedFlow) {
1478
+ actions.push({
1479
+ kind: "suggest.flow_run",
1480
+ status: "proposed",
1481
+ flow: normalizedFlow,
1482
+ command: `xapps dev check flow --name ${normalizedFlow} --run --json`
1483
+ });
1484
+ }
1485
+ const heuristicManifestPatchHints = buildManifestPatchHints(manifest, mockAssets, guidance);
1486
+ let manifestPatchHints = heuristicManifestPatchHints;
1487
+ let llmInfo;
1488
+ if (deps.argFlag(args, "llm")) {
1489
+ llmInfo = { provider: "openai-compatible", model: "unknown", enabled: true, used: false };
1490
+ try {
1491
+ const llmHints = await buildLlmManifestPatchHints(manifest, mockAssets, guidance, args, deps);
1492
+ llmInfo = {
1493
+ provider: llmHints.provider,
1494
+ model: llmHints.model,
1495
+ enabled: true,
1496
+ used: true
1497
+ };
1498
+ if (llmHints.hints.length > 0) {
1499
+ const hasImageMocks = mockAssets.some((m) => m.kind === "image");
1500
+ const completeness = evaluateLlmHintCompleteness(llmHints.hints, {
1501
+ requireInputSchema: hasImageMocks,
1502
+ requireInputUiSchema: hasImageMocks
1503
+ });
1504
+ if (!completeness.ok && hasImageMocks) {
1505
+ heuristicManifestPatchHints.__llm_error = `LLM hints incomplete for image mocks (missing: ${completeness.missing.join(", ")})`;
1506
+ } else {
1507
+ manifestPatchHints = llmHints.hints;
1508
+ }
1509
+ }
1510
+ } catch (err) {
1511
+ const msg = String(err?.message || err || "LLM manifest hint generation failed");
1512
+ llmInfo = {
1513
+ ...llmInfo || { provider: "openai-compatible", model: "unknown", enabled: true },
1514
+ used: false
1515
+ };
1516
+ manifestPatchHints.__llm_error = msg;
1517
+ }
1518
+ }
1519
+ if (manifestPatchHints.length > 0) {
1520
+ actions.push({
1521
+ kind: "suggest.manifest_patch",
1522
+ status: "proposed",
1523
+ target: "manifest.json",
1524
+ description: "Mock-aware JSON Forms manifest upgrade hints (stepper wizard + preview sections) generated for review",
1525
+ hints: manifestPatchHints,
1526
+ ...llmInfo ? {
1527
+ source: llmInfo.used ? "llm" : "heuristic_fallback",
1528
+ llm: llmInfo
1529
+ } : { source: "heuristic" },
1530
+ ...manifestPatchHints.__llm_error ? { llm_error: manifestPatchHints.__llm_error } : {}
1531
+ });
1532
+ }
1533
+ const payload = {
1534
+ schema_version: "xapps.ai.plan.v1",
1535
+ ok: true,
1536
+ mode,
1537
+ read_only: true,
1538
+ source: {
1539
+ manifest_path: filePath,
1540
+ manifest_sha256: context && isPlainObject3(context.source) && typeof context.source.manifest_sha256 === "string" ? context.source.manifest_sha256 : void 0
1541
+ },
1542
+ context: {
1543
+ summary: isPlainObject3(context.summary) ? context.summary : {},
1544
+ refs,
1545
+ ...guidance ? {
1546
+ ai_inputs: {
1547
+ guidance
1548
+ }
1549
+ } : {},
1550
+ ...mockAssets.length > 0 ? {
1551
+ mock_assets: {
1552
+ count: mockAssets.length,
1553
+ kinds: Array.from(new Set(mockAssets.map((item) => item.kind))).sort(),
1554
+ items: mockAssets
1555
+ }
1556
+ } : {},
1557
+ ...llmInfo ? { llm: llmInfo } : {},
1558
+ ...presetPayload ? { preset: presetPayload } : {}
1559
+ },
1560
+ actions,
1561
+ warnings: [
1562
+ ...refs.every((ref) => ref.exists) ? [] : ["Some internal engineering refs are missing"],
1563
+ ...manifestPatchHints.__llm_error ? [
1564
+ `LLM manifest hint generation failed, heuristic fallback used: ${manifestPatchHints.__llm_error}`
1565
+ ] : []
1566
+ ],
1567
+ errors: []
1568
+ };
1569
+ if (normalizedFlow) payload.flow = normalizedFlow;
1570
+ payload.coverage = {
1571
+ renderers: rendererFamilies,
1572
+ flows_supported: Array.from(supportedFlows.values()),
1573
+ flow_selected: normalizedFlow || null,
1574
+ preset_selected: presetPayload ? "internal-v1" : null
1575
+ };
1576
+ if (deps.argFlag(args, "apply-manifest-hints")) {
1577
+ const patchAction = actions.find((a) => a.kind === "suggest.manifest_patch");
1578
+ const hints = Array.isArray(patchAction?.hints) ? patchAction.hints.filter((h) => isPlainObject3(h)) : [];
1579
+ if (!hints.length) {
1580
+ payload.manifest_apply = {
1581
+ applied: true,
1582
+ changed: false,
1583
+ file: filePath,
1584
+ applied_count: 0,
1585
+ summaries: ["skip: no manifest patch hints available"],
1586
+ diff: "(no changes)"
1587
+ };
1588
+ } else {
1589
+ payload.manifest_apply = applyManifestPatchHintsToFile(filePath, manifest, hints);
1590
+ }
1591
+ }
1592
+ const outPath = deps.argString(args, "out");
1593
+ const rendered = `${JSON.stringify(payload, null, 2)}
1594
+ `;
1595
+ if (outPath) {
1596
+ const target = import_node_path5.default.resolve(process.cwd(), outPath);
1597
+ import_node_fs4.default.mkdirSync(import_node_path5.default.dirname(target), { recursive: true });
1598
+ import_node_fs4.default.writeFileSync(target, rendered);
1599
+ console.log(`AI plan exported: ${target}`);
1600
+ return;
1601
+ }
1602
+ if (deps.argFlag(args, "json")) {
1603
+ console.log(rendered.trimEnd());
1604
+ return;
1605
+ }
1606
+ if (payload.manifest_apply) {
1607
+ const apply = payload.manifest_apply;
1608
+ console.log(
1609
+ [
1610
+ "Manifest hints apply:",
1611
+ `File: ${String(apply.file || filePath)}`,
1612
+ `Changed: ${String(Boolean(apply.changed))}`,
1613
+ `Applied hints: ${String(apply.applied_count || 0)}`,
1614
+ ...Array.isArray(apply.summaries) ? apply.summaries.map((s) => `- ${String(s)}`) : [],
1615
+ "Diff:",
1616
+ String(apply.diff || "(no diff)"),
1617
+ ""
1618
+ ].join("\n")
1619
+ );
1620
+ }
1621
+ console.log(
1622
+ [
1623
+ `AI plan (${mode}, read-only)`,
1624
+ `Manifest: ${payload.source.manifest_path}`,
1625
+ `Slug: ${String(payload.context.summary.slug || "n/a")}`,
1626
+ `Proposed actions: ${payload.actions.length}`
1627
+ ].join("\n")
1628
+ );
1629
+ }
1630
+ function runAiCheckCommand(args, deps) {
1631
+ const subcommand = deps.argString(args, "_subcommand");
1632
+ if (subcommand !== "check") {
1633
+ throw deps.makeCliError("CLI_INVALID_ARGS", "Missing required subcommand: ai plan|check");
1634
+ }
1635
+ const mode = parseAiMode(args, deps);
1636
+ const planFile = deps.argString(args, "plan");
1637
+ if (!planFile) {
1638
+ throw deps.makeCliError("CLI_INVALID_ARGS", "Missing required argument: --plan <plan.json>");
1639
+ }
1640
+ const payload = deps.readJsonFile(planFile);
1641
+ const policyFile = deps.argString(args, "policy");
1642
+ const policyPreset = deps.argString(args, "policy-preset");
1643
+ if (policyFile && policyPreset) {
1644
+ throw deps.makeCliError(
1645
+ "CLI_INVALID_ARGS",
1646
+ "Use either --policy <policy.json> or --policy-preset <name>, not both"
1647
+ );
1648
+ }
1649
+ let policy = null;
1650
+ if (policyFile) {
1651
+ policy = parseAiCheckPolicy(deps.readJsonFile(policyFile), policyFile, deps);
1652
+ } else if (policyPreset) {
1653
+ policy = buildAiCheckPolicyPreset(policyPreset, deps);
1654
+ }
1655
+ const checks = [];
1656
+ const plan = isPlainObject3(payload) ? payload : {};
1657
+ checks.push({
1658
+ key: "schema_version",
1659
+ ok: plan.schema_version === "xapps.ai.plan.v1",
1660
+ details: { actual: plan.schema_version || null }
1661
+ });
1662
+ checks.push({
1663
+ key: "mode",
1664
+ ok: plan.mode === mode,
1665
+ details: { actual: plan.mode || null, expected: mode }
1666
+ });
1667
+ checks.push({
1668
+ key: "read_only",
1669
+ ok: plan.read_only === true,
1670
+ details: { actual: plan.read_only ?? null }
1671
+ });
1672
+ checks.push({
1673
+ key: "actions_array",
1674
+ ok: Array.isArray(plan.actions),
1675
+ details: { actualType: Array.isArray(plan.actions) ? "array" : typeof plan.actions }
1676
+ });
1677
+ if (policy) {
1678
+ const requireReadOnly = policy.require_read_only !== false;
1679
+ checks.push({
1680
+ key: "policy_require_read_only",
1681
+ ok: !requireReadOnly || plan.read_only === true,
1682
+ details: { required: requireReadOnly, actual: plan.read_only ?? null }
1683
+ });
1684
+ if (typeof policy.max_actions === "number" && Number.isFinite(policy.max_actions)) {
1685
+ const actionCount = Array.isArray(plan.actions) ? plan.actions.length : 0;
1686
+ checks.push({
1687
+ key: "policy_max_actions",
1688
+ ok: actionCount <= policy.max_actions,
1689
+ details: { max_actions: policy.max_actions, actual: actionCount }
1690
+ });
1691
+ }
1692
+ if (Array.isArray(policy.allow_action_kinds)) {
1693
+ const allowed = new Set(
1694
+ policy.allow_action_kinds.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
1695
+ );
1696
+ const disallowedKinds = Array.isArray(plan.actions) ? plan.actions.filter((item) => isPlainObject3(item)).map((item) => String(item.kind || "").trim()).filter((kind) => kind && !allowed.has(kind)) : [];
1697
+ checks.push({
1698
+ key: "policy_allow_action_kinds",
1699
+ ok: disallowedKinds.length === 0,
1700
+ details: { disallowed_kinds: disallowedKinds }
1701
+ });
1702
+ }
1703
+ }
1704
+ const result = {
1705
+ schema_version: "xapps.ai.check.v1",
1706
+ ok: checks.every((check) => check.ok),
1707
+ mode,
1708
+ read_only: true,
1709
+ checks,
1710
+ errors: checks.filter((check) => !check.ok).map((check) => ({ code: "CLI_AI_PLAN_INVALID", check: check.key })),
1711
+ ...policy ? {
1712
+ policy: {
1713
+ applied: true,
1714
+ ...policyFile ? { source: import_node_path5.default.resolve(process.cwd(), policyFile) } : {},
1715
+ ...policyPreset ? { preset: String(policyPreset).trim().toLowerCase() } : {}
1716
+ }
1717
+ } : {}
1718
+ };
1719
+ const outPath = deps.argString(args, "out");
1720
+ const rendered = `${JSON.stringify(result, null, 2)}
1721
+ `;
1722
+ if (outPath) {
1723
+ const target = import_node_path5.default.resolve(process.cwd(), outPath);
1724
+ import_node_fs4.default.mkdirSync(import_node_path5.default.dirname(target), { recursive: true });
1725
+ import_node_fs4.default.writeFileSync(target, rendered);
1726
+ console.log(`AI check exported: ${target}`);
1727
+ } else if (deps.argFlag(args, "json")) {
1728
+ console.log(rendered.trimEnd());
1729
+ } else {
1730
+ console.log(
1731
+ [
1732
+ `AI check (${mode}) ${result.ok ? "PASS" : "FAIL"}`,
1733
+ ...checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.key}`)
1734
+ ].join("\n")
1735
+ );
1736
+ }
1737
+ if (!result.ok) {
1738
+ throw deps.makeCliError("CLI_AI_PLAN_INVALID", "xapps ai check failed: invalid plan contract", {
1739
+ checks
1740
+ });
1741
+ }
1742
+ }
1743
+
1744
+ // src/publisherCommands.ts
1745
+ var import_server_sdk2 = require("@xapps-platform/server-sdk");
1746
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
1747
+ function normalizePublisherGatewayApiBaseUrl(input2) {
1748
+ const raw = String(input2 || "").trim();
1749
+ if (!raw) return "";
1750
+ const parsed = new URL(raw);
1751
+ const pathname = parsed.pathname || "/";
1752
+ if (pathname === "/" || pathname === "") {
1753
+ parsed.pathname = "/v1";
1754
+ }
1755
+ return parsed.toString().replace(/\/+$/g, "");
1756
+ }
1757
+ function parseHttpUrlOrThrow(value, flagName) {
1758
+ const trimmed = String(value || "").trim();
1759
+ if (!trimmed) throw new Error(`Invalid ${flagName}: empty`);
1760
+ try {
1761
+ const url = new URL(trimmed);
1762
+ if (!/^https?:$/.test(url.protocol)) {
1763
+ throw new Error(`unsupported protocol ${url.protocol}`);
1764
+ }
1765
+ return url.toString();
1766
+ } catch (err) {
1767
+ throw new Error(`Invalid ${flagName}: ${err?.message || String(err)}`);
1768
+ }
1769
+ }
1770
+ function resolveSecretInput(args, deps) {
1771
+ const secretRef = deps.argString(args, "secret-ref");
1772
+ const secretEnvName = deps.argString(args, "secret-env");
1773
+ const secretStdin = deps.argFlag(args, "secret-stdin");
1774
+ const selected = [Boolean(secretRef), Boolean(secretEnvName), Boolean(secretStdin)].filter(
1775
+ Boolean
1776
+ ).length;
1777
+ if (selected !== 1) {
1778
+ throw deps.makeCliError(
1779
+ "CLI_INVALID_ARGS",
1780
+ "Provide exactly one of --secret-ref, --secret-env, or --secret-stdin"
1781
+ );
1782
+ }
1783
+ if (secretRef) {
1784
+ return { secretRef, source: "secret_ref" };
1785
+ }
1786
+ if (secretEnvName) {
1787
+ const envValue = String(process.env[secretEnvName] || "");
1788
+ if (!envValue.trim()) {
1789
+ throw deps.makeCliError(
1790
+ "CLI_INVALID_ARGS",
1791
+ `Environment variable is empty or missing for --secret-env ${secretEnvName}`
1792
+ );
1793
+ }
1794
+ return { secret: envValue, source: "env", sourceName: secretEnvName };
1795
+ }
1796
+ const secret = import_node_fs5.default.readFileSync(0, "utf8");
1797
+ if (!String(secret || "").trim()) {
1798
+ throw deps.makeCliError("CLI_INVALID_ARGS", "--secret-stdin provided but stdin is empty");
1799
+ }
1800
+ return { secret, source: "stdin" };
1801
+ }
1802
+ function pickLatestVersion(items) {
1803
+ if (!Array.isArray(items) || items.length === 0) return null;
1804
+ const published = items.filter((v) => String(v.status || "") === "published");
1805
+ const target = published.length > 0 ? published : items;
1806
+ return [...target].sort(
1807
+ (a, b) => String(b.published_at || b.created_at || "").localeCompare(
1808
+ String(a.published_at || a.created_at || "")
1809
+ )
1810
+ )[0];
1811
+ }
1812
+ async function resolveEndpointIdOrThrow(args, deps, client) {
1813
+ const endpointIdArg = deps.argString(args, "endpoint-id");
1814
+ const xappSlug = deps.argString(args, "xapp-slug");
1815
+ const targetClientSlug = deps.argString(args, "target-client-slug", "target_client_slug");
1816
+ const endpointEnv = deps.argString(args, "env") || "prod";
1817
+ if (endpointIdArg) {
1818
+ return { endpointId: endpointIdArg, endpointEnv, ...xappSlug ? { xappSlug } : {} };
1819
+ }
1820
+ if (!xappSlug) {
1821
+ throw deps.makeCliError(
1822
+ "CLI_INVALID_ARGS",
1823
+ "Provide --endpoint-id or (--xapp-slug and optional --env)"
1824
+ );
1825
+ }
1826
+ let targetClientId = "";
1827
+ if (targetClientSlug) {
1828
+ const clients = await client.listClients();
1829
+ const matches = (clients.items || []).filter(
1830
+ (item) => String(item.slug || "") === targetClientSlug
1831
+ );
1832
+ if (matches.length === 0) {
1833
+ throw deps.makeCliError(
1834
+ "CLI_NOT_FOUND",
1835
+ `Publisher client not found for slug=${targetClientSlug}`
1836
+ );
1837
+ }
1838
+ if (matches.length > 1) {
1839
+ throw deps.makeCliError(
1840
+ "CLI_CONFLICT",
1841
+ `Publisher client slug matched multiple rows: ${targetClientSlug}`
1842
+ );
1843
+ }
1844
+ targetClientId = String(matches[0].id || "").trim();
1845
+ if (!targetClientId) {
1846
+ throw deps.makeCliError(
1847
+ "CLI_INVALID_RESPONSE",
1848
+ `Resolved client id is missing for slug=${targetClientSlug}`
1849
+ );
1850
+ }
1851
+ }
1852
+ const xapps = await client.listXapps();
1853
+ const candidates = (xapps.items || []).filter((item) => {
1854
+ if (String(item.slug || "") !== xappSlug) return false;
1855
+ if (!targetClientId) return true;
1856
+ return String(item.target_client_id || "") === targetClientId;
1857
+ });
1858
+ if (candidates.length === 0) {
1859
+ throw deps.makeCliError(
1860
+ "CLI_NOT_FOUND",
1861
+ targetClientId ? `Publisher xapp not found for slug=${xappSlug} target_client_slug=${targetClientSlug}` : `Publisher xapp not found for slug=${xappSlug}`
1862
+ );
1863
+ }
1864
+ if (candidates.length > 1) {
1865
+ throw deps.makeCliError(
1866
+ "CLI_CONFLICT",
1867
+ targetClientId ? `Multiple publisher xapps matched slug=${xappSlug} target_client_slug=${targetClientSlug}` : `Multiple publisher xapps matched slug=${xappSlug}; pass --target-client-slug`
1868
+ );
1869
+ }
1870
+ const xapp = candidates[0];
1871
+ if (!String(xapp.id || "")) {
1872
+ throw deps.makeCliError("CLI_NOT_FOUND", `Publisher xapp not found for slug=${xappSlug}`);
1873
+ }
1874
+ const versions = await client.listXappVersions(String(xapp.id));
1875
+ const version = pickLatestVersion(versions.items);
1876
+ if (!version || !String(version.id || "")) {
1877
+ throw deps.makeCliError(
1878
+ "CLI_NOT_FOUND",
1879
+ `No publisher xapp versions found for slug=${xappSlug}`
1880
+ );
1881
+ }
1882
+ const endpoints = await client.listEndpoints(String(version.id));
1883
+ const endpoint = endpoints.items.find(
1884
+ (item) => String(item.env || "").trim() === endpointEnv
1885
+ );
1886
+ if (!endpoint || !String(endpoint.id || "")) {
1887
+ throw deps.makeCliError(
1888
+ "CLI_NOT_FOUND",
1889
+ `Endpoint not found for xapp=${xappSlug} env=${endpointEnv}`
1890
+ );
1891
+ }
1892
+ return { endpointId: String(endpoint.id), endpointEnv, xappSlug };
1893
+ }
1894
+ function normalizeAuthTypeForMatch(input2) {
1895
+ const raw = String(input2 || "").trim().toLowerCase().replace(/[^a-z0-9]/g, "");
1896
+ if (!raw) return "";
1897
+ if (raw === "none") return "none";
1898
+ if (raw.includes("api") && raw.includes("key")) return "api_key_header";
1899
+ if (raw === "header") return "header";
1900
+ if (raw === "hmac" || raw === "hmacsha256") return "hmac";
1901
+ return raw;
1902
+ }
1903
+ function readCredentialHeaderName(item) {
1904
+ const direct = item.auth_header_name;
1905
+ if (typeof direct === "string" && direct.trim()) return direct.trim();
1906
+ const configRaw = item.config ?? item.config_jsonb;
1907
+ const cfg = configRaw && typeof configRaw === "string" ? (() => {
1908
+ try {
1909
+ return JSON.parse(configRaw);
1910
+ } catch {
1911
+ return null;
1912
+ }
1913
+ })() : configRaw;
1914
+ if (cfg && typeof cfg === "object") {
1915
+ const header = cfg.headerName ?? cfg.header_name;
1916
+ if (typeof header === "string" && header.trim()) return header.trim();
1917
+ }
1918
+ return "";
1919
+ }
1920
+ function findMatchingEndpointCredential(items, input2) {
1921
+ const wantAuth = normalizeAuthTypeForMatch(input2.authType);
1922
+ const wantHeader = String(input2.headerName || "").trim().toLowerCase();
1923
+ for (const item of items) {
1924
+ const authMatch = normalizeAuthTypeForMatch(item.auth_type) === wantAuth;
1925
+ if (!authMatch) continue;
1926
+ if (wantAuth === "api_key_header" || wantAuth === "header") {
1927
+ const existingHeader = readCredentialHeaderName(item).toLowerCase();
1928
+ if (wantHeader && existingHeader !== wantHeader) continue;
1929
+ }
1930
+ return item;
1931
+ }
1932
+ return null;
1933
+ }
1934
+ async function runPublisherEndpointCredentialSetCommand(args, deps) {
1935
+ const gatewayUrlRaw = deps.argString(args, "gateway-url", "publisher-gateway-url") || process.env.XAPPS_CLI_PUBLISHER_GATEWAY_URL;
1936
+ if (!gatewayUrlRaw) {
1937
+ throw deps.makeCliError(
1938
+ "CLI_INVALID_ARGS",
1939
+ "Missing required argument: --gateway-url <url> (or --publisher-gateway-url)"
1940
+ );
1941
+ }
1942
+ const gatewayUrl = normalizePublisherGatewayApiBaseUrl(
1943
+ parseHttpUrlOrThrow(gatewayUrlRaw, "--gateway-url")
1944
+ );
1945
+ const apiKey = deps.argString(args, "api-key", "apiKey") || process.env.XAPPS_CLI_API_KEY || "";
1946
+ const token = deps.argString(args, "token") || process.env.XAPPS_CLI_TOKEN || "";
1947
+ if (!apiKey && !token) {
1948
+ throw deps.makeCliError(
1949
+ "CLI_INVALID_ARGS",
1950
+ "Missing auth: provide --api-key or --token (or XAPPS_CLI_API_KEY/XAPPS_CLI_TOKEN)"
1951
+ );
1952
+ }
1953
+ let endpointId = "";
1954
+ const authType = deps.argString(args, "auth-type") || "api-key";
1955
+ const headerName = deps.argString(args, "header-name") || "x-xplace-api-key";
1956
+ const keyStatus = deps.argString(args, "key-status") || "active";
1957
+ const keyAlgorithm = deps.argString(args, "key-algorithm") || "hmac-sha256";
1958
+ const secretInput = resolveSecretInput(args, deps);
1959
+ const client = (0, import_server_sdk2.createPublisherApiClient)({ baseUrl: gatewayUrl, apiKey, token });
1960
+ try {
1961
+ endpointId = (await resolveEndpointIdOrThrow(args, deps, client)).endpointId;
1962
+ const result = await client.createEndpointCredential(endpointId, {
1963
+ authType,
1964
+ config: {
1965
+ headerName
1966
+ },
1967
+ initialKey: {
1968
+ ...secretInput.secret ? { secret: secretInput.secret } : {},
1969
+ ...secretInput.secretRef ? { secretRef: secretInput.secretRef } : {},
1970
+ status: keyStatus,
1971
+ algorithm: keyAlgorithm
1972
+ }
1973
+ });
1974
+ const credential = result.credential || {};
1975
+ const output2 = {
1976
+ ok: true,
1977
+ gateway_url: gatewayUrl,
1978
+ endpoint_id: endpointId,
1979
+ auth_type: String(credential.auth_type || authType),
1980
+ credential_id: String(credential.id || ""),
1981
+ active_kid: String(credential.active_kid || ""),
1982
+ secret_source: secretInput.source,
1983
+ ...secretInput.sourceName ? { secret_env: secretInput.sourceName } : {},
1984
+ header_name: headerName
1985
+ };
1986
+ if (deps.argFlag(args, "json")) {
1987
+ console.log(JSON.stringify(output2, null, 2));
1988
+ return;
1989
+ }
1990
+ console.log(
1991
+ `Publisher endpoint credential set: endpoint=${output2.endpoint_id} credential=${output2.credential_id || "-"} auth=${output2.auth_type} header=${output2.header_name} secret_source=${output2.secret_source}`
1992
+ );
1993
+ } catch (err) {
1994
+ if (err instanceof import_server_sdk2.PublisherApiClientError) {
1995
+ throw deps.makeCliError(
1996
+ `CLI_${err.code}`,
1997
+ `Publisher endpoint credential set failed: ${err.message}`,
1998
+ { status: err.status, details: err.details, gateway_url: gatewayUrl }
1999
+ );
2000
+ }
2001
+ throw err;
2002
+ }
2003
+ }
2004
+ async function runPublisherEndpointCredentialEnsureCommand(args, deps) {
2005
+ const gatewayUrlRaw = deps.argString(args, "gateway-url", "publisher-gateway-url") || process.env.XAPPS_CLI_PUBLISHER_GATEWAY_URL;
2006
+ if (!gatewayUrlRaw) {
2007
+ throw deps.makeCliError(
2008
+ "CLI_INVALID_ARGS",
2009
+ "Missing required argument: --gateway-url <url> (or --publisher-gateway-url)"
2010
+ );
2011
+ }
2012
+ const gatewayUrl = normalizePublisherGatewayApiBaseUrl(
2013
+ parseHttpUrlOrThrow(gatewayUrlRaw, "--gateway-url")
2014
+ );
2015
+ const apiKey = deps.argString(args, "api-key", "apiKey") || process.env.XAPPS_CLI_API_KEY || "";
2016
+ const token = deps.argString(args, "token") || process.env.XAPPS_CLI_TOKEN || "";
2017
+ if (!apiKey && !token) {
2018
+ throw deps.makeCliError(
2019
+ "CLI_INVALID_ARGS",
2020
+ "Missing auth: provide --api-key or --token (or XAPPS_CLI_API_KEY/XAPPS_CLI_TOKEN)"
2021
+ );
2022
+ }
2023
+ const authType = deps.argString(args, "auth-type") || "api-key";
2024
+ const headerName = deps.argString(args, "header-name") || "x-xplace-api-key";
2025
+ const keyStatus = deps.argString(args, "key-status") || "active";
2026
+ const keyAlgorithm = deps.argString(args, "key-algorithm") || "hmac-sha256";
2027
+ const client = (0, import_server_sdk2.createPublisherApiClient)({ baseUrl: gatewayUrl, apiKey, token });
2028
+ try {
2029
+ const resolved = await resolveEndpointIdOrThrow(args, deps, client);
2030
+ const endpointId = resolved.endpointId;
2031
+ const existing = await client.listEndpointCredentials(endpointId);
2032
+ const match = findMatchingEndpointCredential(existing.items, {
2033
+ authType,
2034
+ headerName
2035
+ });
2036
+ if (match) {
2037
+ const output3 = {
2038
+ ok: true,
2039
+ ensured: true,
2040
+ created: false,
2041
+ gateway_url: gatewayUrl,
2042
+ endpoint_id: endpointId,
2043
+ credential_id: String(match.id || ""),
2044
+ auth_type: String(match.auth_type || authType),
2045
+ header_name: readCredentialHeaderName(match) || headerName,
2046
+ active_kid: String(match.active_kid || "")
2047
+ };
2048
+ if (deps.argFlag(args, "json")) {
2049
+ console.log(JSON.stringify(output3, null, 2));
2050
+ return;
2051
+ }
2052
+ console.log(
2053
+ `Publisher endpoint credential ensure: endpoint=${output3.endpoint_id} credential=${output3.credential_id || "-"} auth=${output3.auth_type} header=${output3.header_name} outcome=existing`
2054
+ );
2055
+ return;
2056
+ }
2057
+ const secretInput = resolveSecretInput(args, deps);
2058
+ const result = await client.createEndpointCredential(endpointId, {
2059
+ authType,
2060
+ config: {
2061
+ headerName
2062
+ },
2063
+ initialKey: {
2064
+ ...secretInput.secret ? { secret: secretInput.secret } : {},
2065
+ ...secretInput.secretRef ? { secretRef: secretInput.secretRef } : {},
2066
+ status: keyStatus,
2067
+ algorithm: keyAlgorithm
2068
+ }
2069
+ });
2070
+ const credential = result.credential || {};
2071
+ const output2 = {
2072
+ ok: true,
2073
+ ensured: true,
2074
+ created: true,
2075
+ gateway_url: gatewayUrl,
2076
+ endpoint_id: endpointId,
2077
+ auth_type: String(credential.auth_type || authType),
2078
+ credential_id: String(credential.id || ""),
2079
+ active_kid: String(credential.active_kid || ""),
2080
+ secret_source: secretInput.source,
2081
+ ...secretInput.sourceName ? { secret_env: secretInput.sourceName } : {},
2082
+ header_name: headerName
2083
+ };
2084
+ if (deps.argFlag(args, "json")) {
2085
+ console.log(JSON.stringify(output2, null, 2));
2086
+ return;
2087
+ }
2088
+ console.log(
2089
+ `Publisher endpoint credential ensure: endpoint=${output2.endpoint_id} credential=${output2.credential_id || "-"} auth=${output2.auth_type} header=${output2.header_name} outcome=created secret_source=${output2.secret_source}`
2090
+ );
2091
+ } catch (err) {
2092
+ if (err instanceof import_server_sdk2.PublisherApiClientError) {
2093
+ throw deps.makeCliError(
2094
+ `CLI_${err.code}`,
2095
+ `Publisher endpoint credential ensure failed: ${err.message}`,
2096
+ { status: err.status, details: err.details, gateway_url: gatewayUrl }
2097
+ );
2098
+ }
2099
+ throw err;
2100
+ }
2101
+ }
2102
+
2103
+ // src/commands/devCommands.ts
2104
+ function runDevStatusRefsCliCommand(args, deps) {
2105
+ runDevStatusRefsCommand(args, {
2106
+ argFlag: deps.argFlag,
2107
+ findRepoRoot: deps.findRepoRoot,
2108
+ makeCliError: deps.makeCliError
2109
+ });
2110
+ }
2111
+ function runDevCheckV1CliCommand(args, deps) {
2112
+ runDevCheckV1Command(args, {
2113
+ argFlag: deps.argFlag,
2114
+ findRepoRoot: deps.findRepoRoot,
2115
+ makeCliError: deps.makeCliError
2116
+ });
2117
+ }
2118
+ function runDevCheckFlowCliCommand(args, deps) {
2119
+ const repoRoot = deps.findRepoRoot();
2120
+ if (!repoRoot) {
2121
+ throw deps.makeCliError(
2122
+ "CLI_REPO_NOT_FOUND",
2123
+ "xapps dev check flow is repo-only and requires the xapps monorepo checkout"
2124
+ );
2125
+ }
2126
+ const flowFile = deps.argString(args, "from");
2127
+ const flowName = deps.argString(args, "name");
2128
+ if (!flowFile && !flowName) {
2129
+ throw deps.makeCliError(
2130
+ "CLI_INVALID_ARGS",
2131
+ "Missing required argument: --name <flow> or --from <flow.json>"
2132
+ );
2133
+ }
2134
+ if (flowFile && flowName) {
2135
+ throw deps.makeCliError(
2136
+ "CLI_INVALID_ARGS",
2137
+ "Use either --name <flow> or --from <flow.json>, not both"
2138
+ );
2139
+ }
2140
+ const normalized = flowName ? flowName.trim().toLowerCase() : null;
2141
+ const artifactsDirRaw = deps.argString(args, "artifacts-dir");
2142
+ const artifactsDir = artifactsDirRaw ? import_node_path6.default.resolve(repoRoot, String(artifactsDirRaw).trim()) : "/tmp";
2143
+ const plans = buildBuiltInDevCheckFlows(artifactsDir);
2144
+ let selectedFlow;
2145
+ const readFlowFromFile = (inputFlowFile) => {
2146
+ const filePath = import_node_path6.default.resolve(repoRoot, inputFlowFile);
2147
+ const parsed = deps.readJsonFile(filePath);
2148
+ const parsedFlow = parseDevFlowObject(parsed, import_node_path6.default.basename(filePath));
2149
+ if (parsedFlow.errors.length) {
2150
+ throw deps.makeCliError(
2151
+ "CLI_INVALID_OPTION",
2152
+ `Invalid flow file: ${inputFlowFile} (${parsedFlow.errors.join(",")})`,
2153
+ {
2154
+ label: "--from",
2155
+ value: inputFlowFile,
2156
+ errors: parsedFlow.errors
2157
+ }
2158
+ );
2159
+ }
2160
+ return {
2161
+ filePath,
2162
+ flow: parsedFlow.flow
2163
+ };
2164
+ };
2165
+ if (flowFile) {
2166
+ selectedFlow = readFlowFromFile(flowFile).flow;
2167
+ } else {
2168
+ const plan = normalized ? plans[normalized] : void 0;
2169
+ if (!plan) {
2170
+ throw deps.makeCliError("CLI_INVALID_OPTION", `Unknown flow: ${flowName}`, {
2171
+ label: "--name",
2172
+ value: flowName,
2173
+ allowed: Object.keys(plans)
2174
+ });
2175
+ }
2176
+ selectedFlow = { key: normalized, ...plan };
2177
+ }
2178
+ const resolvedCommands = selectedFlow.commands.map(
2179
+ (cmd) => deps.applyFlowCommandTemplates(cmd, { artifactsDir })
2180
+ );
2181
+ const payload = {
2182
+ schema_version: "xapps.dev.check.flow.v1",
2183
+ ok: true,
2184
+ repo_root: repoRoot,
2185
+ flow: selectedFlow.key,
2186
+ artifacts_dir: artifactsDir,
2187
+ title: selectedFlow.title,
2188
+ description: selectedFlow.description,
2189
+ commands: resolvedCommands,
2190
+ refs: selectedFlow.refs,
2191
+ ...flowFile ? { flow_file: import_node_path6.default.relative(repoRoot, import_node_path6.default.resolve(repoRoot, flowFile)) } : {}
2192
+ };
2193
+ if (deps.argFlag(args, "run")) {
2194
+ try {
2195
+ import_node_fs6.default.mkdirSync(artifactsDir, { recursive: true });
2196
+ } catch (error) {
2197
+ throw deps.makeCliError(
2198
+ "CLI_DEV_CHECK_FAILED",
2199
+ `Unable to prepare artifacts directory: ${artifactsDir}`,
2200
+ {
2201
+ flow: normalized || "",
2202
+ artifacts_dir: artifactsDir,
2203
+ error: String(error?.message || error)
2204
+ }
2205
+ );
2206
+ }
2207
+ }
2208
+ if (deps.argFlag(args, "run")) {
2209
+ const runs = executeDevCheckFlowCommands(resolvedCommands, repoRoot);
2210
+ const runSummary = {
2211
+ executed: true,
2212
+ ok: runs.every((run) => run.ok),
2213
+ runs
2214
+ };
2215
+ payload.run = runSummary;
2216
+ payload.ok = runSummary.ok;
2217
+ }
2218
+ if (deps.argFlag(args, "json")) {
2219
+ console.log(JSON.stringify(payload, null, 2));
2220
+ if (!payload.ok) {
2221
+ throw deps.makeCliError(
2222
+ "CLI_DEV_CHECK_FAILED",
2223
+ `xapps dev check flow failed: ${String(selectedFlow.key || normalized || "unknown")}`,
2224
+ {
2225
+ flow: String(selectedFlow.key || normalized || "")
2226
+ }
2227
+ );
2228
+ }
2229
+ return;
2230
+ }
2231
+ console.log(
2232
+ [
2233
+ `Flow check plan: ${payload.flow}`,
2234
+ `Title: ${payload.title}`,
2235
+ `Description: ${payload.description}`,
2236
+ "Commands:",
2237
+ ...payload.commands.map((cmd) => `- ${cmd}`),
2238
+ ...payload.run && typeof payload.run === "object" && Array.isArray(payload.run.runs) ? [
2239
+ "Run results:",
2240
+ ...payload.run.runs.map(
2241
+ (run) => `- ${run.ok ? "OK" : "FAIL"} (${run.exit_code ?? "?"}) ${run.command}`
2242
+ )
2243
+ ] : [],
2244
+ "Refs:",
2245
+ ...payload.refs.map((ref) => `- ${ref}`)
2246
+ ].join("\n")
2247
+ );
2248
+ if (!payload.ok) {
2249
+ throw deps.makeCliError(
2250
+ "CLI_DEV_CHECK_FAILED",
2251
+ `xapps dev check flow failed: ${String(selectedFlow.key || normalized || "unknown")}`,
2252
+ {
2253
+ flow: String(selectedFlow.key || normalized || "")
2254
+ }
2255
+ );
2256
+ }
2257
+ }
2258
+ function runDevCheckFlowLintCliCommand(args, deps) {
2259
+ const repoRoot = deps.findRepoRoot();
2260
+ if (!repoRoot) {
2261
+ throw deps.makeCliError(
2262
+ "CLI_REPO_NOT_FOUND",
2263
+ "xapps dev check flow lint is repo-only and requires the xapps monorepo checkout"
2264
+ );
2265
+ }
2266
+ const flowFile = deps.argString(args, "from");
2267
+ if (!flowFile) {
2268
+ throw deps.makeCliError("CLI_INVALID_ARGS", "Missing required argument: --from <flow.json>");
2269
+ }
2270
+ const filePath = import_node_path6.default.resolve(repoRoot, flowFile);
2271
+ const parsed = deps.readJsonFile(filePath);
2272
+ const parsedFlow = parseDevFlowObject(parsed, import_node_path6.default.basename(filePath));
2273
+ if (parsedFlow.errors.length) {
2274
+ throw deps.makeCliError(
2275
+ "CLI_INVALID_OPTION",
2276
+ `Invalid flow file: ${flowFile} (${parsedFlow.errors.join(",")})`,
2277
+ {
2278
+ label: "--from",
2279
+ value: flowFile,
2280
+ errors: parsedFlow.errors
2281
+ }
2282
+ );
2283
+ }
2284
+ const checks = lintDevFlowDefinition(parsedFlow.flow);
2285
+ const payload = {
2286
+ schema_version: "xapps.dev.check.flow.lint.v1",
2287
+ ok: checks.every((c) => c.ok),
2288
+ repo_root: repoRoot,
2289
+ flow_file: import_node_path6.default.relative(repoRoot, filePath),
2290
+ flow: parsedFlow.flow.key || null,
2291
+ checks
2292
+ };
2293
+ if (deps.argFlag(args, "json")) {
2294
+ console.log(JSON.stringify(payload, null, 2));
2295
+ if (!payload.ok) {
2296
+ throw deps.makeCliError("CLI_DEV_CHECK_FAILED", "xapps dev check flow lint failed", {
2297
+ flow_file: payload.flow_file,
2298
+ checks
2299
+ });
2300
+ }
2301
+ return;
2302
+ }
2303
+ console.log(
2304
+ [
2305
+ `Flow lint: ${payload.ok ? "PASS" : "FAIL"}`,
2306
+ `Flow file: ${payload.flow_file}`,
2307
+ `Flow: ${payload.flow || "n/a"}`,
2308
+ ...checks.map((c) => `${c.ok ? "OK" : "FAIL"} ${c.key}`)
2309
+ ].join("\n")
2310
+ );
2311
+ if (!payload.ok) {
2312
+ throw deps.makeCliError("CLI_DEV_CHECK_FAILED", "xapps dev check flow lint failed", {
2313
+ flow_file: payload.flow_file,
2314
+ checks
2315
+ });
2316
+ }
2317
+ }
2318
+ function buildDevFlowInitTemplate2(type, makeCliError) {
2319
+ const template = buildDevFlowInitTemplate(type);
2320
+ if (template) return template;
2321
+ throw makeCliError("CLI_INVALID_OPTION", `Unknown flow template type: ${type}`, {
2322
+ label: "--type",
2323
+ value: type,
2324
+ allowed: ["ai-artifacts", "manual-loop"]
2325
+ });
2326
+ }
2327
+ function runDevCheckFlowInitCliCommand(args, deps) {
2328
+ const repoRoot = deps.findRepoRoot();
2329
+ if (!repoRoot) {
2330
+ throw deps.makeCliError(
2331
+ "CLI_REPO_NOT_FOUND",
2332
+ "Could not locate repo root for xapps dev check flow init"
2333
+ );
2334
+ }
2335
+ const out = deps.argString(args, "out");
2336
+ if (!out) {
2337
+ throw deps.makeCliError("CLI_INVALID_ARGS", "Missing required argument: --out <flow.json>");
2338
+ }
2339
+ const type = deps.argString(args, "type");
2340
+ if (!type) {
2341
+ throw deps.makeCliError(
2342
+ "CLI_INVALID_ARGS",
2343
+ "Missing required argument: --type <ai-artifacts|manual-loop>"
2344
+ );
2345
+ }
2346
+ const normalizedType = String(type).trim().toLowerCase();
2347
+ const flowTemplate = buildDevFlowInitTemplate2(type, deps.makeCliError);
2348
+ const flowId = deps.argString(args, "flow-id");
2349
+ if (flowId && String(flowId).trim()) {
2350
+ flowTemplate.flow = String(flowId).trim();
2351
+ }
2352
+ const manifestPathArg = deps.argString(args, "manifest");
2353
+ const policyPathArg = deps.argString(args, "policy");
2354
+ const smokeScriptArg = deps.argString(args, "smoke-script");
2355
+ if (normalizedType === "ai-artifacts") {
2356
+ const manifestPath = manifestPathArg ? String(manifestPathArg).trim() : "";
2357
+ const policyPath = policyPathArg ? String(policyPathArg).trim() : "";
2358
+ if (manifestPath) {
2359
+ flowTemplate.commands = flowTemplate.commands.map(
2360
+ (cmd) => cmd.replaceAll("./manifest.json", manifestPath)
2361
+ );
2362
+ flowTemplate.refs = flowTemplate.refs.map(
2363
+ (ref) => ref === "./manifest.json" ? manifestPath : ref
2364
+ );
2365
+ }
2366
+ if (policyPath) {
2367
+ flowTemplate.commands = flowTemplate.commands.map(
2368
+ (cmd) => cmd.replaceAll(
2369
+ "--policy-preset internal-readonly",
2370
+ `--policy ${deps.shellEscapeArg(policyPath)}`
2371
+ )
2372
+ );
2373
+ flowTemplate.refs = flowTemplate.refs.map(
2374
+ (ref) => ref === "./ai/policy.readonly.internal-v1.json" ? policyPath : ref
2375
+ );
2376
+ }
2377
+ }
2378
+ if (normalizedType === "manual-loop") {
2379
+ const smokeScriptPath = smokeScriptArg ? String(smokeScriptArg).trim() : "";
2380
+ if (smokeScriptPath) {
2381
+ flowTemplate.commands = [`node ${deps.shellEscapeArg(smokeScriptPath)}`];
2382
+ flowTemplate.refs = flowTemplate.refs.map(
2383
+ (ref) => ref === "./scripts/manual-loop-smoke.mjs" ? smokeScriptPath : ref
2384
+ );
2385
+ }
2386
+ }
2387
+ const target = import_node_path6.default.resolve(process.cwd(), out);
2388
+ import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(target), { recursive: true });
2389
+ const rendered = `${JSON.stringify(flowTemplate, null, 2)}
2390
+ `;
2391
+ import_node_fs6.default.writeFileSync(target, rendered);
2392
+ const payload = {
2393
+ schema_version: "xapps.dev.check.flow.init.v1",
2394
+ ok: true,
2395
+ type: String(type).trim().toLowerCase(),
2396
+ out: target,
2397
+ flow: flowTemplate.flow,
2398
+ ...manifestPathArg ? { manifest: String(manifestPathArg).trim() } : {},
2399
+ ...policyPathArg ? { policy: String(policyPathArg).trim() } : {},
2400
+ ...smokeScriptArg ? { smoke_script: String(smokeScriptArg).trim() } : {}
2401
+ };
2402
+ if (deps.argFlag(args, "json")) {
2403
+ console.log(JSON.stringify(payload, null, 2));
2404
+ return;
2405
+ }
2406
+ console.log(`Flow template created: ${target}
2407
+ Type: ${payload.type}
2408
+ Flow: ${payload.flow}`);
2409
+ }
2410
+ async function runAiPlanCliCommand(args, deps) {
2411
+ await runAiPlanCommand(args, {
2412
+ argString: deps.argString,
2413
+ argFlag: deps.argFlag,
2414
+ findRepoRoot: deps.findRepoRoot,
2415
+ parseManifestFromFile: deps.parseManifestFromFile,
2416
+ buildContextExportPayload: (manifest, filePath) => buildContextExportPayload(manifest, filePath, {
2417
+ canonicalizeJson: deps.canonicalizeJson
2418
+ }),
2419
+ buildDevRefs: deps.buildDevRefs,
2420
+ buildInternalV1ContextPreset: (repoRoot) => buildInternalV1ContextPreset(repoRoot, { buildDevRefs: deps.buildDevRefs }),
2421
+ readJsonFile: deps.readJsonFile,
2422
+ makeCliError: deps.makeCliError
2423
+ });
2424
+ }
2425
+ async function runAiCheckCliCommand(args, deps) {
2426
+ await runAiCheckCommand(args, {
2427
+ argString: deps.argString,
2428
+ argFlag: deps.argFlag,
2429
+ findRepoRoot: deps.findRepoRoot,
2430
+ parseManifestFromFile: deps.parseManifestFromFile,
2431
+ buildContextExportPayload: (manifest, filePath) => buildContextExportPayload(manifest, filePath, {
2432
+ canonicalizeJson: deps.canonicalizeJson
2433
+ }),
2434
+ buildDevRefs: deps.buildDevRefs,
2435
+ buildInternalV1ContextPreset: (repoRoot) => buildInternalV1ContextPreset(repoRoot, { buildDevRefs: deps.buildDevRefs }),
2436
+ readJsonFile: deps.readJsonFile,
2437
+ makeCliError: deps.makeCliError
2438
+ });
2439
+ }
2440
+ async function runPublisherCliCommand(args, deps) {
2441
+ const subcommand = deps.argString(args, "_subcommand");
2442
+ const subcommand2 = deps.argString(args, "_subcommand2");
2443
+ const subcommand3 = deps.argString(args, "_subcommand3");
2444
+ if (subcommand === "endpoint" && subcommand2 === "credential" && subcommand3 === "set") {
2445
+ await runPublisherEndpointCredentialSetCommand(args, {
2446
+ argString: deps.argString,
2447
+ argFlag: deps.argFlag,
2448
+ makeCliError: deps.makeCliError
2449
+ });
2450
+ return;
2451
+ }
2452
+ if (subcommand === "endpoint" && subcommand2 === "credential" && subcommand3 === "ensure") {
2453
+ await runPublisherEndpointCredentialEnsureCommand(args, {
2454
+ argString: deps.argString,
2455
+ argFlag: deps.argFlag,
2456
+ makeCliError: deps.makeCliError
2457
+ });
2458
+ return;
2459
+ }
2460
+ throw deps.makeCliError(
2461
+ "CLI_INVALID_ARGS",
2462
+ "Missing required subcommand: publisher endpoint credential set|ensure"
2463
+ );
2464
+ }
2465
+ async function runDevCommand(args, deps) {
2466
+ const subcommand = deps.argString(args, "_subcommand");
2467
+ const subcommand2 = deps.argString(args, "_subcommand2");
2468
+ if (subcommand === "status" && subcommand2 === "refs") {
2469
+ runDevStatusRefsCliCommand(args, deps);
2470
+ return;
2471
+ }
2472
+ if (subcommand === "check" && subcommand2 === "v1") {
2473
+ runDevCheckV1CliCommand(args, deps);
2474
+ return;
2475
+ }
2476
+ if (subcommand === "check" && subcommand2 === "flow") {
2477
+ if (deps.argString(args, "_subcommand3") === "init") {
2478
+ runDevCheckFlowInitCliCommand(args, deps);
2479
+ return;
2480
+ }
2481
+ if (deps.argString(args, "_subcommand3") === "lint") {
2482
+ runDevCheckFlowLintCliCommand(args, deps);
2483
+ return;
2484
+ }
2485
+ runDevCheckFlowCliCommand(args, deps);
2486
+ return;
2487
+ }
2488
+ const from = deps.argString(args, "from");
2489
+ if (!from) {
2490
+ throw new Error("Missing required argument: --from <manifest.json>");
2491
+ }
2492
+ const once = deps.argFlag(args, "once");
2493
+ const watchEnabled = !deps.argFlag(args, "no-watch");
2494
+ const host = deps.argString(args, "host") || "127.0.0.1";
2495
+ const port = Number(deps.argString(args, "port") || "4011");
2496
+ const first = deps.parseManifestFromFile(from);
2497
+ console.log(
2498
+ `Dev manifest loaded: ${first.filePath}
2499
+ Slug: ${first.manifest.slug}
2500
+ Tools: ${first.manifest.tools.length}
2501
+ Widgets: ${first.manifest.widgets.length}`
2502
+ );
2503
+ deps.runDryRun(args, first.manifest);
2504
+ if (once) return;
2505
+ const manifestPath = first.filePath;
2506
+ let currentManifest = first.manifest;
2507
+ let watchTimer = null;
2508
+ const watcher = watchEnabled ? import_node_fs6.default.watch(manifestPath, () => {
2509
+ if (watchTimer) clearTimeout(watchTimer);
2510
+ watchTimer = setTimeout(() => {
2511
+ try {
2512
+ const parsed = deps.parseManifestFromFile(manifestPath);
2513
+ currentManifest = parsed.manifest;
2514
+ console.log(
2515
+ `Manifest revalidated: ${parsed.filePath}
2516
+ Slug: ${parsed.manifest.slug}
2517
+ Tools: ${parsed.manifest.tools.length}
2518
+ Widgets: ${parsed.manifest.widgets.length}`
2519
+ );
2520
+ } catch (err) {
2521
+ console.error(`Manifest validation failed: ${err?.message || String(err)}`);
2522
+ }
2523
+ }, 120);
2524
+ }) : null;
2525
+ const server = import_node_http.default.createServer(async (req, res) => {
2526
+ const method = (req.method || "GET").toUpperCase();
2527
+ const url = req.url || "/";
2528
+ const eventMatch = /^\/v1\/requests\/([^/]+)\/events\/?$/.exec(url);
2529
+ const completeMatch = /^\/v1\/requests\/([^/]+)\/complete\/?$/.exec(url);
2530
+ if (method === "POST" && (eventMatch || completeMatch)) {
2531
+ const requestId = decodeURIComponent((eventMatch || completeMatch)[1]);
2532
+ const payload = await deps.readRequestBody(req);
2533
+ const kind = eventMatch ? "event" : "complete";
2534
+ const payloadText = JSON.stringify(payload);
2535
+ console.log(
2536
+ `[dev-callback] ${kind} request_id=${requestId} slug=${currentManifest.slug} payload=${payloadText}`
2537
+ );
2538
+ const response = {
2539
+ ok: true,
2540
+ simulated: true,
2541
+ kind,
2542
+ request_id: requestId
2543
+ };
2544
+ res.statusCode = 200;
2545
+ res.setHeader("content-type", "application/json");
2546
+ res.end(JSON.stringify(response));
2547
+ return;
2548
+ }
2549
+ res.statusCode = 404;
2550
+ res.setHeader("content-type", "application/json");
2551
+ res.end(JSON.stringify({ ok: false, error: "not_found" }));
2552
+ });
2553
+ await new Promise((resolve, reject) => {
2554
+ server.once("error", reject);
2555
+ server.listen(port, host, () => {
2556
+ server.off("error", reject);
2557
+ resolve();
2558
+ });
2559
+ });
2560
+ console.log(
2561
+ `xapps dev running
2562
+ Manifest watch: ${watchEnabled ? "on" : "off"}
2563
+ Mock callbacks: http://${host}:${port}
2564
+ Endpoints: POST /v1/requests/:id/events and POST /v1/requests/:id/complete`
2565
+ );
2566
+ await new Promise((resolve) => {
2567
+ const shutdown = () => {
2568
+ if (watchTimer) {
2569
+ clearTimeout(watchTimer);
2570
+ }
2571
+ watcher?.close();
2572
+ server.close(() => resolve());
2573
+ };
2574
+ process.once("SIGINT", shutdown);
2575
+ process.once("SIGTERM", shutdown);
2576
+ });
2577
+ }
2578
+
2579
+ // src/cli.ts
2580
+ var DEFAULT_PUBLISH_BUNDLES_DIR = import_node_path7.default.join("artifacts", "xapps-publish");
2581
+ var CliError = class extends Error {
2582
+ code;
2583
+ details;
2584
+ exitCode;
2585
+ constructor(code, message, details, options) {
2586
+ super(message);
2587
+ this.name = "CliError";
2588
+ this.code = code;
2589
+ this.details = details;
2590
+ this.exitCode = options?.exitCode;
2591
+ }
2592
+ };
2593
+ function printUsage() {
2594
+ console.log(`xapps CLI
2595
+
2596
+ Usage:
2597
+ Public/stable publish surface:
2598
+ xapps import --from <openapi-file-or-url> --out <manifest.json> [options]
2599
+ xapps init --out <directory> [options]
2600
+ xapps validate --from <manifest.json>
2601
+ xapps test --from <manifest.json> --vectors <test-vectors.json>
2602
+ xapps publish --from <manifest.json> [options]
2603
+ xapps logs [options]
2604
+ xapps publisher endpoint credential set [options]
2605
+ xapps publisher endpoint credential ensure [options]
2606
+
2607
+ Local/repo-only helpers:
2608
+ xapps dev --from <manifest.json> [options]
2609
+ xapps context export --from <manifest.json> [options]
2610
+ xapps tunnel --target <base-url> [options]
2611
+ xapps ai plan --mode internal --from <manifest.json> [--preset <internal-v1>] [--flow <name>] [--mocks-dir <dir>] [--guidance <text> | --guidance-file <file> | --ask-guidance] [--llm] [--llm-model <model>] [--llm-timeout-ms <n>] [--apply-manifest-hints] [--json]
2612
+ Example guidance: "Infer complete form fields from mockups, build a step wizard, add supported preview sections, and exclude payment screens because payment is handled by guards."
2613
+ LLM flags: --llm-api-key <key> --llm-base-url <url> --llm-model <model> --llm-timeout-ms <n> (default model: gpt-5.2, default timeout: 30000ms)
2614
+ xapps ai check --mode internal --plan <plan.json> [--policy <policy.json> | --policy-preset <internal-readonly>] [--json]
2615
+ xapps dev status refs [--json]
2616
+ xapps dev check v1 [--json]
2617
+ xapps dev check flow --name <flow> [--json] [--run] [--artifacts-dir <dir>]
2618
+ xapps dev check flow --from <flow.json> [--json] [--run] [--artifacts-dir <dir>]
2619
+ xapps dev check flow init --out <flow.json> --type <ai-artifacts|manual-loop> [--flow-id <id>] [--manifest <path>] [--policy <path>] [--smoke-script <path>] [--json]
2620
+ xapps dev check flow lint --from <flow.json> [--json]
2621
+
2622
+ Global options:
2623
+ --profile <name> Profile name from ~/.xapps/config (or env XAPPS_CLI_PROFILE)
2624
+
2625
+ Import options:
2626
+ --name <name> App name (default: OpenAPI info.title)
2627
+ --slug <slug> Manifest slug (default: slugified app name)
2628
+ --version <semver> Manifest version (default: 1.0.0)
2629
+ --description <text> Manifest description
2630
+ --endpoint <base_url> Endpoint base_url (default: https://api.example.com)
2631
+ --endpoint-env <env> Endpoint env key (default: prod)
2632
+ --fetch-timeout-ms <n> Remote OpenAPI fetch timeout in ms (default: 10000)
2633
+
2634
+ Init options:
2635
+ --name <name> App name (default: My Xapp)
2636
+ --slug <slug> Manifest slug (default: slugified app name)
2637
+ --version <semver> Manifest version (default: 1.0.0)
2638
+ --force Overwrite existing files
2639
+
2640
+ Test options:
2641
+ --from <manifest.json> Manifest file to validate against
2642
+ --vectors <vectors.json> Request/response contract vectors
2643
+
2644
+ Publish options:
2645
+ --from <manifest.json> Manifest file to package
2646
+ --out <file-or-dir> Output file or directory (default: artifacts/xapps-publish/<slug>/<version>)
2647
+ --dry-run Validate + print summary only (no files written)
2648
+ --yes Skip release confirmation prompt (required in non-interactive mode)
2649
+ --force Overwrite target artifact if it exists
2650
+ --publish-url <url> Optional remote publish endpoint (POST bundle JSON)
2651
+ --publisher-gateway-url <url> Optional local gateway publisher API base URL (uses /publisher/* routes)
2652
+ --target-client-slug <slug> Resolve tenant slug to target_client_id via --publisher-gateway-url for publish
2653
+ --replace <A=B> Replace placeholder/text A with literal B in manifest before publish (repeatable)
2654
+ --replace-env <A=ENV> Replace placeholder/text A with process.env[ENV] before publish (repeatable)
2655
+ --bump-version <kind> Bump manifest version before publish: patch|minor|major
2656
+ --write-manifest-version Write bumped version back to source manifest file (only version field is persisted)
2657
+ --allow-unresolved-placeholders Skip preflight failure when __PLACEHOLDER__ tokens remain after replacements
2658
+ --token <token> Bearer token for --publish-url (or env XAPPS_CLI_TOKEN)
2659
+ --api-key <key> API key header value (or env XAPPS_CLI_API_KEY)
2660
+ --signing-key <key> HMAC key for signed publish metadata headers (or env XAPPS_CLI_SIGNING_KEY)
2661
+ --version-conflict <mode> Version conflict policy: fail|allow (default: fail)
2662
+ --release-channel <name> Release channel label for remote publish handoff (default: stable)
2663
+ --registry-target <name> Registry target/namespace label for remote publish handoff
2664
+ --idempotency-key <key> Idempotency key header for --publish-url handoff
2665
+ --timeout-ms <n> Remote publish timeout in ms (default: 10000)
2666
+ --retry-attempts <n> Remote publish retry attempts for transient failures (default: 1)
2667
+ --retry-backoff-ms <n> Base backoff between publish retries (default: 250)
2668
+
2669
+ Publisher endpoint credential set/ensure options:
2670
+ --gateway-url <url> Gateway base URL (auto-uses /v1 if path omitted)
2671
+ --api-key <key> Publisher API key (or env XAPPS_CLI_API_KEY)
2672
+ --token <token> Publisher bearer token (or env XAPPS_CLI_TOKEN)
2673
+ --endpoint-id <id> Endpoint id (optional if using --xapp-slug + --env)
2674
+ --xapp-slug <slug> Resolve endpoint by publisher xapp slug
2675
+ --target-client-slug <slug> Resolve the tenant-scoped xapp variant before endpoint lookup
2676
+ --env <name> Endpoint env when resolving by xapp slug (default: prod)
2677
+ --auth-type <type> Endpoint auth type (default: api-key)
2678
+ --header-name <name> API key header name for api-key header auth (default: x-xplace-api-key)
2679
+ --secret-env <ENV> Read initial credential key secret from environment variable
2680
+ --secret-stdin Read initial credential key secret from stdin
2681
+ --secret-ref <ref> Store a gateway secret provider reference instead of literal secret
2682
+ --key-status <status> Initial key status (default: active)
2683
+ --key-algorithm <alg> Initial key algorithm (default: hmac-sha256)
2684
+ --json Machine-readable output
2685
+
2686
+ Logs options:
2687
+ --from <bundle.json> Read a single local publish bundle
2688
+ --dir <path> Scan publish bundles in directory (default: artifacts/xapps-publish)
2689
+ --limit <n> Max entries (default: 10)
2690
+ --json Print JSON output
2691
+ --logs-url <url> Optional remote logs endpoint (GET)
2692
+ --token <token> Bearer token for --logs-url (or env XAPPS_CLI_TOKEN)
2693
+ --api-key <key> API key header value (or env XAPPS_CLI_API_KEY)
2694
+ --cursor <value> Optional pagination cursor for remote logs
2695
+ --lease-id <value> Optional managed log stream lease identifier
2696
+ --checkpoint <value> Optional managed log stream checkpoint token
2697
+ --severity <value> Optional remote filter (ex: error or error,warn)
2698
+ --since <iso> Optional remote filter start timestamp
2699
+ --until <iso> Optional remote filter end timestamp
2700
+ --follow Keep polling remote logs and resume from next cursor
2701
+ --follow-interval-ms <n> Poll interval for --follow mode (default: 2000)
2702
+ --follow-max-cycles <n> Optional max follow polling cycles (for automation/testing)
2703
+ --follow-cursor-file <path> Persist/restore follow cursor state to local file
2704
+ --timeout-ms <n> Remote logs timeout in ms (default: 10000)
2705
+ --retry-attempts <n> Remote logs retry attempts for transient failures (default: 1)
2706
+ --retry-backoff-ms <n> Base backoff between logs retries (default: 250)
2707
+
2708
+ Context export options:
2709
+ export --from <manifest.json> Manifest file to export deterministic context from
2710
+ --out <file> Write context JSON to file (default: stdout)
2711
+ --preset <name> Optional repo-only context preset (Phase 1: internal-v1)
2712
+
2713
+ Tunnel options:
2714
+ --target <base-url> Relay target base URL (required)
2715
+ --allow-target-hosts <csv> Explicit target host allowlist (default: 127.0.0.1,localhost,::1)
2716
+ --host <host> Relay bind host (default: 127.0.0.1)
2717
+ --listen-port <port> Relay bind port (default: 4041)
2718
+ --session-id <id> Stable tunnel session id (optional)
2719
+ --session-file <path> Persist/reuse session id across restarts
2720
+ --session-policy <mode> Session lifecycle policy: reuse|require|rotate (default: reuse)
2721
+ --require-auth-token <tok> Require inbound relay token (header x-xapps-tunnel-token or Bearer)
2722
+ --upstream-timeout-ms <n> Upstream request timeout in ms (default: 15000)
2723
+ --retry-attempts <n> Upstream retry attempts for GET/HEAD/OPTIONS transient failures (default: 1)
2724
+ --retry-backoff-ms <n> Base backoff between upstream retries (default: 200)
2725
+ --once Start, print config, and exit
2726
+
2727
+ Dev options:
2728
+ --from <manifest.json> Manifest file to validate/watch
2729
+ --dry-run <request.json> Inspect a request payload against manifest tools
2730
+ --port <port> Mock callback server port (default: 4011)
2731
+ --host <host> Mock callback server host (default: 127.0.0.1)
2732
+ --once Validate + dry-run only; skip watch/server
2733
+ --no-watch Disable manifest watch loop
2734
+
2735
+ Notes:
2736
+ - Public/stable surface focuses on manifest authoring, validation, publish, logs, and publisher endpoint credentials.
2737
+ - AI/internal preset/dev-check commands are repo-only helpers and require the xapps monorepo checkout.
2738
+ `);
2739
+ }
2740
+ function parseArgs(argv) {
2741
+ const [command = "", ...rest] = argv;
2742
+ const args = {};
2743
+ let subcommandCount = 0;
2744
+ for (let i = 0; i < rest.length; i += 1) {
2745
+ const token = rest[i];
2746
+ if (!token.startsWith("--")) {
2747
+ subcommandCount += 1;
2748
+ if (subcommandCount === 1) args._subcommand = token;
2749
+ else if (subcommandCount === 2) args._subcommand2 = token;
2750
+ else if (subcommandCount === 3) args._subcommand3 = token;
2751
+ continue;
2752
+ }
2753
+ const key = token.slice(2);
2754
+ const next = rest[i + 1];
2755
+ if (!next || next.startsWith("--")) {
2756
+ if (args[key] === void 0) {
2757
+ args[key] = true;
2758
+ } else if (Array.isArray(args[key])) {
2759
+ args[key].push("true");
2760
+ } else if (typeof args[key] === "string") {
2761
+ args[key] = [args[key], "true"];
2762
+ }
2763
+ continue;
2764
+ }
2765
+ if (args[key] === void 0) {
2766
+ args[key] = next;
2767
+ } else if (Array.isArray(args[key])) {
2768
+ args[key].push(next);
2769
+ } else if (typeof args[key] === "string") {
2770
+ args[key] = [args[key], next];
2771
+ } else {
2772
+ args[key] = [next];
2773
+ }
2774
+ i += 1;
2775
+ }
2776
+ return { command, args };
2777
+ }
2778
+ function argString(args, ...keys) {
2779
+ for (const key of keys) {
2780
+ const value = args[key];
2781
+ if (Array.isArray(value)) {
2782
+ for (let i = value.length - 1; i >= 0; i -= 1) {
2783
+ const item = value[i];
2784
+ if (typeof item === "string" && item.trim()) return item.trim();
2785
+ }
2786
+ continue;
2787
+ }
2788
+ if (typeof value === "string" && value.trim()) {
2789
+ return value.trim();
2790
+ }
2791
+ }
2792
+ return void 0;
2793
+ }
2794
+ function argStrings(args, ...keys) {
2795
+ const out = [];
2796
+ for (const key of keys) {
2797
+ const value = args[key];
2798
+ if (Array.isArray(value)) {
2799
+ for (const item of value) {
2800
+ if (typeof item === "string" && item.trim()) out.push(item.trim());
2801
+ }
2802
+ continue;
2803
+ }
2804
+ if (typeof value === "string" && value.trim()) {
2805
+ out.push(value.trim());
2806
+ }
2807
+ }
2808
+ return out;
2809
+ }
2810
+ function argFlag(args, key) {
2811
+ const value = args[key];
2812
+ if (value === true) return true;
2813
+ if (Array.isArray(value)) return value.includes("true");
2814
+ return false;
2815
+ }
2816
+ function shellEscapeArg2(value) {
2817
+ const input2 = String(value ?? "");
2818
+ if (/^[A-Za-z0-9_./:-]+$/.test(input2)) return input2;
2819
+ return `'${input2.replace(/'/g, `'"'"'`)}'`;
2820
+ }
2821
+ function applyFlowCommandTemplates(command, params) {
2822
+ return String(command || "").replaceAll("{{ARTIFACTS_DIR}}", params.artifactsDir);
2823
+ }
2824
+ function normalizePublisherGatewayApiBaseUrl2(input2) {
2825
+ const raw = String(input2 || "").trim();
2826
+ if (!raw) return "";
2827
+ const parsed = new URL(raw);
2828
+ const pathname = parsed.pathname || "/";
2829
+ if (pathname === "/" || pathname === "") {
2830
+ parsed.pathname = "/v1";
2831
+ return parsed.toString().replace(/\/+$/g, "");
2832
+ }
2833
+ return parsed.toString().replace(/\/+$/g, "");
2834
+ }
2835
+ function defaultCliConfigPath() {
2836
+ return import_node_path7.default.join(import_node_os.default.homedir(), ".xapps", "config");
2837
+ }
2838
+ function parseVersionConflictPolicy(value, fallback = "fail") {
2839
+ if (!value) return fallback;
2840
+ const normalized = String(value).trim().toLowerCase();
2841
+ if (normalized === "fail" || normalized === "allow") return normalized;
2842
+ throw new CliError(
2843
+ "CLI_INVALID_OPTION",
2844
+ `Invalid --version-conflict: ${value} (expected fail|allow)`,
2845
+ { label: "--version-conflict", value }
2846
+ );
2847
+ }
2848
+ function parseManifestReplaceLiteral(raw) {
2849
+ const idx = raw.indexOf("=");
2850
+ if (idx <= 0) {
2851
+ throw new CliError("CLI_INVALID_OPTION", `Invalid --replace value: ${raw} (expected FROM=TO)`, {
2852
+ label: "--replace",
2853
+ value: raw
2854
+ });
2855
+ }
2856
+ const from = raw.slice(0, idx).trim();
2857
+ const to = raw.slice(idx + 1);
2858
+ if (!from) {
2859
+ throw new CliError("CLI_INVALID_OPTION", `Invalid --replace value: ${raw} (empty FROM)`, {
2860
+ label: "--replace",
2861
+ value: raw
2862
+ });
2863
+ }
2864
+ return { from, to, source: "literal" };
2865
+ }
2866
+ function parseManifestReplaceEnv(raw) {
2867
+ const idx = raw.indexOf("=");
2868
+ if (idx <= 0) {
2869
+ throw new CliError(
2870
+ "CLI_INVALID_OPTION",
2871
+ `Invalid --replace-env value: ${raw} (expected FROM=ENV_VAR)`,
2872
+ { label: "--replace-env", value: raw }
2873
+ );
2874
+ }
2875
+ const from = raw.slice(0, idx).trim();
2876
+ const envName = raw.slice(idx + 1).trim();
2877
+ if (!from || !envName) {
2878
+ throw new CliError(
2879
+ "CLI_INVALID_OPTION",
2880
+ `Invalid --replace-env value: ${raw} (empty FROM or ENV_VAR)`,
2881
+ { label: "--replace-env", value: raw }
2882
+ );
2883
+ }
2884
+ const envValue = process.env[envName];
2885
+ if (typeof envValue !== "string" || envValue.length === 0) {
2886
+ throw new CliError(
2887
+ "CLI_INVALID_OPTION",
2888
+ `Environment variable missing/empty for --replace-env ${envName}`,
2889
+ { label: "--replace-env", value: raw, env: envName }
2890
+ );
2891
+ }
2892
+ return { from, to: envValue, source: "env", sourceName: envName };
2893
+ }
2894
+ function applyManifestReplaceRulesDeep(input2, rules) {
2895
+ if (!rules.length) return input2;
2896
+ const replaceToken = (raw) => {
2897
+ let next = raw;
2898
+ for (const rule of rules) {
2899
+ next = next.split(rule.from).join(rule.to);
2900
+ }
2901
+ return next;
2902
+ };
2903
+ const walk = (value) => {
2904
+ if (typeof value === "string") {
2905
+ return replaceToken(value);
2906
+ }
2907
+ if (Array.isArray(value)) return value.map(walk);
2908
+ if (value && typeof value === "object") {
2909
+ const out = {};
2910
+ for (const [k, v] of Object.entries(value)) {
2911
+ out[replaceToken(k)] = walk(v);
2912
+ }
2913
+ return out;
2914
+ }
2915
+ return value;
2916
+ };
2917
+ return walk(input2);
2918
+ }
2919
+ function findUnresolvedPlaceholderTokens(input2) {
2920
+ const seen = /* @__PURE__ */ new Set();
2921
+ const re = /__[A-Z0-9_]+__/g;
2922
+ const collect = (raw) => {
2923
+ const matches = raw.match(re);
2924
+ if (matches) for (const m of matches) seen.add(m);
2925
+ };
2926
+ const walk = (value) => {
2927
+ if (typeof value === "string") {
2928
+ collect(value);
2929
+ return;
2930
+ }
2931
+ if (Array.isArray(value)) {
2932
+ for (const item of value) walk(item);
2933
+ return;
2934
+ }
2935
+ if (value && typeof value === "object") {
2936
+ for (const [k, v] of Object.entries(value)) {
2937
+ collect(k);
2938
+ walk(v);
2939
+ }
2940
+ return;
2941
+ }
2942
+ };
2943
+ walk(input2);
2944
+ return Array.from(seen).sort();
2945
+ }
2946
+ function parseVersionBumpKind(value) {
2947
+ if (!value) return null;
2948
+ const normalized = String(value).trim().toLowerCase();
2949
+ if (normalized === "patch" || normalized === "minor" || normalized === "major") {
2950
+ return normalized;
2951
+ }
2952
+ throw new CliError(
2953
+ "CLI_INVALID_OPTION",
2954
+ `Invalid --bump-version: ${value} (expected patch|minor|major)`,
2955
+ { label: "--bump-version", value }
2956
+ );
2957
+ }
2958
+ function bumpSemverVersionOrThrow(version, kind) {
2959
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version || "").trim());
2960
+ if (!match) {
2961
+ throw new CliError("CLI_INVALID_OPTION", `Cannot ${kind}-bump non-semver version: ${version}`, {
2962
+ label: "--bump-version",
2963
+ version
2964
+ });
2965
+ }
2966
+ let major = Number(match[1]);
2967
+ let minor = Number(match[2]);
2968
+ let patch = Number(match[3]);
2969
+ if (kind === "major") {
2970
+ major += 1;
2971
+ minor = 0;
2972
+ patch = 0;
2973
+ } else if (kind === "minor") {
2974
+ minor += 1;
2975
+ patch = 0;
2976
+ } else {
2977
+ patch += 1;
2978
+ }
2979
+ return `${major}.${minor}.${patch}`;
2980
+ }
2981
+ function parseTunnelSessionPolicy(value, fallback = "reuse") {
2982
+ if (!value) return fallback;
2983
+ const normalized = String(value).trim().toLowerCase();
2984
+ if (normalized === "reuse" || normalized === "require" || normalized === "rotate") {
2985
+ return normalized;
2986
+ }
2987
+ throw new CliError(
2988
+ "CLI_INVALID_OPTION",
2989
+ `Invalid --session-policy: ${value} (expected reuse|require|rotate)`,
2990
+ { label: "--session-policy", value }
2991
+ );
2992
+ }
2993
+ function parseReleaseLabel(label, fallback, optionName) {
2994
+ const raw = String(label || fallback || "").trim().toLowerCase();
2995
+ if (!raw)
2996
+ return String(fallback || "").trim().toLowerCase();
2997
+ if (!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(raw)) {
2998
+ throw new CliError(
2999
+ "CLI_INVALID_OPTION",
3000
+ `Invalid ${optionName}: ${label} (expected 1-64 chars: a-z, 0-9, ., _, -)`,
3001
+ { label: optionName, value: label }
3002
+ );
3003
+ }
3004
+ return raw;
3005
+ }
3006
+ function parseCsvSet(value, fallbackCsv) {
3007
+ const raw = String(value || fallbackCsv);
3008
+ return new Set(
3009
+ raw.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean)
3010
+ );
3011
+ }
3012
+ function randomSessionId(prefix = "tnl") {
3013
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
3014
+ }
3015
+ function resolveCursorStatePath(rawPath) {
3016
+ return import_node_path7.default.resolve(process.cwd(), rawPath);
3017
+ }
3018
+ function loadLogsResumeState(filePathRaw) {
3019
+ const filePath = resolveCursorStatePath(filePathRaw);
3020
+ if (!import_node_fs7.default.existsSync(filePath)) return {};
3021
+ try {
3022
+ const raw = import_node_fs7.default.readFileSync(filePath, "utf8").trim();
3023
+ if (!raw) return {};
3024
+ if (!raw.startsWith("{")) {
3025
+ return { cursor: raw };
3026
+ }
3027
+ const parsed = JSON.parse(raw);
3028
+ if (!isPlainObject4(parsed)) {
3029
+ throw new Error("resume state must be a JSON object");
3030
+ }
3031
+ const cursor = typeof parsed.cursor === "string" ? String(parsed.cursor) : typeof parsed.next_cursor === "string" ? String(parsed.next_cursor) : "";
3032
+ const leaseId = typeof parsed.lease_id === "string" ? String(parsed.lease_id) : typeof parsed.leaseId === "string" ? String(parsed.leaseId) : "";
3033
+ const checkpoint = typeof parsed.checkpoint === "string" ? String(parsed.checkpoint) : typeof parsed.checkpoint_token === "string" ? String(parsed.checkpoint_token) : typeof parsed.checkpointToken === "string" ? String(parsed.checkpointToken) : "";
3034
+ return {
3035
+ ...cursor ? { cursor } : {},
3036
+ ...leaseId ? { leaseId } : {},
3037
+ ...checkpoint ? { checkpoint } : {}
3038
+ };
3039
+ } catch (err) {
3040
+ throw new CliError(
3041
+ "CLI_CURSOR_STATE_IO",
3042
+ `Failed reading cursor state file: ${filePath} (${err?.message || String(err)})`,
3043
+ { filePath }
3044
+ );
3045
+ }
3046
+ }
3047
+ function saveLogsResumeState(filePathRaw, state) {
3048
+ const filePath = resolveCursorStatePath(filePathRaw);
3049
+ try {
3050
+ import_node_fs7.default.mkdirSync(import_node_path7.default.dirname(filePath), { recursive: true });
3051
+ const cursor = String(state.cursor || "").trim();
3052
+ const leaseId = String(state.leaseId || "").trim();
3053
+ const checkpoint = String(state.checkpoint || "").trim();
3054
+ if (!leaseId && !checkpoint) {
3055
+ import_node_fs7.default.writeFileSync(filePath, `${cursor}
3056
+ `);
3057
+ return;
3058
+ }
3059
+ const payload = {
3060
+ v: 1,
3061
+ ...cursor ? { cursor } : {},
3062
+ ...leaseId ? { lease_id: leaseId } : {},
3063
+ ...checkpoint ? { checkpoint } : {},
3064
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3065
+ };
3066
+ import_node_fs7.default.writeFileSync(filePath, JSON.stringify(payload, null, 2));
3067
+ } catch (err) {
3068
+ throw new CliError(
3069
+ "CLI_CURSOR_STATE_IO",
3070
+ `Failed writing cursor state file: ${filePath} (${err?.message || String(err)})`,
3071
+ { filePath }
3072
+ );
3073
+ }
3074
+ }
3075
+ function loadTunnelSessionState(filePathRaw) {
3076
+ const filePath = import_node_path7.default.resolve(process.cwd(), filePathRaw);
3077
+ if (!import_node_fs7.default.existsSync(filePath)) return null;
3078
+ try {
3079
+ const raw = import_node_fs7.default.readFileSync(filePath, "utf8").trim();
3080
+ if (!raw) return null;
3081
+ if (!raw.startsWith("{")) {
3082
+ return { sessionId: raw };
3083
+ }
3084
+ const parsed = JSON.parse(raw);
3085
+ if (!isPlainObject4(parsed)) {
3086
+ throw new Error("session state must be a JSON object");
3087
+ }
3088
+ const sessionId = typeof parsed.session_id === "string" ? String(parsed.session_id).trim() : typeof parsed.sessionId === "string" ? String(parsed.sessionId).trim() : "";
3089
+ if (!sessionId) {
3090
+ throw new Error("session_id is required");
3091
+ }
3092
+ const target = typeof parsed.target === "string" ? String(parsed.target).trim() : "";
3093
+ const updatedAt = typeof parsed.updated_at === "string" ? String(parsed.updated_at).trim() : typeof parsed.updatedAt === "string" ? String(parsed.updatedAt).trim() : "";
3094
+ const schemaVersion = typeof parsed.v === "number" ? Number(parsed.v) : typeof parsed.version === "number" ? Number(parsed.version) : void 0;
3095
+ return {
3096
+ sessionId,
3097
+ ...target ? { target } : {},
3098
+ ...updatedAt ? { updatedAt } : {},
3099
+ ...schemaVersion !== void 0 ? { schemaVersion } : {}
3100
+ };
3101
+ } catch (err) {
3102
+ throw new CliError(
3103
+ "CLI_TUNNEL_SESSION_IO",
3104
+ `Failed reading tunnel session file: ${filePath} (${err?.message || String(err)})`,
3105
+ { filePath }
3106
+ );
3107
+ }
3108
+ }
3109
+ function saveTunnelSessionState(filePathRaw, state) {
3110
+ const filePath = import_node_path7.default.resolve(process.cwd(), filePathRaw);
3111
+ try {
3112
+ import_node_fs7.default.mkdirSync(import_node_path7.default.dirname(filePath), { recursive: true });
3113
+ const payload = {
3114
+ v: 1,
3115
+ session_id: state.sessionId,
3116
+ ...state.target ? { target: state.target } : {},
3117
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3118
+ };
3119
+ import_node_fs7.default.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}
3120
+ `);
3121
+ } catch (err) {
3122
+ throw new CliError(
3123
+ "CLI_TUNNEL_SESSION_IO",
3124
+ `Failed writing tunnel session file: ${filePath} (${err?.message || String(err)})`,
3125
+ { filePath }
3126
+ );
3127
+ }
3128
+ }
3129
+ function base64url(input2) {
3130
+ return input2.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
3131
+ }
3132
+ function stableStringify(input2) {
3133
+ const keys = Object.keys(input2).sort((a, b) => a.localeCompare(b));
3134
+ const out = {};
3135
+ for (const key of keys) out[key] = input2[key];
3136
+ return JSON.stringify(out);
3137
+ }
3138
+ function canonicalizeJson(value) {
3139
+ if (Array.isArray(value)) {
3140
+ return value.map((item) => canonicalizeJson(item));
3141
+ }
3142
+ if (value && typeof value === "object") {
3143
+ const out = {};
3144
+ for (const key of Object.keys(value).sort(
3145
+ (a, b) => a.localeCompare(b)
3146
+ )) {
3147
+ out[key] = canonicalizeJson(value[key]);
3148
+ }
3149
+ return out;
3150
+ }
3151
+ return value;
3152
+ }
3153
+ function parseCliConfig(raw, sourcePath) {
3154
+ try {
3155
+ const parsed = JSON.parse(raw);
3156
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3157
+ throw new CliError("CLI_CONFIG_INVALID", "config root must be a JSON object", {
3158
+ sourcePath
3159
+ });
3160
+ }
3161
+ return parsed;
3162
+ } catch (err) {
3163
+ if (err instanceof CliError) throw err;
3164
+ throw new CliError(
3165
+ "CLI_CONFIG_INVALID",
3166
+ `Invalid CLI config JSON (${sourcePath}): ${err?.message || String(err)}`,
3167
+ { sourcePath }
3168
+ );
3169
+ }
3170
+ }
3171
+ function loadCliConfig(_args) {
3172
+ const configPath = process.env.XAPPS_CLI_CONFIG || defaultCliConfigPath();
3173
+ if (!import_node_fs7.default.existsSync(configPath)) {
3174
+ return { config: {}, configPath: null };
3175
+ }
3176
+ const raw = import_node_fs7.default.readFileSync(configPath, "utf8");
3177
+ return { config: parseCliConfig(raw, configPath), configPath };
3178
+ }
3179
+ function getProfile(args) {
3180
+ const { config, configPath } = loadCliConfig(args);
3181
+ const profileName = argString(args, "profile") || process.env.XAPPS_CLI_PROFILE || config.defaultProfile || "";
3182
+ if (!profileName) {
3183
+ return { profileName: "", profile: {}, configPath };
3184
+ }
3185
+ const profile = config.profiles?.[profileName];
3186
+ if (!profile || typeof profile !== "object") {
3187
+ const configLabel = configPath || defaultCliConfigPath();
3188
+ throw new CliError(
3189
+ "CLI_PROFILE_NOT_FOUND",
3190
+ `Unknown CLI profile '${profileName}' in ${configLabel}`,
3191
+ {
3192
+ profileName,
3193
+ configPath: configLabel
3194
+ }
3195
+ );
3196
+ }
3197
+ return { profileName, profile, configPath };
3198
+ }
3199
+ async function confirmPublishOrThrow(summary) {
3200
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3201
+ throw new CliError(
3202
+ "CLI_CONFIRM_REQUIRED",
3203
+ "Publish confirmation required in non-interactive mode. Re-run with --yes."
3204
+ );
3205
+ }
3206
+ const rl = import_promises2.default.createInterface({ input: process.stdin, output: process.stdout });
3207
+ try {
3208
+ const answer = await rl.question(
3209
+ `Release ${summary.slug}@${summary.version} (tools=${summary.tools}, widgets=${summary.widgets})? [y/N] `
3210
+ );
3211
+ const normalized = String(answer || "").trim().toLowerCase();
3212
+ if (normalized !== "y" && normalized !== "yes") {
3213
+ throw new CliError("CLI_CONFIRM_DECLINED", "Publish cancelled by user.");
3214
+ }
3215
+ } finally {
3216
+ rl.close();
3217
+ }
3218
+ }
3219
+ async function readInput(from, fetchTimeoutMs) {
3220
+ if (/^https?:\/\//i.test(from)) {
3221
+ const controller = new AbortController();
3222
+ const timer = setTimeout(() => controller.abort(), fetchTimeoutMs);
3223
+ try {
3224
+ const res = await fetch(from, { signal: controller.signal });
3225
+ if (!res.ok) {
3226
+ throw new CliError(
3227
+ "CLI_REMOTE_FETCH_ERROR",
3228
+ `Failed to fetch OpenAPI URL (${res.status}): ${from}`,
3229
+ {
3230
+ from,
3231
+ status: res.status,
3232
+ timeout_ms: fetchTimeoutMs
3233
+ }
3234
+ );
3235
+ }
3236
+ return await res.text();
3237
+ } catch (err) {
3238
+ if (err instanceof CliError) throw err;
3239
+ if (err?.name === "AbortError") {
3240
+ throw new CliError(
3241
+ "CLI_REMOTE_FETCH_ERROR",
3242
+ `OpenAPI fetch timed out after ${fetchTimeoutMs}ms: ${from}`,
3243
+ { from, timeout_ms: fetchTimeoutMs }
3244
+ );
3245
+ }
3246
+ throw new CliError("CLI_REMOTE_FETCH_ERROR", `Failed to fetch OpenAPI URL: ${from}`, {
3247
+ from,
3248
+ timeout_ms: fetchTimeoutMs,
3249
+ cause: String(err?.message || err || "fetch_failed")
3250
+ });
3251
+ } finally {
3252
+ clearTimeout(timer);
3253
+ }
3254
+ }
3255
+ const absolute = import_node_path7.default.resolve(process.cwd(), from);
3256
+ return import_node_fs7.default.readFileSync(absolute, "utf8");
3257
+ }
3258
+ function readJsonFile(file) {
3259
+ const filePath = import_node_path7.default.resolve(process.cwd(), file);
3260
+ const raw = import_node_fs7.default.readFileSync(filePath, "utf8");
3261
+ try {
3262
+ return JSON.parse(raw);
3263
+ } catch (err) {
3264
+ throw new Error(`Invalid JSON in ${filePath}: ${err?.message || String(err)}`);
3265
+ }
3266
+ }
3267
+ function parseManifestFromFile(from) {
3268
+ const filePath = import_node_path7.default.resolve(process.cwd(), from);
3269
+ const parsed = readJsonFile(from);
3270
+ return { manifest: (0, import_server_sdk3.parseXappManifest)(parsed), filePath };
3271
+ }
3272
+ function runContextExport(args) {
3273
+ runContextExportCommand(args, {
3274
+ argString,
3275
+ findRepoRoot,
3276
+ parseManifestFromFile,
3277
+ buildDevRefs,
3278
+ canonicalizeJson,
3279
+ makeCliError: (code, message, details) => new CliError(code, message, details)
3280
+ });
3281
+ }
3282
+ async function readRequestBody(req) {
3283
+ const chunks = [];
3284
+ for await (const chunk of req) {
3285
+ if (typeof chunk === "string") {
3286
+ chunks.push(Buffer.from(chunk));
3287
+ } else {
3288
+ chunks.push(chunk);
3289
+ }
3290
+ }
3291
+ if (!chunks.length) return {};
3292
+ const raw = Buffer.concat(chunks).toString("utf8");
3293
+ try {
3294
+ return JSON.parse(raw);
3295
+ } catch {
3296
+ return { _raw: raw };
3297
+ }
3298
+ }
3299
+ async function readRawRequestBody(req) {
3300
+ const chunks = [];
3301
+ for await (const chunk of req) {
3302
+ if (typeof chunk === "string") chunks.push(Buffer.from(chunk));
3303
+ else chunks.push(chunk);
3304
+ }
3305
+ return Buffer.concat(chunks);
3306
+ }
3307
+ function runDryRun(args, manifest) {
3308
+ const dryRunFile = argString(args, "dry-run");
3309
+ if (!dryRunFile) return;
3310
+ const requestPath = import_node_path7.default.resolve(process.cwd(), dryRunFile);
3311
+ const raw = import_node_fs7.default.readFileSync(requestPath, "utf8");
3312
+ let payload;
3313
+ try {
3314
+ payload = JSON.parse(raw);
3315
+ } catch (err) {
3316
+ throw new Error(
3317
+ `Invalid JSON in dry-run payload ${requestPath}: ${err?.message || String(err)}`
3318
+ );
3319
+ }
3320
+ const toolName = typeof payload?.tool_name === "string" ? payload.tool_name : "";
3321
+ const tool = manifest.tools.find((item) => item.tool_name === toolName);
3322
+ const inputKeys = payload && typeof payload.input === "object" && payload.input ? Object.keys(payload.input) : [];
3323
+ console.log(
3324
+ [
3325
+ `Dry-run payload: ${requestPath}`,
3326
+ `Request id: ${typeof payload?.request_id === "string" ? payload.request_id : "n/a"}`,
3327
+ `Tool: ${toolName || "n/a"}${tool ? " (manifest match)" : " (not found in manifest)"}`,
3328
+ `Input keys: ${inputKeys.length ? inputKeys.join(", ") : "(none)"}`
3329
+ ].join("\n")
3330
+ );
3331
+ }
3332
+ function findRepoRoot(startDir = process.cwd()) {
3333
+ let current = import_node_path7.default.resolve(startDir);
3334
+ for (let i = 0; i < 8; i += 1) {
3335
+ if (import_node_fs7.default.existsSync(import_node_path7.default.join(current, "package.json")) && import_node_fs7.default.existsSync(import_node_path7.default.join(current, "dev"))) {
3336
+ return current;
3337
+ }
3338
+ const parent = import_node_path7.default.dirname(current);
3339
+ if (parent === current) break;
3340
+ current = parent;
3341
+ }
3342
+ return null;
3343
+ }
3344
+ function isPlainObject4(value) {
3345
+ return !!value && typeof value === "object" && !Array.isArray(value);
3346
+ }
3347
+ function resolvePublishBundlePath(outArg, slug, version) {
3348
+ if (!outArg) {
3349
+ return import_node_path7.default.resolve(process.cwd(), DEFAULT_PUBLISH_BUNDLES_DIR, slug, version, "bundle.json");
3350
+ }
3351
+ const resolved = import_node_path7.default.resolve(process.cwd(), outArg);
3352
+ if (resolved.toLowerCase().endsWith(".json")) {
3353
+ return resolved;
3354
+ }
3355
+ return import_node_path7.default.join(resolved, "bundle.json");
3356
+ }
3357
+ function parsePositiveIntOrThrow(value, fallback, label) {
3358
+ if (!value) return fallback;
3359
+ const parsed = Number(value);
3360
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
3361
+ throw new CliError(
3362
+ "CLI_INVALID_OPTION",
3363
+ `Invalid ${label}: ${value} (expected positive integer)`,
3364
+ { label, value }
3365
+ );
3366
+ }
3367
+ return parsed;
3368
+ }
3369
+ function parseHttpUrlOrThrow2(value, label) {
3370
+ let parsed;
3371
+ try {
3372
+ parsed = new URL(String(value || "").trim());
3373
+ } catch {
3374
+ throw new CliError("CLI_INVALID_OPTION", `Invalid ${label}: ${value}`, { label, value });
3375
+ }
3376
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3377
+ throw new CliError(
3378
+ "CLI_INVALID_OPTION",
3379
+ `Invalid ${label} protocol (http/https required): ${value}`,
3380
+ { label, value }
3381
+ );
3382
+ }
3383
+ return parsed.toString();
3384
+ }
3385
+ function parseListenPortOrThrow(value, fallback) {
3386
+ if (!value) return fallback;
3387
+ const parsed = Number(value);
3388
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
3389
+ throw new CliError(
3390
+ "CLI_INVALID_OPTION",
3391
+ `Invalid --listen-port: ${value} (expected integer 1-65535)`,
3392
+ { label: "--listen-port", value }
3393
+ );
3394
+ }
3395
+ return parsed;
3396
+ }
3397
+ function sleep(ms) {
3398
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
3399
+ }
3400
+ function isRetryableStatus(status) {
3401
+ return status === 408 || status === 425 || status === 429 || status >= 500;
3402
+ }
3403
+ function appendForwardedHeader(existing, next) {
3404
+ const normalized = next.trim();
3405
+ if (!normalized) return existing || "";
3406
+ if (!existing) return normalized;
3407
+ return `${existing}, ${normalized}`;
3408
+ }
3409
+ function makeRetryableError(message, retryable = false) {
3410
+ const err = new Error(message);
3411
+ err.retryable = retryable;
3412
+ return err;
3413
+ }
3414
+ function normalizeRemoteErrorDetails(bodyText) {
3415
+ const text = String(bodyText || "").trim();
3416
+ if (!text) return "";
3417
+ try {
3418
+ const parsed = JSON.parse(text);
3419
+ if (!isPlainObject4(parsed)) {
3420
+ return `body=${text.slice(0, 400)}`;
3421
+ }
3422
+ const errorCode = typeof parsed.error === "string" ? parsed.error : typeof parsed.code === "string" ? parsed.code : "";
3423
+ const message = typeof parsed.message === "string" ? parsed.message : typeof parsed.detail === "string" ? parsed.detail : "";
3424
+ const parts = [];
3425
+ if (errorCode) parts.push(`error=${errorCode}`);
3426
+ if (message) parts.push(`message=${message.slice(0, 300)}`);
3427
+ return parts.length ? parts.join(" ") : `body=${text.slice(0, 400)}`;
3428
+ } catch {
3429
+ return `body=${text.slice(0, 400)}`;
3430
+ }
3431
+ }
3432
+ function buildRemoteHttpFailureMessage(input2) {
3433
+ const detail = normalizeRemoteErrorDetails(input2.bodyText);
3434
+ const retryable = isRetryableStatus(input2.status);
3435
+ return [
3436
+ `Remote ${input2.channel} failed (status=${input2.status})`,
3437
+ `url=${input2.url}`,
3438
+ `retryable=${retryable ? "yes" : "no"}`,
3439
+ detail
3440
+ ].filter(Boolean).join(" ");
3441
+ }
3442
+ function normalizePublishOutcome(raw) {
3443
+ const normalized = String(raw ?? "").trim().toLowerCase();
3444
+ if (!normalized) return "";
3445
+ if (normalized === "created" || normalized === "new" || normalized === "published" || normalized === "success")
3446
+ return "created";
3447
+ if (normalized === "updated" || normalized === "modified" || normalized === "replaced")
3448
+ return "updated";
3449
+ if (normalized === "already_exists" || normalized === "already-exists" || normalized === "exists" || normalized === "version_exists" || normalized === "version-exists" || normalized === "conflict")
3450
+ return "already_exists";
3451
+ return "";
3452
+ }
3453
+ function pickPublishAckContainers(payload) {
3454
+ const containers = [payload];
3455
+ const nestedKeys = ["ack", "data", "publish", "release", "result"];
3456
+ for (const key of nestedKeys) {
3457
+ const value = payload[key];
3458
+ if (isPlainObject4(value)) {
3459
+ containers.push(value);
3460
+ }
3461
+ }
3462
+ return containers;
3463
+ }
3464
+ function parsePublishAck(payload) {
3465
+ if (!isPlainObject4(payload)) {
3466
+ return { hasOutcomeSignal: false };
3467
+ }
3468
+ const candidates = pickPublishAckContainers(payload);
3469
+ let outcome = "";
3470
+ let hasOutcomeSignal = false;
3471
+ let releaseId = "";
3472
+ let registryEntry = "";
3473
+ let provider = "";
3474
+ for (const candidate of candidates) {
3475
+ const outcomeRaw = candidate.outcome ?? candidate.result ?? candidate.status ?? candidate.state ?? candidate.disposition;
3476
+ if (outcomeRaw !== void 0) {
3477
+ hasOutcomeSignal = true;
3478
+ const normalized = normalizePublishOutcome(outcomeRaw);
3479
+ if (normalized) outcome = normalized;
3480
+ }
3481
+ if (!releaseId) {
3482
+ const value = candidate.release_id ?? candidate.releaseId ?? candidate.id;
3483
+ if (typeof value === "string" && value.trim()) releaseId = value.trim();
3484
+ }
3485
+ if (!registryEntry) {
3486
+ const value = candidate.registry_entry ?? candidate.registryEntry;
3487
+ if (typeof value === "string" && value.trim()) registryEntry = value.trim();
3488
+ }
3489
+ if (!provider) {
3490
+ const value = candidate.provider ?? candidate.publisher ?? candidate.vendor;
3491
+ if (typeof value === "string" && value.trim()) provider = value.trim();
3492
+ }
3493
+ }
3494
+ return {
3495
+ ...outcome ? { outcome } : {},
3496
+ ...releaseId ? { releaseId } : {},
3497
+ ...registryEntry ? { registryEntry } : {},
3498
+ ...provider ? { provider } : {},
3499
+ hasOutcomeSignal
3500
+ };
3501
+ }
3502
+ function detectPublishConflict(bodyText) {
3503
+ const text = String(bodyText || "").trim();
3504
+ if (!text) return false;
3505
+ try {
3506
+ const parsed = JSON.parse(text);
3507
+ if (!isPlainObject4(parsed)) return false;
3508
+ const parts = [];
3509
+ const pushIfString = (value) => {
3510
+ if (typeof value === "string" && value.trim()) parts.push(value.trim().toLowerCase());
3511
+ };
3512
+ const containers = pickPublishAckContainers(parsed);
3513
+ for (const item of containers) {
3514
+ pushIfString(item.error);
3515
+ pushIfString(item.code);
3516
+ pushIfString(item.reason);
3517
+ pushIfString(item.message);
3518
+ pushIfString(item.outcome);
3519
+ pushIfString(item.result);
3520
+ pushIfString(item.status);
3521
+ pushIfString(item.state);
3522
+ }
3523
+ return parts.some(
3524
+ (value) => /already[_-]?exists|version[_-]?exists|version[_-]?conflict|conflict/.test(value)
3525
+ );
3526
+ } catch {
3527
+ return false;
3528
+ }
3529
+ }
3530
+ async function withRetries(input2) {
3531
+ const attempts = Math.max(1, input2.attempts);
3532
+ let lastError = null;
3533
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
3534
+ try {
3535
+ return await input2.action(attempt);
3536
+ } catch (err) {
3537
+ lastError = err;
3538
+ const retryable = Boolean(err?.retryable);
3539
+ const isLast = attempt >= attempts;
3540
+ if (!retryable || isLast) {
3541
+ break;
3542
+ }
3543
+ const backoff = Math.max(0, input2.backoffMs) * attempt;
3544
+ await sleep(backoff);
3545
+ }
3546
+ }
3547
+ throw lastError;
3548
+ }
3549
+ async function postPublishBundle(input2) {
3550
+ return withRetries({
3551
+ attempts: input2.retryAttempts,
3552
+ backoffMs: input2.retryBackoffMs,
3553
+ action: async (attempt) => {
3554
+ const controller = new AbortController();
3555
+ const timer = setTimeout(() => controller.abort(), input2.timeoutMs);
3556
+ try {
3557
+ const headers = new Headers({ "content-type": "application/json" });
3558
+ if (input2.token) {
3559
+ headers.set("authorization", `Bearer ${input2.token}`);
3560
+ }
3561
+ if (input2.apiKey) {
3562
+ headers.set("x-api-key", input2.apiKey);
3563
+ }
3564
+ if (input2.metadataHeaders) {
3565
+ headers.set("x-xapps-meta", input2.metadataHeaders.meta);
3566
+ headers.set("x-xapps-meta-signature", input2.metadataHeaders.signature);
3567
+ }
3568
+ headers.set("x-xapps-version-conflict-policy", input2.versionConflictPolicy);
3569
+ headers.set("x-xapps-release-channel", input2.releaseChannel);
3570
+ if (input2.registryTarget) {
3571
+ headers.set("x-xapps-registry-target", input2.registryTarget);
3572
+ }
3573
+ if (input2.idempotencyKey) {
3574
+ headers.set("x-idempotency-key", input2.idempotencyKey);
3575
+ }
3576
+ const res = await fetch(input2.publishUrl, {
3577
+ method: "POST",
3578
+ headers,
3579
+ body: JSON.stringify(input2.payload),
3580
+ signal: controller.signal
3581
+ });
3582
+ if (!res.ok) {
3583
+ const body = await res.text().catch(() => "");
3584
+ if (res.status === 409) {
3585
+ const details = normalizeRemoteErrorDetails(body);
3586
+ const looksLikeConflict = detectPublishConflict(body) || details.includes("already_exists");
3587
+ if (looksLikeConflict) {
3588
+ return {
3589
+ status: res.status,
3590
+ attempt,
3591
+ outcome: "already_exists",
3592
+ detail: details
3593
+ };
3594
+ }
3595
+ }
3596
+ const message = buildRemoteHttpFailureMessage({
3597
+ channel: "publish",
3598
+ status: res.status,
3599
+ url: input2.publishUrl,
3600
+ bodyText: body
3601
+ });
3602
+ throw makeRetryableError(message, isRetryableStatus(res.status));
3603
+ }
3604
+ const contentType = String(res.headers.get("content-type") || "").toLowerCase();
3605
+ if (contentType.includes("application/json")) {
3606
+ const payloadText = await res.text().catch(() => "");
3607
+ const trimmed = payloadText.trim();
3608
+ if (trimmed.length > 0) {
3609
+ let parsed;
3610
+ try {
3611
+ parsed = JSON.parse(trimmed);
3612
+ } catch {
3613
+ throw makeRetryableError(
3614
+ `Remote publish returned malformed JSON ack url=${input2.publishUrl}`,
3615
+ false
3616
+ );
3617
+ }
3618
+ if (isPlainObject4(parsed) && parsed.ok === false) {
3619
+ const detail = normalizeRemoteErrorDetails(trimmed);
3620
+ throw makeRetryableError(
3621
+ `Remote publish rejected by ack url=${input2.publishUrl} ${detail}`,
3622
+ false
3623
+ );
3624
+ }
3625
+ if (isPlainObject4(parsed)) {
3626
+ const ack = parsePublishAck(parsed);
3627
+ const outcome = ack.outcome || "";
3628
+ if (outcome === "updated") {
3629
+ return {
3630
+ status: res.status,
3631
+ attempt,
3632
+ outcome: "updated",
3633
+ ...ack.releaseId ? { releaseId: ack.releaseId } : {},
3634
+ ...ack.registryEntry ? { registryEntry: ack.registryEntry } : {},
3635
+ ...ack.provider ? { provider: ack.provider } : {}
3636
+ };
3637
+ }
3638
+ if (outcome === "already_exists") {
3639
+ return {
3640
+ status: res.status,
3641
+ attempt,
3642
+ outcome: "already_exists",
3643
+ ...ack.releaseId ? { releaseId: ack.releaseId } : {},
3644
+ ...ack.registryEntry ? { registryEntry: ack.registryEntry } : {},
3645
+ ...ack.provider ? { provider: ack.provider } : {}
3646
+ };
3647
+ }
3648
+ if (ack.hasOutcomeSignal && !outcome) {
3649
+ throw makeRetryableError(
3650
+ `Remote publish returned unsupported outcome url=${input2.publishUrl}`,
3651
+ false
3652
+ );
3653
+ }
3654
+ if (ack.releaseId || ack.registryEntry || ack.provider) {
3655
+ return {
3656
+ status: res.status,
3657
+ attempt,
3658
+ outcome: "created",
3659
+ ...ack.releaseId ? { releaseId: ack.releaseId } : {},
3660
+ ...ack.registryEntry ? { registryEntry: ack.registryEntry } : {},
3661
+ ...ack.provider ? { provider: ack.provider } : {}
3662
+ };
3663
+ }
3664
+ }
3665
+ }
3666
+ }
3667
+ return { status: res.status, attempt, outcome: "created" };
3668
+ } catch (err) {
3669
+ if (err?.name === "AbortError") {
3670
+ throw makeRetryableError(
3671
+ `Remote publish timed out after ${input2.timeoutMs}ms url=${input2.publishUrl}`,
3672
+ true
3673
+ );
3674
+ }
3675
+ if (err?.retryable) {
3676
+ throw err;
3677
+ }
3678
+ throw makeRetryableError(
3679
+ `Remote publish request failed url=${input2.publishUrl} cause=${err?.message || String(err)}`,
3680
+ true
3681
+ );
3682
+ } finally {
3683
+ clearTimeout(timer);
3684
+ }
3685
+ }
3686
+ });
3687
+ }
3688
+ async function runPublish(args) {
3689
+ const from = argString(args, "from");
3690
+ if (!from) {
3691
+ throw new Error("Missing required argument: --from <manifest.json>");
3692
+ }
3693
+ const dryRun = argFlag(args, "dry-run");
3694
+ const force = argFlag(args, "force");
3695
+ const filePath = import_node_path7.default.resolve(process.cwd(), from);
3696
+ const sourceManifestObject = readJsonFile(from);
3697
+ if (!sourceManifestObject || typeof sourceManifestObject !== "object" || Array.isArray(sourceManifestObject)) {
3698
+ throw new CliError("CLI_INVALID_ARGS", `Manifest file must contain a JSON object: ${filePath}`);
3699
+ }
3700
+ const replaceRules = [
3701
+ ...argStrings(args, "replace").map(parseManifestReplaceLiteral),
3702
+ ...argStrings(args, "replace-env").map(parseManifestReplaceEnv)
3703
+ ];
3704
+ const bumpVersionKind = parseVersionBumpKind(argString(args, "bump-version"));
3705
+ const writeManifestVersion = argFlag(args, "write-manifest-version");
3706
+ const allowUnresolvedPlaceholders = argFlag(args, "allow-unresolved-placeholders");
3707
+ if (writeManifestVersion && !bumpVersionKind) {
3708
+ throw new CliError(
3709
+ "CLI_INVALID_ARGS",
3710
+ "--write-manifest-version requires --bump-version patch|minor|major"
3711
+ );
3712
+ }
3713
+ let manifestSourceForPublish = JSON.parse(JSON.stringify(sourceManifestObject));
3714
+ if (replaceRules.length > 0) {
3715
+ manifestSourceForPublish = applyManifestReplaceRulesDeep(
3716
+ manifestSourceForPublish,
3717
+ replaceRules
3718
+ );
3719
+ }
3720
+ const sourceVersionBeforeBump = String(manifestSourceForPublish.version ?? "").trim();
3721
+ let sourceVersionAfterBump = sourceVersionBeforeBump;
3722
+ if (bumpVersionKind) {
3723
+ sourceVersionAfterBump = bumpSemverVersionOrThrow(sourceVersionBeforeBump, bumpVersionKind);
3724
+ manifestSourceForPublish.version = sourceVersionAfterBump;
3725
+ }
3726
+ const unresolvedPlaceholders = findUnresolvedPlaceholderTokens(manifestSourceForPublish);
3727
+ if (unresolvedPlaceholders.length > 0 && !allowUnresolvedPlaceholders) {
3728
+ throw new CliError(
3729
+ "CLI_INVALID_ARGS",
3730
+ `Manifest contains unresolved placeholders after replacements: ${unresolvedPlaceholders.join(", ")}`,
3731
+ { placeholders: unresolvedPlaceholders, manifest: filePath }
3732
+ );
3733
+ }
3734
+ const manifest = (0, import_server_sdk3.parseXappManifest)(manifestSourceForPublish);
3735
+ const { profile } = getProfile(args);
3736
+ const publishUrlRaw = argString(args, "publish-url") || process.env.XAPPS_CLI_PUBLISH_URL || profile.publishUrl;
3737
+ const publishUrl = publishUrlRaw ? parseHttpUrlOrThrow2(publishUrlRaw, "--publish-url") : "";
3738
+ const publisherGatewayUrlRaw = argString(args, "publisher-gateway-url") || process.env.XAPPS_CLI_PUBLISHER_GATEWAY_URL;
3739
+ const publisherGatewayUrl = publisherGatewayUrlRaw ? parseHttpUrlOrThrow2(publisherGatewayUrlRaw, "--publisher-gateway-url") : "";
3740
+ const publisherGatewayApiBaseUrl = publisherGatewayUrl ? normalizePublisherGatewayApiBaseUrl2(publisherGatewayUrl) : "";
3741
+ const targetClientSlug = argString(args, "target-client-slug", "target_client_slug");
3742
+ const token = argString(args, "token") || process.env.XAPPS_CLI_TOKEN || profile.token;
3743
+ const apiKey = argString(args, "api-key", "apiKey") || process.env.XAPPS_CLI_API_KEY || profile.apiKey;
3744
+ const signingKey = argString(args, "signing-key") || process.env.XAPPS_CLI_SIGNING_KEY || profile.signingKey;
3745
+ const versionConflictPolicy = parseVersionConflictPolicy(
3746
+ argString(args, "version-conflict"),
3747
+ profile.versionConflict || "fail"
3748
+ );
3749
+ const releaseChannel = parseReleaseLabel(
3750
+ argString(args, "release-channel") || process.env.XAPPS_CLI_RELEASE_CHANNEL || profile.releaseChannel,
3751
+ "stable",
3752
+ "--release-channel"
3753
+ );
3754
+ const registryTarget = parseReleaseLabel(
3755
+ argString(args, "registry-target") || process.env.XAPPS_CLI_REGISTRY_TARGET || profile.registryTarget,
3756
+ "",
3757
+ "--registry-target"
3758
+ );
3759
+ const retryAttempts = parsePositiveIntOrThrow(
3760
+ argString(args, "retry-attempts"),
3761
+ 1,
3762
+ "--retry-attempts"
3763
+ );
3764
+ const retryBackoffMs = parsePositiveIntOrThrow(
3765
+ argString(args, "retry-backoff-ms"),
3766
+ 250,
3767
+ "--retry-backoff-ms"
3768
+ );
3769
+ const timeoutMsStrict = parsePositiveIntOrThrow(
3770
+ argString(args, "timeout-ms"),
3771
+ 1e4,
3772
+ "--timeout-ms"
3773
+ );
3774
+ let manifestForPublish = manifest;
3775
+ if (targetClientSlug) {
3776
+ if (!publisherGatewayApiBaseUrl) {
3777
+ throw new CliError(
3778
+ "CLI_INVALID_ARGS",
3779
+ "Resolving --target-client-slug requires --publisher-gateway-url (tenant slug lookup uses publisher API).",
3780
+ { target_client_slug: targetClientSlug }
3781
+ );
3782
+ }
3783
+ const client = (0, import_server_sdk3.createPublisherApiClient)({
3784
+ baseUrl: publisherGatewayApiBaseUrl,
3785
+ apiKey,
3786
+ token
3787
+ });
3788
+ let clientsPayload;
3789
+ try {
3790
+ clientsPayload = await client.listClients();
3791
+ } catch (err) {
3792
+ if (err instanceof import_server_sdk3.PublisherApiClientError) {
3793
+ throw new CliError(
3794
+ `CLI_${err.code}`,
3795
+ `Publisher tenant lookup failed: ${err.message}`,
3796
+ {
3797
+ url: publisherGatewayUrl,
3798
+ api_base_url: publisherGatewayApiBaseUrl,
3799
+ status: err.status,
3800
+ details: err.details,
3801
+ target_client_slug: targetClientSlug
3802
+ },
3803
+ { exitCode: 4 }
3804
+ );
3805
+ }
3806
+ throw err;
3807
+ }
3808
+ const matches = clientsPayload.items.filter(
3809
+ (item) => String(item.slug || "").trim() === targetClientSlug
3810
+ );
3811
+ if (matches.length === 0) {
3812
+ throw new CliError(
3813
+ "CLI_INVALID_ARGS",
3814
+ `Tenant slug not found via publisher API: ${targetClientSlug}`,
3815
+ { target_client_slug: targetClientSlug, api_base_url: publisherGatewayApiBaseUrl }
3816
+ );
3817
+ }
3818
+ if (matches.length > 1) {
3819
+ throw new CliError(
3820
+ "CLI_INVALID_ARGS",
3821
+ `Tenant slug resolved ambiguously via publisher API: ${targetClientSlug}`,
3822
+ { target_client_slug: targetClientSlug, matches: matches.map((m) => m.id || m.slug) }
3823
+ );
3824
+ }
3825
+ const resolvedClientId = String(matches[0]?.id || "").trim();
3826
+ if (!resolvedClientId) {
3827
+ throw new CliError(
3828
+ "CLI_INVALID_ARGS",
3829
+ `Publisher API returned tenant without id for slug: ${targetClientSlug}`,
3830
+ { target_client_slug: targetClientSlug, match: matches[0] }
3831
+ );
3832
+ }
3833
+ manifestForPublish = {
3834
+ ...manifest,
3835
+ target_client_id: resolvedClientId
3836
+ };
3837
+ }
3838
+ if (!Object.prototype.hasOwnProperty.call(manifestForPublish, "target_client_id") || !String(manifestForPublish.target_client_id || "").trim()) {
3839
+ throw new CliError(
3840
+ "CLI_INVALID_ARGS",
3841
+ "Manifest publish requires target_client_id (tenant-aware publish is required). Use manifest.target_client_id or --target-client-slug with --publisher-gateway-url.",
3842
+ { manifest: filePath }
3843
+ );
3844
+ }
3845
+ const manifestJson = JSON.stringify(manifestForPublish);
3846
+ const manifestSha256 = (0, import_node_crypto2.createHash)("sha256").update(manifestJson).digest("hex");
3847
+ const idempotencyKey = argString(args, "idempotency-key") || `xapps-cli:${manifest.slug}:${manifest.version}:${manifestSha256.slice(0, 16)}`;
3848
+ const outPath = resolvePublishBundlePath(argString(args, "out"), manifest.slug, manifest.version);
3849
+ const summary = {
3850
+ slug: manifest.slug,
3851
+ version: manifest.version,
3852
+ tools: manifest.tools.length,
3853
+ widgets: manifest.widgets.length,
3854
+ manifest_sha256: manifestSha256,
3855
+ release_channel: releaseChannel,
3856
+ ...registryTarget ? { registry_target: registryTarget } : {}
3857
+ };
3858
+ if (dryRun) {
3859
+ console.log(
3860
+ `Publish dry-run (no deploy)
3861
+ Manifest: ${filePath}
3862
+ Bundle target: ${outPath}
3863
+ Remote target: ${publishUrl || "(none)"}
3864
+ Summary: ${JSON.stringify(summary)}`
3865
+ );
3866
+ return;
3867
+ }
3868
+ if (!argFlag(args, "yes")) {
3869
+ await confirmPublishOrThrow(summary);
3870
+ }
3871
+ if (!force && import_node_fs7.default.existsSync(outPath)) {
3872
+ throw new Error(`Publish bundle already exists: ${outPath} (use --force to overwrite)`);
3873
+ }
3874
+ if (writeManifestVersion) {
3875
+ if (sourceVersionBeforeBump !== sourceVersionAfterBump) {
3876
+ const sourceUpdated = {
3877
+ ...sourceManifestObject,
3878
+ version: sourceVersionAfterBump
3879
+ };
3880
+ import_node_fs7.default.writeFileSync(filePath, `${JSON.stringify(sourceUpdated, null, 2)}
3881
+ `);
3882
+ }
3883
+ }
3884
+ const bundle = {
3885
+ schema_version: "xapps.publish.v0",
3886
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
3887
+ manifest_source: filePath,
3888
+ summary,
3889
+ ...replaceRules.length > 0 ? {
3890
+ manifest_transforms: {
3891
+ replacements: replaceRules.map((rule) => ({
3892
+ from: rule.from,
3893
+ source: rule.source,
3894
+ ...rule.sourceName ? { source_name: rule.sourceName } : {}
3895
+ })),
3896
+ ...bumpVersionKind ? {
3897
+ version_bump: {
3898
+ kind: bumpVersionKind,
3899
+ from: sourceVersionBeforeBump,
3900
+ to: sourceVersionAfterBump,
3901
+ ...writeManifestVersion ? { wrote_source_manifest_version: true } : {}
3902
+ }
3903
+ } : {}
3904
+ }
3905
+ } : bumpVersionKind ? {
3906
+ manifest_transforms: {
3907
+ version_bump: {
3908
+ kind: bumpVersionKind,
3909
+ from: sourceVersionBeforeBump,
3910
+ to: sourceVersionAfterBump,
3911
+ ...writeManifestVersion ? { wrote_source_manifest_version: true } : {}
3912
+ }
3913
+ }
3914
+ } : {},
3915
+ release: {
3916
+ channel: releaseChannel,
3917
+ ...registryTarget ? { registry_target: registryTarget } : {},
3918
+ version_conflict_policy: versionConflictPolicy
3919
+ },
3920
+ manifest: manifestForPublish
3921
+ };
3922
+ import_node_fs7.default.mkdirSync(import_node_path7.default.dirname(outPath), { recursive: true });
3923
+ import_node_fs7.default.writeFileSync(outPath, JSON.stringify(bundle, null, 2));
3924
+ const lines = [
3925
+ `Publish bundle created: ${outPath}`,
3926
+ `Summary: ${JSON.stringify(summary)}`
3927
+ ];
3928
+ const metadataPayload = {
3929
+ generated_at: bundle.generated_at,
3930
+ idempotency_key: idempotencyKey,
3931
+ manifest_sha256: manifestSha256,
3932
+ slug: manifest.slug,
3933
+ version: manifest.version,
3934
+ release_channel: releaseChannel,
3935
+ ...registryTarget ? { registry_target: registryTarget } : {}
3936
+ };
3937
+ const metadataJson = stableStringify(metadataPayload);
3938
+ const metadataSignature = signingKey ? `hmac-sha256:${base64url((0, import_node_crypto2.createHmac)("sha256", signingKey).update(metadataJson).digest())}` : "";
3939
+ if (publishUrl) {
3940
+ const remote = await postPublishBundle({
3941
+ publishUrl,
3942
+ token,
3943
+ apiKey,
3944
+ metadataHeaders: signingKey ? { meta: metadataJson, signature: metadataSignature } : void 0,
3945
+ versionConflictPolicy,
3946
+ releaseChannel,
3947
+ registryTarget,
3948
+ idempotencyKey,
3949
+ timeoutMs: timeoutMsStrict,
3950
+ retryAttempts,
3951
+ retryBackoffMs,
3952
+ payload: bundle
3953
+ });
3954
+ if (remote.outcome === "already_exists") {
3955
+ if (versionConflictPolicy === "allow") {
3956
+ lines.push(
3957
+ `Remote publish version conflict allowed: outcome=already_exists url=${publishUrl} attempts=${remote.attempt}`
3958
+ );
3959
+ if (remote.releaseId || remote.registryEntry || remote.provider) {
3960
+ lines.push(
3961
+ `Remote release metadata: release_id=${remote.releaseId || "-"} registry_entry=${remote.registryEntry || "-"} provider=${remote.provider || "-"}`
3962
+ );
3963
+ }
3964
+ console.log(lines.join("\n"));
3965
+ return;
3966
+ }
3967
+ throw new CliError(
3968
+ "CLI_VERSION_CONFLICT",
3969
+ `Remote publish version conflict: already_exists url=${publishUrl} (set --version-conflict allow to continue)`,
3970
+ { url: publishUrl, outcome: remote.outcome },
3971
+ { exitCode: 3 }
3972
+ );
3973
+ }
3974
+ lines.push(
3975
+ `Remote publish delivered: status=${remote.status} url=${publishUrl} attempts=${remote.attempt} outcome=${remote.outcome}`
3976
+ );
3977
+ if (remote.releaseId || remote.registryEntry || remote.provider) {
3978
+ lines.push(
3979
+ `Remote release metadata: release_id=${remote.releaseId || "-"} registry_entry=${remote.registryEntry || "-"} provider=${remote.provider || "-"}`
3980
+ );
3981
+ }
3982
+ }
3983
+ if (publisherGatewayApiBaseUrl) {
3984
+ try {
3985
+ const client = (0, import_server_sdk3.createPublisherApiClient)({
3986
+ baseUrl: publisherGatewayApiBaseUrl,
3987
+ apiKey,
3988
+ token
3989
+ });
3990
+ const result = await client.importAndPublishManifest(manifestForPublish);
3991
+ const versionStatus = result && result.version && typeof result.version === "object" ? String(result.version.status || "") : "";
3992
+ lines.push(
3993
+ `Publisher gateway import+publish delivered: base=${publisherGatewayApiBaseUrl} xapp_id=${result.xappId} version_id=${result.versionId}${versionStatus ? ` status=${versionStatus}` : ""}`
3994
+ );
3995
+ } catch (err) {
3996
+ if (err instanceof import_server_sdk3.PublisherApiClientError) {
3997
+ if (err.code === "PUBLISHER_API_CONFLICT") {
3998
+ const conflictCode = String(err.details?.code || "").trim();
3999
+ const isVersionConflict = conflictCode === "VERSION_CONFLICT" || /version.*exist/i.test(String(err.message || ""));
4000
+ if (isVersionConflict) {
4001
+ if (versionConflictPolicy === "allow") {
4002
+ lines.push(
4003
+ `Publisher gateway version conflict allowed: base=${publisherGatewayApiBaseUrl} outcome=already_exists`
4004
+ );
4005
+ console.log(lines.join("\n"));
4006
+ return;
4007
+ }
4008
+ throw new CliError(
4009
+ "CLI_VERSION_CONFLICT",
4010
+ `Publisher gateway version conflict: already_exists base=${publisherGatewayApiBaseUrl} (set --version-conflict allow to continue)`,
4011
+ {
4012
+ url: publisherGatewayUrl,
4013
+ api_base_url: publisherGatewayApiBaseUrl,
4014
+ status: err.status,
4015
+ details: err.details
4016
+ },
4017
+ { exitCode: 3 }
4018
+ );
4019
+ }
4020
+ }
4021
+ throw new CliError(
4022
+ `CLI_${err.code}`,
4023
+ `Publisher gateway publish failed: ${err.message}`,
4024
+ {
4025
+ url: publisherGatewayUrl,
4026
+ api_base_url: publisherGatewayApiBaseUrl,
4027
+ status: err.status,
4028
+ details: err.details
4029
+ },
4030
+ { exitCode: 4 }
4031
+ );
4032
+ }
4033
+ throw err;
4034
+ }
4035
+ }
4036
+ console.log(lines.join("\n"));
4037
+ }
4038
+ function normalizeBundleSummary(value, fallbackPath, fallbackGeneratedAt = "") {
4039
+ if (!isPlainObject4(value)) return null;
4040
+ const bundlePath = typeof value.bundle_path === "string" ? value.bundle_path : typeof value.path === "string" ? value.path : fallbackPath;
4041
+ const generatedAt = typeof value.generated_at === "string" ? value.generated_at : typeof value.created_at === "string" ? value.created_at : fallbackGeneratedAt;
4042
+ const slug = typeof value.slug === "string" ? value.slug.trim() : "";
4043
+ const version = typeof value.version === "string" ? value.version.trim() : "";
4044
+ const tools = typeof value.tools === "number" ? value.tools : NaN;
4045
+ const widgets = typeof value.widgets === "number" ? value.widgets : NaN;
4046
+ const manifestSha256 = typeof value.manifest_sha256 === "string" ? value.manifest_sha256 : "";
4047
+ if (!bundlePath || !generatedAt || !slug || !version) return null;
4048
+ if (!Number.isFinite(tools) || tools < 0 || !Number.isFinite(widgets) || widgets < 0) return null;
4049
+ return {
4050
+ bundle_path: bundlePath,
4051
+ generated_at: generatedAt,
4052
+ slug,
4053
+ version,
4054
+ tools,
4055
+ widgets,
4056
+ manifest_sha256: manifestSha256
4057
+ };
4058
+ }
4059
+ function toBundleSummary(bundlePath, payload) {
4060
+ if (!isPlainObject4(payload)) {
4061
+ throw new Error(`Invalid bundle payload in ${bundlePath}`);
4062
+ }
4063
+ if (payload.schema_version !== "xapps.publish.v0") {
4064
+ throw new Error(`Unsupported bundle schema in ${bundlePath}`);
4065
+ }
4066
+ const summary = isPlainObject4(payload.summary) ? payload.summary : {};
4067
+ return {
4068
+ bundle_path: bundlePath,
4069
+ generated_at: typeof payload.generated_at === "string" ? payload.generated_at : "",
4070
+ slug: typeof summary.slug === "string" ? summary.slug : "",
4071
+ version: typeof summary.version === "string" ? summary.version : "",
4072
+ tools: typeof summary.tools === "number" ? summary.tools : 0,
4073
+ widgets: typeof summary.widgets === "number" ? summary.widgets : 0,
4074
+ manifest_sha256: typeof summary.manifest_sha256 === "string" ? summary.manifest_sha256 : ""
4075
+ };
4076
+ }
4077
+ function collectBundleFiles(dirPath) {
4078
+ const out = [];
4079
+ if (!import_node_fs7.default.existsSync(dirPath)) {
4080
+ return out;
4081
+ }
4082
+ const entries = import_node_fs7.default.readdirSync(dirPath, { withFileTypes: true });
4083
+ for (const entry of entries) {
4084
+ const full = import_node_path7.default.join(dirPath, entry.name);
4085
+ if (entry.isDirectory()) {
4086
+ out.push(...collectBundleFiles(full));
4087
+ continue;
4088
+ }
4089
+ if (entry.isFile() && entry.name === "bundle.json") {
4090
+ out.push(full);
4091
+ }
4092
+ }
4093
+ return out;
4094
+ }
4095
+ async function fetchRemoteLogs(input2) {
4096
+ return withRetries({
4097
+ attempts: input2.retryAttempts,
4098
+ backoffMs: input2.retryBackoffMs,
4099
+ action: async () => {
4100
+ const controller = new AbortController();
4101
+ const timer = setTimeout(() => controller.abort(), input2.timeoutMs);
4102
+ try {
4103
+ const headers = new Headers();
4104
+ if (input2.token) headers.set("authorization", `Bearer ${input2.token}`);
4105
+ if (input2.apiKey) headers.set("x-api-key", input2.apiKey);
4106
+ const url = new URL(input2.logsUrl);
4107
+ if (!url.searchParams.has("limit")) {
4108
+ url.searchParams.set("limit", String(input2.limit));
4109
+ }
4110
+ if (input2.cursor) url.searchParams.set("cursor", input2.cursor);
4111
+ if (input2.leaseId) url.searchParams.set("lease_id", input2.leaseId);
4112
+ if (input2.checkpoint) url.searchParams.set("checkpoint", input2.checkpoint);
4113
+ if (input2.severity) url.searchParams.set("severity", input2.severity);
4114
+ if (input2.since) url.searchParams.set("since", input2.since);
4115
+ if (input2.until) url.searchParams.set("until", input2.until);
4116
+ const res = await fetch(url, {
4117
+ method: "GET",
4118
+ headers,
4119
+ signal: controller.signal
4120
+ });
4121
+ if (!res.ok) {
4122
+ const body = await res.text().catch(() => "");
4123
+ const message = buildRemoteHttpFailureMessage({
4124
+ channel: "logs",
4125
+ status: res.status,
4126
+ url: input2.logsUrl,
4127
+ bodyText: body
4128
+ });
4129
+ throw makeRetryableError(message, isRetryableStatus(res.status));
4130
+ }
4131
+ const payload = await res.json();
4132
+ const rowsRaw = Array.isArray(payload) ? payload : isPlainObject4(payload) && Array.isArray(payload.entries) ? payload.entries : null;
4133
+ if (!rowsRaw) {
4134
+ throw makeRetryableError(
4135
+ `Remote logs returned malformed payload url=${input2.logsUrl}`,
4136
+ false
4137
+ );
4138
+ }
4139
+ const rows = rowsRaw.map((entry) => normalizeBundleSummary(entry, "remote://logs")).filter((entry) => !!entry);
4140
+ if (rowsRaw.length > 0 && rows.length === 0) {
4141
+ throw makeRetryableError(
4142
+ `Remote logs returned malformed entries url=${input2.logsUrl}`,
4143
+ false
4144
+ );
4145
+ }
4146
+ const nextCursor = isPlainObject4(payload) && (typeof payload.next_cursor === "string" || typeof payload.nextCursor === "string") ? String(payload.next_cursor ?? payload.nextCursor ?? "") : "";
4147
+ const payloadHasLease = isPlainObject4(payload) && (Object.prototype.hasOwnProperty.call(payload, "lease_id") || Object.prototype.hasOwnProperty.call(payload, "leaseId"));
4148
+ const payloadHasCheckpoint = isPlainObject4(payload) && (Object.prototype.hasOwnProperty.call(payload, "checkpoint") || Object.prototype.hasOwnProperty.call(payload, "checkpoint_token") || Object.prototype.hasOwnProperty.call(payload, "checkpointToken"));
4149
+ const leaseId = isPlainObject4(payload) && (typeof payload.lease_id === "string" || typeof payload.leaseId === "string") ? String(payload.lease_id ?? payload.leaseId ?? "") : "";
4150
+ const checkpoint = isPlainObject4(payload) && (typeof payload.checkpoint === "string" || typeof payload.checkpoint_token === "string" || typeof payload.checkpointToken === "string") ? String(
4151
+ payload.checkpoint ?? payload.checkpoint_token ?? payload.checkpointToken ?? ""
4152
+ ) : "";
4153
+ if (payloadHasLease && !leaseId) {
4154
+ throw makeRetryableError(
4155
+ `Remote logs returned malformed lease_id field url=${input2.logsUrl}`,
4156
+ false
4157
+ );
4158
+ }
4159
+ if (payloadHasCheckpoint && !checkpoint) {
4160
+ throw makeRetryableError(
4161
+ `Remote logs returned malformed checkpoint field url=${input2.logsUrl}`,
4162
+ false
4163
+ );
4164
+ }
4165
+ return {
4166
+ entries: rows.sort((a, b) => String(b.generated_at).localeCompare(String(a.generated_at))).slice(0, input2.limit),
4167
+ ...nextCursor ? { nextCursor } : {},
4168
+ ...leaseId ? { leaseId } : {},
4169
+ ...checkpoint ? { checkpoint } : {}
4170
+ };
4171
+ } catch (err) {
4172
+ if (err?.name === "AbortError") {
4173
+ throw makeRetryableError(
4174
+ `Remote logs timed out after ${input2.timeoutMs}ms url=${input2.logsUrl}`,
4175
+ true
4176
+ );
4177
+ }
4178
+ if (err?.retryable) {
4179
+ throw err;
4180
+ }
4181
+ throw makeRetryableError(
4182
+ `Remote logs request failed url=${input2.logsUrl} cause=${err?.message || String(err)}`,
4183
+ true
4184
+ );
4185
+ } finally {
4186
+ clearTimeout(timer);
4187
+ }
4188
+ }
4189
+ });
4190
+ }
4191
+ async function runLogs(args) {
4192
+ const { profile } = getProfile(args);
4193
+ const json = argFlag(args, "json");
4194
+ const limit = parsePositiveIntOrThrow(argString(args, "limit"), 10, "--limit");
4195
+ const from = argString(args, "from");
4196
+ const dir = import_node_path7.default.resolve(process.cwd(), argString(args, "dir") || DEFAULT_PUBLISH_BUNDLES_DIR);
4197
+ const logsUrlRaw = argString(args, "logs-url") || process.env.XAPPS_CLI_LOGS_URL || profile.logsUrl;
4198
+ const logsUrl = logsUrlRaw ? parseHttpUrlOrThrow2(logsUrlRaw, "--logs-url") : "";
4199
+ const token = argString(args, "token") || process.env.XAPPS_CLI_TOKEN || profile.token;
4200
+ const apiKey = argString(args, "api-key", "apiKey") || process.env.XAPPS_CLI_API_KEY || profile.apiKey;
4201
+ const cursor = argString(args, "cursor");
4202
+ const leaseId = argString(args, "lease-id");
4203
+ const checkpoint = argString(args, "checkpoint");
4204
+ const severity = argString(args, "severity");
4205
+ const since = argString(args, "since");
4206
+ const until = argString(args, "until");
4207
+ const follow = argFlag(args, "follow");
4208
+ const followCursorFile = argString(args, "follow-cursor-file");
4209
+ const followIntervalMs = parsePositiveIntOrThrow(
4210
+ argString(args, "follow-interval-ms"),
4211
+ 2e3,
4212
+ "--follow-interval-ms"
4213
+ );
4214
+ const followMaxCycles = parsePositiveIntOrThrow(
4215
+ argString(args, "follow-max-cycles"),
4216
+ 1,
4217
+ "--follow-max-cycles"
4218
+ );
4219
+ const timeoutMs = parsePositiveIntOrThrow(argString(args, "timeout-ms"), 1e4, "--timeout-ms");
4220
+ const retryAttempts = parsePositiveIntOrThrow(
4221
+ argString(args, "retry-attempts"),
4222
+ 1,
4223
+ "--retry-attempts"
4224
+ );
4225
+ const retryBackoffMs = parsePositiveIntOrThrow(
4226
+ argString(args, "retry-backoff-ms"),
4227
+ 250,
4228
+ "--retry-backoff-ms"
4229
+ );
4230
+ const localSummaries = logsUrl ? null : (() => {
4231
+ const bundleFiles = from ? [import_node_path7.default.resolve(process.cwd(), from)] : collectBundleFiles(dir);
4232
+ if (!bundleFiles.length) {
4233
+ throw new Error(
4234
+ from ? `Bundle not found: ${import_node_path7.default.resolve(process.cwd(), from)}` : `No bundles found in ${dir}`
4235
+ );
4236
+ }
4237
+ return bundleFiles.map((file) => toBundleSummary(file, readJsonFile(file))).sort((a, b) => String(b.generated_at).localeCompare(String(a.generated_at))).slice(0, limit);
4238
+ })();
4239
+ async function printRows(rows, nextCursor, leaseId2, checkpoint2) {
4240
+ if (!rows.length) return;
4241
+ if (json) {
4242
+ console.log(
4243
+ JSON.stringify(
4244
+ {
4245
+ count: rows.length,
4246
+ entries: rows,
4247
+ ...nextCursor ? { next_cursor: nextCursor } : {},
4248
+ ...leaseId2 ? { lease_id: leaseId2 } : {},
4249
+ ...checkpoint2 ? { checkpoint: checkpoint2 } : {}
4250
+ },
4251
+ null,
4252
+ 2
4253
+ )
4254
+ );
4255
+ return;
4256
+ }
4257
+ const lines = [`Logs entries: ${rows.length}`];
4258
+ for (const entry of rows) {
4259
+ lines.push(
4260
+ [
4261
+ `- ${entry.slug}@${entry.version}`,
4262
+ `generated_at=${entry.generated_at || "n/a"}`,
4263
+ `tools=${entry.tools}`,
4264
+ `widgets=${entry.widgets}`,
4265
+ `bundle=${entry.bundle_path}`
4266
+ ].join(" ")
4267
+ );
4268
+ }
4269
+ if (nextCursor) lines.push(`next_cursor=${nextCursor}`);
4270
+ if (leaseId2) lines.push(`lease_id=${leaseId2}`);
4271
+ if (checkpoint2) lines.push(`checkpoint=${checkpoint2}`);
4272
+ console.log(lines.join("\n"));
4273
+ }
4274
+ if (!logsUrl && localSummaries) {
4275
+ if (!localSummaries.length) {
4276
+ throw new Error(
4277
+ from ? `Bundle not found: ${import_node_path7.default.resolve(process.cwd(), from)}` : `No bundles found in ${dir}`
4278
+ );
4279
+ }
4280
+ await printRows(localSummaries);
4281
+ return;
4282
+ }
4283
+ if (!logsUrl) return;
4284
+ let currentCursor = cursor || "";
4285
+ let currentLeaseId = leaseId || "";
4286
+ let currentCheckpoint = checkpoint || "";
4287
+ if (followCursorFile) {
4288
+ const state = loadLogsResumeState(followCursorFile);
4289
+ if (!currentCursor && state.cursor) currentCursor = state.cursor;
4290
+ if (state.leaseId) currentLeaseId = state.leaseId;
4291
+ if (state.checkpoint) currentCheckpoint = state.checkpoint;
4292
+ }
4293
+ let cycles = 0;
4294
+ let seen = 0;
4295
+ while (true) {
4296
+ cycles += 1;
4297
+ try {
4298
+ const remote = await fetchRemoteLogs({
4299
+ logsUrl,
4300
+ token,
4301
+ apiKey,
4302
+ timeoutMs,
4303
+ limit,
4304
+ cursor: currentCursor || void 0,
4305
+ leaseId: currentLeaseId || void 0,
4306
+ checkpoint: currentCheckpoint || void 0,
4307
+ severity,
4308
+ since,
4309
+ until,
4310
+ retryAttempts,
4311
+ retryBackoffMs
4312
+ });
4313
+ if (remote.entries.length) {
4314
+ seen += remote.entries.length;
4315
+ await printRows(remote.entries, remote.nextCursor, remote.leaseId, remote.checkpoint);
4316
+ } else if (!follow && seen === 0) {
4317
+ throw new Error(`No log entries returned by remote endpoint: ${logsUrl}`);
4318
+ }
4319
+ if (remote.leaseId) currentLeaseId = remote.leaseId;
4320
+ if (remote.checkpoint) currentCheckpoint = remote.checkpoint;
4321
+ if (remote.nextCursor) {
4322
+ currentCursor = remote.nextCursor;
4323
+ }
4324
+ if (followCursorFile) {
4325
+ saveLogsResumeState(followCursorFile, {
4326
+ ...currentCursor ? { cursor: currentCursor } : {},
4327
+ ...currentLeaseId ? { leaseId: currentLeaseId } : {},
4328
+ ...currentCheckpoint ? { checkpoint: currentCheckpoint } : {}
4329
+ });
4330
+ }
4331
+ } catch (err) {
4332
+ if (!follow) throw err;
4333
+ console.error(`[logs-follow] reconnecting after error: ${err?.message || String(err)}`);
4334
+ }
4335
+ if (!follow) return;
4336
+ if (cycles >= followMaxCycles) return;
4337
+ await sleep(followIntervalMs);
4338
+ }
4339
+ }
4340
+ async function runTunnel(args) {
4341
+ const { profile } = getProfile(args);
4342
+ const target = argString(args, "target");
4343
+ if (!target) {
4344
+ throw new Error("Missing required argument: --target <base-url>");
4345
+ }
4346
+ const targetBase = parseHttpUrlOrThrow2(String(target).replace(/\/$/, ""), "--target").replace(
4347
+ /\/$/,
4348
+ ""
4349
+ );
4350
+ const targetHostname = new URL(targetBase).hostname.toLowerCase();
4351
+ const allowTargetHosts = parseCsvSet(
4352
+ argString(args, "allow-target-hosts") || process.env.XAPPS_CLI_TUNNEL_ALLOW_TARGET_HOSTS,
4353
+ "127.0.0.1,localhost,::1"
4354
+ );
4355
+ if (!allowTargetHosts.has(targetHostname)) {
4356
+ throw new CliError(
4357
+ "CLI_TUNNEL_TARGET_NOT_ALLOWED",
4358
+ `Tunnel target host is not allowed: ${targetHostname} (allowlist=${Array.from(allowTargetHosts).join(",")})`,
4359
+ { targetHostname, allowlist: Array.from(allowTargetHosts) }
4360
+ );
4361
+ }
4362
+ const host = argString(args, "host") || "127.0.0.1";
4363
+ const port = parseListenPortOrThrow(argString(args, "listen-port"), 4041);
4364
+ const sessionFile = argString(args, "session-file");
4365
+ const sessionIdArg = argString(args, "session-id");
4366
+ const sessionPolicy = parseTunnelSessionPolicy(argString(args, "session-policy"), "reuse");
4367
+ const sessionStateFromFile = sessionFile ? loadTunnelSessionState(sessionFile) : null;
4368
+ let sessionId = "";
4369
+ let sessionSource = "generated";
4370
+ if (sessionPolicy === "rotate") {
4371
+ if (sessionIdArg) {
4372
+ sessionId = sessionIdArg;
4373
+ sessionSource = "arg";
4374
+ } else {
4375
+ sessionId = randomSessionId("tnl");
4376
+ sessionSource = "rotated";
4377
+ }
4378
+ } else if (sessionIdArg) {
4379
+ if (sessionStateFromFile?.sessionId && sessionStateFromFile.sessionId !== sessionIdArg && sessionPolicy !== "rotate") {
4380
+ throw new CliError(
4381
+ "CLI_TUNNEL_SESSION_CONFLICT",
4382
+ `Provided --session-id does not match persisted session file (${sessionFile})`,
4383
+ {
4384
+ provided_session_id: sessionIdArg,
4385
+ persisted_session_id: sessionStateFromFile.sessionId,
4386
+ session_file: sessionFile,
4387
+ session_policy: sessionPolicy
4388
+ }
4389
+ );
4390
+ }
4391
+ sessionId = sessionIdArg;
4392
+ sessionSource = "arg";
4393
+ } else if (sessionStateFromFile?.sessionId) {
4394
+ const persistedTarget = String(sessionStateFromFile.target || "").trim();
4395
+ if (persistedTarget && persistedTarget !== targetBase) {
4396
+ if (sessionPolicy === "require") {
4397
+ throw new CliError(
4398
+ "CLI_TUNNEL_SESSION_TARGET_MISMATCH",
4399
+ `Persisted tunnel session target does not match current --target (${persistedTarget} != ${targetBase})`,
4400
+ {
4401
+ persisted_target: persistedTarget,
4402
+ current_target: targetBase,
4403
+ session_file: sessionFile || ""
4404
+ }
4405
+ );
4406
+ }
4407
+ sessionId = randomSessionId("tnl");
4408
+ sessionSource = "rotated";
4409
+ } else {
4410
+ sessionId = sessionStateFromFile.sessionId;
4411
+ sessionSource = "file";
4412
+ }
4413
+ } else if (sessionPolicy === "require") {
4414
+ throw new CliError(
4415
+ "CLI_TUNNEL_SESSION_REQUIRED",
4416
+ "Session policy 'require' needs existing --session-file state or explicit --session-id",
4417
+ {
4418
+ session_file: sessionFile || "",
4419
+ session_policy: sessionPolicy
4420
+ }
4421
+ );
4422
+ } else {
4423
+ sessionId = randomSessionId("tnl");
4424
+ sessionSource = "generated";
4425
+ }
4426
+ if (sessionFile) {
4427
+ saveTunnelSessionState(sessionFile, {
4428
+ sessionId,
4429
+ target: targetBase
4430
+ });
4431
+ }
4432
+ const once = argFlag(args, "once");
4433
+ const requiredToken = argString(args, "require-auth-token") || process.env.XAPPS_CLI_TUNNEL_TOKEN || profile.tunnelToken || void 0;
4434
+ const upstreamTimeoutMs = parsePositiveIntOrThrow(
4435
+ argString(args, "upstream-timeout-ms"),
4436
+ 15e3,
4437
+ "--upstream-timeout-ms"
4438
+ );
4439
+ const retryAttempts = parsePositiveIntOrThrow(
4440
+ argString(args, "retry-attempts"),
4441
+ 1,
4442
+ "--retry-attempts"
4443
+ );
4444
+ const retryBackoffMs = parsePositiveIntOrThrow(
4445
+ argString(args, "retry-backoff-ms"),
4446
+ 200,
4447
+ "--retry-backoff-ms"
4448
+ );
4449
+ const idempotentMethods = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
4450
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4451
+ let proxiedRequests = 0;
4452
+ let proxyErrors = 0;
4453
+ let lastUpstreamStatus = 0;
4454
+ const announce = `xapps tunnel relay
4455
+ Session: ${sessionId}
4456
+ Session policy: ${sessionPolicy}
4457
+ Session source: ${sessionSource}
4458
+ Listen: http://${host}:${port}
4459
+ Target: ${targetBase}
4460
+ Allowlist: ${Array.from(allowTargetHosts).join(",")}
4461
+ Mode: local relay baseline (no external public URL)
4462
+ Upstream timeout: ${upstreamTimeoutMs}ms`;
4463
+ if (once) {
4464
+ console.log(announce);
4465
+ return;
4466
+ }
4467
+ const server = import_node_http2.default.createServer(async (req, res) => {
4468
+ const method = (req.method || "GET").toUpperCase();
4469
+ const requestUrl = req.url || "/";
4470
+ const headerToken = typeof req.headers["x-xapps-tunnel-token"] === "string" ? req.headers["x-xapps-tunnel-token"] : void 0;
4471
+ const authHeader = typeof req.headers.authorization === "string" ? req.headers.authorization.trim() : "";
4472
+ const bearerToken = authHeader.toLowerCase().startsWith("bearer ") ? authHeader.slice(7).trim() : void 0;
4473
+ const suppliedToken = headerToken || bearerToken;
4474
+ if (requiredToken && suppliedToken !== requiredToken) {
4475
+ res.statusCode = 401;
4476
+ res.setHeader("content-type", "application/json");
4477
+ res.end(
4478
+ JSON.stringify({
4479
+ ok: false,
4480
+ error: "tunnel_auth_required",
4481
+ message: "Missing or invalid tunnel auth token"
4482
+ })
4483
+ );
4484
+ return;
4485
+ }
4486
+ if (requestUrl === "/__ready") {
4487
+ res.statusCode = 200;
4488
+ res.setHeader("content-type", "application/json");
4489
+ res.end(
4490
+ JSON.stringify({
4491
+ ok: true,
4492
+ tunnel: "ready",
4493
+ session_id: sessionId,
4494
+ session_policy: sessionPolicy,
4495
+ session_source: sessionSource,
4496
+ target: targetBase
4497
+ })
4498
+ );
4499
+ return;
4500
+ }
4501
+ if (requestUrl === "/__status") {
4502
+ res.statusCode = 200;
4503
+ res.setHeader("content-type", "application/json");
4504
+ res.end(
4505
+ JSON.stringify({
4506
+ ok: true,
4507
+ tunnel: "status",
4508
+ session_id: sessionId,
4509
+ session_policy: sessionPolicy,
4510
+ session_source: sessionSource,
4511
+ session_file: sessionFile || null,
4512
+ target: targetBase,
4513
+ listen: { host, port },
4514
+ started_at: startedAt,
4515
+ proxied_requests: proxiedRequests,
4516
+ proxy_errors: proxyErrors,
4517
+ last_upstream_status: lastUpstreamStatus || null
4518
+ })
4519
+ );
4520
+ return;
4521
+ }
4522
+ const body = await readRawRequestBody(req);
4523
+ const headers = new Headers();
4524
+ for (const [key, value] of Object.entries(req.headers)) {
4525
+ if (!value) continue;
4526
+ const lower = key.toLowerCase();
4527
+ if (lower === "host") continue;
4528
+ if (lower === "connection" || lower === "keep-alive" || lower === "proxy-authorization" || lower === "proxy-authenticate" || lower === "te" || lower === "trailers" || lower === "transfer-encoding" || lower === "upgrade") {
4529
+ continue;
4530
+ }
4531
+ if (Array.isArray(value)) headers.set(key, value.join(", "));
4532
+ else headers.set(key, String(value));
4533
+ }
4534
+ headers.set(
4535
+ "x-forwarded-host",
4536
+ appendForwardedHeader(headers.get("x-forwarded-host"), `${host}:${port}`)
4537
+ );
4538
+ headers.set(
4539
+ "x-forwarded-proto",
4540
+ appendForwardedHeader(headers.get("x-forwarded-proto"), "http")
4541
+ );
4542
+ headers.set(
4543
+ "x-forwarded-port",
4544
+ appendForwardedHeader(headers.get("x-forwarded-port"), String(port))
4545
+ );
4546
+ if (req.socket?.remoteAddress) {
4547
+ const existingForwardedFor = headers.get("x-forwarded-for");
4548
+ headers.set(
4549
+ "x-forwarded-for",
4550
+ appendForwardedHeader(existingForwardedFor, req.socket.remoteAddress)
4551
+ );
4552
+ }
4553
+ const targetUrl = `${targetBase}${requestUrl}`;
4554
+ try {
4555
+ const attempts = idempotentMethods.has(method) ? retryAttempts : 1;
4556
+ const upstream = await withRetries({
4557
+ attempts,
4558
+ backoffMs: retryBackoffMs,
4559
+ action: async () => {
4560
+ const controller = new AbortController();
4561
+ const timer = setTimeout(() => controller.abort(), upstreamTimeoutMs);
4562
+ try {
4563
+ const resUpstream = await fetch(targetUrl, {
4564
+ method,
4565
+ headers,
4566
+ body: method === "GET" || method === "HEAD" ? void 0 : body,
4567
+ signal: controller.signal
4568
+ });
4569
+ if (isRetryableStatus(resUpstream.status)) {
4570
+ throw makeRetryableError(`upstream status ${resUpstream.status}`, true);
4571
+ }
4572
+ return resUpstream;
4573
+ } catch (err) {
4574
+ if (err?.name === "AbortError") {
4575
+ throw makeRetryableError("upstream timeout", true);
4576
+ }
4577
+ if (err?.retryable) throw err;
4578
+ throw makeRetryableError(err?.message || String(err), true);
4579
+ } finally {
4580
+ clearTimeout(timer);
4581
+ }
4582
+ }
4583
+ });
4584
+ const upstreamBody = Buffer.from(await upstream.arrayBuffer());
4585
+ res.statusCode = upstream.status;
4586
+ proxiedRequests += 1;
4587
+ lastUpstreamStatus = upstream.status;
4588
+ upstream.headers.forEach((value, key) => {
4589
+ if (key.toLowerCase() === "transfer-encoding") return;
4590
+ res.setHeader(key, value);
4591
+ });
4592
+ res.end(upstreamBody);
4593
+ console.log(
4594
+ `[tunnel] ${method} ${requestUrl} -> ${targetUrl} status=${upstream.status} bytes=${upstreamBody.length}`
4595
+ );
4596
+ } catch (err) {
4597
+ proxyErrors += 1;
4598
+ const timeout = String(err?.message || "").toLowerCase().includes("timeout");
4599
+ res.statusCode = timeout ? 504 : 502;
4600
+ res.setHeader("content-type", "application/json");
4601
+ res.end(
4602
+ JSON.stringify({
4603
+ ok: false,
4604
+ error: timeout ? "upstream_timeout" : "upstream_unreachable",
4605
+ message: err?.message || String(err)
4606
+ })
4607
+ );
4608
+ console.error(
4609
+ `[tunnel] upstream error ${method} ${requestUrl}: ${err?.message || String(err)}`
4610
+ );
4611
+ }
4612
+ });
4613
+ await new Promise((resolve, reject) => {
4614
+ server.once("error", reject);
4615
+ server.listen(port, host, () => {
4616
+ server.off("error", reject);
4617
+ resolve();
4618
+ });
4619
+ });
4620
+ console.log(announce);
4621
+ await new Promise((resolve) => {
4622
+ const shutdown = () => server.close(() => resolve());
4623
+ process.once("SIGINT", shutdown);
4624
+ process.once("SIGTERM", shutdown);
4625
+ });
4626
+ }
4627
+ async function runCli(argv) {
4628
+ try {
4629
+ const { command, args } = parseArgs(argv);
4630
+ if (!command || command === "--help" || command === "-h" || command === "help") {
4631
+ printUsage();
4632
+ return;
4633
+ }
4634
+ if (command === "import") {
4635
+ await runImportCommand(args, {
4636
+ argString,
4637
+ argFlag,
4638
+ readInput,
4639
+ parsePositiveIntOrThrow,
4640
+ parseManifestFromFile,
4641
+ readJsonFile
4642
+ });
4643
+ return;
4644
+ }
4645
+ if (command === "validate") {
4646
+ runValidateCommand(args, {
4647
+ argString,
4648
+ argFlag,
4649
+ readInput,
4650
+ parsePositiveIntOrThrow,
4651
+ parseManifestFromFile,
4652
+ readJsonFile
4653
+ });
4654
+ return;
4655
+ }
4656
+ if (command === "test") {
4657
+ runTestCommand(args, {
4658
+ argString,
4659
+ argFlag,
4660
+ readInput,
4661
+ parsePositiveIntOrThrow,
4662
+ parseManifestFromFile,
4663
+ readJsonFile
4664
+ });
4665
+ return;
4666
+ }
4667
+ if (command === "publish") {
4668
+ await runPublish(args);
4669
+ return;
4670
+ }
4671
+ if (command === "logs") {
4672
+ await runLogs(args);
4673
+ return;
4674
+ }
4675
+ if (command === "context") {
4676
+ const subcommand = argString(args, "_subcommand");
4677
+ if (subcommand !== "export") {
4678
+ throw new CliError("CLI_INVALID_ARGS", "Missing required subcommand: context export");
4679
+ }
4680
+ runContextExport(args);
4681
+ return;
4682
+ }
4683
+ if (command === "tunnel") {
4684
+ await runTunnel(args);
4685
+ return;
4686
+ }
4687
+ if (command === "init") {
4688
+ runInitCommand(args, {
4689
+ argString,
4690
+ argFlag,
4691
+ readInput,
4692
+ parsePositiveIntOrThrow,
4693
+ parseManifestFromFile,
4694
+ readJsonFile
4695
+ });
4696
+ return;
4697
+ }
4698
+ if (command === "dev") {
4699
+ await runDevCommand(args, {
4700
+ argString,
4701
+ argFlag,
4702
+ shellEscapeArg: shellEscapeArg2,
4703
+ applyFlowCommandTemplates,
4704
+ findRepoRoot,
4705
+ readJsonFile,
4706
+ parseManifestFromFile,
4707
+ runDryRun,
4708
+ buildDevRefs,
4709
+ canonicalizeJson,
4710
+ readRequestBody,
4711
+ makeCliError: (code, message, details) => new CliError(code, message, details)
4712
+ });
4713
+ return;
4714
+ }
4715
+ if (command === "ai") {
4716
+ const subcommand = argString(args, "_subcommand");
4717
+ if (subcommand === "plan") {
4718
+ await runAiPlanCliCommand(args, {
4719
+ argString,
4720
+ argFlag,
4721
+ shellEscapeArg: shellEscapeArg2,
4722
+ applyFlowCommandTemplates,
4723
+ findRepoRoot,
4724
+ readJsonFile,
4725
+ parseManifestFromFile,
4726
+ runDryRun,
4727
+ buildDevRefs,
4728
+ canonicalizeJson,
4729
+ readRequestBody,
4730
+ makeCliError: (code, message, details) => new CliError(code, message, details)
4731
+ });
4732
+ return;
4733
+ }
4734
+ if (subcommand === "check") {
4735
+ await runAiCheckCliCommand(args, {
4736
+ argString,
4737
+ argFlag,
4738
+ shellEscapeArg: shellEscapeArg2,
4739
+ applyFlowCommandTemplates,
4740
+ findRepoRoot,
4741
+ readJsonFile,
4742
+ parseManifestFromFile,
4743
+ runDryRun,
4744
+ buildDevRefs,
4745
+ canonicalizeJson,
4746
+ readRequestBody,
4747
+ makeCliError: (code, message, details) => new CliError(code, message, details)
4748
+ });
4749
+ return;
4750
+ }
4751
+ throw new CliError("CLI_INVALID_ARGS", "Missing required subcommand: ai plan|check");
4752
+ }
4753
+ if (command === "publisher") {
4754
+ await runPublisherCliCommand(args, {
4755
+ argString,
4756
+ argFlag,
4757
+ shellEscapeArg: shellEscapeArg2,
4758
+ applyFlowCommandTemplates,
4759
+ findRepoRoot,
4760
+ readJsonFile,
4761
+ parseManifestFromFile,
4762
+ runDryRun,
4763
+ buildDevRefs,
4764
+ canonicalizeJson,
4765
+ readRequestBody,
4766
+ makeCliError: (code, message, details) => new CliError(code, message, details)
4767
+ });
4768
+ return;
4769
+ }
4770
+ throw new CliError("CLI_UNKNOWN_COMMAND", `Unknown command: ${command}`);
4771
+ } catch (err) {
4772
+ if (err instanceof CliError) throw err;
4773
+ const message = String(err?.message || err || "Unknown CLI error");
4774
+ let code = "CLI_RUNTIME_ERROR";
4775
+ if (message.includes("Missing required argument")) code = "CLI_INVALID_ARGS";
4776
+ else if (message.includes("Missing required arguments")) code = "CLI_INVALID_ARGS";
4777
+ else if (message.includes("Invalid --")) code = "CLI_INVALID_OPTION";
4778
+ else if (message.includes("Invalid JSON")) code = "CLI_INVALID_JSON";
4779
+ else if (message.includes("Unknown CLI profile")) code = "CLI_PROFILE_NOT_FOUND";
4780
+ else if (message.includes("Invalid CLI config JSON")) code = "CLI_CONFIG_INVALID";
4781
+ else if (message.includes("Unsupported --mode")) code = "CLI_AI_MODE_UNSUPPORTED";
4782
+ else if (message.includes("Invalid AI policy JSON")) code = "CLI_AI_POLICY_INVALID";
4783
+ else if (message.includes("ai check failed")) code = "CLI_AI_PLAN_INVALID";
4784
+ else if (message.includes("unsupported outcome")) code = "CLI_REMOTE_PUBLISH_ACK_INVALID";
4785
+ else if (message.includes("Remote publish")) code = "CLI_REMOTE_PUBLISH_ERROR";
4786
+ else if (message.includes("Remote logs")) code = "CLI_REMOTE_LOGS_ERROR";
4787
+ else if (message.includes("No bundles found")) code = "CLI_BUNDLE_NOT_FOUND";
4788
+ else if (message.includes("Bundle not found")) code = "CLI_BUNDLE_NOT_FOUND";
4789
+ throw new CliError(code, message);
4790
+ }
4791
+ }
4792
+ if (/(?:^|[\\/])cli\.(?:cjs|mjs|js)$/.test(String(process.argv[1] || ""))) {
4793
+ runCli(process.argv.slice(2)).catch((err) => {
4794
+ const code = typeof err?.code === "string" ? err.code : "CLI_RUNTIME_ERROR";
4795
+ const message = err?.message || String(err);
4796
+ console.error(`[${code}] ${message}`);
4797
+ const exitCode = Number.isInteger(err?.exitCode) ? err.exitCode : 1;
4798
+ process.exit(exitCode);
4799
+ });
4800
+ }
4801
+ // Annotate the CommonJS export names for ESM import in node:
4802
+ 0 && (module.exports = {
4803
+ CliError,
4804
+ runCli
4805
+ });
4806
+ //# sourceMappingURL=cli.cjs.map