@kaddo/cli 3.82.0 → 3.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>admin</title>
8
- <script type="module" crossorigin src="/assets/index-DIfpc20n.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CsOMxNTw.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-DNkI-1xG.css">
10
10
  </head>
11
11
  <body>
@@ -71,6 +71,16 @@ import {
71
71
  getSystemMapProjection as coreGetSystemMapProjection,
72
72
  buildTopologyEnrichmentHandoff as coreBuildTopologyHandoff,
73
73
  listIntegrations as coreListIntegrations,
74
+ getIntegration as coreGetIntegration,
75
+ getIntegrationSecretStatus as coreGetIntegrationSecretStatus,
76
+ getAvailableIntegrationTypes as coreGetAvailableIntegrationTypes,
77
+ createIntegration as coreCreateIntegration,
78
+ updateIntegration as coreUpdateIntegration,
79
+ deleteIntegration as coreDeleteIntegration,
80
+ enableIntegration as coreEnableIntegration,
81
+ disableIntegration as coreDisableIntegration,
82
+ setIntegrationSecret as coreSetIntegrationSecret,
83
+ removeIntegrationSecret as coreRemoveIntegrationSecret,
74
84
  verifyIntegration as coreVerifyIntegration,
75
85
  listExternalWorkItems as coreListExternalWorkItems,
76
86
  getExternalWorkItem as coreGetExternalWorkItem,
@@ -395,6 +405,72 @@ async function importIntegrationWorkItem(dir, id, externalId, opts) {
395
405
  mapIntegrationError(err);
396
406
  }
397
407
  }
408
+ function getIntegrationDetail(dir, id) {
409
+ try {
410
+ return coreGetIntegration(dir, id);
411
+ } catch (err) {
412
+ mapIntegrationError(err);
413
+ }
414
+ }
415
+ async function getIntegrationSecretStatusAdmin(dir, id) {
416
+ try {
417
+ return await coreGetIntegrationSecretStatus(dir, id);
418
+ } catch (err) {
419
+ mapIntegrationError(err);
420
+ }
421
+ }
422
+ function getAvailableIntegrationTypesAdmin() {
423
+ return coreGetAvailableIntegrationTypes();
424
+ }
425
+ function createIntegrationAdmin(dir, body) {
426
+ try {
427
+ return coreCreateIntegration(dir, body);
428
+ } catch (err) {
429
+ mapIntegrationError(err);
430
+ }
431
+ }
432
+ function updateIntegrationAdmin(dir, id, body) {
433
+ try {
434
+ return coreUpdateIntegration(dir, id, body);
435
+ } catch (err) {
436
+ mapIntegrationError(err);
437
+ }
438
+ }
439
+ function deleteIntegrationAdmin(dir, id) {
440
+ try {
441
+ coreDeleteIntegration(dir, id);
442
+ } catch (err) {
443
+ mapIntegrationError(err);
444
+ }
445
+ }
446
+ function enableIntegrationAdmin(dir, id) {
447
+ try {
448
+ return coreEnableIntegration(dir, id);
449
+ } catch (err) {
450
+ mapIntegrationError(err);
451
+ }
452
+ }
453
+ function disableIntegrationAdmin(dir, id) {
454
+ try {
455
+ return coreDisableIntegration(dir, id);
456
+ } catch (err) {
457
+ mapIntegrationError(err);
458
+ }
459
+ }
460
+ async function setIntegrationSecretAdmin(dir, id, secretName, value) {
461
+ try {
462
+ await coreSetIntegrationSecret(dir, id, secretName, value);
463
+ } catch (err) {
464
+ mapIntegrationError(err);
465
+ }
466
+ }
467
+ async function removeIntegrationSecretAdmin(dir, id, secretName) {
468
+ try {
469
+ await coreRemoveIntegrationSecret(dir, id, secretName);
470
+ } catch (err) {
471
+ mapIntegrationError(err);
472
+ }
473
+ }
398
474
 
399
475
  // src/contracts/schemas.ts
400
476
  import { z } from "zod";
@@ -896,10 +972,70 @@ async function createAdminServer(opts) {
896
972
  }
897
973
  };
898
974
  app.get("/api/v1/admin/integrations", coreRoute(getIntegrations));
975
+ app.get("/api/v1/admin/integrations/types", coreRoute(() => getAvailableIntegrationTypesAdmin()));
976
+ app.get("/api/v1/admin/integrations/:id", async (request, reply) => {
977
+ try {
978
+ return getIntegrationDetail(projectDir, request.params.id);
979
+ } catch (err) {
980
+ if (err instanceof CoreError) return reply.code(statusForCode(err.code)).send({ error: { code: err.code, message: err.message } });
981
+ throw err;
982
+ }
983
+ });
984
+ app.get(
985
+ "/api/v1/admin/integrations/:id/secrets",
986
+ async (request, reply) => asyncCore(reply, () => getIntegrationSecretStatusAdmin(projectDir, request.params.id))
987
+ );
899
988
  app.get(
900
989
  "/api/v1/admin/integrations/:id/status",
901
990
  async (request, reply) => asyncCore(reply, () => getIntegrationStatus(projectDir, request.params.id))
902
991
  );
992
+ app.post(
993
+ "/api/v1/admin/integrations",
994
+ async (request, reply) => {
995
+ const { id, adapter, enabled, config, secrets } = request.body ?? {};
996
+ if (!id || !adapter) return reply.code(400).send({ error: { code: "INVALID_INPUT", message: "id and adapter are required." } });
997
+ return writeHandler(reply, () => createIntegrationAdmin(projectDir, { id, adapter, enabled, config, secrets }));
998
+ }
999
+ );
1000
+ app.put(
1001
+ "/api/v1/admin/integrations/:id",
1002
+ async (request, reply) => writeHandler(reply, () => updateIntegrationAdmin(projectDir, request.params.id, request.body ?? {}))
1003
+ );
1004
+ app.delete("/api/v1/admin/integrations/:id", async (request, reply) => {
1005
+ try {
1006
+ deleteIntegrationAdmin(projectDir, request.params.id);
1007
+ return { ok: true };
1008
+ } catch (err) {
1009
+ if (err instanceof CoreError) return reply.code(statusForCode(err.code)).send({ error: { code: err.code, message: err.message } });
1010
+ throw err;
1011
+ }
1012
+ });
1013
+ app.post(
1014
+ "/api/v1/admin/integrations/:id/enable",
1015
+ async (request, reply) => writeHandler(reply, () => enableIntegrationAdmin(projectDir, request.params.id))
1016
+ );
1017
+ app.post(
1018
+ "/api/v1/admin/integrations/:id/disable",
1019
+ async (request, reply) => writeHandler(reply, () => disableIntegrationAdmin(projectDir, request.params.id))
1020
+ );
1021
+ app.post(
1022
+ "/api/v1/admin/integrations/:id/secrets/:name",
1023
+ async (request, reply) => {
1024
+ const { value } = request.body ?? {};
1025
+ if (!value || typeof value !== "string") return reply.code(400).send({ error: { code: "INVALID_INPUT", message: "A secret value is required." } });
1026
+ return asyncCore(reply, async () => {
1027
+ await setIntegrationSecretAdmin(projectDir, request.params.id, request.params.name, value);
1028
+ return { ok: true };
1029
+ });
1030
+ }
1031
+ );
1032
+ app.delete(
1033
+ "/api/v1/admin/integrations/:id/secrets/:name",
1034
+ async (request, reply) => asyncCore(reply, async () => {
1035
+ await removeIntegrationSecretAdmin(projectDir, request.params.id, request.params.name);
1036
+ return { ok: true };
1037
+ })
1038
+ );
903
1039
  app.get(
904
1040
  "/api/v1/admin/integrations/:id/work-items",
905
1041
  async (request, reply) => asyncCore(reply, () => getExternalWorkItems(projectDir, request.params.id, {
package/dist/core.js CHANGED
@@ -7,6 +7,10 @@ function exists(p2) {
7
7
  function readFile(p2) {
8
8
  return fs.readFileSync(p2, "utf-8");
9
9
  }
10
+ function writeFile(filePath, content) {
11
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
12
+ fs.writeFileSync(filePath, content, "utf-8");
13
+ }
10
14
  function readDir(dirPath) {
11
15
  if (!exists(dirPath)) return [];
12
16
  return fs.readdirSync(dirPath);
@@ -9371,7 +9375,7 @@ function buildRefinementHandoff(dir, workItemId) {
9371
9375
 
9372
9376
  // src/services/integrations.ts
9373
9377
  import matter8 from "gray-matter";
9374
- import { parse as parseYaml9 } from "yaml";
9378
+ import { parse as parseYaml9, stringify as stringifyYaml5 } from "yaml";
9375
9379
 
9376
9380
  // ../integrations/src/errors.ts
9377
9381
  var RETRYABLE = /* @__PURE__ */ new Set([
@@ -9456,6 +9460,26 @@ function parseCredentials(raw, id, findings) {
9456
9460
  }
9457
9461
  return creds;
9458
9462
  }
9463
+ function parseSecrets(raw, id, findings) {
9464
+ const secrets = {};
9465
+ if (raw == null) return secrets;
9466
+ if (typeof raw !== "object" || Array.isArray(raw)) {
9467
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secrets must be a mapping of logical references.` });
9468
+ return secrets;
9469
+ }
9470
+ for (const [key, value] of Object.entries(raw)) {
9471
+ if (typeof value === "string" && value.trim()) {
9472
+ if (value.length > 100 || /^(ghp_|sk-|xox[bpsa]-|glpat-|ey[A-Za-z0-9])/i.test(value)) {
9473
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" appears to contain an actual credential, not a reference name.` });
9474
+ continue;
9475
+ }
9476
+ secrets[key] = value.trim();
9477
+ } else {
9478
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring non-string secret entry "${key}".` });
9479
+ }
9480
+ }
9481
+ return secrets;
9482
+ }
9459
9483
  function parseIntegrationsConfig(raw, opts) {
9460
9484
  const findings = [];
9461
9485
  const integrations = [];
@@ -9488,12 +9512,13 @@ function parseIntegrationsConfig(raw, opts) {
9488
9512
  const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
9489
9513
  const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
9490
9514
  const credentials = parseCredentials(o.credentials, id, findings);
9515
+ const secrets = parseSecrets(o.secrets, id, findings);
9491
9516
  const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
9492
- integrations.push({ id, adapter, enabled, config, credentials, timeoutMs });
9517
+ integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
9493
9518
  }
9494
9519
  return { integrations, findings };
9495
9520
  }
9496
- function resolveCredentials(integration, env) {
9521
+ async function resolveAllCredentials(integration, resolver, env) {
9497
9522
  const credentials = {};
9498
9523
  const missing = [];
9499
9524
  for (const [name, ref] of Object.entries(integration.credentials)) {
@@ -9501,8 +9526,47 @@ function resolveCredentials(integration, env) {
9501
9526
  if (value && value.length > 0) credentials[name] = value;
9502
9527
  else missing.push(ref.env);
9503
9528
  }
9529
+ for (const [name, ref] of Object.entries(integration.secrets)) {
9530
+ if (credentials[name]) continue;
9531
+ const value = await resolver.resolve(ref);
9532
+ if (value !== void 0) credentials[name] = value;
9533
+ else missing.push(ref);
9534
+ }
9504
9535
  return { credentials, missing };
9505
9536
  }
9537
+ function serializeIntegrationsConfig(integrations) {
9538
+ return {
9539
+ integrations: integrations.map((i) => {
9540
+ const entry = {
9541
+ id: i.id,
9542
+ adapter: i.adapter,
9543
+ enabled: i.enabled
9544
+ };
9545
+ if (Object.keys(i.config).length > 0) entry.config = i.config;
9546
+ if (Object.keys(i.credentials).length > 0) {
9547
+ const creds = {};
9548
+ for (const [name, ref] of Object.entries(i.credentials)) {
9549
+ creds[`${name}${ENV_SUFFIX}`] = ref.env;
9550
+ }
9551
+ entry.credentials = creds;
9552
+ }
9553
+ if (Object.keys(i.secrets).length > 0) entry.secrets = i.secrets;
9554
+ if (i.timeoutMs !== void 0) entry.timeout_ms = i.timeoutMs;
9555
+ return entry;
9556
+ })
9557
+ };
9558
+ }
9559
+ function integrationConfigFromInput(input) {
9560
+ return {
9561
+ id: input.id,
9562
+ adapter: input.adapter,
9563
+ enabled: input.enabled ?? true,
9564
+ config: input.config ?? {},
9565
+ credentials: {},
9566
+ secrets: input.secrets ?? {},
9567
+ timeoutMs: input.timeoutMs
9568
+ };
9569
+ }
9506
9570
 
9507
9571
  // ../integrations/src/preview.ts
9508
9572
  function buildImportPreview(item, opts) {
@@ -9525,6 +9589,85 @@ function buildImportPreview(item, opts) {
9525
9589
  };
9526
9590
  }
9527
9591
 
9592
+ // ../integrations/src/secrets.ts
9593
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
9594
+ import { dirname as dirname2 } from "path";
9595
+ function secretRefKey(integrationId, secretName) {
9596
+ return `${integrationId}.${secretName}`;
9597
+ }
9598
+ var SECRETS_FILENAME = ".secrets.json";
9599
+ function secretsPath(projectDir) {
9600
+ return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
9601
+ }
9602
+ function loadStore(filePath) {
9603
+ if (!existsSync2(filePath)) return {};
9604
+ try {
9605
+ const raw = readFileSync2(filePath, "utf-8");
9606
+ const parsed = JSON.parse(raw);
9607
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
9608
+ return {};
9609
+ } catch {
9610
+ return {};
9611
+ }
9612
+ }
9613
+ function saveStore(filePath, store) {
9614
+ mkdirSync(dirname2(filePath), { recursive: true });
9615
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
9616
+ }
9617
+ function createLocalSecretProvider(projectDir) {
9618
+ const fp = secretsPath(projectDir);
9619
+ return {
9620
+ async get(key) {
9621
+ const store = loadStore(fp);
9622
+ const val = store[key];
9623
+ return val !== void 0 && val !== "" ? val : void 0;
9624
+ },
9625
+ async set(key, value) {
9626
+ const store = loadStore(fp);
9627
+ store[key] = value;
9628
+ saveStore(fp, store);
9629
+ },
9630
+ async delete(key) {
9631
+ const store = loadStore(fp);
9632
+ delete store[key];
9633
+ saveStore(fp, store);
9634
+ },
9635
+ async exists(key) {
9636
+ const store = loadStore(fp);
9637
+ return key in store && store[key] !== void 0 && store[key] !== "";
9638
+ }
9639
+ };
9640
+ }
9641
+ function createEnvSecretProvider(env = process.env) {
9642
+ return {
9643
+ async get(key) {
9644
+ const val = env[key];
9645
+ return val !== void 0 && val !== "" ? val : void 0;
9646
+ },
9647
+ async set() {
9648
+ throw new Error("Environment secret provider is read-only.");
9649
+ },
9650
+ async delete() {
9651
+ throw new Error("Environment secret provider is read-only.");
9652
+ },
9653
+ async exists(key) {
9654
+ const val = env[key];
9655
+ return val !== void 0 && val !== "";
9656
+ }
9657
+ };
9658
+ }
9659
+ function createCompositeResolver(...providers) {
9660
+ return {
9661
+ async resolve(reference) {
9662
+ for (const provider of providers) {
9663
+ const val = await provider.get(reference);
9664
+ if (val !== void 0) return val;
9665
+ }
9666
+ return void 0;
9667
+ }
9668
+ };
9669
+ }
9670
+
9528
9671
  // ../integrations/src/registry.ts
9529
9672
  var DuplicateAdapterError = class extends Error {
9530
9673
  constructor(id) {
@@ -9609,7 +9752,31 @@ function createMockAdapter(opts = {}) {
9609
9752
  id: MOCK_ADAPTER_ID,
9610
9753
  displayName: "Mock Work Source",
9611
9754
  version: "1.0.0",
9612
- description: "Deterministic offline reference adapter for validating the integration foundation."
9755
+ description: "Deterministic offline reference adapter for validating the integration foundation.",
9756
+ configSchema: {
9757
+ simulate: {
9758
+ type: "select",
9759
+ required: false,
9760
+ label: "Simulation mode",
9761
+ description: "Controls what the mock adapter simulates during verify/read operations.",
9762
+ options: [
9763
+ { value: "available", label: "Available" },
9764
+ { value: "unauthorized", label: "Unauthorized" },
9765
+ { value: "rate-limited", label: "Rate Limited" },
9766
+ { value: "unavailable", label: "Unavailable" },
9767
+ { value: "timeout", label: "Timeout" }
9768
+ ],
9769
+ defaultValue: "available"
9770
+ }
9771
+ },
9772
+ secretSchema: {
9773
+ token: {
9774
+ type: "string",
9775
+ required: false,
9776
+ label: "API Token",
9777
+ description: "Optional token for testing secret handling (not used by the mock adapter)."
9778
+ }
9779
+ }
9613
9780
  },
9614
9781
  capabilities: {
9615
9782
  workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
@@ -9683,12 +9850,23 @@ function loadRaw(dir) {
9683
9850
  function loadIntegrations(dir) {
9684
9851
  return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
9685
9852
  }
9853
+ function saveIntegrations(dir, integrations) {
9854
+ const abs = join(dir, INTEGRATIONS_FILE);
9855
+ const data = serializeIntegrationsConfig(integrations);
9856
+ writeFile(abs, stringifyYaml5(data, { lineWidth: 120 }));
9857
+ }
9686
9858
  function configStatus(integration, findings) {
9687
9859
  if (!integration.enabled) return "disabled";
9688
9860
  const blocking = findings.some((f) => f.id === integration.id && f.level === "blocking");
9689
9861
  if (blocking || !registry.has(integration.adapter)) return "invalid-config";
9690
9862
  return "configured";
9691
9863
  }
9864
+ function secretProvider(dir) {
9865
+ return createLocalSecretProvider(dir);
9866
+ }
9867
+ function secretResolver(dir, env) {
9868
+ return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
9869
+ }
9692
9870
  function listIntegrations(dir) {
9693
9871
  const { integrations, findings } = loadIntegrations(dir);
9694
9872
  return integrations.map((integration) => {
@@ -9702,23 +9880,70 @@ function listIntegrations(dir) {
9702
9880
  capabilities: adapter?.capabilities ?? null,
9703
9881
  metadata: adapter?.metadata ?? null,
9704
9882
  credentialRefs: Object.values(integration.credentials).map((c) => c.env),
9883
+ secretRefs: Object.values(integration.secrets),
9884
+ secretStatus: {},
9705
9885
  findings: findings.filter((f) => f.id === integration.id)
9706
9886
  };
9707
9887
  });
9708
9888
  }
9889
+ function getIntegration(dir, id) {
9890
+ const { integrations, findings } = loadIntegrations(dir);
9891
+ const integration = integrations.find((i) => i.id === id);
9892
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
9893
+ const adapter = registry.get(integration.adapter);
9894
+ return {
9895
+ id: integration.id,
9896
+ adapter: integration.adapter,
9897
+ enabled: integration.enabled,
9898
+ status: configStatus(integration, findings),
9899
+ displayName: adapter?.metadata.displayName ?? integration.adapter,
9900
+ capabilities: adapter?.capabilities ?? null,
9901
+ metadata: adapter?.metadata ?? null,
9902
+ credentialRefs: Object.values(integration.credentials).map((c) => c.env),
9903
+ secretRefs: Object.values(integration.secrets),
9904
+ secretStatus: {},
9905
+ findings: findings.filter((f) => f.id === integration.id)
9906
+ };
9907
+ }
9908
+ async function getIntegrationSecretStatus(dir, id) {
9909
+ const { integrations } = loadIntegrations(dir);
9910
+ const integration = integrations.find((i) => i.id === id);
9911
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured.`);
9912
+ const sp = secretProvider(dir);
9913
+ const status = {};
9914
+ for (const [name, ref] of Object.entries(integration.secrets)) {
9915
+ status[name] = await sp.exists(ref);
9916
+ }
9917
+ for (const [name, ref] of Object.entries(integration.credentials)) {
9918
+ const val = process.env[ref.env];
9919
+ status[name] = val !== void 0 && val !== "";
9920
+ }
9921
+ return status;
9922
+ }
9923
+ function getAvailableIntegrationTypes() {
9924
+ return registry.list().map((a) => ({
9925
+ id: a.id,
9926
+ displayName: a.metadata.displayName,
9927
+ description: a.metadata.description,
9928
+ configSchema: a.metadata.configSchema ?? {},
9929
+ secretSchema: a.metadata.secretSchema ?? {},
9930
+ capabilities: a.capabilities
9931
+ }));
9932
+ }
9709
9933
  function requireIntegration(dir, id) {
9710
9934
  const { integrations, findings } = loadIntegrations(dir);
9711
9935
  const integration = integrations.find((i) => i.id === id);
9712
9936
  if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
9713
- return { integration, findings };
9937
+ return { integration, findings, all: integrations };
9714
9938
  }
9715
9939
  function resolveAdapter(integration) {
9716
9940
  const adapter = registry.get(integration.adapter);
9717
9941
  if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
9718
9942
  return adapter;
9719
9943
  }
9720
- function buildContext(integration, env) {
9721
- const { credentials, missing } = resolveCredentials(integration, env);
9944
+ async function buildContextWithSecrets(dir, integration, env) {
9945
+ const resolver = secretResolver(dir, env);
9946
+ const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
9722
9947
  return {
9723
9948
  context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
9724
9949
  missing
@@ -9735,13 +9960,90 @@ async function withTimeout(op, ms) {
9735
9960
  if (timer) clearTimeout(timer);
9736
9961
  }
9737
9962
  }
9963
+ function validateId(id) {
9964
+ if (!id || !id.trim()) throw new IntegrationServiceError("INTEGRATION_INVALID_INPUT", "Integration id is required.");
9965
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(id)) throw new IntegrationServiceError("INTEGRATION_INVALID_INPUT", "Integration id must be alphanumeric with dashes, dots or underscores.");
9966
+ if (id.length > 64) throw new IntegrationServiceError("INTEGRATION_INVALID_INPUT", "Integration id must be 64 characters or fewer.");
9967
+ }
9968
+ function createIntegration(dir, input) {
9969
+ validateId(input.id);
9970
+ if (!input.adapter || !input.adapter.trim()) throw new IntegrationServiceError("INTEGRATION_INVALID_INPUT", "An adapter type is required.");
9971
+ const { integrations } = loadIntegrations(dir);
9972
+ if (integrations.find((i) => i.id === input.id)) {
9973
+ throw new IntegrationServiceError("INTEGRATION_ALREADY_EXISTS", `An integration with id "${input.id}" already exists.`);
9974
+ }
9975
+ const newConfig = integrationConfigFromInput(input);
9976
+ integrations.push(newConfig);
9977
+ saveIntegrations(dir, integrations);
9978
+ return getIntegration(dir, input.id);
9979
+ }
9980
+ function updateIntegration(dir, id, input) {
9981
+ const { integrations } = loadIntegrations(dir);
9982
+ const idx = integrations.findIndex((i) => i.id === id);
9983
+ if (idx < 0) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured.`);
9984
+ const existing = integrations[idx];
9985
+ if (input.enabled !== void 0) existing.enabled = input.enabled;
9986
+ if (input.config !== void 0) existing.config = input.config;
9987
+ if (input.secrets !== void 0) existing.secrets = input.secrets;
9988
+ if (input.timeoutMs !== void 0) existing.timeoutMs = input.timeoutMs;
9989
+ integrations[idx] = existing;
9990
+ saveIntegrations(dir, integrations);
9991
+ return getIntegration(dir, id);
9992
+ }
9993
+ function deleteIntegration(dir, id) {
9994
+ const { integrations } = loadIntegrations(dir);
9995
+ const idx = integrations.findIndex((i) => i.id === id);
9996
+ if (idx < 0) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured.`);
9997
+ const removed = integrations[idx];
9998
+ integrations.splice(idx, 1);
9999
+ saveIntegrations(dir, integrations);
10000
+ const sp = secretProvider(dir);
10001
+ for (const ref of Object.values(removed.secrets)) {
10002
+ sp.delete(ref).catch(() => {
10003
+ });
10004
+ }
10005
+ }
10006
+ function enableIntegration(dir, id) {
10007
+ return updateIntegration(dir, id, { enabled: true });
10008
+ }
10009
+ function disableIntegration(dir, id) {
10010
+ return updateIntegration(dir, id, { enabled: false });
10011
+ }
10012
+ async function setIntegrationSecret(dir, id, secretName, value) {
10013
+ const { integration } = requireIntegration(dir, id);
10014
+ const refKey = secretRefKey(id, secretName);
10015
+ const sp = secretProvider(dir);
10016
+ await sp.set(refKey, value);
10017
+ if (!integration.secrets[secretName] || integration.secrets[secretName] !== refKey) {
10018
+ const { integrations } = loadIntegrations(dir);
10019
+ const idx = integrations.findIndex((i) => i.id === id);
10020
+ if (idx >= 0) {
10021
+ integrations[idx].secrets[secretName] = refKey;
10022
+ saveIntegrations(dir, integrations);
10023
+ }
10024
+ }
10025
+ }
10026
+ async function removeIntegrationSecret(dir, id, secretName) {
10027
+ const { integration } = requireIntegration(dir, id);
10028
+ const refKey = integration.secrets[secretName];
10029
+ if (refKey) {
10030
+ const sp = secretProvider(dir);
10031
+ await sp.delete(refKey);
10032
+ }
10033
+ const { integrations } = loadIntegrations(dir);
10034
+ const idx = integrations.findIndex((i) => i.id === id);
10035
+ if (idx >= 0 && integrations[idx].secrets[secretName]) {
10036
+ delete integrations[idx].secrets[secretName];
10037
+ saveIntegrations(dir, integrations);
10038
+ }
10039
+ }
9738
10040
  async function verifyIntegration(dir, id, env = process.env) {
9739
10041
  const { integration, findings } = requireIntegration(dir, id);
9740
10042
  const cfgStatus = configStatus(integration, findings);
9741
10043
  if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
9742
10044
  if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
9743
10045
  const adapter = resolveAdapter(integration);
9744
- const { context, missing } = buildContext(integration, env);
10046
+ const { context, missing } = await buildContextWithSecrets(dir, integration, env);
9745
10047
  try {
9746
10048
  const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
9747
10049
  return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
@@ -9766,14 +10068,14 @@ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
9766
10068
  const { integration } = requireIntegration(dir, id);
9767
10069
  const adapter = resolveAdapter(integration);
9768
10070
  if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
9769
- const { context } = buildContext(integration, env);
10071
+ const { context } = await buildContextWithSecrets(dir, integration, env);
9770
10072
  return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
9771
10073
  }
9772
10074
  async function getExternalWorkItem(dir, id, externalId, env = process.env) {
9773
10075
  const { integration } = requireIntegration(dir, id);
9774
10076
  const adapter = resolveAdapter(integration);
9775
10077
  if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
9776
- const { context } = buildContext(integration, env);
10078
+ const { context } = await buildContextWithSecrets(dir, integration, env);
9777
10079
  return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
9778
10080
  }
9779
10081
  function findLinkedWorkItem(dir, integrationId, externalId) {
@@ -9837,15 +10139,22 @@ export {
9837
10139
  buildRefinementHandoff,
9838
10140
  buildTopologyEnrichmentHandoff,
9839
10141
  computeRefinementStatus,
10142
+ createIntegration,
9840
10143
  createWorkItem,
9841
10144
  cwd,
10145
+ deleteIntegration,
10146
+ disableIntegration,
9842
10147
  discoverKnowledge,
9843
10148
  discoverWorkItems,
10149
+ enableIntegration,
9844
10150
  exists,
9845
10151
  findLinkedWorkItem,
9846
10152
  findSystemPaths,
10153
+ getAvailableIntegrationTypes,
9847
10154
  getExternalWorkItem,
9848
10155
  getImpactCandidates,
10156
+ getIntegration,
10157
+ getIntegrationSecretStatus,
9849
10158
  getSystemMapProjection,
9850
10159
  getSystemNeighbors,
9851
10160
  getSystemNodeContext,
@@ -9869,9 +10178,12 @@ export {
9869
10178
  loadSystemTopology,
9870
10179
  previewImport,
9871
10180
  readFile,
10181
+ removeIntegrationSecret,
9872
10182
  searchSystemNodes,
10183
+ setIntegrationSecret,
9873
10184
  topologyRevision,
9874
10185
  transitionWorkItem,
10186
+ updateIntegration,
9875
10187
  updateWorkItem,
9876
10188
  validateSystemTopology,
9877
10189
  validateTopologyProposal,