@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/admin-dist/assets/{index-DIfpc20n.js → index-CsOMxNTw.js} +15 -15
- package/dist/admin-dist/index.html +1 -1
- package/dist/admin-server/index.js +136 -0
- package/dist/core.js +322 -10
- package/dist/index.js +143 -10
- package/package.json +1 -1
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
|
|
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
|
|
17898
|
-
const
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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) {
|