thinkwork-cli 0.12.16 → 0.13.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.js +517 -10403
- package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
- package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
- package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
- package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
- package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
- package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
- package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
- package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
- package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
- package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
- package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
- package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
- package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
- package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
- package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
- package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
- package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
- package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
- package/dist/drizzle/0203_threads_mode_override.sql +61 -0
- package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
- package/dist/drizzle/0205_routines_git_python.sql +185 -0
- package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
- package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
- package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
- package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
- package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
- package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
- package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
- package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
- package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
- package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
- package/dist/drizzle/0218_document_plates.sql +30 -0
- package/dist/drizzle/0219_document_section_waivers.sql +29 -0
- package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
- package/dist/drizzle/0221_artifact_shares.sql +29 -0
- package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
- package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
- package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
- package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
- package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
- package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
- package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
- package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
- package/dist/drizzle/0230_analyst_rls.sql +1613 -0
- package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
- package/dist/plugins/catalog/package.json +0 -1
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
- package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
- package/dist/plugins/catalog/src/contracts.ts +112 -0
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
- package/dist/plugins/n8n/README.md +4 -36
- package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
- package/dist/plugins/n8n/src/manifest.ts +0 -5
- package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
- package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
- package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
- package/dist/plugins/n8n/test/manifest.test.ts +0 -1
- package/dist/plugins/twenty/package.json +5 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
- package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
- package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
- package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
- package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
- package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
- package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
- package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
- package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
- package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
- package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
- package/dist/plugins/twenty/src/manifest.ts +99 -0
- package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
- package/dist/plugins/twenty/test/manifest.test.ts +12 -2
- package/dist/terraform/examples/greenfield/main.tf +90 -380
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
- package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
- package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
- package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
- package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
- package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
- package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
- package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
- package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
- package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
- package/dist/terraform/modules/app/ses-email/main.tf +14 -2
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
- package/dist/terraform/modules/thinkwork/README.md +0 -1
- package/dist/terraform/modules/thinkwork/main.tf +167 -446
- package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
- package/dist/terraform/modules/thinkwork/variables.tf +55 -309
- package/dist/terraform/schema.graphql +4 -0
- package/dist/workspace-defaults/files/AGENTS.md +45 -31
- package/dist/workspace-defaults/files/CONTEXT.md +48 -4
- package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
- package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
- package/dist/workspace-defaults/files/SPACE.md +34 -19
- package/dist/workspace-defaults/files/TOOLS.md +30 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
- package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
- package/package.json +1 -1
- package/dist/plugins/company-brain/README.md +0 -37
- package/dist/plugins/company-brain/package.json +0 -33
- package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
- package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
- package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
- package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
- package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
- package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
- package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
- package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
- package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
- package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
- package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
- package/dist/plugins/company-brain/src/api/migration.ts +0 -665
- package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
- package/dist/plugins/company-brain/src/index.ts +0 -62
- package/dist/plugins/company-brain/src/manifest.ts +0 -112
- package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
- package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
- package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
- package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
- package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
- package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
- package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
- package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
- package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
- package/dist/plugins/company-brain/tsconfig.json +0 -8
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Provision TEI's LastMile sales reps as Twenty workspace members.
|
|
4
|
+
*
|
|
5
|
+
* TEI's Twenty does not serve the auth GraphQL schema (see lib/provision-
|
|
6
|
+
* members-db.ts for the evidence), so members are written directly to Twenty's
|
|
7
|
+
* Postgres. INSERT-only, idempotent by email, one transaction per rep.
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* TWENTY_DATABASE_URL Twenty's Postgres (secretsmanager thinkwork/tei-e2e/twenty/db-url)
|
|
11
|
+
* LASTMILE_DATABASE_URL LastMile dispatch Postgres (read-only)
|
|
12
|
+
* TWENTY_REP_PASSWORD Shared validation-window password. ROTATE AT CUTOVER.
|
|
13
|
+
* TWENTY_REP_EMAIL_DOMAIN Fallback email domain (default texasenterprises.com)
|
|
14
|
+
* TWENTY_WORKSPACE_ID Optional; resolved from core.workspace when single-workspace
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* npx tsx scripts/provision-twenty-members.ts # dry-run: who would be created
|
|
18
|
+
* npx tsx scripts/provision-twenty-members.ts --apply # create them
|
|
19
|
+
* npx tsx scripts/provision-twenty-members.ts --apply --only me@x.com # one rep, for proof
|
|
20
|
+
*
|
|
21
|
+
* After provisioning, re-run migrate-lastmile.ts --apply so every company and
|
|
22
|
+
* opportunity picks up its owner.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import process from "node:process";
|
|
26
|
+
|
|
27
|
+
import pg from "pg";
|
|
28
|
+
|
|
29
|
+
import { createLastmileReader } from "./lib/lastmile-reader";
|
|
30
|
+
import { deriveRepEmail } from "./lib/mappers";
|
|
31
|
+
import {
|
|
32
|
+
provisionMembers,
|
|
33
|
+
type ProvisionRep,
|
|
34
|
+
} from "./lib/provision-members-db";
|
|
35
|
+
|
|
36
|
+
function requireEnv(name: string): string {
|
|
37
|
+
const value = process.env[name];
|
|
38
|
+
if (!value) throw new Error(`Missing required environment variable: ${name}`);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function log(message: string): void {
|
|
43
|
+
process.stderr.write(`${message}\n`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface WorkspaceContext {
|
|
47
|
+
workspaceId: string;
|
|
48
|
+
roleId: string;
|
|
49
|
+
applicationId: string;
|
|
50
|
+
workspaceSchema: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read the workspace's own wiring rather than hardcode ids. */
|
|
54
|
+
async function readWorkspaceContext(
|
|
55
|
+
databaseUrl: string,
|
|
56
|
+
workspaceIdOverride: string | undefined,
|
|
57
|
+
): Promise<WorkspaceContext> {
|
|
58
|
+
const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 });
|
|
59
|
+
try {
|
|
60
|
+
// core.workspace has no schemaName column on this version; the data
|
|
61
|
+
// schema is resolved from information_schema below.
|
|
62
|
+
const workspaces = await pool.query<{
|
|
63
|
+
id: string;
|
|
64
|
+
defaultRoleId: string | null;
|
|
65
|
+
}>(
|
|
66
|
+
`select id, "defaultRoleId" from core.workspace where "deletedAt" is null`,
|
|
67
|
+
);
|
|
68
|
+
if (workspaces.rows.length === 0) throw new Error("No workspace found.");
|
|
69
|
+
const workspace = workspaceIdOverride
|
|
70
|
+
? workspaces.rows.find((row) => row.id === workspaceIdOverride)
|
|
71
|
+
: workspaces.rows[0];
|
|
72
|
+
if (!workspace)
|
|
73
|
+
throw new Error(`Workspace ${workspaceIdOverride} not found.`);
|
|
74
|
+
if (workspaces.rows.length > 1 && !workspaceIdOverride) {
|
|
75
|
+
throw new Error("Multiple workspaces — set TWENTY_WORKSPACE_ID.");
|
|
76
|
+
}
|
|
77
|
+
if (!workspace.defaultRoleId) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Workspace has no defaultRoleId; refusing to guess a role.",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Reuse the applicationId the workspace's existing roleTargets carry.
|
|
84
|
+
const roleTarget = await pool.query<{ applicationId: string }>(
|
|
85
|
+
`select "applicationId" from core."roleTarget" where "workspaceId" = $1 limit 1`,
|
|
86
|
+
[workspace.id],
|
|
87
|
+
);
|
|
88
|
+
if (roleTarget.rows.length === 0) {
|
|
89
|
+
throw new Error("No existing roleTarget to copy applicationId from.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const schemas = await pool.query<{ schema_name: string }>(
|
|
93
|
+
`select schema_name from information_schema.schemata where schema_name like 'workspace\\_%'`,
|
|
94
|
+
);
|
|
95
|
+
if (schemas.rows.length !== 1) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Expected exactly one workspace_* data schema, found ${schemas.rows.length}.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const schemaName = schemas.rows[0].schema_name;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
workspaceId: workspace.id,
|
|
104
|
+
roleId: workspace.defaultRoleId,
|
|
105
|
+
applicationId: roleTarget.rows[0].applicationId,
|
|
106
|
+
workspaceSchema: schemaName,
|
|
107
|
+
};
|
|
108
|
+
} finally {
|
|
109
|
+
await pool.end();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main(): Promise<void> {
|
|
114
|
+
const argv = process.argv.slice(2);
|
|
115
|
+
let apply = false;
|
|
116
|
+
let only: string | null = null;
|
|
117
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
118
|
+
const arg = argv[index];
|
|
119
|
+
if (arg === "--apply") apply = true;
|
|
120
|
+
else if (arg === "--dry-run") apply = false;
|
|
121
|
+
else if (arg === "--only") {
|
|
122
|
+
only = argv[++index] ?? null;
|
|
123
|
+
if (!only) throw new Error("--only requires an email");
|
|
124
|
+
} else throw new Error(`Unknown argument: ${arg}`);
|
|
125
|
+
}
|
|
126
|
+
const dryRun = !apply;
|
|
127
|
+
|
|
128
|
+
const twentyDbUrl = requireEnv("TWENTY_DATABASE_URL");
|
|
129
|
+
const lastmileUrl = requireEnv("LASTMILE_DATABASE_URL");
|
|
130
|
+
const password = requireEnv("TWENTY_REP_PASSWORD");
|
|
131
|
+
const emailDomain =
|
|
132
|
+
process.env.TWENTY_REP_EMAIL_DOMAIN ?? "texasenterprises.com";
|
|
133
|
+
|
|
134
|
+
const context = await readWorkspaceContext(
|
|
135
|
+
twentyDbUrl,
|
|
136
|
+
process.env.TWENTY_WORKSPACE_ID,
|
|
137
|
+
);
|
|
138
|
+
log(
|
|
139
|
+
`workspace ${context.workspaceId} schema ${context.workspaceSchema} role ${context.roleId}`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const reader = createLastmileReader(lastmileUrl);
|
|
143
|
+
let reps: ProvisionRep[];
|
|
144
|
+
try {
|
|
145
|
+
const sourceReps = await reader.readReps();
|
|
146
|
+
const seen = new Set<string>();
|
|
147
|
+
reps = [];
|
|
148
|
+
let derived = 0;
|
|
149
|
+
for (const rep of sourceReps) {
|
|
150
|
+
if (rep.archived) continue;
|
|
151
|
+
const email =
|
|
152
|
+
rep.email ?? deriveRepEmail(rep.firstName, rep.lastName, emailDomain);
|
|
153
|
+
if (!email) continue; // house/intercompany/placeholder rows get no login
|
|
154
|
+
if (!rep.email) derived += 1;
|
|
155
|
+
// Two rep rows can share an email (a house row reusing a real address,
|
|
156
|
+
// or one person duplicated). Email is the login identity: one user.
|
|
157
|
+
if (seen.has(email)) continue;
|
|
158
|
+
seen.add(email);
|
|
159
|
+
reps.push({
|
|
160
|
+
repId: rep.id,
|
|
161
|
+
email,
|
|
162
|
+
firstName: rep.firstName,
|
|
163
|
+
lastName: rep.lastName,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
log(
|
|
167
|
+
`reps: ${reps.length} provisionable (${derived} with derived @${emailDomain} emails), ` +
|
|
168
|
+
`${sourceReps.filter((rep) => !rep.archived).length - reps.length} unprovisionable`,
|
|
169
|
+
);
|
|
170
|
+
} finally {
|
|
171
|
+
await reader.close();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (only) {
|
|
175
|
+
const target = only.toLowerCase();
|
|
176
|
+
reps = reps.filter((rep) => rep.email.toLowerCase() === target);
|
|
177
|
+
if (reps.length === 0) throw new Error(`--only ${only} matched no rep`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = await provisionMembers({
|
|
181
|
+
databaseUrl: twentyDbUrl,
|
|
182
|
+
workspaceId: context.workspaceId,
|
|
183
|
+
roleId: context.roleId,
|
|
184
|
+
applicationId: context.applicationId,
|
|
185
|
+
workspaceSchema: context.workspaceSchema,
|
|
186
|
+
password,
|
|
187
|
+
reps,
|
|
188
|
+
dryRun,
|
|
189
|
+
log,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
process.stdout.write(
|
|
193
|
+
`${JSON.stringify(
|
|
194
|
+
{
|
|
195
|
+
mode: dryRun ? "dry-run" : "apply",
|
|
196
|
+
workspaceId: context.workspaceId,
|
|
197
|
+
created: result.created,
|
|
198
|
+
existing: result.existing,
|
|
199
|
+
failed: result.failed,
|
|
200
|
+
rows: result.rows.slice(0, 200),
|
|
201
|
+
},
|
|
202
|
+
null,
|
|
203
|
+
2,
|
|
204
|
+
)}\n`,
|
|
205
|
+
);
|
|
206
|
+
if (result.failed > 0) process.exitCode = 2;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
main().catch((error) => {
|
|
210
|
+
process.stderr.write(
|
|
211
|
+
`${error instanceof Error ? error.message : String(error)}\n`,
|
|
212
|
+
);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
|
@@ -255,5 +255,104 @@ export const twentyManifest = {
|
|
|
255
255
|
},
|
|
256
256
|
],
|
|
257
257
|
},
|
|
258
|
+
{
|
|
259
|
+
version: "0.3.1",
|
|
260
|
+
requiredOauthScopes: [],
|
|
261
|
+
components: [
|
|
262
|
+
{
|
|
263
|
+
type: "mcp-server",
|
|
264
|
+
key: "crm",
|
|
265
|
+
displayName: "Twenty CRM",
|
|
266
|
+
description:
|
|
267
|
+
"CRM records (people, companies, opportunities, notes) on the tenant's deployed Twenty instance.",
|
|
268
|
+
endpointFrom: {
|
|
269
|
+
managedApp: "twenty",
|
|
270
|
+
configKey: "publicUrl",
|
|
271
|
+
path: "/mcp",
|
|
272
|
+
},
|
|
273
|
+
auth: { mode: "oauth-per-instance" },
|
|
274
|
+
recordLinkHints: {
|
|
275
|
+
schemaVersion: 1,
|
|
276
|
+
source: "plugin-manifest",
|
|
277
|
+
routes: [
|
|
278
|
+
{
|
|
279
|
+
objectType: "opportunity",
|
|
280
|
+
routeTemplate: "/object/opportunity/{id}",
|
|
281
|
+
idFields: [
|
|
282
|
+
"id",
|
|
283
|
+
"opportunityId",
|
|
284
|
+
"record.id",
|
|
285
|
+
"opportunity.id",
|
|
286
|
+
],
|
|
287
|
+
labelFields: [
|
|
288
|
+
"name",
|
|
289
|
+
"opportunityName",
|
|
290
|
+
"record.name",
|
|
291
|
+
"opportunity.name",
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
resultTransforms: [
|
|
297
|
+
{
|
|
298
|
+
type: "scaled-integer-to-decimal",
|
|
299
|
+
sourceField: "amountMicros",
|
|
300
|
+
targetField: "value",
|
|
301
|
+
scale: 6,
|
|
302
|
+
removeSource: true,
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
toolNotes: [
|
|
306
|
+
"Twenty's MCP server exposes CRM object tools (people, companies, opportunities, tasks, notes); list tools first — the set varies by Twenty release.",
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: "infrastructure",
|
|
311
|
+
key: "runtime",
|
|
312
|
+
managedAppKey: "twenty",
|
|
313
|
+
terraformInputs: {
|
|
314
|
+
imageUri: {
|
|
315
|
+
description:
|
|
316
|
+
"Twenty CRM container image URI pinned with @sha256.",
|
|
317
|
+
type: "string",
|
|
318
|
+
},
|
|
319
|
+
dbUrlSecretArn: {
|
|
320
|
+
description: "Secrets Manager ARN containing PG_DATABASE_URL.",
|
|
321
|
+
type: "string",
|
|
322
|
+
},
|
|
323
|
+
encryptionKeySecretArn: {
|
|
324
|
+
description: "Secrets Manager ARN containing ENCRYPTION_KEY.",
|
|
325
|
+
type: "string",
|
|
326
|
+
},
|
|
327
|
+
publicUrl: {
|
|
328
|
+
description: "Public HTTPS origin for Twenty CRM.",
|
|
329
|
+
type: "string",
|
|
330
|
+
},
|
|
331
|
+
certificateArn: {
|
|
332
|
+
description: "ACM certificate ARN for the public HTTPS listener.",
|
|
333
|
+
type: "string",
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
type: "ui-surface",
|
|
339
|
+
key: "client-engagement",
|
|
340
|
+
displayName: "Client Engagement",
|
|
341
|
+
intendedMount: "apps.main",
|
|
342
|
+
launch: {
|
|
343
|
+
schemaVersion: 1,
|
|
344
|
+
type: "app",
|
|
345
|
+
appKey: "twenty-client-engagement",
|
|
346
|
+
routeSegment: "client-engagement",
|
|
347
|
+
mount: "main-shell",
|
|
348
|
+
runtime: "trusted-bundled-react",
|
|
349
|
+
description:
|
|
350
|
+
"Account and opportunity engagement workspace for Twenty CRM records.",
|
|
351
|
+
icon: "layout-dashboard",
|
|
352
|
+
entitlementProductKey: "twenty-client-engagement",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
258
357
|
],
|
|
259
358
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Twenty CRM App Module
|
|
2
2
|
|
|
3
3
|
This module provisions the optional Twenty CRM managed application substrate for
|
|
4
|
-
ThinkWork. It follows the
|
|
4
|
+
ThinkWork. It follows the managed optional-app shape but uses a public HTTPS ALB,
|
|
5
5
|
separate ECS services for the Twenty server and worker, a dedicated PostgreSQL
|
|
6
6
|
database URL injected from Secrets Manager, EFS for local server storage, and
|
|
7
7
|
ElastiCache for Valkey/Redis OSS.
|
|
@@ -108,6 +108,7 @@ describe("twenty plugin manifest", () => {
|
|
|
108
108
|
"0.1.0",
|
|
109
109
|
"0.2.0",
|
|
110
110
|
"0.3.0",
|
|
111
|
+
"0.3.1",
|
|
111
112
|
]);
|
|
112
113
|
|
|
113
114
|
const components = latestVersion(validated).components;
|
|
@@ -162,7 +163,7 @@ describe("twenty plugin manifest", () => {
|
|
|
162
163
|
},
|
|
163
164
|
],
|
|
164
165
|
});
|
|
165
|
-
expect(latestVersion(validated).version).toBe("0.3.
|
|
166
|
+
expect(latestVersion(validated).version).toBe("0.3.1");
|
|
166
167
|
expect(latestMcp.recordLinkHints).toEqual({
|
|
167
168
|
schemaVersion: 1,
|
|
168
169
|
source: "plugin-manifest",
|
|
@@ -180,6 +181,15 @@ describe("twenty plugin manifest", () => {
|
|
|
180
181
|
},
|
|
181
182
|
],
|
|
182
183
|
});
|
|
184
|
+
expect(latestMcp.resultTransforms).toEqual([
|
|
185
|
+
{
|
|
186
|
+
type: "scaled-integer-to-decimal",
|
|
187
|
+
sourceField: "amountMicros",
|
|
188
|
+
targetField: "value",
|
|
189
|
+
scale: 6,
|
|
190
|
+
removeSource: true,
|
|
191
|
+
},
|
|
192
|
+
]);
|
|
183
193
|
});
|
|
184
194
|
|
|
185
195
|
it("publishes a launchable Client Engagement app surface in the latest version", () => {
|
|
@@ -457,7 +467,7 @@ describe("twenty plugin manifest", () => {
|
|
|
457
467
|
validatedTwentyManifest.versions.map(
|
|
458
468
|
(version) => version.requiredOauthScopes,
|
|
459
469
|
),
|
|
460
|
-
).toEqual([[], [], []]);
|
|
470
|
+
).toEqual([[], [], [], []]);
|
|
461
471
|
expect(() => validatePluginManifest(twentyManifest)).not.toThrow();
|
|
462
472
|
});
|
|
463
473
|
});
|