@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.
package/dist/index.js CHANGED
@@ -17381,7 +17381,7 @@ async function runTopologyApply(file, opts = {}) {
17381
17381
 
17382
17382
  // src/services/integrations.ts
17383
17383
  import matter11 from "gray-matter";
17384
- import { parse as parseYaml17 } from "yaml";
17384
+ import { parse as parseYaml17, stringify as stringifyYaml8 } from "yaml";
17385
17385
 
17386
17386
  // src/core/work-item-write.ts
17387
17387
  import fs4 from "fs";
@@ -17636,6 +17636,26 @@ function parseCredentials(raw, id, findings) {
17636
17636
  }
17637
17637
  return creds;
17638
17638
  }
17639
+ function parseSecrets(raw, id, findings) {
17640
+ const secrets = {};
17641
+ if (raw == null) return secrets;
17642
+ if (typeof raw !== "object" || Array.isArray(raw)) {
17643
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secrets must be a mapping of logical references.` });
17644
+ return secrets;
17645
+ }
17646
+ for (const [key, value] of Object.entries(raw)) {
17647
+ if (typeof value === "string" && value.trim()) {
17648
+ if (value.length > 100 || /^(ghp_|sk-|xox[bpsa]-|glpat-|ey[A-Za-z0-9])/i.test(value)) {
17649
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" appears to contain an actual credential, not a reference name.` });
17650
+ continue;
17651
+ }
17652
+ secrets[key] = value.trim();
17653
+ } else {
17654
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring non-string secret entry "${key}".` });
17655
+ }
17656
+ }
17657
+ return secrets;
17658
+ }
17639
17659
  function parseIntegrationsConfig(raw, opts) {
17640
17660
  const findings = [];
17641
17661
  const integrations = [];
@@ -17668,12 +17688,13 @@ function parseIntegrationsConfig(raw, opts) {
17668
17688
  const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
17669
17689
  const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
17670
17690
  const credentials = parseCredentials(o.credentials, id, findings);
17691
+ const secrets = parseSecrets(o.secrets, id, findings);
17671
17692
  const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
17672
- integrations.push({ id, adapter, enabled, config, credentials, timeoutMs });
17693
+ integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
17673
17694
  }
17674
17695
  return { integrations, findings };
17675
17696
  }
17676
- function resolveCredentials(integration, env) {
17697
+ async function resolveAllCredentials(integration, resolver, env) {
17677
17698
  const credentials = {};
17678
17699
  const missing = [];
17679
17700
  for (const [name, ref] of Object.entries(integration.credentials)) {
@@ -17681,6 +17702,12 @@ function resolveCredentials(integration, env) {
17681
17702
  if (value && value.length > 0) credentials[name] = value;
17682
17703
  else missing.push(ref.env);
17683
17704
  }
17705
+ for (const [name, ref] of Object.entries(integration.secrets)) {
17706
+ if (credentials[name]) continue;
17707
+ const value = await resolver.resolve(ref);
17708
+ if (value !== void 0) credentials[name] = value;
17709
+ else missing.push(ref);
17710
+ }
17684
17711
  return { credentials, missing };
17685
17712
  }
17686
17713
 
@@ -17705,6 +17732,82 @@ function buildImportPreview(item, opts) {
17705
17732
  };
17706
17733
  }
17707
17734
 
17735
+ // ../integrations/src/secrets.ts
17736
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
17737
+ import { dirname as dirname2 } from "path";
17738
+ var SECRETS_FILENAME = ".secrets.json";
17739
+ function secretsPath(projectDir) {
17740
+ return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
17741
+ }
17742
+ function loadStore(filePath) {
17743
+ if (!existsSync2(filePath)) return {};
17744
+ try {
17745
+ const raw = readFileSync2(filePath, "utf-8");
17746
+ const parsed = JSON.parse(raw);
17747
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
17748
+ return {};
17749
+ } catch {
17750
+ return {};
17751
+ }
17752
+ }
17753
+ function saveStore(filePath, store) {
17754
+ mkdirSync(dirname2(filePath), { recursive: true });
17755
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
17756
+ }
17757
+ function createLocalSecretProvider(projectDir) {
17758
+ const fp = secretsPath(projectDir);
17759
+ return {
17760
+ async get(key) {
17761
+ const store = loadStore(fp);
17762
+ const val = store[key];
17763
+ return val !== void 0 && val !== "" ? val : void 0;
17764
+ },
17765
+ async set(key, value) {
17766
+ const store = loadStore(fp);
17767
+ store[key] = value;
17768
+ saveStore(fp, store);
17769
+ },
17770
+ async delete(key) {
17771
+ const store = loadStore(fp);
17772
+ delete store[key];
17773
+ saveStore(fp, store);
17774
+ },
17775
+ async exists(key) {
17776
+ const store = loadStore(fp);
17777
+ return key in store && store[key] !== void 0 && store[key] !== "";
17778
+ }
17779
+ };
17780
+ }
17781
+ function createEnvSecretProvider(env = process.env) {
17782
+ return {
17783
+ async get(key) {
17784
+ const val = env[key];
17785
+ return val !== void 0 && val !== "" ? val : void 0;
17786
+ },
17787
+ async set() {
17788
+ throw new Error("Environment secret provider is read-only.");
17789
+ },
17790
+ async delete() {
17791
+ throw new Error("Environment secret provider is read-only.");
17792
+ },
17793
+ async exists(key) {
17794
+ const val = env[key];
17795
+ return val !== void 0 && val !== "";
17796
+ }
17797
+ };
17798
+ }
17799
+ function createCompositeResolver(...providers) {
17800
+ return {
17801
+ async resolve(reference) {
17802
+ for (const provider of providers) {
17803
+ const val = await provider.get(reference);
17804
+ if (val !== void 0) return val;
17805
+ }
17806
+ return void 0;
17807
+ }
17808
+ };
17809
+ }
17810
+
17708
17811
  // ../integrations/src/registry.ts
17709
17812
  var DuplicateAdapterError = class extends Error {
17710
17813
  constructor(id) {
@@ -17789,7 +17892,31 @@ function createMockAdapter(opts = {}) {
17789
17892
  id: MOCK_ADAPTER_ID,
17790
17893
  displayName: "Mock Work Source",
17791
17894
  version: "1.0.0",
17792
- description: "Deterministic offline reference adapter for validating the integration foundation."
17895
+ description: "Deterministic offline reference adapter for validating the integration foundation.",
17896
+ configSchema: {
17897
+ simulate: {
17898
+ type: "select",
17899
+ required: false,
17900
+ label: "Simulation mode",
17901
+ description: "Controls what the mock adapter simulates during verify/read operations.",
17902
+ options: [
17903
+ { value: "available", label: "Available" },
17904
+ { value: "unauthorized", label: "Unauthorized" },
17905
+ { value: "rate-limited", label: "Rate Limited" },
17906
+ { value: "unavailable", label: "Unavailable" },
17907
+ { value: "timeout", label: "Timeout" }
17908
+ ],
17909
+ defaultValue: "available"
17910
+ }
17911
+ },
17912
+ secretSchema: {
17913
+ token: {
17914
+ type: "string",
17915
+ required: false,
17916
+ label: "API Token",
17917
+ description: "Optional token for testing secret handling (not used by the mock adapter)."
17918
+ }
17919
+ }
17793
17920
  },
17794
17921
  capabilities: {
17795
17922
  workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
@@ -17866,6 +17993,9 @@ function configStatus(integration, findings) {
17866
17993
  if (blocking || !registry.has(integration.adapter)) return "invalid-config";
17867
17994
  return "configured";
17868
17995
  }
17996
+ function secretResolver(dir, env) {
17997
+ return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
17998
+ }
17869
17999
  function listIntegrations(dir) {
17870
18000
  const { integrations, findings } = loadIntegrations(dir);
17871
18001
  return integrations.map((integration) => {
@@ -17879,6 +18009,8 @@ function listIntegrations(dir) {
17879
18009
  capabilities: adapter?.capabilities ?? null,
17880
18010
  metadata: adapter?.metadata ?? null,
17881
18011
  credentialRefs: Object.values(integration.credentials).map((c) => c.env),
18012
+ secretRefs: Object.values(integration.secrets),
18013
+ secretStatus: {},
17882
18014
  findings: findings.filter((f) => f.id === integration.id)
17883
18015
  };
17884
18016
  });
@@ -17887,15 +18019,16 @@ function requireIntegration(dir, id) {
17887
18019
  const { integrations, findings } = loadIntegrations(dir);
17888
18020
  const integration = integrations.find((i) => i.id === id);
17889
18021
  if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
17890
- return { integration, findings };
18022
+ return { integration, findings, all: integrations };
17891
18023
  }
17892
18024
  function resolveAdapter(integration) {
17893
18025
  const adapter = registry.get(integration.adapter);
17894
18026
  if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
17895
18027
  return adapter;
17896
18028
  }
17897
- function buildContext(integration, env) {
17898
- const { credentials, missing } = resolveCredentials(integration, env);
18029
+ async function buildContextWithSecrets(dir, integration, env) {
18030
+ const resolver = secretResolver(dir, env);
18031
+ const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
17899
18032
  return {
17900
18033
  context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
17901
18034
  missing
@@ -17918,7 +18051,7 @@ async function verifyIntegration(dir, id, env = process.env) {
17918
18051
  if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
17919
18052
  if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
17920
18053
  const adapter = resolveAdapter(integration);
17921
- const { context, missing } = buildContext(integration, env);
18054
+ const { context, missing } = await buildContextWithSecrets(dir, integration, env);
17922
18055
  try {
17923
18056
  const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
17924
18057
  return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
@@ -17943,14 +18076,14 @@ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
17943
18076
  const { integration } = requireIntegration(dir, id);
17944
18077
  const adapter = resolveAdapter(integration);
17945
18078
  if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
17946
- const { context } = buildContext(integration, env);
18079
+ const { context } = await buildContextWithSecrets(dir, integration, env);
17947
18080
  return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
17948
18081
  }
17949
18082
  async function getExternalWorkItem(dir, id, externalId, env = process.env) {
17950
18083
  const { integration } = requireIntegration(dir, id);
17951
18084
  const adapter = resolveAdapter(integration);
17952
18085
  if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
17953
- const { context } = buildContext(integration, env);
18086
+ const { context } = await buildContextWithSecrets(dir, integration, env);
17954
18087
  return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
17955
18088
  }
17956
18089
  function findLinkedWorkItem(dir, integrationId, externalId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.82.0",
3
+ "version": "3.83.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {