@elevasis/sdk 1.15.0 → 1.16.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/cli.cjs +2325 -124
- package/dist/index.d.ts +882 -794
- package/dist/index.js +170 -46
- package/dist/node/index.d.ts +69 -0
- package/dist/node/index.js +273 -0
- package/dist/test-utils/index.d.ts +857 -711
- package/dist/test-utils/index.js +2 -0
- package/dist/types/worker/adapters/lead.d.ts +1 -1
- package/dist/types/worker/platform.d.ts +2 -9
- package/dist/worker/index.js +1 -0
- package/package.json +12 -3
- package/reference/_navigation.md +23 -1
- package/reference/_reference-manifest.json +98 -0
- package/reference/claude-config/rules/agent-start-here.md +13 -0
- package/reference/claude-config/rules/organization-model.md +40 -40
- package/reference/claude-config/rules/organization-os.md +16 -16
- package/reference/claude-config/rules/ui.md +2 -6
- package/reference/claude-config/rules/vibe.md +13 -13
- package/reference/claude-config/skills/knowledge/SKILL.md +253 -0
- package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-a.md +100 -100
- package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-b.md +158 -158
- package/reference/claude-config/skills/knowledge/operations/customers.md +109 -0
- package/reference/claude-config/skills/knowledge/operations/features.md +113 -0
- package/reference/claude-config/skills/knowledge/operations/goals.md +118 -0
- package/reference/claude-config/skills/knowledge/operations/identity.md +93 -0
- package/reference/claude-config/skills/knowledge/operations/labels.md +89 -0
- package/reference/claude-config/skills/knowledge/operations/offerings.md +109 -0
- package/reference/claude-config/skills/knowledge/operations/roles.md +99 -0
- package/reference/claude-config/skills/knowledge/operations/techStack.md +102 -0
- package/reference/claude-config/skills/run-ui/SKILL.md +73 -0
- package/reference/claude-config/skills/setup/SKILL.md +270 -270
- package/reference/claude-config/skills/tutorial/SKILL.md +249 -0
- package/reference/claude-config/skills/tutorial/progress-template.md +74 -0
- package/reference/claude-config/skills/tutorial/technical.md +1309 -0
- package/reference/claude-config/skills/tutorial/vibe-coder.md +890 -0
- package/reference/claude-config/sync-notes/2026-05-02-crm-ownership-next-action.md +58 -0
- package/reference/claude-config/sync-notes/2026-05-02-template-hardcode-workos-config.md +56 -0
- package/reference/claude-config/sync-notes/2026-05-04-elevasis-workspace.md +71 -0
- package/reference/claude-config/sync-notes/2026-05-04-template-skills-run-ui-and-tutorial.md +59 -0
- package/reference/deployment/index.mdx +5 -5
- package/reference/examples/organization-model.ts +40 -0
- package/reference/framework/index.mdx +1 -1
- package/reference/framework/tutorial-system.mdx +86 -173
- package/reference/packages/core/src/knowledge/README.md +32 -0
- package/reference/packages/ui/src/knowledge/README.md +31 -0
- package/reference/packages/ui/src/theme/presets/README.md +19 -0
- package/reference/scaffold/core/organization-model.mdx +1 -1
- package/reference/scaffold/recipes/add-a-feature.md +1 -1
- package/reference/scaffold/recipes/customize-crm-actions.md +3 -3
- package/reference/scaffold/recipes/customize-organization-model.md +3 -3
- package/reference/scaffold/recipes/extend-crm.md +12 -8
- package/reference/scaffold/recipes/extend-lead-gen.md +129 -20
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +1 -1
- package/reference/scaffold/recipes/index.md +6 -0
- package/reference/scaffold/reference/contracts.md +829 -595
- package/reference/scaffold/reference/feature-registry.md +2 -1
- package/reference/scaffold/ui/composition-extensibility.mdx +17 -0
- package/reference/claude-config/skills/configure/SKILL.md +0 -98
- package/reference/claude-config/skills/configure/operations/customers.md +0 -150
- package/reference/claude-config/skills/configure/operations/features.md +0 -162
- package/reference/claude-config/skills/configure/operations/goals.md +0 -147
- package/reference/claude-config/skills/configure/operations/identity.md +0 -133
- package/reference/claude-config/skills/configure/operations/labels.md +0 -128
- package/reference/claude-config/skills/configure/operations/offerings.md +0 -159
- package/reference/claude-config/skills/configure/operations/roles.md +0 -153
- package/reference/claude-config/skills/configure/operations/techStack.md +0 -139
package/dist/index.js
CHANGED
|
@@ -37,11 +37,105 @@ var ToolingError = class extends ExecutionError {
|
|
|
37
37
|
this.details = details;
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
z.string().uuid();
|
|
41
|
+
z.string().trim().min(1).max(1e3);
|
|
42
|
+
z.enum(["agent", "workflow"]);
|
|
43
|
+
z.enum(["agent", "workflow", "scheduler", "api"]);
|
|
44
|
+
z.string().trim().toLowerCase().min(1, "Credential name required").max(100, "Credential name too long (max 100 chars)").regex(
|
|
45
|
+
/^[a-z0-9]+(-[a-z0-9]+)+$/,
|
|
46
|
+
"Credential name must be lowercase letters, numbers, and hyphens in format: service-environment (e.g., gmail-prod, attio-dev)"
|
|
47
|
+
);
|
|
48
|
+
z.enum(["google-sheets", "dropbox"]);
|
|
49
|
+
z.string().min(10, "Authorization code too short").max(1e3, "Authorization code too long");
|
|
50
|
+
z.string().min(10, "State parameter too short").max(2048, "State parameter too long");
|
|
51
|
+
z.string().trim().transform((str) => str.replace(/[<>'"]/g, ""));
|
|
52
|
+
var EmailSchema = z.string().email();
|
|
53
|
+
z.string().url();
|
|
54
|
+
z.object({
|
|
55
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
56
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
57
|
+
});
|
|
58
|
+
z.string().datetime();
|
|
59
|
+
z.object({
|
|
60
|
+
startDate: z.string().datetime(),
|
|
61
|
+
endDate: z.string().datetime()
|
|
62
|
+
});
|
|
63
|
+
var ORGANIZATION_MODEL_ICON_TOKENS = [
|
|
64
|
+
"nav.dashboard",
|
|
65
|
+
"nav.sales",
|
|
66
|
+
"nav.crm",
|
|
67
|
+
"nav.lead-gen",
|
|
68
|
+
"nav.projects",
|
|
69
|
+
"nav.operations",
|
|
70
|
+
"nav.monitoring",
|
|
71
|
+
"nav.knowledge",
|
|
72
|
+
"nav.settings",
|
|
73
|
+
"nav.admin",
|
|
74
|
+
"nav.archive",
|
|
75
|
+
"knowledge.playbook",
|
|
76
|
+
"knowledge.strategy",
|
|
77
|
+
"knowledge.reference",
|
|
78
|
+
"feature.dashboard",
|
|
79
|
+
"feature.sales",
|
|
80
|
+
"feature.crm",
|
|
81
|
+
"feature.finance",
|
|
82
|
+
"feature.lead-gen",
|
|
83
|
+
"feature.platform",
|
|
84
|
+
"feature.projects",
|
|
85
|
+
"feature.operations",
|
|
86
|
+
"feature.knowledge",
|
|
87
|
+
"feature.monitoring",
|
|
88
|
+
"feature.settings",
|
|
89
|
+
"feature.admin",
|
|
90
|
+
"feature.archive",
|
|
91
|
+
"feature.seo",
|
|
92
|
+
"resource.agent",
|
|
93
|
+
"resource.workflow",
|
|
94
|
+
"resource.integration",
|
|
95
|
+
"resource.database",
|
|
96
|
+
"resource.user",
|
|
97
|
+
"resource.team",
|
|
98
|
+
"integration.gmail",
|
|
99
|
+
"integration.google-sheets",
|
|
100
|
+
"integration.attio",
|
|
101
|
+
"surface.dashboard",
|
|
102
|
+
"surface.overview",
|
|
103
|
+
"surface.command-view",
|
|
104
|
+
"surface.command-queue",
|
|
105
|
+
"surface.pipeline",
|
|
106
|
+
"surface.lists",
|
|
107
|
+
"surface.resources",
|
|
108
|
+
"surface.settings",
|
|
109
|
+
"status.success",
|
|
110
|
+
"status.error",
|
|
111
|
+
"status.warning",
|
|
112
|
+
"status.info",
|
|
113
|
+
"status.pending",
|
|
114
|
+
"action.approve",
|
|
115
|
+
"action.reject",
|
|
116
|
+
"action.retry",
|
|
117
|
+
"action.edit",
|
|
118
|
+
"action.view",
|
|
119
|
+
"action.launch",
|
|
120
|
+
"action.message",
|
|
121
|
+
"action.escalate",
|
|
122
|
+
"action.promote",
|
|
123
|
+
"action.submit",
|
|
124
|
+
"action.email"
|
|
125
|
+
];
|
|
126
|
+
var CustomIconTokenSchema = z.string().trim().max(80).regex(/^custom\.[a-z0-9]+(?:[-._][a-z0-9]+)*$/, "Custom icon tokens must start with custom.");
|
|
127
|
+
var OrganizationModelBuiltinIconTokenSchema = z.enum(ORGANIZATION_MODEL_ICON_TOKENS);
|
|
128
|
+
var OrganizationModelIconTokenSchema = z.union([
|
|
129
|
+
OrganizationModelBuiltinIconTokenSchema,
|
|
130
|
+
CustomIconTokenSchema
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
// ../core/src/organization-model/domains/shared.ts
|
|
40
134
|
var ModelIdSchema = z.string().trim().min(1).max(100).regex(/^[a-z0-9]+(?:[-._][a-z0-9]+)*$/, "IDs must be lowercase and use -, _, or . separators");
|
|
41
135
|
var LabelSchema = z.string().trim().min(1).max(120);
|
|
42
136
|
var DescriptionSchema = z.string().trim().min(1).max(2e3);
|
|
43
137
|
var ColorTokenSchema = z.string().trim().min(1).max(50);
|
|
44
|
-
var IconNameSchema =
|
|
138
|
+
var IconNameSchema = OrganizationModelIconTokenSchema;
|
|
45
139
|
z.string().trim().startsWith("/").max(300);
|
|
46
140
|
var ReferenceIdsSchema = z.array(ModelIdSchema).default([]);
|
|
47
141
|
var DisplayMetadataSchema = z.object({
|
|
@@ -3694,48 +3788,6 @@ var ResourceRegistry = class {
|
|
|
3694
3788
|
}
|
|
3695
3789
|
return cache.commandView;
|
|
3696
3790
|
}
|
|
3697
|
-
/**
|
|
3698
|
-
* List resources that have UI interfaces configured
|
|
3699
|
-
* Used by Execution Runner Catalog UI
|
|
3700
|
-
*
|
|
3701
|
-
* @param organizationName - Organization name
|
|
3702
|
-
* @param environment - Optional environment filter ('dev' or 'prod')
|
|
3703
|
-
* @returns Array of resources with interfaces
|
|
3704
|
-
*/
|
|
3705
|
-
listExecutable(organizationName, environment) {
|
|
3706
|
-
const cache = this.serializedCache.get(organizationName);
|
|
3707
|
-
if (!cache) {
|
|
3708
|
-
return [];
|
|
3709
|
-
}
|
|
3710
|
-
const results = [];
|
|
3711
|
-
cache.definitions.workflows.forEach((serializedWorkflow, resourceId) => {
|
|
3712
|
-
if (!serializedWorkflow.interface) return;
|
|
3713
|
-
if (environment && serializedWorkflow.config.status !== environment) return;
|
|
3714
|
-
results.push({
|
|
3715
|
-
resourceId,
|
|
3716
|
-
resourceName: serializedWorkflow.config.name,
|
|
3717
|
-
resourceType: "workflow",
|
|
3718
|
-
description: serializedWorkflow.config.description,
|
|
3719
|
-
status: serializedWorkflow.config.status,
|
|
3720
|
-
version: serializedWorkflow.config.version,
|
|
3721
|
-
interface: serializedWorkflow.interface
|
|
3722
|
-
});
|
|
3723
|
-
});
|
|
3724
|
-
cache.definitions.agents.forEach((serializedAgent, resourceId) => {
|
|
3725
|
-
if (!serializedAgent.interface) return;
|
|
3726
|
-
if (environment && serializedAgent.config.status !== environment) return;
|
|
3727
|
-
results.push({
|
|
3728
|
-
resourceId,
|
|
3729
|
-
resourceName: serializedAgent.config.name,
|
|
3730
|
-
resourceType: "agent",
|
|
3731
|
-
description: serializedAgent.config.description,
|
|
3732
|
-
status: serializedAgent.config.status,
|
|
3733
|
-
version: serializedAgent.config.version,
|
|
3734
|
-
interface: serializedAgent.interface
|
|
3735
|
-
});
|
|
3736
|
-
});
|
|
3737
|
-
return results;
|
|
3738
|
-
}
|
|
3739
3791
|
};
|
|
3740
3792
|
var StageChangeEventSchema = z.object({
|
|
3741
3793
|
type: z.literal("stage_change"),
|
|
@@ -3787,6 +3839,13 @@ var DealCreatedEventSchema = z.object({
|
|
|
3787
3839
|
type: z.literal("deal_created"),
|
|
3788
3840
|
timestamp: z.string().datetime()
|
|
3789
3841
|
});
|
|
3842
|
+
var ActionFailedEventSchema = z.object({
|
|
3843
|
+
type: z.literal("action_failed"),
|
|
3844
|
+
timestamp: z.string().datetime(),
|
|
3845
|
+
actionKey: z.string(),
|
|
3846
|
+
errorMessage: z.string(),
|
|
3847
|
+
payload: z.record(z.string(), z.unknown()).optional()
|
|
3848
|
+
});
|
|
3790
3849
|
var ActivityEventSchema = z.discriminatedUnion("type", [
|
|
3791
3850
|
StageChangeEventSchema,
|
|
3792
3851
|
StateChangeEventSchema,
|
|
@@ -3795,8 +3854,67 @@ var ActivityEventSchema = z.discriminatedUnion("type", [
|
|
|
3795
3854
|
ApprovalResolvedEventSchema,
|
|
3796
3855
|
ApprovalStaleEventSchema,
|
|
3797
3856
|
TaskCreatedEventSchema,
|
|
3798
|
-
DealCreatedEventSchema
|
|
3857
|
+
DealCreatedEventSchema,
|
|
3858
|
+
ActionFailedEventSchema
|
|
3799
3859
|
]);
|
|
3860
|
+
|
|
3861
|
+
// ../core/src/business/acquisition/deal-ownership.ts
|
|
3862
|
+
var INBOUND_EVENT_TYPES = ["reply_received"];
|
|
3863
|
+
var OUTBOUND_EVENT_TYPES = [
|
|
3864
|
+
"reply_sent_to_lead",
|
|
3865
|
+
"booking_nudge_sent",
|
|
3866
|
+
"reminder_sent",
|
|
3867
|
+
"rebook_sent",
|
|
3868
|
+
"followup_email_sent",
|
|
3869
|
+
"reply_followup_sent",
|
|
3870
|
+
"lead_deferred_next_step"
|
|
3871
|
+
];
|
|
3872
|
+
var INBOUND_SET = new Set(INBOUND_EVENT_TYPES);
|
|
3873
|
+
var OUTBOUND_SET = new Set(OUTBOUND_EVENT_TYPES);
|
|
3874
|
+
function getDealOwnership(deal) {
|
|
3875
|
+
if (deal.state_key === "closed_won" || deal.state_key === "closed_lost") {
|
|
3876
|
+
return null;
|
|
3877
|
+
}
|
|
3878
|
+
if (!Array.isArray(deal.activity_log)) return null;
|
|
3879
|
+
let latestInboundMs = null;
|
|
3880
|
+
let latestOutboundMs = null;
|
|
3881
|
+
for (const entry of deal.activity_log) {
|
|
3882
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
3883
|
+
const record = entry;
|
|
3884
|
+
const type = typeof record.type === "string" ? record.type : null;
|
|
3885
|
+
if (!type) continue;
|
|
3886
|
+
const isInbound = INBOUND_SET.has(type);
|
|
3887
|
+
const isOutbound = OUTBOUND_SET.has(type);
|
|
3888
|
+
if (!isInbound && !isOutbound) continue;
|
|
3889
|
+
const occurredAt = resolveTimestamp(record);
|
|
3890
|
+
if (occurredAt === null) continue;
|
|
3891
|
+
if (isInbound && (latestInboundMs === null || occurredAt > latestInboundMs)) {
|
|
3892
|
+
latestInboundMs = occurredAt;
|
|
3893
|
+
}
|
|
3894
|
+
if (isOutbound && (latestOutboundMs === null || occurredAt > latestOutboundMs)) {
|
|
3895
|
+
latestOutboundMs = occurredAt;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
if (latestInboundMs === null && latestOutboundMs === null) return null;
|
|
3899
|
+
if (latestOutboundMs !== null && (latestInboundMs === null || latestOutboundMs >= latestInboundMs)) {
|
|
3900
|
+
return "them";
|
|
3901
|
+
}
|
|
3902
|
+
return "us";
|
|
3903
|
+
}
|
|
3904
|
+
function resolveTimestamp(record) {
|
|
3905
|
+
return parseMs(record.timestamp) ?? parseMs(record.occurredAt) ?? parseMs(record.createdAt) ?? parseMs(record.updatedAt) ?? parseMs(record.sentAt) ?? null;
|
|
3906
|
+
}
|
|
3907
|
+
function parseMs(value) {
|
|
3908
|
+
if (value instanceof Date) {
|
|
3909
|
+
const ms2 = value.getTime();
|
|
3910
|
+
return Number.isNaN(ms2) ? null : ms2;
|
|
3911
|
+
}
|
|
3912
|
+
if (typeof value !== "string") return null;
|
|
3913
|
+
const ms = new Date(value).getTime();
|
|
3914
|
+
return Number.isNaN(ms) ? null : ms;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
// ../core/src/business/acquisition/derive-actions.ts
|
|
3800
3918
|
var SendReplyActionPayloadSchema = z.object({
|
|
3801
3919
|
replyBody: z.string().trim().min(1).max(1e4)
|
|
3802
3920
|
}).strict();
|
|
@@ -3834,7 +3952,7 @@ var DEFAULT_CRM_ACTIONS = [
|
|
|
3834
3952
|
{
|
|
3835
3953
|
key: "send_reply",
|
|
3836
3954
|
label: "Send Reply",
|
|
3837
|
-
isAvailableFor: (deal) => deal.stage_key === "interested" && (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey || deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey || deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
|
|
3955
|
+
isAvailableFor: (deal) => deal.stage_key === "interested" && isOurReplyAction(deal) && (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey || deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey || deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
|
|
3838
3956
|
workflowId: "crm-send-reply-workflow",
|
|
3839
3957
|
payloadSchema: SendReplyActionPayloadSchema
|
|
3840
3958
|
},
|
|
@@ -3869,5 +3987,11 @@ var DEFAULT_CRM_ACTIONS = [
|
|
|
3869
3987
|
function deriveActions(deal, actions = DEFAULT_CRM_ACTIONS) {
|
|
3870
3988
|
return actions.filter((a) => a.isAvailableFor(deal)).map(({ key, label, payloadSchema }) => ({ key, label, payloadSchema }));
|
|
3871
3989
|
}
|
|
3990
|
+
function isOurReplyAction(deal) {
|
|
3991
|
+
if (Object.prototype.hasOwnProperty.call(deal, "nextAction")) {
|
|
3992
|
+
return deal.nextAction === "send_reply";
|
|
3993
|
+
}
|
|
3994
|
+
return (deal.ownership ?? getDealOwnership(deal)) === "us";
|
|
3995
|
+
}
|
|
3872
3996
|
|
|
3873
|
-
export { ActivityEventSchema, DEFAULT_CRM_ACTIONS, ExecutionError, RegistryValidationError, ResourceRegistry, StepType, ToolingError, deriveActions };
|
|
3997
|
+
export { ActivityEventSchema, DEFAULT_CRM_ACTIONS, EmailSchema, ExecutionError, RegistryValidationError, ResourceRegistry, StepType, ToolingError, deriveActions };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type KnowledgeKind = 'playbook' | 'strategy' | 'reference';
|
|
2
|
+
interface KnowledgeCodegenNode {
|
|
3
|
+
id: string;
|
|
4
|
+
kind: KnowledgeKind;
|
|
5
|
+
title: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
body: string;
|
|
9
|
+
links: {
|
|
10
|
+
nodeId: string;
|
|
11
|
+
}[];
|
|
12
|
+
ownerIds: string[];
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
interface GenerateKnowledgeNodesOptions {
|
|
16
|
+
sourceDir: string;
|
|
17
|
+
outputPath: string;
|
|
18
|
+
typeImportPath?: string;
|
|
19
|
+
exportedName?: string;
|
|
20
|
+
sourceLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
interface GenerateKnowledgeNodesResult {
|
|
23
|
+
nodes: KnowledgeCodegenNode[];
|
|
24
|
+
outputPath: string;
|
|
25
|
+
}
|
|
26
|
+
declare function readKnowledgeNodeMdx(filePath: string): KnowledgeCodegenNode;
|
|
27
|
+
declare function generateKnowledgeNodesTs(options: {
|
|
28
|
+
nodes: KnowledgeCodegenNode[];
|
|
29
|
+
typeImportPath?: string;
|
|
30
|
+
exportedName?: string;
|
|
31
|
+
sourceLabel?: string;
|
|
32
|
+
}): string;
|
|
33
|
+
declare function generateKnowledgeNodes(options: GenerateKnowledgeNodesOptions): GenerateKnowledgeNodesResult;
|
|
34
|
+
|
|
35
|
+
interface KnowledgeNodeInput {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: string;
|
|
38
|
+
title: string;
|
|
39
|
+
summary: string;
|
|
40
|
+
body: string;
|
|
41
|
+
}
|
|
42
|
+
interface KnowledgeSearchEntry {
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
summary: string;
|
|
46
|
+
bodyText: string;
|
|
47
|
+
}
|
|
48
|
+
interface CodegenResult {
|
|
49
|
+
bodiesTsx: string;
|
|
50
|
+
searchIndex: KnowledgeSearchEntry[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compiles knowledge nodes into generated file contents.
|
|
54
|
+
* Pure function: same input -> same output. No file I/O.
|
|
55
|
+
*/
|
|
56
|
+
declare function generateKnowledgeBodies(nodes: KnowledgeNodeInput[]): Promise<CodegenResult>;
|
|
57
|
+
|
|
58
|
+
interface ResolvedKnowledgeLayout {
|
|
59
|
+
mode: 'monorepo' | 'external';
|
|
60
|
+
projectRoot: string;
|
|
61
|
+
omSourcePath: string;
|
|
62
|
+
mdxNodesDir: string;
|
|
63
|
+
generatedDir: string;
|
|
64
|
+
watchPaths: string[];
|
|
65
|
+
}
|
|
66
|
+
declare function runKnowledgeCodegen(layout: ResolvedKnowledgeLayout): Promise<void>;
|
|
67
|
+
|
|
68
|
+
export { generateKnowledgeBodies, generateKnowledgeNodes, generateKnowledgeNodesTs, readKnowledgeNodeMdx, runKnowledgeCodegen };
|
|
69
|
+
export type { CodegenResult, GenerateKnowledgeNodesOptions, GenerateKnowledgeNodesResult, KnowledgeCodegenNode, KnowledgeKind, KnowledgeNodeInput, KnowledgeSearchEntry, ResolvedKnowledgeLayout };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { relative, dirname, resolve, join, extname } from 'path';
|
|
3
|
+
import { compile } from '@mdx-js/mdx';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
|
|
6
|
+
// src/knowledge-codegen.ts
|
|
7
|
+
function listMdxFiles(directory) {
|
|
8
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
9
|
+
const path = join(directory, entry.name);
|
|
10
|
+
if (entry.isDirectory()) return listMdxFiles(path);
|
|
11
|
+
return entry.isFile() && extname(entry.name) === ".mdx" ? [path] : [];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function parseScalar(value) {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
const quoted = trimmed.match(/^['"]([\s\S]*)['"]$/);
|
|
17
|
+
return quoted ? quoted[1] : trimmed;
|
|
18
|
+
}
|
|
19
|
+
function parseFrontmatter(raw, filePath) {
|
|
20
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
21
|
+
if (!match) throw new Error(`[knowledge-node-codegen] Missing frontmatter in ${filePath}`);
|
|
22
|
+
const frontmatter = {};
|
|
23
|
+
const lines = match[1].split(/\r?\n/);
|
|
24
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
25
|
+
const line = lines[index];
|
|
26
|
+
if (!line.trim()) continue;
|
|
27
|
+
const pair = line.match(/^([A-Za-z][A-Za-z0-9]*):(?:\s*(.*))?$/);
|
|
28
|
+
if (!pair) throw new Error(`[knowledge-node-codegen] Invalid frontmatter line in ${filePath}: ${line}`);
|
|
29
|
+
const key = pair[1];
|
|
30
|
+
const inlineValue = pair[2]?.trim() ?? "";
|
|
31
|
+
if (inlineValue) {
|
|
32
|
+
frontmatter[key] = parseScalar(inlineValue);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const values = [];
|
|
36
|
+
while (index + 1 < lines.length && /^\s+-\s+/.test(lines[index + 1])) {
|
|
37
|
+
index += 1;
|
|
38
|
+
values.push(parseScalar(lines[index].replace(/^\s+-\s+/, "")));
|
|
39
|
+
}
|
|
40
|
+
frontmatter[key] = values;
|
|
41
|
+
}
|
|
42
|
+
return { frontmatter, body: match[2].trim() };
|
|
43
|
+
}
|
|
44
|
+
function assertString(frontmatter, key, filePath) {
|
|
45
|
+
const value = frontmatter[key];
|
|
46
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
47
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter requires string "${key}"`);
|
|
48
|
+
}
|
|
49
|
+
return value.trim();
|
|
50
|
+
}
|
|
51
|
+
function optionalString(frontmatter, key, filePath) {
|
|
52
|
+
const value = frontmatter[key];
|
|
53
|
+
if (value === void 0) return void 0;
|
|
54
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
55
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a nonempty string`);
|
|
56
|
+
}
|
|
57
|
+
return value.trim();
|
|
58
|
+
}
|
|
59
|
+
function assertKind(value, filePath) {
|
|
60
|
+
if (value === "playbook" || value === "strategy" || value === "reference") return value;
|
|
61
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} has invalid kind "${value}"`);
|
|
62
|
+
}
|
|
63
|
+
function optionalStringArray(frontmatter, key, filePath) {
|
|
64
|
+
const value = frontmatter[key];
|
|
65
|
+
if (value === void 0) return [];
|
|
66
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
67
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a string array`);
|
|
68
|
+
}
|
|
69
|
+
return value.map((entry) => entry.trim()).filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
function readKnowledgeNodeMdx(filePath) {
|
|
72
|
+
const raw = readFileSync(filePath, "utf8");
|
|
73
|
+
const { frontmatter, body } = parseFrontmatter(raw, filePath);
|
|
74
|
+
return {
|
|
75
|
+
id: assertString(frontmatter, "id", filePath),
|
|
76
|
+
kind: assertKind(assertString(frontmatter, "kind", filePath), filePath),
|
|
77
|
+
title: assertString(frontmatter, "title", filePath),
|
|
78
|
+
summary: assertString(frontmatter, "description", filePath),
|
|
79
|
+
icon: optionalString(frontmatter, "icon", filePath),
|
|
80
|
+
body,
|
|
81
|
+
links: optionalStringArray(frontmatter, "links", filePath).map((nodeId) => ({ nodeId })),
|
|
82
|
+
ownerIds: optionalStringArray(frontmatter, "ownerIds", filePath),
|
|
83
|
+
updatedAt: assertString(frontmatter, "updatedAt", filePath)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function generateKnowledgeNodesTs(options) {
|
|
87
|
+
const exportedName = options.exportedName ?? "mdxKnowledgeNodes";
|
|
88
|
+
const typeImport = options.typeImportPath ? [`import type { OrgKnowledgeNode } from '${options.typeImportPath}'`, ""] : [];
|
|
89
|
+
const typeSatisfies = options.typeImportPath ? " satisfies OrgKnowledgeNode[]" : "";
|
|
90
|
+
return [
|
|
91
|
+
"// @generated by generate-knowledge-nodes -- DO NOT EDIT",
|
|
92
|
+
"// Regenerate: elevasis-sdk knowledge:generate",
|
|
93
|
+
`// Source: ${options.sourceLabel ?? "knowledge/nodes/**/*.mdx"}`,
|
|
94
|
+
"",
|
|
95
|
+
...typeImport,
|
|
96
|
+
`export const ${exportedName} = ${JSON.stringify(options.nodes, null, 2)}${typeSatisfies}`,
|
|
97
|
+
""
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
function generateKnowledgeNodes(options) {
|
|
101
|
+
const files = listMdxFiles(options.sourceDir).sort(
|
|
102
|
+
(a, b) => relative(options.sourceDir, a).localeCompare(relative(options.sourceDir, b))
|
|
103
|
+
);
|
|
104
|
+
const nodes = files.map(readKnowledgeNodeMdx);
|
|
105
|
+
const ids = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const node of nodes) {
|
|
107
|
+
if (ids.has(node.id)) throw new Error(`[knowledge-node-codegen] Duplicate knowledge node id: ${node.id}`);
|
|
108
|
+
ids.add(node.id);
|
|
109
|
+
}
|
|
110
|
+
mkdirSync(dirname(options.outputPath), { recursive: true });
|
|
111
|
+
writeFileSync(
|
|
112
|
+
options.outputPath,
|
|
113
|
+
generateKnowledgeNodesTs({
|
|
114
|
+
nodes,
|
|
115
|
+
typeImportPath: options.typeImportPath,
|
|
116
|
+
exportedName: options.exportedName,
|
|
117
|
+
sourceLabel: options.sourceLabel
|
|
118
|
+
}),
|
|
119
|
+
"utf8"
|
|
120
|
+
);
|
|
121
|
+
return { nodes, outputPath: options.outputPath };
|
|
122
|
+
}
|
|
123
|
+
var ALLOWED_COMPONENTS = /* @__PURE__ */ new Set(["Card", "Cards", "Step", "Steps", "Callout", "Tab", "Tabs"]);
|
|
124
|
+
function extractCustomComponents(compiledCode) {
|
|
125
|
+
const found = /* @__PURE__ */ new Set();
|
|
126
|
+
const destructurePattern = /\{([^}]+)\}\s*=\s*_components/g;
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = destructurePattern.exec(compiledCode)) !== null) {
|
|
129
|
+
for (const part of match[1].split(",")) {
|
|
130
|
+
const name = part.trim().split(":")[0].trim();
|
|
131
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
132
|
+
found.add(name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return [...found];
|
|
137
|
+
}
|
|
138
|
+
function validateComponents(nodeId, compiledCode) {
|
|
139
|
+
const components = extractCustomComponents(compiledCode);
|
|
140
|
+
const unknown = components.filter((c) => !ALLOWED_COMPONENTS.has(c));
|
|
141
|
+
if (unknown.length > 0) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`[knowledge-codegen] node "${nodeId}" uses unknown JSX component(s): ${unknown.join(", ")}.
|
|
144
|
+
Allowed components: ${[...ALLOWED_COMPONENTS].join(", ")}.
|
|
145
|
+
To add a component:
|
|
146
|
+
1. Add it to ALLOWED_COMPONENTS in packages/sdk/src/node/knowledge-bodies-codegen.ts
|
|
147
|
+
2. Add it to the runtime KnowledgeMDXProvider allowlist (Wave 3d)`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function stripToPlainText(body) {
|
|
152
|
+
return body.replace(/^(import|export)\s+.+$/gm, "").replace(/<[A-Z][^>]*>/g, "").replace(/<\/[A-Z][^>]*>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/[*_`~]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim();
|
|
153
|
+
}
|
|
154
|
+
var BODIES_HEADER = [
|
|
155
|
+
"// @generated by generate-knowledge-bodies -- DO NOT EDIT",
|
|
156
|
+
"// Regenerate: pnpm scaffold:sync",
|
|
157
|
+
"// Source: packages/elevasis-core/src/organization-model.ts",
|
|
158
|
+
"",
|
|
159
|
+
"import { Fragment, jsx, jsxs } from 'react/jsx-runtime'",
|
|
160
|
+
"import type { ComponentType } from 'react'",
|
|
161
|
+
""
|
|
162
|
+
].join("\n");
|
|
163
|
+
var FACTORY_BLOCK = [
|
|
164
|
+
"// ---------------------------------------------------------------------------",
|
|
165
|
+
"// Inline compiled MDX component factory",
|
|
166
|
+
"// ---------------------------------------------------------------------------",
|
|
167
|
+
"",
|
|
168
|
+
"type MDXRuntime = {",
|
|
169
|
+
" Fragment: typeof Fragment",
|
|
170
|
+
" jsx: typeof jsx",
|
|
171
|
+
" jsxs: typeof jsxs",
|
|
172
|
+
"}",
|
|
173
|
+
"",
|
|
174
|
+
"type KnowledgeBodyProps = {",
|
|
175
|
+
" components?: Record<string, ComponentType>",
|
|
176
|
+
"}",
|
|
177
|
+
"",
|
|
178
|
+
"/**",
|
|
179
|
+
" * Wraps a compiled MDX function-body string into a React component.",
|
|
180
|
+
" * The function-body format from @mdx-js/mdx expects arguments[0]",
|
|
181
|
+
" * to be { Fragment, jsx, jsxs } from react/jsx-runtime.",
|
|
182
|
+
" */",
|
|
183
|
+
"function makeKnowledgeComponent(fnBody: string): ComponentType<KnowledgeBodyProps> {",
|
|
184
|
+
" // eslint-disable-next-line @typescript-eslint/no-implied-eval",
|
|
185
|
+
" const factory = new Function(fnBody) as (runtime: MDXRuntime) => { default: ComponentType }",
|
|
186
|
+
" return function KnowledgeBody(props: KnowledgeBodyProps) {",
|
|
187
|
+
" const runtime = { Fragment, jsx, jsxs, ...props } as unknown as MDXRuntime",
|
|
188
|
+
" const mod = factory(runtime)",
|
|
189
|
+
" const Content = mod.default",
|
|
190
|
+
" return Content ? jsx(Content as ComponentType<KnowledgeBodyProps>, props) : null",
|
|
191
|
+
" }",
|
|
192
|
+
"}",
|
|
193
|
+
""
|
|
194
|
+
].join("\n");
|
|
195
|
+
var MAP_HEADER = [
|
|
196
|
+
"// ---------------------------------------------------------------------------",
|
|
197
|
+
"// Generated map: node id -> compiled React component",
|
|
198
|
+
"// ---------------------------------------------------------------------------",
|
|
199
|
+
""
|
|
200
|
+
].join("\n");
|
|
201
|
+
async function generateKnowledgeBodies(nodes) {
|
|
202
|
+
if (nodes.length === 0) {
|
|
203
|
+
const bodiesTsx2 = [
|
|
204
|
+
"// @generated by generate-knowledge-bodies -- DO NOT EDIT",
|
|
205
|
+
"// Regenerate: pnpm scaffold:sync",
|
|
206
|
+
"// Source: packages/elevasis-core/src/organization-model.ts",
|
|
207
|
+
"",
|
|
208
|
+
"import type { ComponentType } from 'react'",
|
|
209
|
+
"",
|
|
210
|
+
"export const KNOWLEDGE_BODIES: Record<",
|
|
211
|
+
" string,",
|
|
212
|
+
" ComponentType<{ components?: Record<string, ComponentType> }>",
|
|
213
|
+
"> = {}",
|
|
214
|
+
""
|
|
215
|
+
].join("\n");
|
|
216
|
+
return { bodiesTsx: bodiesTsx2, searchIndex: [] };
|
|
217
|
+
}
|
|
218
|
+
const nodeComments = [];
|
|
219
|
+
const mapEntries = [];
|
|
220
|
+
const searchIndex = [];
|
|
221
|
+
for (const node of nodes) {
|
|
222
|
+
if (/^(import|export)\s+/m.test(node.body)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`[knowledge-codegen] node "${node.id}" body contains import/export statements, which are not allowed in Phase 1 MDX bodies.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const compiled = await compile(node.body, {
|
|
228
|
+
outputFormat: "function-body",
|
|
229
|
+
jsx: false,
|
|
230
|
+
remarkPlugins: [remarkGfm]
|
|
231
|
+
});
|
|
232
|
+
const fnBodyCode = String(compiled);
|
|
233
|
+
validateComponents(node.id, fnBodyCode);
|
|
234
|
+
const bodyText = stripToPlainText(node.body);
|
|
235
|
+
searchIndex.push({ id: node.id, title: node.title, summary: node.summary, bodyText });
|
|
236
|
+
nodeComments.push(`// Node: ${node.id} (${node.kind})`);
|
|
237
|
+
mapEntries.push(` '${node.id}': makeKnowledgeComponent(${JSON.stringify(fnBodyCode)})`);
|
|
238
|
+
}
|
|
239
|
+
const mapBlock = `export const KNOWLEDGE_BODIES: Record<string, ComponentType<KnowledgeBodyProps>> = {
|
|
240
|
+
` + mapEntries.join(",\n") + "\n}\n";
|
|
241
|
+
const bodiesTsx = BODIES_HEADER + FACTORY_BLOCK + MAP_HEADER + (nodeComments.length > 0 ? nodeComments.join("\n") + "\n\n" : "") + mapBlock;
|
|
242
|
+
return { bodiesTsx, searchIndex };
|
|
243
|
+
}
|
|
244
|
+
async function runKnowledgeCodegen(layout) {
|
|
245
|
+
let nodes;
|
|
246
|
+
if (layout.mode === "monorepo") {
|
|
247
|
+
const omSpecifier = "@repo/elevasis-core/organization-model";
|
|
248
|
+
const om = await import(
|
|
249
|
+
/* @vite-ignore */
|
|
250
|
+
omSpecifier
|
|
251
|
+
);
|
|
252
|
+
nodes = om.canonicalOrganizationModel.knowledge.nodes;
|
|
253
|
+
} else {
|
|
254
|
+
const outputPath = resolve(layout.generatedDir, "nodes.ts");
|
|
255
|
+
const result = generateKnowledgeNodes({
|
|
256
|
+
sourceDir: layout.mdxNodesDir,
|
|
257
|
+
outputPath,
|
|
258
|
+
typeImportPath: "@elevasis/core/organization-model",
|
|
259
|
+
exportedName: "mdxKnowledgeNodes"
|
|
260
|
+
});
|
|
261
|
+
nodes = result.nodes;
|
|
262
|
+
}
|
|
263
|
+
const { bodiesTsx, searchIndex } = await generateKnowledgeBodies(nodes);
|
|
264
|
+
mkdirSync(layout.generatedDir, { recursive: true });
|
|
265
|
+
writeFileSync(resolve(layout.generatedDir, "knowledge-bodies.tsx"), bodiesTsx, "utf8");
|
|
266
|
+
writeFileSync(
|
|
267
|
+
resolve(layout.generatedDir, "knowledge-search-index.json"),
|
|
268
|
+
JSON.stringify(searchIndex, null, 2) + "\n",
|
|
269
|
+
"utf8"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { generateKnowledgeBodies, generateKnowledgeNodes, generateKnowledgeNodesTs, readKnowledgeNodeMdx, runKnowledgeCodegen };
|