@base44-preview/cli 0.0.19-pr.104.3636d73 → 0.0.19-pr.112.10b56a5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +214 -806
- package/dist/templates/templates.json +6 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import O from "node:readline";
|
|
|
10
10
|
import Stream, { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
|
|
11
11
|
import os, { constants, homedir, tmpdir } from "node:os";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
-
import fs$1, { access, constants as constants$1, copyFile, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
13
|
+
import fs$1, { access, constants as constants$1, copyFile, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
14
14
|
import { finished } from "node:stream/promises";
|
|
15
15
|
import path$1, { dirname as dirname$1, parse } from "path";
|
|
16
16
|
import EE, { EventEmitter as EventEmitter$1 } from "events";
|
|
@@ -5818,97 +5818,6 @@ function handleTupleResult(result, final, index) {
|
|
|
5818
5818
|
if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
|
|
5819
5819
|
final.value[index] = result.value;
|
|
5820
5820
|
}
|
|
5821
|
-
const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
5822
|
-
$ZodType.init(inst, def);
|
|
5823
|
-
inst._zod.parse = (payload, ctx) => {
|
|
5824
|
-
const input = payload.value;
|
|
5825
|
-
if (!isPlainObject$1(input)) {
|
|
5826
|
-
payload.issues.push({
|
|
5827
|
-
expected: "record",
|
|
5828
|
-
code: "invalid_type",
|
|
5829
|
-
input,
|
|
5830
|
-
inst
|
|
5831
|
-
});
|
|
5832
|
-
return payload;
|
|
5833
|
-
}
|
|
5834
|
-
const proms = [];
|
|
5835
|
-
const values = def.keyType._zod.values;
|
|
5836
|
-
if (values) {
|
|
5837
|
-
payload.value = {};
|
|
5838
|
-
const recordKeys = /* @__PURE__ */ new Set();
|
|
5839
|
-
for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
5840
|
-
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
5841
|
-
const result = def.valueType._zod.run({
|
|
5842
|
-
value: input[key],
|
|
5843
|
-
issues: []
|
|
5844
|
-
}, ctx);
|
|
5845
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5846
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5847
|
-
payload.value[key] = result$1.value;
|
|
5848
|
-
}));
|
|
5849
|
-
else {
|
|
5850
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5851
|
-
payload.value[key] = result.value;
|
|
5852
|
-
}
|
|
5853
|
-
}
|
|
5854
|
-
let unrecognized;
|
|
5855
|
-
for (const key in input) if (!recordKeys.has(key)) {
|
|
5856
|
-
unrecognized = unrecognized ?? [];
|
|
5857
|
-
unrecognized.push(key);
|
|
5858
|
-
}
|
|
5859
|
-
if (unrecognized && unrecognized.length > 0) payload.issues.push({
|
|
5860
|
-
code: "unrecognized_keys",
|
|
5861
|
-
input,
|
|
5862
|
-
inst,
|
|
5863
|
-
keys: unrecognized
|
|
5864
|
-
});
|
|
5865
|
-
} else {
|
|
5866
|
-
payload.value = {};
|
|
5867
|
-
for (const key of Reflect.ownKeys(input)) {
|
|
5868
|
-
if (key === "__proto__") continue;
|
|
5869
|
-
let keyResult = def.keyType._zod.run({
|
|
5870
|
-
value: key,
|
|
5871
|
-
issues: []
|
|
5872
|
-
}, ctx);
|
|
5873
|
-
if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5874
|
-
if (typeof key === "string" && number$1.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number")) {
|
|
5875
|
-
const retryResult = def.keyType._zod.run({
|
|
5876
|
-
value: Number(key),
|
|
5877
|
-
issues: []
|
|
5878
|
-
}, ctx);
|
|
5879
|
-
if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5880
|
-
if (retryResult.issues.length === 0) keyResult = retryResult;
|
|
5881
|
-
}
|
|
5882
|
-
if (keyResult.issues.length) {
|
|
5883
|
-
if (def.mode === "loose") payload.value[key] = input[key];
|
|
5884
|
-
else payload.issues.push({
|
|
5885
|
-
code: "invalid_key",
|
|
5886
|
-
origin: "record",
|
|
5887
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
5888
|
-
input: key,
|
|
5889
|
-
path: [key],
|
|
5890
|
-
inst
|
|
5891
|
-
});
|
|
5892
|
-
continue;
|
|
5893
|
-
}
|
|
5894
|
-
const result = def.valueType._zod.run({
|
|
5895
|
-
value: input[key],
|
|
5896
|
-
issues: []
|
|
5897
|
-
}, ctx);
|
|
5898
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5899
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5900
|
-
payload.value[keyResult.value] = result$1.value;
|
|
5901
|
-
}));
|
|
5902
|
-
else {
|
|
5903
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5904
|
-
payload.value[keyResult.value] = result.value;
|
|
5905
|
-
}
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
|
-
if (proms.length) return Promise.all(proms).then(() => payload);
|
|
5909
|
-
return payload;
|
|
5910
|
-
};
|
|
5911
|
-
});
|
|
5912
5821
|
const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
5913
5822
|
$ZodType.init(inst, def);
|
|
5914
5823
|
const values = getEnumValues(def.entries);
|
|
@@ -7199,39 +7108,6 @@ const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
7199
7108
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
7200
7109
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
7201
7110
|
};
|
|
7202
|
-
const recordProcessor = (schema, ctx, _json, params) => {
|
|
7203
|
-
const json = _json;
|
|
7204
|
-
const def = schema._zod.def;
|
|
7205
|
-
json.type = "object";
|
|
7206
|
-
const keyType = def.keyType;
|
|
7207
|
-
const patterns = keyType._zod.bag?.patterns;
|
|
7208
|
-
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
7209
|
-
const valueSchema = process$2(def.valueType, ctx, {
|
|
7210
|
-
...params,
|
|
7211
|
-
path: [
|
|
7212
|
-
...params.path,
|
|
7213
|
-
"patternProperties",
|
|
7214
|
-
"*"
|
|
7215
|
-
]
|
|
7216
|
-
});
|
|
7217
|
-
json.patternProperties = {};
|
|
7218
|
-
for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
|
|
7219
|
-
} else {
|
|
7220
|
-
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$2(def.keyType, ctx, {
|
|
7221
|
-
...params,
|
|
7222
|
-
path: [...params.path, "propertyNames"]
|
|
7223
|
-
});
|
|
7224
|
-
json.additionalProperties = process$2(def.valueType, ctx, {
|
|
7225
|
-
...params,
|
|
7226
|
-
path: [...params.path, "additionalProperties"]
|
|
7227
|
-
});
|
|
7228
|
-
}
|
|
7229
|
-
const keyValues = keyType._zod.values;
|
|
7230
|
-
if (keyValues) {
|
|
7231
|
-
const validKeyValues = [...keyValues].filter((v$1) => typeof v$1 === "string" || typeof v$1 === "number");
|
|
7232
|
-
if (validKeyValues.length > 0) json.required = validKeyValues;
|
|
7233
|
-
}
|
|
7234
|
-
};
|
|
7235
7111
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
7236
7112
|
const def = schema._zod.def;
|
|
7237
7113
|
const inner = process$2(def.innerType, ctx, params);
|
|
@@ -7761,21 +7637,6 @@ function tuple(items, _paramsOrRest, _params) {
|
|
|
7761
7637
|
...normalizeParams(params)
|
|
7762
7638
|
});
|
|
7763
7639
|
}
|
|
7764
|
-
const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
7765
|
-
$ZodRecord.init(inst, def);
|
|
7766
|
-
ZodType.init(inst, def);
|
|
7767
|
-
inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);
|
|
7768
|
-
inst.keyType = def.keyType;
|
|
7769
|
-
inst.valueType = def.valueType;
|
|
7770
|
-
});
|
|
7771
|
-
function record(keyType, valueType, params) {
|
|
7772
|
-
return new ZodRecord({
|
|
7773
|
-
type: "record",
|
|
7774
|
-
keyType,
|
|
7775
|
-
valueType,
|
|
7776
|
-
...normalizeParams(params)
|
|
7777
|
-
});
|
|
7778
|
-
}
|
|
7779
7640
|
const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
7780
7641
|
$ZodEnum.init(inst, def);
|
|
7781
7642
|
ZodType.init(inst, def);
|
|
@@ -8042,6 +7903,7 @@ var AuthValidationError = class extends Error {
|
|
|
8042
7903
|
//#endregion
|
|
8043
7904
|
//#region src/core/consts.ts
|
|
8044
7905
|
const PROJECT_SUBDIR = "base44";
|
|
7906
|
+
const CONFIG_FILE_EXTENSION = "jsonc";
|
|
8045
7907
|
const CONFIG_FILE_EXTENSION_GLOB = "{json,jsonc}";
|
|
8046
7908
|
const FUNCTION_CONFIG_FILE = `function.${CONFIG_FILE_EXTENSION_GLOB}`;
|
|
8047
7909
|
const APP_CONFIG_PATTERN = `**/.app.${CONFIG_FILE_EXTENSION_GLOB}`;
|
|
@@ -9920,6 +9782,9 @@ async function deleteFile(filePath) {
|
|
|
9920
9782
|
if (!await pathExists(filePath)) return;
|
|
9921
9783
|
await unlink(filePath);
|
|
9922
9784
|
}
|
|
9785
|
+
async function isDirEmpty(dir = process.cwd()) {
|
|
9786
|
+
return (await readdir(dir)).length === 0;
|
|
9787
|
+
}
|
|
9923
9788
|
|
|
9924
9789
|
//#endregion
|
|
9925
9790
|
//#region src/core/auth/config.ts
|
|
@@ -16820,6 +16685,125 @@ const functionResource = {
|
|
|
16820
16685
|
push: pushFunctions
|
|
16821
16686
|
};
|
|
16822
16687
|
|
|
16688
|
+
//#endregion
|
|
16689
|
+
//#region src/core/resources/agent/schema.ts
|
|
16690
|
+
const EntityToolConfigSchema = object({
|
|
16691
|
+
entity_name: string().min(1),
|
|
16692
|
+
allowed_operations: array(_enum([
|
|
16693
|
+
"read",
|
|
16694
|
+
"create",
|
|
16695
|
+
"update",
|
|
16696
|
+
"delete"
|
|
16697
|
+
])).default([])
|
|
16698
|
+
});
|
|
16699
|
+
const BackendFunctionToolConfigSchema = object({
|
|
16700
|
+
function_name: string().min(1),
|
|
16701
|
+
description: string().default("agent backend function")
|
|
16702
|
+
});
|
|
16703
|
+
const ToolConfigSchema = union([EntityToolConfigSchema, BackendFunctionToolConfigSchema]);
|
|
16704
|
+
const AgentConfigSchema = object({
|
|
16705
|
+
name: string().regex(/^[a-z0-9_]+$/, "Agent name must be lowercase alphanumeric with underscores").min(1).max(100),
|
|
16706
|
+
description: string().min(1, "Agent description cannot be empty"),
|
|
16707
|
+
instructions: string().min(1, "Agent instructions cannot be empty"),
|
|
16708
|
+
tool_configs: array(ToolConfigSchema).default([]),
|
|
16709
|
+
whatsapp_greeting: string().nullable().optional()
|
|
16710
|
+
});
|
|
16711
|
+
const SyncAgentsResponseSchema = object({
|
|
16712
|
+
created: array(string()),
|
|
16713
|
+
updated: array(string()),
|
|
16714
|
+
deleted: array(string())
|
|
16715
|
+
});
|
|
16716
|
+
const AgentConfigApiResponseSchema = object({
|
|
16717
|
+
name: string(),
|
|
16718
|
+
description: string(),
|
|
16719
|
+
instructions: string(),
|
|
16720
|
+
tool_configs: array(ToolConfigSchema).default([]),
|
|
16721
|
+
whatsapp_greeting: string().nullable().optional()
|
|
16722
|
+
});
|
|
16723
|
+
const ListAgentsResponseSchema = object({
|
|
16724
|
+
items: array(AgentConfigApiResponseSchema),
|
|
16725
|
+
total: number()
|
|
16726
|
+
});
|
|
16727
|
+
|
|
16728
|
+
//#endregion
|
|
16729
|
+
//#region src/core/resources/agent/config.ts
|
|
16730
|
+
async function readAgentFile(agentPath) {
|
|
16731
|
+
const parsed = await readJsonFile(agentPath);
|
|
16732
|
+
const result = AgentConfigSchema.safeParse(parsed);
|
|
16733
|
+
if (!result.success) throw new Error(`Invalid agent configuration in ${agentPath}: ${result.error.issues.map((e$1) => e$1.message).join(", ")}`);
|
|
16734
|
+
return result.data;
|
|
16735
|
+
}
|
|
16736
|
+
async function readAllAgents(agentsDir) {
|
|
16737
|
+
if (!await pathExists(agentsDir)) return [];
|
|
16738
|
+
const files = await globby(`*.${CONFIG_FILE_EXTENSION_GLOB}`, {
|
|
16739
|
+
cwd: agentsDir,
|
|
16740
|
+
absolute: true
|
|
16741
|
+
});
|
|
16742
|
+
const agents = await Promise.all(files.map((filePath) => readAgentFile(filePath)));
|
|
16743
|
+
const names = /* @__PURE__ */ new Set();
|
|
16744
|
+
for (const agent of agents) {
|
|
16745
|
+
if (names.has(agent.name)) throw new Error(`Duplicate agent name "${agent.name}"`);
|
|
16746
|
+
names.add(agent.name);
|
|
16747
|
+
}
|
|
16748
|
+
return agents;
|
|
16749
|
+
}
|
|
16750
|
+
async function writeAgents(agentsDir, agents) {
|
|
16751
|
+
const existingAgents = await readAllAgents(agentsDir);
|
|
16752
|
+
const newNames = new Set(agents.map((a$1) => a$1.name));
|
|
16753
|
+
const toDelete = existingAgents.filter((a$1) => !newNames.has(a$1.name));
|
|
16754
|
+
for (const agent of toDelete) await deleteFile(join(agentsDir, `${agent.name}.${CONFIG_FILE_EXTENSION}`));
|
|
16755
|
+
for (const agent of agents) await writeJsonFile(join(agentsDir, `${agent.name}.${CONFIG_FILE_EXTENSION}`), {
|
|
16756
|
+
name: agent.name,
|
|
16757
|
+
description: agent.description,
|
|
16758
|
+
instructions: agent.instructions,
|
|
16759
|
+
tool_configs: agent.tool_configs,
|
|
16760
|
+
whatsapp_greeting: agent.whatsapp_greeting ?? null
|
|
16761
|
+
});
|
|
16762
|
+
return {
|
|
16763
|
+
written: agents.map((a$1) => a$1.name),
|
|
16764
|
+
deleted: toDelete.map((a$1) => a$1.name)
|
|
16765
|
+
};
|
|
16766
|
+
}
|
|
16767
|
+
|
|
16768
|
+
//#endregion
|
|
16769
|
+
//#region src/core/resources/agent/api.ts
|
|
16770
|
+
async function pushAgents(agents) {
|
|
16771
|
+
const appClient = getAppClient();
|
|
16772
|
+
const payload = { configs: agents.map((agent) => ({
|
|
16773
|
+
name: agent.name,
|
|
16774
|
+
description: agent.description,
|
|
16775
|
+
instructions: agent.instructions,
|
|
16776
|
+
tool_configs: agent.tool_configs,
|
|
16777
|
+
whatsapp_greeting: agent.whatsapp_greeting ?? null
|
|
16778
|
+
})) };
|
|
16779
|
+
const response = await appClient.put("agent-configs", {
|
|
16780
|
+
json: payload,
|
|
16781
|
+
throwHttpErrors: false
|
|
16782
|
+
});
|
|
16783
|
+
if (!response.ok) {
|
|
16784
|
+
const errorJson = await response.json();
|
|
16785
|
+
const errorMessage = errorJson.detail || errorJson.message || "Unknown error";
|
|
16786
|
+
throw new Error(`Error occurred while syncing agents: ${errorMessage}`);
|
|
16787
|
+
}
|
|
16788
|
+
return SyncAgentsResponseSchema.parse(await response.json());
|
|
16789
|
+
}
|
|
16790
|
+
async function fetchAgents() {
|
|
16791
|
+
const response = await getAppClient().get("agent-configs", { throwHttpErrors: false });
|
|
16792
|
+
if (!response.ok) {
|
|
16793
|
+
const errorJson = await response.json();
|
|
16794
|
+
const errorMessage = errorJson.detail || errorJson.message || "Unknown error";
|
|
16795
|
+
throw new Error(`Error occurred while fetching agents: ${errorMessage}`);
|
|
16796
|
+
}
|
|
16797
|
+
return ListAgentsResponseSchema.parse(await response.json());
|
|
16798
|
+
}
|
|
16799
|
+
|
|
16800
|
+
//#endregion
|
|
16801
|
+
//#region src/core/resources/agent/resource.ts
|
|
16802
|
+
const agentResource = {
|
|
16803
|
+
readAll: readAllAgents,
|
|
16804
|
+
push: pushAgents
|
|
16805
|
+
};
|
|
16806
|
+
|
|
16823
16807
|
//#endregion
|
|
16824
16808
|
//#region src/core/project/schema.ts
|
|
16825
16809
|
const TemplateSchema = object({
|
|
@@ -16835,15 +16819,13 @@ const SiteConfigSchema = object({
|
|
|
16835
16819
|
outputDirectory: string().optional(),
|
|
16836
16820
|
installCommand: string().optional()
|
|
16837
16821
|
});
|
|
16838
|
-
const ConnectorConfigSchema = object({ scopes: array(string()).optional() });
|
|
16839
|
-
const ConnectorsConfigSchema = record(string(), ConnectorConfigSchema);
|
|
16840
16822
|
const ProjectConfigSchema = object({
|
|
16841
16823
|
name: string().min(1, "App name cannot be empty"),
|
|
16842
16824
|
description: string().optional(),
|
|
16843
16825
|
site: SiteConfigSchema.optional(),
|
|
16844
|
-
connectors: ConnectorsConfigSchema.optional(),
|
|
16845
16826
|
entitiesDir: string().optional().default("entities"),
|
|
16846
|
-
functionsDir: string().optional().default("functions")
|
|
16827
|
+
functionsDir: string().optional().default("functions"),
|
|
16828
|
+
agentsDir: string().optional().default("agents")
|
|
16847
16829
|
});
|
|
16848
16830
|
const AppConfigSchema = object({ id: string().min(1, "id cannot be empty") });
|
|
16849
16831
|
const CreateProjectResponseSchema = looseObject({ id: string() });
|
|
@@ -16915,7 +16897,11 @@ async function readProjectConfig(projectRoot) {
|
|
|
16915
16897
|
if (!result.success) throw new Error(`Invalid project configuration: ${result.error.message}`);
|
|
16916
16898
|
const project = result.data;
|
|
16917
16899
|
const configDir = dirname(configPath);
|
|
16918
|
-
const [entities, functions] = await Promise.all([
|
|
16900
|
+
const [entities, functions, agents] = await Promise.all([
|
|
16901
|
+
entityResource.readAll(join(configDir, project.entitiesDir)),
|
|
16902
|
+
functionResource.readAll(join(configDir, project.functionsDir)),
|
|
16903
|
+
agentResource.readAll(join(configDir, project.agentsDir))
|
|
16904
|
+
]);
|
|
16919
16905
|
return {
|
|
16920
16906
|
project: {
|
|
16921
16907
|
...project,
|
|
@@ -16923,38 +16909,10 @@ async function readProjectConfig(projectRoot) {
|
|
|
16923
16909
|
configPath
|
|
16924
16910
|
},
|
|
16925
16911
|
entities,
|
|
16926
|
-
functions
|
|
16912
|
+
functions,
|
|
16913
|
+
agents
|
|
16927
16914
|
};
|
|
16928
16915
|
}
|
|
16929
|
-
/**
|
|
16930
|
-
* Writes project configuration to the config file.
|
|
16931
|
-
* Updates the config file while preserving its location and format.
|
|
16932
|
-
*
|
|
16933
|
-
* @param updates - Partial project config to merge with existing config
|
|
16934
|
-
* @param projectRoot - Optional path to start searching from. Defaults to cwd.
|
|
16935
|
-
* @returns The path to the updated config file
|
|
16936
|
-
* @throws {Error} If no config file is found
|
|
16937
|
-
*
|
|
16938
|
-
* @example
|
|
16939
|
-
* await writeProjectConfig({ connectors: { slack: {} } });
|
|
16940
|
-
*/
|
|
16941
|
-
async function writeProjectConfig(updates, projectRoot) {
|
|
16942
|
-
let found;
|
|
16943
|
-
if (projectRoot) {
|
|
16944
|
-
const configPath$1 = await findConfigInDir(projectRoot);
|
|
16945
|
-
found = configPath$1 ? {
|
|
16946
|
-
root: projectRoot,
|
|
16947
|
-
configPath: configPath$1
|
|
16948
|
-
} : null;
|
|
16949
|
-
} else found = await findProjectRoot();
|
|
16950
|
-
if (!found) throw new Error(`Project root not found. Please ensure config.jsonc or config.json exists in the project directory or ${PROJECT_SUBDIR}/ subdirectory.`);
|
|
16951
|
-
const { configPath } = found;
|
|
16952
|
-
await writeJsonFile(configPath, {
|
|
16953
|
-
...await readJsonFile(configPath),
|
|
16954
|
-
...updates
|
|
16955
|
-
});
|
|
16956
|
-
return configPath;
|
|
16957
|
-
}
|
|
16958
16916
|
|
|
16959
16917
|
//#endregion
|
|
16960
16918
|
//#region src/core/project/api.ts
|
|
@@ -31191,10 +31149,7 @@ const theme = {
|
|
|
31191
31149
|
base44OrangeBackground: source_default.bgHex("#E86B3C"),
|
|
31192
31150
|
shinyOrange: source_default.hex("#FFD700"),
|
|
31193
31151
|
links: source_default.hex("#00D4FF"),
|
|
31194
|
-
white: source_default.white
|
|
31195
|
-
success: source_default.green,
|
|
31196
|
-
warning: source_default.yellow,
|
|
31197
|
-
error: source_default.red
|
|
31152
|
+
white: source_default.white
|
|
31198
31153
|
},
|
|
31199
31154
|
styles: {
|
|
31200
31155
|
header: source_default.dim,
|
|
@@ -31526,6 +31481,52 @@ const entitiesPushCommand = new Command("entities").description("Manage project
|
|
|
31526
31481
|
await runCommand(pushEntitiesAction, { requireAuth: true });
|
|
31527
31482
|
}));
|
|
31528
31483
|
|
|
31484
|
+
//#endregion
|
|
31485
|
+
//#region src/cli/commands/agents/pull.ts
|
|
31486
|
+
async function pullAgentsAction() {
|
|
31487
|
+
const { project } = await readProjectConfig();
|
|
31488
|
+
const agentsDir = join(dirname(project.configPath), project.agentsDir);
|
|
31489
|
+
const response = await runTask("Fetching agents from Base44", async () => {
|
|
31490
|
+
return await fetchAgents();
|
|
31491
|
+
}, {
|
|
31492
|
+
successMessage: "Agents fetched successfully",
|
|
31493
|
+
errorMessage: "Failed to fetch agents"
|
|
31494
|
+
});
|
|
31495
|
+
if (response.items.length === 0) return { outroMessage: "No agents found on Base44" };
|
|
31496
|
+
const { written, deleted } = await runTask("Writing agent files", async () => {
|
|
31497
|
+
return await writeAgents(agentsDir, response.items);
|
|
31498
|
+
}, {
|
|
31499
|
+
successMessage: "Agent files written successfully",
|
|
31500
|
+
errorMessage: "Failed to write agent files"
|
|
31501
|
+
});
|
|
31502
|
+
if (written.length > 0) M.success(`Written: ${written.join(", ")}`);
|
|
31503
|
+
if (deleted.length > 0) M.warn(`Deleted: ${deleted.join(", ")}`);
|
|
31504
|
+
return { outroMessage: `Pulled ${response.total} agents to ${agentsDir}` };
|
|
31505
|
+
}
|
|
31506
|
+
const agentsPullCommand = new Command("pull").description("Pull agents from Base44 to local files (replaces all local agent configs)").action(async () => {
|
|
31507
|
+
await runCommand(pullAgentsAction, { requireAuth: true });
|
|
31508
|
+
});
|
|
31509
|
+
|
|
31510
|
+
//#endregion
|
|
31511
|
+
//#region src/cli/commands/agents/push.ts
|
|
31512
|
+
async function pushAgentsAction() {
|
|
31513
|
+
const { agents } = await readProjectConfig();
|
|
31514
|
+
M.info(agents.length === 0 ? "No local agents found - this will delete all remote agents" : `Found ${agents.length} agents to push`);
|
|
31515
|
+
const result = await runTask("Pushing agents to Base44", async () => {
|
|
31516
|
+
return await pushAgents(agents);
|
|
31517
|
+
}, {
|
|
31518
|
+
successMessage: "Agents pushed successfully",
|
|
31519
|
+
errorMessage: "Failed to push agents"
|
|
31520
|
+
});
|
|
31521
|
+
if (result.created.length > 0) M.success(`Created: ${result.created.join(", ")}`);
|
|
31522
|
+
if (result.updated.length > 0) M.success(`Updated: ${result.updated.join(", ")}`);
|
|
31523
|
+
if (result.deleted.length > 0) M.warn(`Deleted: ${result.deleted.join(", ")}`);
|
|
31524
|
+
return {};
|
|
31525
|
+
}
|
|
31526
|
+
const agentsCommand = new Command("agents").description("Manage project agents").addCommand(new Command("push").description("Push local agents to Base44 (replaces all remote agent configs)").action(async () => {
|
|
31527
|
+
await runCommand(pushAgentsAction, { requireAuth: true });
|
|
31528
|
+
})).addCommand(agentsPullCommand);
|
|
31529
|
+
|
|
31529
31530
|
//#endregion
|
|
31530
31531
|
//#region src/cli/commands/functions/deploy.ts
|
|
31531
31532
|
async function deployFunctionsAction() {
|
|
@@ -38229,9 +38230,8 @@ async function getTemplateById(templateId) {
|
|
|
38229
38230
|
return template;
|
|
38230
38231
|
}
|
|
38231
38232
|
function validateNonInteractiveFlags$1(command) {
|
|
38232
|
-
const {
|
|
38233
|
-
|
|
38234
|
-
if (providedCount > 0 && providedCount < 2) command.error("Non-interactive mode requires all flags: --name, --path");
|
|
38233
|
+
const { path: path$16 } = command.opts();
|
|
38234
|
+
if (path$16 && !command.args.length) command.error("Non-interactive mode requires all flags: --name, --path");
|
|
38235
38235
|
}
|
|
38236
38236
|
async function chooseCreate(options) {
|
|
38237
38237
|
if (!!(options.name && options.path)) await runCommand(() => createNonInteractive(options), {
|
|
@@ -38255,19 +38255,18 @@ async function createInteractive(options) {
|
|
|
38255
38255
|
message: "Pick a template",
|
|
38256
38256
|
options: templateOptions
|
|
38257
38257
|
}),
|
|
38258
|
-
name: () =>
|
|
38259
|
-
|
|
38260
|
-
|
|
38261
|
-
|
|
38262
|
-
|
|
38263
|
-
|
|
38264
|
-
|
|
38265
|
-
|
|
38266
|
-
|
|
38267
|
-
|
|
38268
|
-
}),
|
|
38258
|
+
name: () => {
|
|
38259
|
+
return options.name ? Promise.resolve(options.name) : he({
|
|
38260
|
+
message: "What is the name of your project?",
|
|
38261
|
+
placeholder: basename(process.cwd()),
|
|
38262
|
+
initialValue: basename(process.cwd()),
|
|
38263
|
+
validate: (value) => {
|
|
38264
|
+
if (!value || value.trim().length === 0) return "Every project deserves a name";
|
|
38265
|
+
}
|
|
38266
|
+
});
|
|
38267
|
+
},
|
|
38269
38268
|
projectPath: async ({ results }) => {
|
|
38270
|
-
const suggestedPath = `./${(0, import_lodash.default)(results.name)}`;
|
|
38269
|
+
const suggestedPath = await isDirEmpty() ? `./` : `./${(0, import_lodash.default)(results.name)}`;
|
|
38271
38270
|
return he({
|
|
38272
38271
|
message: "Where should we create your project?",
|
|
38273
38272
|
placeholder: suggestedPath,
|
|
@@ -38278,7 +38277,6 @@ async function createInteractive(options) {
|
|
|
38278
38277
|
return await executeCreate({
|
|
38279
38278
|
template: result.template,
|
|
38280
38279
|
name: result.name,
|
|
38281
|
-
description: result.description || void 0,
|
|
38282
38280
|
projectPath: result.projectPath,
|
|
38283
38281
|
deploy: options.deploy,
|
|
38284
38282
|
skills: options.skills,
|
|
@@ -38289,7 +38287,6 @@ async function createNonInteractive(options) {
|
|
|
38289
38287
|
return await executeCreate({
|
|
38290
38288
|
template: await getTemplateById(options.template ?? DEFAULT_TEMPLATE_ID),
|
|
38291
38289
|
name: options.name,
|
|
38292
|
-
description: options.description,
|
|
38293
38290
|
projectPath: options.path,
|
|
38294
38291
|
deploy: options.deploy,
|
|
38295
38292
|
skills: options.skills,
|
|
@@ -38356,32 +38353,32 @@ async function executeCreate({ template, name: rawName, description, projectPath
|
|
|
38356
38353
|
finalAppUrl = appUrl;
|
|
38357
38354
|
}
|
|
38358
38355
|
}
|
|
38359
|
-
|
|
38360
|
-
|
|
38361
|
-
|
|
38362
|
-
|
|
38363
|
-
|
|
38364
|
-
|
|
38365
|
-
|
|
38366
|
-
|
|
38367
|
-
|
|
38368
|
-
|
|
38369
|
-
|
|
38370
|
-
|
|
38371
|
-
|
|
38372
|
-
|
|
38356
|
+
if (skills ?? true) try {
|
|
38357
|
+
await runTask("Installing AI agent skills...", async () => {
|
|
38358
|
+
await execa("npx", [
|
|
38359
|
+
"-y",
|
|
38360
|
+
"add-skill",
|
|
38361
|
+
"base44/skills",
|
|
38362
|
+
"-y"
|
|
38363
|
+
], {
|
|
38364
|
+
cwd: resolvedPath,
|
|
38365
|
+
shell: true
|
|
38366
|
+
});
|
|
38367
|
+
}, {
|
|
38368
|
+
successMessage: theme.colors.base44Orange("AI agent skills added successfully"),
|
|
38369
|
+
errorMessage: "Failed to add AI agent skills - you can add them later with: npx add-skill base44/skills"
|
|
38373
38370
|
});
|
|
38374
|
-
}
|
|
38375
|
-
successMessage: theme.colors.base44Orange("AI agent skills added successfully"),
|
|
38376
|
-
errorMessage: "Failed to add AI agent skills - you can add them later with: npx add-skill base44/skills"
|
|
38377
|
-
});
|
|
38371
|
+
} catch {}
|
|
38378
38372
|
M.message(`${theme.styles.header("Project")}: ${theme.colors.base44Orange(name$1)}`);
|
|
38379
38373
|
M.message(`${theme.styles.header("Dashboard")}: ${theme.colors.links(getDashboardUrl(projectId))}`);
|
|
38380
38374
|
if (finalAppUrl) M.message(`${theme.styles.header("Site")}: ${theme.colors.links(finalAppUrl)}`);
|
|
38381
38375
|
return { outroMessage: "Your project is set up and ready to use" };
|
|
38382
38376
|
}
|
|
38383
|
-
const createCommand = new Command("create").description("Create a new Base44 project").
|
|
38384
|
-
await chooseCreate(
|
|
38377
|
+
const createCommand = new Command("create").description("Create a new Base44 project").addArgument(new Argument("name", "Project name").argOptional()).option("-p, --path <path>", "Path where to create the project").option("-t, --template <id>", "Template ID (e.g., backend-only, backend-and-client)").option("--deploy", "Build and deploy the site").option("--skills", "Add AI agent skills").hook("preAction", validateNonInteractiveFlags$1).action(async (name$1, options) => {
|
|
38378
|
+
await chooseCreate({
|
|
38379
|
+
name: name$1,
|
|
38380
|
+
...options
|
|
38381
|
+
});
|
|
38385
38382
|
});
|
|
38386
38383
|
|
|
38387
38384
|
//#endregion
|
|
@@ -39104,595 +39101,6 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
|
|
|
39104
39101
|
await runCommand(() => deployAction(options), { requireAuth: true });
|
|
39105
39102
|
}));
|
|
39106
39103
|
|
|
39107
|
-
//#endregion
|
|
39108
|
-
//#region src/core/connectors/schema.ts
|
|
39109
|
-
/**
|
|
39110
|
-
* Response from POST /api/apps/{app_id}/external-auth/initiate
|
|
39111
|
-
*/
|
|
39112
|
-
const InitiateResponseSchema = object({
|
|
39113
|
-
redirect_url: string().nullish(),
|
|
39114
|
-
connection_id: string().nullish(),
|
|
39115
|
-
already_authorized: boolean().nullish(),
|
|
39116
|
-
other_user_email: string().nullish(),
|
|
39117
|
-
error: string().nullish()
|
|
39118
|
-
}).transform((data) => ({
|
|
39119
|
-
redirectUrl: data.redirect_url,
|
|
39120
|
-
connectionId: data.connection_id,
|
|
39121
|
-
alreadyAuthorized: data.already_authorized,
|
|
39122
|
-
otherUserEmail: data.other_user_email,
|
|
39123
|
-
error: data.error
|
|
39124
|
-
}));
|
|
39125
|
-
/**
|
|
39126
|
-
* Response from GET /api/apps/{app_id}/external-auth/status
|
|
39127
|
-
*/
|
|
39128
|
-
const StatusResponseSchema = object({
|
|
39129
|
-
status: _enum([
|
|
39130
|
-
"ACTIVE",
|
|
39131
|
-
"PENDING",
|
|
39132
|
-
"FAILED"
|
|
39133
|
-
]),
|
|
39134
|
-
account_email: string().nullish(),
|
|
39135
|
-
error: string().nullish()
|
|
39136
|
-
}).transform((data) => ({
|
|
39137
|
-
status: data.status,
|
|
39138
|
-
accountEmail: data.account_email,
|
|
39139
|
-
error: data.error
|
|
39140
|
-
}));
|
|
39141
|
-
/**
|
|
39142
|
-
* A connected integration from the list endpoint
|
|
39143
|
-
*/
|
|
39144
|
-
const ConnectorSchema = object({
|
|
39145
|
-
integration_type: string(),
|
|
39146
|
-
status: string(),
|
|
39147
|
-
connected_at: string().nullish(),
|
|
39148
|
-
account_info: object({
|
|
39149
|
-
email: string().nullish(),
|
|
39150
|
-
name: string().nullish()
|
|
39151
|
-
}).nullish()
|
|
39152
|
-
}).transform((data) => ({
|
|
39153
|
-
integrationType: data.integration_type,
|
|
39154
|
-
status: data.status,
|
|
39155
|
-
connectedAt: data.connected_at,
|
|
39156
|
-
accountInfo: data.account_info
|
|
39157
|
-
}));
|
|
39158
|
-
/**
|
|
39159
|
-
* Response from GET /api/apps/{app_id}/external-auth/list
|
|
39160
|
-
*/
|
|
39161
|
-
const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
|
|
39162
|
-
|
|
39163
|
-
//#endregion
|
|
39164
|
-
//#region src/core/connectors/api.ts
|
|
39165
|
-
/**
|
|
39166
|
-
* Initiates OAuth flow for a connector integration.
|
|
39167
|
-
* Returns a redirect URL to open in the browser.
|
|
39168
|
-
*/
|
|
39169
|
-
async function initiateOAuth(integrationType, scopes = null) {
|
|
39170
|
-
const json = await (await getAppClient().post("external-auth/initiate", { json: {
|
|
39171
|
-
integration_type: integrationType,
|
|
39172
|
-
scopes
|
|
39173
|
-
} })).json();
|
|
39174
|
-
return InitiateResponseSchema.parse(json);
|
|
39175
|
-
}
|
|
39176
|
-
/**
|
|
39177
|
-
* Checks the status of an OAuth connection attempt.
|
|
39178
|
-
*/
|
|
39179
|
-
async function checkOAuthStatus(integrationType, connectionId) {
|
|
39180
|
-
const json = await (await getAppClient().get("external-auth/status", { searchParams: {
|
|
39181
|
-
integration_type: integrationType,
|
|
39182
|
-
connection_id: connectionId
|
|
39183
|
-
} })).json();
|
|
39184
|
-
return StatusResponseSchema.parse(json);
|
|
39185
|
-
}
|
|
39186
|
-
/**
|
|
39187
|
-
* Lists all connected integrations for the current app.
|
|
39188
|
-
*/
|
|
39189
|
-
async function listConnectors() {
|
|
39190
|
-
const json = await (await getAppClient().get("external-auth/list")).json();
|
|
39191
|
-
return ListResponseSchema.parse(json).integrations;
|
|
39192
|
-
}
|
|
39193
|
-
/**
|
|
39194
|
-
* Disconnects (soft delete) a connector integration.
|
|
39195
|
-
*/
|
|
39196
|
-
async function disconnectConnector(integrationType) {
|
|
39197
|
-
await getAppClient().delete(`external-auth/integrations/${integrationType}`);
|
|
39198
|
-
}
|
|
39199
|
-
/**
|
|
39200
|
-
* Removes (hard delete) a connector integration.
|
|
39201
|
-
* This permanently removes the connector and cannot be undone.
|
|
39202
|
-
*/
|
|
39203
|
-
async function removeConnector(integrationType) {
|
|
39204
|
-
await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`);
|
|
39205
|
-
}
|
|
39206
|
-
|
|
39207
|
-
//#endregion
|
|
39208
|
-
//#region src/core/connectors/consts.ts
|
|
39209
|
-
/**
|
|
39210
|
-
* Supported OAuth connector integrations.
|
|
39211
|
-
* Based on apper/backend/app/external_auth/models/constants.py
|
|
39212
|
-
*/
|
|
39213
|
-
const SUPPORTED_INTEGRATIONS = [
|
|
39214
|
-
"googlecalendar",
|
|
39215
|
-
"googledrive",
|
|
39216
|
-
"gmail",
|
|
39217
|
-
"googlesheets",
|
|
39218
|
-
"googledocs",
|
|
39219
|
-
"googleslides",
|
|
39220
|
-
"slack",
|
|
39221
|
-
"notion",
|
|
39222
|
-
"salesforce",
|
|
39223
|
-
"hubspot",
|
|
39224
|
-
"linkedin",
|
|
39225
|
-
"tiktok"
|
|
39226
|
-
];
|
|
39227
|
-
/**
|
|
39228
|
-
* Display names for integrations (for CLI output)
|
|
39229
|
-
*/
|
|
39230
|
-
const INTEGRATION_DISPLAY_NAMES = {
|
|
39231
|
-
googlecalendar: "Google Calendar",
|
|
39232
|
-
googledrive: "Google Drive",
|
|
39233
|
-
gmail: "Gmail",
|
|
39234
|
-
googlesheets: "Google Sheets",
|
|
39235
|
-
googledocs: "Google Docs",
|
|
39236
|
-
googleslides: "Google Slides",
|
|
39237
|
-
slack: "Slack",
|
|
39238
|
-
notion: "Notion",
|
|
39239
|
-
salesforce: "Salesforce",
|
|
39240
|
-
hubspot: "HubSpot",
|
|
39241
|
-
linkedin: "LinkedIn",
|
|
39242
|
-
tiktok: "TikTok"
|
|
39243
|
-
};
|
|
39244
|
-
function isValidIntegration(type) {
|
|
39245
|
-
return SUPPORTED_INTEGRATIONS.includes(type);
|
|
39246
|
-
}
|
|
39247
|
-
function getIntegrationDisplayName(integrationType) {
|
|
39248
|
-
return INTEGRATION_DISPLAY_NAMES[integrationType] ?? integrationType;
|
|
39249
|
-
}
|
|
39250
|
-
|
|
39251
|
-
//#endregion
|
|
39252
|
-
//#region src/core/connectors/config.ts
|
|
39253
|
-
/**
|
|
39254
|
-
* Read all connectors from the project config file
|
|
39255
|
-
*/
|
|
39256
|
-
async function readLocalConnectors(projectRoot) {
|
|
39257
|
-
const { project } = await readProjectConfig(projectRoot);
|
|
39258
|
-
const connectorsData = project.connectors;
|
|
39259
|
-
if (!connectorsData) return [];
|
|
39260
|
-
const connectors = [];
|
|
39261
|
-
for (const [type, config$1] of Object.entries(connectorsData)) {
|
|
39262
|
-
if (!isValidIntegration(type)) throw new Error(`Unknown connector type: ${type}`);
|
|
39263
|
-
connectors.push({
|
|
39264
|
-
type,
|
|
39265
|
-
scopes: config$1.scopes
|
|
39266
|
-
});
|
|
39267
|
-
}
|
|
39268
|
-
return connectors;
|
|
39269
|
-
}
|
|
39270
|
-
/**
|
|
39271
|
-
* Write connectors to the project config file
|
|
39272
|
-
*/
|
|
39273
|
-
async function writeLocalConnectors(connectors, projectRoot) {
|
|
39274
|
-
const connectorsData = {};
|
|
39275
|
-
for (const connector of connectors) connectorsData[connector.type] = { ...connector.scopes && { scopes: connector.scopes } };
|
|
39276
|
-
return await writeProjectConfig({ connectors: Object.keys(connectorsData).length > 0 ? connectorsData : void 0 }, projectRoot);
|
|
39277
|
-
}
|
|
39278
|
-
/**
|
|
39279
|
-
* Add a connector to the project config file
|
|
39280
|
-
*/
|
|
39281
|
-
async function addLocalConnector(type, scopes, projectRoot) {
|
|
39282
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39283
|
-
const existing = connectors.find((c$1) => c$1.type === type);
|
|
39284
|
-
if (existing) {
|
|
39285
|
-
if (scopes) existing.scopes = scopes;
|
|
39286
|
-
} else connectors.push({
|
|
39287
|
-
type,
|
|
39288
|
-
scopes
|
|
39289
|
-
});
|
|
39290
|
-
return await writeLocalConnectors(connectors, projectRoot);
|
|
39291
|
-
}
|
|
39292
|
-
/**
|
|
39293
|
-
* Remove a connector from the project config file
|
|
39294
|
-
*/
|
|
39295
|
-
async function removeLocalConnector(type, projectRoot) {
|
|
39296
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39297
|
-
const filtered = connectors.filter((c$1) => c$1.type !== type);
|
|
39298
|
-
if (filtered.length === connectors.length) return null;
|
|
39299
|
-
return await writeLocalConnectors(filtered, projectRoot);
|
|
39300
|
-
}
|
|
39301
|
-
|
|
39302
|
-
//#endregion
|
|
39303
|
-
//#region src/core/connectors/state.ts
|
|
39304
|
-
/**
|
|
39305
|
-
* Fetches both local config and backend connectors in parallel.
|
|
39306
|
-
* Gracefully handles failures by returning empty arrays.
|
|
39307
|
-
*/
|
|
39308
|
-
async function fetchConnectorState() {
|
|
39309
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39310
|
-
return {
|
|
39311
|
-
local,
|
|
39312
|
-
backend
|
|
39313
|
-
};
|
|
39314
|
-
}
|
|
39315
|
-
|
|
39316
|
-
//#endregion
|
|
39317
|
-
//#region src/cli/commands/connectors/utils.ts
|
|
39318
|
-
/**
|
|
39319
|
-
* OAuth polling configuration
|
|
39320
|
-
*/
|
|
39321
|
-
const OAUTH_POLL_INTERVAL_MS = 2e3;
|
|
39322
|
-
const OAUTH_POLL_TIMEOUT_MS = 300 * 1e3;
|
|
39323
|
-
/**
|
|
39324
|
-
* Polls for OAuth completion status.
|
|
39325
|
-
* Returns when status becomes ACTIVE or FAILED, or times out.
|
|
39326
|
-
*/
|
|
39327
|
-
async function waitForOAuthCompletion(integrationType, connectionId, options) {
|
|
39328
|
-
let accountEmail;
|
|
39329
|
-
let error;
|
|
39330
|
-
try {
|
|
39331
|
-
await pWaitFor(async () => {
|
|
39332
|
-
const status = await checkOAuthStatus(integrationType, connectionId);
|
|
39333
|
-
if (status.status === "ACTIVE") {
|
|
39334
|
-
accountEmail = status.accountEmail ?? void 0;
|
|
39335
|
-
return true;
|
|
39336
|
-
}
|
|
39337
|
-
if (status.status === "FAILED") {
|
|
39338
|
-
error = status.error || "Authorization failed";
|
|
39339
|
-
throw new Error(error);
|
|
39340
|
-
}
|
|
39341
|
-
options?.onPending?.();
|
|
39342
|
-
return false;
|
|
39343
|
-
}, {
|
|
39344
|
-
interval: OAUTH_POLL_INTERVAL_MS,
|
|
39345
|
-
timeout: OAUTH_POLL_TIMEOUT_MS
|
|
39346
|
-
});
|
|
39347
|
-
return {
|
|
39348
|
-
success: true,
|
|
39349
|
-
accountEmail
|
|
39350
|
-
};
|
|
39351
|
-
} catch (err) {
|
|
39352
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39353
|
-
success: false,
|
|
39354
|
-
error: "Authorization timed out. Please try again."
|
|
39355
|
-
};
|
|
39356
|
-
return {
|
|
39357
|
-
success: false,
|
|
39358
|
-
error: error || (err instanceof Error ? err.message : "Unknown error")
|
|
39359
|
-
};
|
|
39360
|
-
}
|
|
39361
|
-
}
|
|
39362
|
-
|
|
39363
|
-
//#endregion
|
|
39364
|
-
//#region src/cli/commands/connectors/add.ts
|
|
39365
|
-
function assertValidIntegrationType(type) {
|
|
39366
|
-
if (!isValidIntegration(type)) {
|
|
39367
|
-
const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
|
|
39368
|
-
throw new Error(`Unsupported connector: ${type}\nSupported connectors: ${supportedList}`);
|
|
39369
|
-
}
|
|
39370
|
-
}
|
|
39371
|
-
async function promptForIntegrationType() {
|
|
39372
|
-
const selected = await ve({
|
|
39373
|
-
message: "Select an integration to connect:",
|
|
39374
|
-
options: Object.entries(INTEGRATION_DISPLAY_NAMES).map(([type, label]) => ({
|
|
39375
|
-
value: type,
|
|
39376
|
-
label
|
|
39377
|
-
}))
|
|
39378
|
-
});
|
|
39379
|
-
if (pD(selected)) {
|
|
39380
|
-
xe("Operation cancelled.");
|
|
39381
|
-
process.exit(0);
|
|
39382
|
-
}
|
|
39383
|
-
return selected;
|
|
39384
|
-
}
|
|
39385
|
-
async function addConnector(integrationType) {
|
|
39386
|
-
let selectedType;
|
|
39387
|
-
if (integrationType) {
|
|
39388
|
-
assertValidIntegrationType(integrationType);
|
|
39389
|
-
selectedType = integrationType;
|
|
39390
|
-
} else selectedType = await promptForIntegrationType();
|
|
39391
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39392
|
-
const result = await runTask(`Initiating ${displayName} connection...`, async (updateMessage) => {
|
|
39393
|
-
const initiateResponse = await initiateOAuth(selectedType);
|
|
39394
|
-
if (initiateResponse.alreadyAuthorized) {
|
|
39395
|
-
await addLocalConnector(selectedType);
|
|
39396
|
-
return { alreadyAuthorized: true };
|
|
39397
|
-
}
|
|
39398
|
-
if (initiateResponse.error === "different_user" && initiateResponse.otherUserEmail) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.otherUserEmail}`);
|
|
39399
|
-
if (!initiateResponse.redirectUrl || !initiateResponse.connectionId) throw new Error("Invalid response from server: missing redirect URL or connection ID");
|
|
39400
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirectUrl)}`);
|
|
39401
|
-
updateMessage("Waiting for authorization...");
|
|
39402
|
-
const oauthResult = await waitForOAuthCompletion(selectedType, initiateResponse.connectionId);
|
|
39403
|
-
if (!oauthResult.success) throw new Error(oauthResult.error || "Authorization failed");
|
|
39404
|
-
await addLocalConnector(selectedType);
|
|
39405
|
-
return {
|
|
39406
|
-
alreadyAuthorized: false,
|
|
39407
|
-
accountEmail: oauthResult.accountEmail
|
|
39408
|
-
};
|
|
39409
|
-
}, {
|
|
39410
|
-
successMessage: "Authorization completed!",
|
|
39411
|
-
errorMessage: `Failed to connect ${displayName}`
|
|
39412
|
-
});
|
|
39413
|
-
if (result.alreadyAuthorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)} (added to config)` };
|
|
39414
|
-
const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
|
|
39415
|
-
return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
|
|
39416
|
-
}
|
|
39417
|
-
const connectorsAddCommand = new Command("add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
|
|
39418
|
-
await runCommand(() => addConnector(type), {
|
|
39419
|
-
requireAuth: true,
|
|
39420
|
-
requireAppConfig: true
|
|
39421
|
-
});
|
|
39422
|
-
});
|
|
39423
|
-
|
|
39424
|
-
//#endregion
|
|
39425
|
-
//#region src/cli/commands/connectors/list.ts
|
|
39426
|
-
function mergeConnectors(local, backend) {
|
|
39427
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39428
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39429
|
-
type: connector.type,
|
|
39430
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39431
|
-
inLocal: true,
|
|
39432
|
-
inBackend: false
|
|
39433
|
-
});
|
|
39434
|
-
for (const connector of backend) {
|
|
39435
|
-
const existing = merged.get(connector.integrationType);
|
|
39436
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39437
|
-
if (existing) {
|
|
39438
|
-
existing.inBackend = true;
|
|
39439
|
-
existing.status = connector.status;
|
|
39440
|
-
existing.accountEmail = accountEmail;
|
|
39441
|
-
} else merged.set(connector.integrationType, {
|
|
39442
|
-
type: connector.integrationType,
|
|
39443
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39444
|
-
inLocal: false,
|
|
39445
|
-
inBackend: true,
|
|
39446
|
-
status: connector.status,
|
|
39447
|
-
accountEmail
|
|
39448
|
-
});
|
|
39449
|
-
}
|
|
39450
|
-
return Array.from(merged.values());
|
|
39451
|
-
}
|
|
39452
|
-
function formatConnectorLine(connector) {
|
|
39453
|
-
const { displayName, inLocal, inBackend, status, accountEmail } = connector;
|
|
39454
|
-
const isConnected$1 = inBackend && status?.toLowerCase() === "active";
|
|
39455
|
-
const isPending = inLocal && !inBackend;
|
|
39456
|
-
const isOrphaned = inBackend && !inLocal;
|
|
39457
|
-
let bullet;
|
|
39458
|
-
let statusText = "";
|
|
39459
|
-
if (isConnected$1) {
|
|
39460
|
-
bullet = theme.colors.success("●");
|
|
39461
|
-
if (accountEmail) statusText = ` - ${accountEmail}`;
|
|
39462
|
-
} else if (isPending) {
|
|
39463
|
-
bullet = theme.colors.warning("○");
|
|
39464
|
-
statusText = theme.styles.dim(" (not connected)");
|
|
39465
|
-
} else if (isOrphaned) {
|
|
39466
|
-
bullet = theme.colors.error("○");
|
|
39467
|
-
statusText = theme.styles.dim(" (not in local config)");
|
|
39468
|
-
} else {
|
|
39469
|
-
bullet = theme.colors.error("○");
|
|
39470
|
-
statusText = theme.styles.dim(` (${status || "disconnected"})`);
|
|
39471
|
-
}
|
|
39472
|
-
return `${bullet} ${displayName}${statusText}`;
|
|
39473
|
-
}
|
|
39474
|
-
async function listConnectorsCommand() {
|
|
39475
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Fetching connectors...", fetchConnectorState, {
|
|
39476
|
-
successMessage: "Connectors loaded",
|
|
39477
|
-
errorMessage: "Failed to fetch connectors"
|
|
39478
|
-
});
|
|
39479
|
-
const merged = mergeConnectors(localConnectors, backendConnectors);
|
|
39480
|
-
if (merged.length === 0) return { outroMessage: `No connectors configured. Run ${theme.styles.bold("base44 connectors add")} to connect an integration.` };
|
|
39481
|
-
M.message("");
|
|
39482
|
-
for (const connector of merged) M.message(formatConnectorLine(connector));
|
|
39483
|
-
if (merged.filter((c$1) => c$1.inLocal && !c$1.inBackend).length > 0) return { outroMessage: `Run ${theme.styles.bold("base44 connectors push")} to connect pending integrations.` };
|
|
39484
|
-
return { outroMessage: "" };
|
|
39485
|
-
}
|
|
39486
|
-
const connectorsListCommand = new Command("list").description("List all connected OAuth integrations").action(async () => {
|
|
39487
|
-
await runCommand(listConnectorsCommand, {
|
|
39488
|
-
requireAuth: true,
|
|
39489
|
-
requireAppConfig: true
|
|
39490
|
-
});
|
|
39491
|
-
});
|
|
39492
|
-
|
|
39493
|
-
//#endregion
|
|
39494
|
-
//#region src/cli/commands/connectors/push.ts
|
|
39495
|
-
function findPendingConnectors(local, backend) {
|
|
39496
|
-
const connectedTypes = new Set(backend.filter((c$1) => c$1.status.toLowerCase() === "active").map((c$1) => c$1.integrationType));
|
|
39497
|
-
return local.filter((c$1) => !connectedTypes.has(c$1.type)).map((c$1) => ({
|
|
39498
|
-
type: c$1.type,
|
|
39499
|
-
displayName: getIntegrationDisplayName(c$1.type),
|
|
39500
|
-
scopes: c$1.scopes
|
|
39501
|
-
}));
|
|
39502
|
-
}
|
|
39503
|
-
function findOrphanedConnectors(local, backend) {
|
|
39504
|
-
const localTypes = new Set(local.map((c$1) => c$1.type));
|
|
39505
|
-
return backend.filter((c$1) => c$1.status.toLowerCase() === "active").filter((c$1) => !localTypes.has(c$1.integrationType)).filter((c$1) => isValidIntegration(c$1.integrationType)).map((c$1) => ({
|
|
39506
|
-
type: c$1.integrationType,
|
|
39507
|
-
displayName: getIntegrationDisplayName(c$1.integrationType),
|
|
39508
|
-
accountEmail: (c$1.accountInfo?.email || c$1.accountInfo?.name) ?? void 0
|
|
39509
|
-
}));
|
|
39510
|
-
}
|
|
39511
|
-
async function connectSingleConnector(connector) {
|
|
39512
|
-
const { type, displayName, scopes } = connector;
|
|
39513
|
-
return await runTask(`Initiating ${displayName} connection...`, async (updateMessage) => {
|
|
39514
|
-
const initiateResponse = await initiateOAuth(type, scopes || null);
|
|
39515
|
-
if (initiateResponse.alreadyAuthorized) return { success: true };
|
|
39516
|
-
if (initiateResponse.error === "different_user") return {
|
|
39517
|
-
success: false,
|
|
39518
|
-
error: `Already connected by ${initiateResponse.otherUserEmail}`
|
|
39519
|
-
};
|
|
39520
|
-
if (!initiateResponse.redirectUrl || !initiateResponse.connectionId) return {
|
|
39521
|
-
success: false,
|
|
39522
|
-
error: "Invalid response from server"
|
|
39523
|
-
};
|
|
39524
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirectUrl)}`);
|
|
39525
|
-
updateMessage("Waiting for authorization...");
|
|
39526
|
-
return await waitForOAuthCompletion(type, initiateResponse.connectionId);
|
|
39527
|
-
}, {
|
|
39528
|
-
successMessage: "Authorization completed!",
|
|
39529
|
-
errorMessage: "Authorization failed"
|
|
39530
|
-
});
|
|
39531
|
-
}
|
|
39532
|
-
function buildSummaryMessage(connected, removed, failed) {
|
|
39533
|
-
const parts = [];
|
|
39534
|
-
if (connected > 0) parts.push(`${connected} connected`);
|
|
39535
|
-
if (removed > 0) parts.push(`${removed} removed`);
|
|
39536
|
-
if (failed > 0) parts.push(`${failed} failed`);
|
|
39537
|
-
return parts.join(", ");
|
|
39538
|
-
}
|
|
39539
|
-
async function pushConnectorsCommand() {
|
|
39540
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Checking connector status...", fetchConnectorState, {
|
|
39541
|
-
successMessage: "Status checked",
|
|
39542
|
-
errorMessage: "Failed to check status"
|
|
39543
|
-
});
|
|
39544
|
-
const pending = findPendingConnectors(localConnectors, backendConnectors);
|
|
39545
|
-
const orphaned = findOrphanedConnectors(localConnectors, backendConnectors);
|
|
39546
|
-
if (pending.length === 0 && orphaned.length === 0) return { outroMessage: "All connectors are in sync" };
|
|
39547
|
-
M.message("");
|
|
39548
|
-
if (pending.length > 0) {
|
|
39549
|
-
M.info(`${pending.length} connector${pending.length === 1 ? "" : "s"} to connect:`);
|
|
39550
|
-
for (const c$1 of pending) M.message(` ${theme.colors.success("+")} ${c$1.displayName}`);
|
|
39551
|
-
}
|
|
39552
|
-
if (orphaned.length > 0) {
|
|
39553
|
-
M.info(`${orphaned.length} connector${orphaned.length === 1 ? "" : "s"} to remove:`);
|
|
39554
|
-
for (const c$1 of orphaned) {
|
|
39555
|
-
const accountInfo = c$1.accountEmail ? ` (${c$1.accountEmail})` : "";
|
|
39556
|
-
M.message(` ${theme.colors.error("-")} ${c$1.displayName}${accountInfo}`);
|
|
39557
|
-
}
|
|
39558
|
-
}
|
|
39559
|
-
M.message("");
|
|
39560
|
-
const totalChanges = pending.length + orphaned.length;
|
|
39561
|
-
const shouldProceed = await ye({ message: `Apply ${totalChanges} change${totalChanges === 1 ? "" : "s"}?` });
|
|
39562
|
-
if (pD(shouldProceed) || !shouldProceed) return { outroMessage: "Cancelled" };
|
|
39563
|
-
let connected = 0;
|
|
39564
|
-
let removed = 0;
|
|
39565
|
-
let failed = 0;
|
|
39566
|
-
for (const connector of orphaned) try {
|
|
39567
|
-
await runTask(`Removing ${connector.displayName}...`, () => disconnectConnector(connector.type), {
|
|
39568
|
-
successMessage: `${connector.displayName} removed`,
|
|
39569
|
-
errorMessage: `Failed to remove ${connector.displayName}`
|
|
39570
|
-
});
|
|
39571
|
-
removed++;
|
|
39572
|
-
} catch (err) {
|
|
39573
|
-
M.error(`Failed to remove ${connector.displayName}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
39574
|
-
failed++;
|
|
39575
|
-
}
|
|
39576
|
-
for (const connector of pending) {
|
|
39577
|
-
const result = await connectSingleConnector(connector);
|
|
39578
|
-
if (result.success) {
|
|
39579
|
-
const accountInfo = result.accountEmail ? ` as ${result.accountEmail}` : "";
|
|
39580
|
-
M.success(`${connector.displayName} connected${accountInfo}`);
|
|
39581
|
-
connected++;
|
|
39582
|
-
} else {
|
|
39583
|
-
M.error(`${connector.displayName} failed: ${result.error}`);
|
|
39584
|
-
failed++;
|
|
39585
|
-
}
|
|
39586
|
-
}
|
|
39587
|
-
return { outroMessage: buildSummaryMessage(connected, removed, failed) };
|
|
39588
|
-
}
|
|
39589
|
-
const connectorsPushCommand = new Command("push").description("Sync connectors with backend").action(async () => {
|
|
39590
|
-
await runCommand(pushConnectorsCommand, {
|
|
39591
|
-
requireAuth: true,
|
|
39592
|
-
requireAppConfig: true
|
|
39593
|
-
});
|
|
39594
|
-
});
|
|
39595
|
-
|
|
39596
|
-
//#endregion
|
|
39597
|
-
//#region src/cli/commands/connectors/remove.ts
|
|
39598
|
-
function mergeConnectorsForRemoval(local, backend) {
|
|
39599
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39600
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39601
|
-
type: connector.type,
|
|
39602
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39603
|
-
inLocal: true,
|
|
39604
|
-
inBackend: false
|
|
39605
|
-
});
|
|
39606
|
-
for (const connector of backend) {
|
|
39607
|
-
if (!isValidIntegration(connector.integrationType)) continue;
|
|
39608
|
-
const existing = merged.get(connector.integrationType);
|
|
39609
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39610
|
-
if (existing) {
|
|
39611
|
-
existing.inBackend = true;
|
|
39612
|
-
existing.accountEmail = accountEmail;
|
|
39613
|
-
} else merged.set(connector.integrationType, {
|
|
39614
|
-
type: connector.integrationType,
|
|
39615
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39616
|
-
inLocal: false,
|
|
39617
|
-
inBackend: true,
|
|
39618
|
-
accountEmail
|
|
39619
|
-
});
|
|
39620
|
-
}
|
|
39621
|
-
return Array.from(merged.values());
|
|
39622
|
-
}
|
|
39623
|
-
function validateConnectorType(type, merged) {
|
|
39624
|
-
if (!isValidIntegration(type)) throw new Error(`Invalid connector type: ${type}`);
|
|
39625
|
-
const connector = merged.find((c$1) => c$1.type === type);
|
|
39626
|
-
if (!connector) throw new Error(`No ${getIntegrationDisplayName(type)} connector found`);
|
|
39627
|
-
return {
|
|
39628
|
-
type,
|
|
39629
|
-
connector
|
|
39630
|
-
};
|
|
39631
|
-
}
|
|
39632
|
-
async function promptForConnectorToRemove(connectors) {
|
|
39633
|
-
const selected = await ve({
|
|
39634
|
-
message: "Select a connector to remove:",
|
|
39635
|
-
options: connectors.map((c$1) => {
|
|
39636
|
-
let label = c$1.displayName;
|
|
39637
|
-
if (c$1.accountEmail) label += ` (${c$1.accountEmail})`;
|
|
39638
|
-
else if (c$1.inLocal && !c$1.inBackend) label += " (not connected)";
|
|
39639
|
-
return {
|
|
39640
|
-
value: c$1.type,
|
|
39641
|
-
label
|
|
39642
|
-
};
|
|
39643
|
-
})
|
|
39644
|
-
});
|
|
39645
|
-
if (pD(selected)) {
|
|
39646
|
-
xe("Operation cancelled.");
|
|
39647
|
-
process.exit(0);
|
|
39648
|
-
}
|
|
39649
|
-
return {
|
|
39650
|
-
type: selected,
|
|
39651
|
-
connector: connectors.find((c$1) => c$1.type === selected)
|
|
39652
|
-
};
|
|
39653
|
-
}
|
|
39654
|
-
async function removeConnectorCommand(integrationType, options = {}) {
|
|
39655
|
-
const isHardDelete = options.hard === true;
|
|
39656
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Fetching connectors...", fetchConnectorState, {
|
|
39657
|
-
successMessage: "Connectors loaded",
|
|
39658
|
-
errorMessage: "Failed to fetch connectors"
|
|
39659
|
-
});
|
|
39660
|
-
const merged = mergeConnectorsForRemoval(localConnectors, backendConnectors);
|
|
39661
|
-
if (merged.length === 0) return { outroMessage: "No connectors to remove" };
|
|
39662
|
-
const { type: selectedType, connector: selectedConnector } = integrationType ? validateConnectorType(integrationType, merged) : await promptForConnectorToRemove(merged);
|
|
39663
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39664
|
-
const accountInfo = selectedConnector.accountEmail ? ` (${selectedConnector.accountEmail})` : "";
|
|
39665
|
-
if (!options.yes) {
|
|
39666
|
-
const shouldRemove = await ye({
|
|
39667
|
-
message: `${isHardDelete ? "Permanently remove" : "Remove"} ${displayName}${accountInfo}?`,
|
|
39668
|
-
initialValue: false
|
|
39669
|
-
});
|
|
39670
|
-
if (pD(shouldRemove) || !shouldRemove) {
|
|
39671
|
-
xe("Operation cancelled.");
|
|
39672
|
-
process.exit(0);
|
|
39673
|
-
}
|
|
39674
|
-
}
|
|
39675
|
-
await runTask(`Removing ${displayName}...`, async () => {
|
|
39676
|
-
if (selectedConnector.inBackend) if (isHardDelete) await removeConnector(selectedType);
|
|
39677
|
-
else await disconnectConnector(selectedType);
|
|
39678
|
-
await removeLocalConnector(selectedType);
|
|
39679
|
-
}, {
|
|
39680
|
-
successMessage: `${displayName} removed`,
|
|
39681
|
-
errorMessage: `Failed to remove ${displayName}`
|
|
39682
|
-
});
|
|
39683
|
-
return { outroMessage: `Successfully removed ${theme.styles.bold(displayName)}` };
|
|
39684
|
-
}
|
|
39685
|
-
const connectorsRemoveCommand = new Command("remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").option("--hard", "Permanently remove the connector (cannot be undone)").option("-y, --yes", "Skip confirmation prompt").description("Remove an OAuth integration").action(async (type, options) => {
|
|
39686
|
-
await runCommand(() => removeConnectorCommand(type, options), {
|
|
39687
|
-
requireAuth: true,
|
|
39688
|
-
requireAppConfig: true
|
|
39689
|
-
});
|
|
39690
|
-
});
|
|
39691
|
-
|
|
39692
|
-
//#endregion
|
|
39693
|
-
//#region src/cli/commands/connectors/index.ts
|
|
39694
|
-
const connectorsCommand = new Command("connectors").description("Manage OAuth connectors").addCommand(connectorsAddCommand).addCommand(connectorsListCommand).addCommand(connectorsPushCommand).addCommand(connectorsRemoveCommand);
|
|
39695
|
-
|
|
39696
39104
|
//#endregion
|
|
39697
39105
|
//#region package.json
|
|
39698
39106
|
var version = "0.0.19";
|
|
@@ -39710,9 +39118,9 @@ program.addCommand(dashboardCommand);
|
|
|
39710
39118
|
program.addCommand(deployCommand);
|
|
39711
39119
|
program.addCommand(linkCommand);
|
|
39712
39120
|
program.addCommand(entitiesPushCommand);
|
|
39121
|
+
program.addCommand(agentsCommand);
|
|
39713
39122
|
program.addCommand(functionsDeployCommand);
|
|
39714
39123
|
program.addCommand(siteDeployCommand);
|
|
39715
|
-
program.addCommand(connectorsCommand);
|
|
39716
39124
|
|
|
39717
39125
|
//#endregion
|
|
39718
39126
|
export { CLIExitError, program };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"templates": [
|
|
3
|
-
{
|
|
4
|
-
"id": "backend-only",
|
|
5
|
-
"name": "Create a basic project",
|
|
6
|
-
"description": "Minimal Base44 backend for defining your data models and logic",
|
|
7
|
-
"path": "backend-only"
|
|
8
|
-
},
|
|
9
3
|
{
|
|
10
4
|
"id": "backend-and-client",
|
|
11
5
|
"name": "Start from a template",
|
|
12
6
|
"description": "Full-stack example with a Base44 backend and a Vite + React client application",
|
|
13
7
|
"path": "backend-and-client"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "backend-only",
|
|
11
|
+
"name": "Create a basic project",
|
|
12
|
+
"description": "Minimal Base44 backend for defining your data models and logic",
|
|
13
|
+
"path": "backend-only"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|