@kaddo/cli 3.81.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-CsOMxNTw.js +44 -0
- package/dist/admin-dist/index.html +1 -1
- package/dist/admin-server/index.js +234 -0
- package/dist/core.js +779 -2
- package/dist/index.js +972 -57
- package/package.json +1 -1
- package/dist/admin-dist/assets/index-xgxqXGxn.js +0 -44
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);
|
|
@@ -6583,6 +6587,7 @@ function parseWorkItemSource(frontmatter) {
|
|
|
6583
6587
|
title: optStr(obj.title) ?? optStr(frontmatter.source_title),
|
|
6584
6588
|
context: optStr(obj.context) ?? optStr(frontmatter.source_context),
|
|
6585
6589
|
provider: optStr(obj.provider) ?? optStr(frontmatter.source_provider),
|
|
6590
|
+
integration: optStr(obj.integration) ?? optStr(frontmatter.source_integration),
|
|
6586
6591
|
url: optStr(obj.url) ?? optStr(frontmatter.source_url),
|
|
6587
6592
|
imported_at: optStr(obj.imported_at) ?? optStr(frontmatter.source_imported_at),
|
|
6588
6593
|
synced_at: optStr(obj.synced_at) ?? optStr(frontmatter.source_synced_at),
|
|
@@ -8834,6 +8839,7 @@ function createWorkItem(dir, opts) {
|
|
|
8834
8839
|
const id = nextWorkItemId(dir);
|
|
8835
8840
|
const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
|
|
8836
8841
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8842
|
+
const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
|
|
8837
8843
|
const data = {
|
|
8838
8844
|
type,
|
|
8839
8845
|
id,
|
|
@@ -8841,7 +8847,7 @@ function createWorkItem(dir, opts) {
|
|
|
8841
8847
|
status: "draft",
|
|
8842
8848
|
work_type: type,
|
|
8843
8849
|
created_at: today,
|
|
8844
|
-
source
|
|
8850
|
+
source,
|
|
8845
8851
|
generated_by: "kaddo-admin",
|
|
8846
8852
|
affected_modules: [],
|
|
8847
8853
|
summary: intent
|
|
@@ -9366,7 +9372,760 @@ function buildRefinementHandoff(dir, workItemId) {
|
|
|
9366
9372
|
text: lines.join("\n")
|
|
9367
9373
|
};
|
|
9368
9374
|
}
|
|
9375
|
+
|
|
9376
|
+
// src/services/integrations.ts
|
|
9377
|
+
import matter8 from "gray-matter";
|
|
9378
|
+
import { parse as parseYaml9, stringify as stringifyYaml5 } from "yaml";
|
|
9379
|
+
|
|
9380
|
+
// ../integrations/src/errors.ts
|
|
9381
|
+
var RETRYABLE = /* @__PURE__ */ new Set([
|
|
9382
|
+
"INTEGRATION_RATE_LIMITED",
|
|
9383
|
+
"INTEGRATION_UNAVAILABLE",
|
|
9384
|
+
"INTEGRATION_TIMEOUT"
|
|
9385
|
+
]);
|
|
9386
|
+
var IntegrationError = class extends Error {
|
|
9387
|
+
code;
|
|
9388
|
+
/** True for transient conditions (rate limit / unavailable / timeout). */
|
|
9389
|
+
retryable;
|
|
9390
|
+
/** A safe, provider-agnostic reason. Never the raw SDK message. */
|
|
9391
|
+
safeMessage;
|
|
9392
|
+
constructor(code, message) {
|
|
9393
|
+
super(message);
|
|
9394
|
+
this.name = "IntegrationError";
|
|
9395
|
+
this.code = code;
|
|
9396
|
+
this.retryable = RETRYABLE.has(code);
|
|
9397
|
+
this.safeMessage = message;
|
|
9398
|
+
}
|
|
9399
|
+
};
|
|
9400
|
+
function defaultMessageFor(code) {
|
|
9401
|
+
switch (code) {
|
|
9402
|
+
case "INTEGRATION_CONFIG_INVALID":
|
|
9403
|
+
return "The integration configuration is invalid.";
|
|
9404
|
+
case "INTEGRATION_UNAUTHORIZED":
|
|
9405
|
+
return "Authentication failed. Check the configured credentials.";
|
|
9406
|
+
case "INTEGRATION_FORBIDDEN":
|
|
9407
|
+
return "The configured credentials lack permission for this operation.";
|
|
9408
|
+
case "INTEGRATION_NOT_FOUND":
|
|
9409
|
+
return "The requested external resource was not found.";
|
|
9410
|
+
case "INTEGRATION_RATE_LIMITED":
|
|
9411
|
+
return "The external provider is rate limiting requests. Try again later.";
|
|
9412
|
+
case "INTEGRATION_UNAVAILABLE":
|
|
9413
|
+
return "The external provider is temporarily unavailable.";
|
|
9414
|
+
case "INTEGRATION_TIMEOUT":
|
|
9415
|
+
return "The external provider did not respond in time.";
|
|
9416
|
+
case "INTEGRATION_PROVIDER_ERROR":
|
|
9417
|
+
return "The external provider returned an error.";
|
|
9418
|
+
case "UNSUPPORTED_CAPABILITY":
|
|
9419
|
+
return "This adapter does not support the requested capability.";
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
function integrationError(code, message) {
|
|
9423
|
+
return new IntegrationError(code, message ?? defaultMessageFor(code));
|
|
9424
|
+
}
|
|
9425
|
+
|
|
9426
|
+
// ../integrations/src/identity.ts
|
|
9427
|
+
function externalIdentityKey(integrationId, externalId) {
|
|
9428
|
+
return `${integrationId}#${externalId}`;
|
|
9429
|
+
}
|
|
9430
|
+
function externalDisplayKey(provider, externalId) {
|
|
9431
|
+
return `${provider}#${externalId}`;
|
|
9432
|
+
}
|
|
9433
|
+
|
|
9434
|
+
// ../integrations/src/config.ts
|
|
9435
|
+
var ENV_SUFFIX = "_env";
|
|
9436
|
+
var SECRET_KEY = /(token|secret|password|key|pat|apikey|api_key)/i;
|
|
9437
|
+
function parseCredentials(raw, id, findings) {
|
|
9438
|
+
const creds = {};
|
|
9439
|
+
if (raw == null) return creds;
|
|
9440
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
9441
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}": credentials must be a mapping of secret references.` });
|
|
9442
|
+
return creds;
|
|
9443
|
+
}
|
|
9444
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
9445
|
+
if (key.endsWith(ENV_SUFFIX)) {
|
|
9446
|
+
const name = key.slice(0, -ENV_SUFFIX.length);
|
|
9447
|
+
if (typeof value === "string" && value.trim()) creds[name] = { env: value.trim() };
|
|
9448
|
+
else findings.push({ level: "blocking", id, message: `Integration "${id}": credential "${key}" must name an environment variable.` });
|
|
9449
|
+
continue;
|
|
9450
|
+
}
|
|
9451
|
+
if (SECRET_KEY.test(key) && typeof value === "string") {
|
|
9452
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" must not be stored in config. Use "${key}${ENV_SUFFIX}: <ENV_VAR_NAME>".` });
|
|
9453
|
+
continue;
|
|
9454
|
+
}
|
|
9455
|
+
if (value && typeof value === "object" && typeof value.env === "string") {
|
|
9456
|
+
creds[key] = { env: String(value.env) };
|
|
9457
|
+
continue;
|
|
9458
|
+
}
|
|
9459
|
+
findings.push({ level: "warning", id, message: `Integration "${id}": ignoring unrecognized credential entry "${key}".` });
|
|
9460
|
+
}
|
|
9461
|
+
return creds;
|
|
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
|
+
}
|
|
9483
|
+
function parseIntegrationsConfig(raw, opts) {
|
|
9484
|
+
const findings = [];
|
|
9485
|
+
const integrations = [];
|
|
9486
|
+
const list2 = raw && typeof raw === "object" && Array.isArray(raw.integrations) ? raw.integrations : Array.isArray(raw) ? raw : [];
|
|
9487
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9488
|
+
for (const entry of list2) {
|
|
9489
|
+
if (!entry || typeof entry !== "object") {
|
|
9490
|
+
findings.push({ level: "blocking", message: "Each integration must be a mapping." });
|
|
9491
|
+
continue;
|
|
9492
|
+
}
|
|
9493
|
+
const o = entry;
|
|
9494
|
+
const id = typeof o.id === "string" ? o.id.trim() : "";
|
|
9495
|
+
const adapter = typeof o.adapter === "string" ? o.adapter.trim() : "";
|
|
9496
|
+
if (!id) {
|
|
9497
|
+
findings.push({ level: "blocking", message: 'An integration is missing a required "id".' });
|
|
9498
|
+
continue;
|
|
9499
|
+
}
|
|
9500
|
+
if (seen.has(id)) {
|
|
9501
|
+
findings.push({ level: "blocking", id, message: `Duplicate integration id "${id}".` });
|
|
9502
|
+
continue;
|
|
9503
|
+
}
|
|
9504
|
+
seen.add(id);
|
|
9505
|
+
if (!adapter) {
|
|
9506
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}" is missing a required "adapter".` });
|
|
9507
|
+
continue;
|
|
9508
|
+
}
|
|
9509
|
+
if (!opts.adapterIds.has(adapter)) {
|
|
9510
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}" references unknown adapter "${adapter}".` });
|
|
9511
|
+
}
|
|
9512
|
+
const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
|
|
9513
|
+
const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
|
|
9514
|
+
const credentials = parseCredentials(o.credentials, id, findings);
|
|
9515
|
+
const secrets = parseSecrets(o.secrets, id, findings);
|
|
9516
|
+
const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
|
|
9517
|
+
integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
|
|
9518
|
+
}
|
|
9519
|
+
return { integrations, findings };
|
|
9520
|
+
}
|
|
9521
|
+
async function resolveAllCredentials(integration, resolver, env) {
|
|
9522
|
+
const credentials = {};
|
|
9523
|
+
const missing = [];
|
|
9524
|
+
for (const [name, ref] of Object.entries(integration.credentials)) {
|
|
9525
|
+
const value = env[ref.env];
|
|
9526
|
+
if (value && value.length > 0) credentials[name] = value;
|
|
9527
|
+
else missing.push(ref.env);
|
|
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
|
+
}
|
|
9535
|
+
return { credentials, missing };
|
|
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
|
+
}
|
|
9570
|
+
|
|
9571
|
+
// ../integrations/src/preview.ts
|
|
9572
|
+
function buildImportPreview(item, opts) {
|
|
9573
|
+
return {
|
|
9574
|
+
source: {
|
|
9575
|
+
provider: item.provider,
|
|
9576
|
+
integration: opts.integrationId,
|
|
9577
|
+
externalId: item.externalId,
|
|
9578
|
+
url: item.url,
|
|
9579
|
+
identityKey: externalIdentityKey(opts.integrationId, item.externalId),
|
|
9580
|
+
displayKey: externalDisplayKey(item.provider, item.externalId)
|
|
9581
|
+
},
|
|
9582
|
+
capturedIntent: item.title,
|
|
9583
|
+
description: item.description,
|
|
9584
|
+
externalType: item.type,
|
|
9585
|
+
externalStatus: item.status,
|
|
9586
|
+
kaddoStatus: "draft",
|
|
9587
|
+
kaddoType: opts.type ? opts.type : null,
|
|
9588
|
+
writes: false
|
|
9589
|
+
};
|
|
9590
|
+
}
|
|
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
|
+
|
|
9671
|
+
// ../integrations/src/registry.ts
|
|
9672
|
+
var DuplicateAdapterError = class extends Error {
|
|
9673
|
+
constructor(id) {
|
|
9674
|
+
super(`An integration adapter with id "${id}" is already registered.`);
|
|
9675
|
+
this.name = "DuplicateAdapterError";
|
|
9676
|
+
}
|
|
9677
|
+
};
|
|
9678
|
+
var IntegrationRegistry = class {
|
|
9679
|
+
adapters = /* @__PURE__ */ new Map();
|
|
9680
|
+
register(adapter) {
|
|
9681
|
+
if (this.adapters.has(adapter.id)) throw new DuplicateAdapterError(adapter.id);
|
|
9682
|
+
this.adapters.set(adapter.id, adapter);
|
|
9683
|
+
}
|
|
9684
|
+
get(id) {
|
|
9685
|
+
return this.adapters.get(id);
|
|
9686
|
+
}
|
|
9687
|
+
has(id) {
|
|
9688
|
+
return this.adapters.has(id);
|
|
9689
|
+
}
|
|
9690
|
+
list() {
|
|
9691
|
+
return [...this.adapters.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
9692
|
+
}
|
|
9693
|
+
ids() {
|
|
9694
|
+
return new Set(this.adapters.keys());
|
|
9695
|
+
}
|
|
9696
|
+
};
|
|
9697
|
+
|
|
9698
|
+
// ../integrations/src/mock-adapter.ts
|
|
9699
|
+
var MOCK_ADAPTER_ID = "mock";
|
|
9700
|
+
var DEFAULT_ITEMS = [
|
|
9701
|
+
{
|
|
9702
|
+
externalId: "EXT-001",
|
|
9703
|
+
provider: MOCK_ADAPTER_ID,
|
|
9704
|
+
title: "Open public registration to everyone",
|
|
9705
|
+
description: "Open self-service registration to the public once the private beta ends.",
|
|
9706
|
+
type: "Feature",
|
|
9707
|
+
status: "Open",
|
|
9708
|
+
url: "https://example.test/mock/EXT-001",
|
|
9709
|
+
labels: ["registration", "beta"],
|
|
9710
|
+
createdAt: "2026-01-05T10:00:00.000Z",
|
|
9711
|
+
updatedAt: "2026-02-01T09:30:00.000Z",
|
|
9712
|
+
rawMetadata: { board: "delivery" }
|
|
9713
|
+
},
|
|
9714
|
+
{
|
|
9715
|
+
externalId: "EXT-002",
|
|
9716
|
+
provider: MOCK_ADAPTER_ID,
|
|
9717
|
+
title: "Personalize onboarding steps",
|
|
9718
|
+
description: "The onboarding checklist should adapt to what the user has already completed.",
|
|
9719
|
+
type: "Task",
|
|
9720
|
+
status: "To Do",
|
|
9721
|
+
url: "https://example.test/mock/EXT-002",
|
|
9722
|
+
labels: ["onboarding"],
|
|
9723
|
+
createdAt: "2026-01-08T12:00:00.000Z",
|
|
9724
|
+
updatedAt: "2026-01-20T15:00:00.000Z"
|
|
9725
|
+
}
|
|
9726
|
+
];
|
|
9727
|
+
function simulationOf(context, fallback) {
|
|
9728
|
+
const fromConfig = context.config?.simulate;
|
|
9729
|
+
return typeof fromConfig === "string" ? fromConfig : fallback;
|
|
9730
|
+
}
|
|
9731
|
+
function readFailure(sim) {
|
|
9732
|
+
switch (sim) {
|
|
9733
|
+
case "unauthorized":
|
|
9734
|
+
throw integrationError("INTEGRATION_UNAUTHORIZED");
|
|
9735
|
+
case "rate-limited":
|
|
9736
|
+
throw integrationError("INTEGRATION_RATE_LIMITED");
|
|
9737
|
+
case "unavailable":
|
|
9738
|
+
throw integrationError("INTEGRATION_UNAVAILABLE");
|
|
9739
|
+
case "timeout":
|
|
9740
|
+
throw integrationError("INTEGRATION_TIMEOUT");
|
|
9741
|
+
case "available":
|
|
9742
|
+
break;
|
|
9743
|
+
}
|
|
9744
|
+
}
|
|
9745
|
+
function createMockAdapter(opts = {}) {
|
|
9746
|
+
const items = opts.items ?? DEFAULT_ITEMS;
|
|
9747
|
+
const defaultSim = opts.simulate ?? "available";
|
|
9748
|
+
const defaultPageSize = opts.pageSize ?? 50;
|
|
9749
|
+
return {
|
|
9750
|
+
id: MOCK_ADAPTER_ID,
|
|
9751
|
+
metadata: {
|
|
9752
|
+
id: MOCK_ADAPTER_ID,
|
|
9753
|
+
displayName: "Mock Work Source",
|
|
9754
|
+
version: "1.0.0",
|
|
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
|
+
}
|
|
9780
|
+
},
|
|
9781
|
+
capabilities: {
|
|
9782
|
+
workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
|
|
9783
|
+
},
|
|
9784
|
+
async verifyConnection(context) {
|
|
9785
|
+
const sim = simulationOf(context, defaultSim);
|
|
9786
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9787
|
+
switch (sim) {
|
|
9788
|
+
case "available":
|
|
9789
|
+
return { status: "available", checkedAt };
|
|
9790
|
+
case "unauthorized":
|
|
9791
|
+
return { status: "unauthorized", message: "Check the configured credentials.", checkedAt };
|
|
9792
|
+
case "rate-limited":
|
|
9793
|
+
case "unavailable":
|
|
9794
|
+
case "timeout":
|
|
9795
|
+
return { status: "unavailable", message: "The mock provider is temporarily unavailable.", checkedAt };
|
|
9796
|
+
}
|
|
9797
|
+
},
|
|
9798
|
+
async listWorkItems(request) {
|
|
9799
|
+
readFailure(simulationOf(request.context, defaultSim));
|
|
9800
|
+
let pool = items;
|
|
9801
|
+
const f = request.filters;
|
|
9802
|
+
if (f?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f.status.toLowerCase());
|
|
9803
|
+
if (f?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f.query.toLowerCase()));
|
|
9804
|
+
if (f?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f.updatedSince);
|
|
9805
|
+
const size = Math.max(1, request.pageSize ?? defaultPageSize);
|
|
9806
|
+
const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
|
|
9807
|
+
const slice = pool.slice(start, start + size);
|
|
9808
|
+
const end = start + slice.length;
|
|
9809
|
+
const hasMore = end < pool.length;
|
|
9810
|
+
return { items: slice, hasMore, ...hasMore ? { nextCursor: String(end) } : {} };
|
|
9811
|
+
},
|
|
9812
|
+
async getWorkItem(request) {
|
|
9813
|
+
readFailure(simulationOf(request.context, defaultSim));
|
|
9814
|
+
return items.find((i) => i.externalId === request.externalId) ?? null;
|
|
9815
|
+
}
|
|
9816
|
+
};
|
|
9817
|
+
}
|
|
9818
|
+
|
|
9819
|
+
// ../integrations/src/index.ts
|
|
9820
|
+
function createDefaultRegistry() {
|
|
9821
|
+
const registry2 = new IntegrationRegistry();
|
|
9822
|
+
registry2.register(createMockAdapter());
|
|
9823
|
+
return registry2;
|
|
9824
|
+
}
|
|
9825
|
+
|
|
9826
|
+
// src/services/integrations.ts
|
|
9827
|
+
var INTEGRATIONS_FILE = ".kaddo/integrations.yml";
|
|
9828
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
9829
|
+
var registry = createDefaultRegistry();
|
|
9830
|
+
function integrationRegistry() {
|
|
9831
|
+
return registry;
|
|
9832
|
+
}
|
|
9833
|
+
var IntegrationServiceError = class extends Error {
|
|
9834
|
+
code;
|
|
9835
|
+
constructor(code, message) {
|
|
9836
|
+
super(message);
|
|
9837
|
+
this.name = "IntegrationServiceError";
|
|
9838
|
+
this.code = code;
|
|
9839
|
+
}
|
|
9840
|
+
};
|
|
9841
|
+
function loadRaw(dir) {
|
|
9842
|
+
const abs = join(dir, INTEGRATIONS_FILE);
|
|
9843
|
+
if (!exists(abs)) return { integrations: [] };
|
|
9844
|
+
try {
|
|
9845
|
+
return parseYaml9(readFile(abs)) ?? { integrations: [] };
|
|
9846
|
+
} catch {
|
|
9847
|
+
return { integrations: [] };
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
function loadIntegrations(dir) {
|
|
9851
|
+
return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
|
|
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
|
+
}
|
|
9858
|
+
function configStatus(integration, findings) {
|
|
9859
|
+
if (!integration.enabled) return "disabled";
|
|
9860
|
+
const blocking = findings.some((f) => f.id === integration.id && f.level === "blocking");
|
|
9861
|
+
if (blocking || !registry.has(integration.adapter)) return "invalid-config";
|
|
9862
|
+
return "configured";
|
|
9863
|
+
}
|
|
9864
|
+
function secretProvider(dir) {
|
|
9865
|
+
return createLocalSecretProvider(dir);
|
|
9866
|
+
}
|
|
9867
|
+
function secretResolver(dir, env) {
|
|
9868
|
+
return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
|
|
9869
|
+
}
|
|
9870
|
+
function listIntegrations(dir) {
|
|
9871
|
+
const { integrations, findings } = loadIntegrations(dir);
|
|
9872
|
+
return integrations.map((integration) => {
|
|
9873
|
+
const adapter = registry.get(integration.adapter);
|
|
9874
|
+
return {
|
|
9875
|
+
id: integration.id,
|
|
9876
|
+
adapter: integration.adapter,
|
|
9877
|
+
enabled: integration.enabled,
|
|
9878
|
+
status: configStatus(integration, findings),
|
|
9879
|
+
displayName: adapter?.metadata.displayName ?? integration.adapter,
|
|
9880
|
+
capabilities: adapter?.capabilities ?? null,
|
|
9881
|
+
metadata: adapter?.metadata ?? null,
|
|
9882
|
+
credentialRefs: Object.values(integration.credentials).map((c) => c.env),
|
|
9883
|
+
secretRefs: Object.values(integration.secrets),
|
|
9884
|
+
secretStatus: {},
|
|
9885
|
+
findings: findings.filter((f) => f.id === integration.id)
|
|
9886
|
+
};
|
|
9887
|
+
});
|
|
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
|
+
}
|
|
9933
|
+
function requireIntegration(dir, id) {
|
|
9934
|
+
const { integrations, findings } = loadIntegrations(dir);
|
|
9935
|
+
const integration = integrations.find((i) => i.id === id);
|
|
9936
|
+
if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
|
|
9937
|
+
return { integration, findings, all: integrations };
|
|
9938
|
+
}
|
|
9939
|
+
function resolveAdapter(integration) {
|
|
9940
|
+
const adapter = registry.get(integration.adapter);
|
|
9941
|
+
if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
|
|
9942
|
+
return adapter;
|
|
9943
|
+
}
|
|
9944
|
+
async function buildContextWithSecrets(dir, integration, env) {
|
|
9945
|
+
const resolver = secretResolver(dir, env);
|
|
9946
|
+
const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
|
|
9947
|
+
return {
|
|
9948
|
+
context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
|
|
9949
|
+
missing
|
|
9950
|
+
};
|
|
9951
|
+
}
|
|
9952
|
+
async function withTimeout(op, ms) {
|
|
9953
|
+
let timer;
|
|
9954
|
+
const timeout = new Promise((_, reject) => {
|
|
9955
|
+
timer = setTimeout(() => reject(integrationError("INTEGRATION_TIMEOUT")), ms);
|
|
9956
|
+
});
|
|
9957
|
+
try {
|
|
9958
|
+
return await Promise.race([op, timeout]);
|
|
9959
|
+
} finally {
|
|
9960
|
+
if (timer) clearTimeout(timer);
|
|
9961
|
+
}
|
|
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
|
+
}
|
|
10040
|
+
async function verifyIntegration(dir, id, env = process.env) {
|
|
10041
|
+
const { integration, findings } = requireIntegration(dir, id);
|
|
10042
|
+
const cfgStatus = configStatus(integration, findings);
|
|
10043
|
+
if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
|
|
10044
|
+
if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
|
|
10045
|
+
const adapter = resolveAdapter(integration);
|
|
10046
|
+
const { context, missing } = await buildContextWithSecrets(dir, integration, env);
|
|
10047
|
+
try {
|
|
10048
|
+
const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
|
|
10049
|
+
return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
|
|
10050
|
+
} catch (err) {
|
|
10051
|
+
const e = err instanceof IntegrationError ? err : integrationError("INTEGRATION_PROVIDER_ERROR");
|
|
10052
|
+
return { id, status: e.code === "INTEGRATION_UNAUTHORIZED" ? "unauthorized" : "unavailable", connection: null, missingCredentials: missing, message: e.safeMessage };
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
function statusOf(connection) {
|
|
10056
|
+
switch (connection.status) {
|
|
10057
|
+
case "available":
|
|
10058
|
+
return "available";
|
|
10059
|
+
case "unauthorized":
|
|
10060
|
+
return "unauthorized";
|
|
10061
|
+
case "unavailable":
|
|
10062
|
+
return "unavailable";
|
|
10063
|
+
case "invalid-config":
|
|
10064
|
+
return "invalid-config";
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
10067
|
+
async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
|
|
10068
|
+
const { integration } = requireIntegration(dir, id);
|
|
10069
|
+
const adapter = resolveAdapter(integration);
|
|
10070
|
+
if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
|
|
10071
|
+
const { context } = await buildContextWithSecrets(dir, integration, env);
|
|
10072
|
+
return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
|
|
10073
|
+
}
|
|
10074
|
+
async function getExternalWorkItem(dir, id, externalId, env = process.env) {
|
|
10075
|
+
const { integration } = requireIntegration(dir, id);
|
|
10076
|
+
const adapter = resolveAdapter(integration);
|
|
10077
|
+
if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
|
|
10078
|
+
const { context } = await buildContextWithSecrets(dir, integration, env);
|
|
10079
|
+
return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
|
|
10080
|
+
}
|
|
10081
|
+
function findLinkedWorkItem(dir, integrationId, externalId) {
|
|
10082
|
+
for (const art of discoverWorkItems(dir)) {
|
|
10083
|
+
let data;
|
|
10084
|
+
try {
|
|
10085
|
+
data = matter8(readFile(art.filePath)).data;
|
|
10086
|
+
} catch {
|
|
10087
|
+
continue;
|
|
10088
|
+
}
|
|
10089
|
+
const source = parseWorkItemSource(data);
|
|
10090
|
+
if (source.integration === integrationId && source.id === externalId) {
|
|
10091
|
+
return { workItemId: String(data.id ?? ""), title: String(data.title ?? "") };
|
|
10092
|
+
}
|
|
10093
|
+
}
|
|
10094
|
+
return null;
|
|
10095
|
+
}
|
|
10096
|
+
async function previewImport(dir, id, externalId, opts = {}, env = process.env) {
|
|
10097
|
+
const item = await getExternalWorkItem(dir, id, externalId, env);
|
|
10098
|
+
if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
|
|
10099
|
+
const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
|
|
10100
|
+
return { preview, duplicate: findLinkedWorkItem(dir, id, externalId) };
|
|
10101
|
+
}
|
|
10102
|
+
async function importExternalWorkItem(dir, id, externalId, opts, env = process.env) {
|
|
10103
|
+
const item = await getExternalWorkItem(dir, id, externalId, env);
|
|
10104
|
+
if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
|
|
10105
|
+
const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
|
|
10106
|
+
const existing = findLinkedWorkItem(dir, id, externalId);
|
|
10107
|
+
if (existing) return { workItemId: existing.workItemId, created: false, duplicateOf: existing.workItemId, preview };
|
|
10108
|
+
const intent = item.description ? `${item.title}
|
|
10109
|
+
|
|
10110
|
+
${item.description}` : item.title;
|
|
10111
|
+
const created = createWorkItem(dir, {
|
|
10112
|
+
intent,
|
|
10113
|
+
type: opts.type,
|
|
10114
|
+
source: {
|
|
10115
|
+
type: "external",
|
|
10116
|
+
provider: item.provider,
|
|
10117
|
+
integration: id,
|
|
10118
|
+
id: externalId,
|
|
10119
|
+
url: item.url,
|
|
10120
|
+
imported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10121
|
+
}
|
|
10122
|
+
});
|
|
10123
|
+
return { workItemId: created.id, created: true, preview, path: created.path };
|
|
10124
|
+
}
|
|
9369
10125
|
export {
|
|
10126
|
+
INTEGRATIONS_FILE,
|
|
10127
|
+
IntegrationError,
|
|
10128
|
+
IntegrationServiceError,
|
|
9370
10129
|
TOPOLOGY_FILE,
|
|
9371
10130
|
TopologyWriteError,
|
|
9372
10131
|
WorkItemNotFoundError,
|
|
@@ -9380,13 +10139,22 @@ export {
|
|
|
9380
10139
|
buildRefinementHandoff,
|
|
9381
10140
|
buildTopologyEnrichmentHandoff,
|
|
9382
10141
|
computeRefinementStatus,
|
|
10142
|
+
createIntegration,
|
|
9383
10143
|
createWorkItem,
|
|
9384
10144
|
cwd,
|
|
10145
|
+
deleteIntegration,
|
|
10146
|
+
disableIntegration,
|
|
9385
10147
|
discoverKnowledge,
|
|
9386
10148
|
discoverWorkItems,
|
|
10149
|
+
enableIntegration,
|
|
9387
10150
|
exists,
|
|
10151
|
+
findLinkedWorkItem,
|
|
9388
10152
|
findSystemPaths,
|
|
10153
|
+
getAvailableIntegrationTypes,
|
|
10154
|
+
getExternalWorkItem,
|
|
9389
10155
|
getImpactCandidates,
|
|
10156
|
+
getIntegration,
|
|
10157
|
+
getIntegrationSecretStatus,
|
|
9390
10158
|
getSystemMapProjection,
|
|
9391
10159
|
getSystemNeighbors,
|
|
9392
10160
|
getSystemNodeContext,
|
|
@@ -9395,21 +10163,30 @@ export {
|
|
|
9395
10163
|
getWorkItemForEdit,
|
|
9396
10164
|
getWorkItems,
|
|
9397
10165
|
getWorkItemsSummary,
|
|
10166
|
+
importExternalWorkItem,
|
|
10167
|
+
integrationRegistry,
|
|
9398
10168
|
isActiveState,
|
|
9399
10169
|
isModule,
|
|
9400
10170
|
join,
|
|
9401
10171
|
knowledgeLayers,
|
|
9402
10172
|
lifecycleCounts,
|
|
9403
10173
|
lifecycleStateOf,
|
|
10174
|
+
listExternalWorkItems,
|
|
10175
|
+
listIntegrations,
|
|
9404
10176
|
loadConfig,
|
|
9405
10177
|
loadMappedModules,
|
|
9406
10178
|
loadSystemTopology,
|
|
10179
|
+
previewImport,
|
|
9407
10180
|
readFile,
|
|
10181
|
+
removeIntegrationSecret,
|
|
9408
10182
|
searchSystemNodes,
|
|
10183
|
+
setIntegrationSecret,
|
|
9409
10184
|
topologyRevision,
|
|
9410
10185
|
transitionWorkItem,
|
|
10186
|
+
updateIntegration,
|
|
9411
10187
|
updateWorkItem,
|
|
9412
10188
|
validateSystemTopology,
|
|
9413
10189
|
validateTopologyProposal,
|
|
9414
|
-
validateWorkItem
|
|
10190
|
+
validateWorkItem,
|
|
10191
|
+
verifyIntegration
|
|
9415
10192
|
};
|