@base44-preview/cli 0.0.15-pr.99.e192f83 → 0.0.16-pr.95.3b2ce0c
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/README.md +23 -1
- package/dist/cli/index.js +132 -430
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,9 +43,31 @@ base44 deploy
|
|
|
43
43
|
| Command | Description |
|
|
44
44
|
|---------|-------------|
|
|
45
45
|
| `base44 create` | Create a new Base44 project from a template |
|
|
46
|
-
| `base44 link` | Link
|
|
46
|
+
| `base44 link` | Link a local project to Base44 (create new or link existing) |
|
|
47
47
|
| `base44 dashboard` | Open the app dashboard in your browser |
|
|
48
48
|
|
|
49
|
+
#### Link Command Options
|
|
50
|
+
|
|
51
|
+
The `link` command supports both creating new projects and linking to existing ones:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Interactive mode - choose to create new or link existing
|
|
55
|
+
base44 link
|
|
56
|
+
|
|
57
|
+
# Create a new project (non-interactive)
|
|
58
|
+
base44 link --create --name "my-app" --description "My app description"
|
|
59
|
+
|
|
60
|
+
# Link to an existing project by ID (non-interactive)
|
|
61
|
+
base44 link --existing <app-id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Option | Description |
|
|
65
|
+
|--------|-------------|
|
|
66
|
+
| `-c, --create` | Create a new project (skip selection prompt) |
|
|
67
|
+
| `-e, --existing <id>` | Link to an existing project by ID |
|
|
68
|
+
| `-n, --name <name>` | Project name (required with --create) |
|
|
69
|
+
| `-d, --description <desc>` | Project description (optional) |
|
|
70
|
+
|
|
49
71
|
### Deployment
|
|
50
72
|
|
|
51
73
|
| Command | Description |
|
package/dist/cli/index.js
CHANGED
|
@@ -7900,19 +7900,6 @@ var AuthValidationError = class extends Error {
|
|
|
7900
7900
|
this.name = "AuthValidationError";
|
|
7901
7901
|
}
|
|
7902
7902
|
};
|
|
7903
|
-
var ConnectorApiError = class extends Error {
|
|
7904
|
-
constructor(message, cause) {
|
|
7905
|
-
super(message);
|
|
7906
|
-
this.cause = cause;
|
|
7907
|
-
this.name = "ConnectorApiError";
|
|
7908
|
-
}
|
|
7909
|
-
};
|
|
7910
|
-
var ConnectorValidationError = class extends Error {
|
|
7911
|
-
constructor(message) {
|
|
7912
|
-
super(message);
|
|
7913
|
-
this.name = "ConnectorValidationError";
|
|
7914
|
-
}
|
|
7915
|
-
};
|
|
7916
7903
|
|
|
7917
7904
|
//#endregion
|
|
7918
7905
|
//#region src/core/consts.ts
|
|
@@ -16719,6 +16706,13 @@ const ProjectConfigSchema = object({
|
|
|
16719
16706
|
});
|
|
16720
16707
|
const AppConfigSchema = object({ id: string().min(1, "id cannot be empty") });
|
|
16721
16708
|
const CreateProjectResponseSchema = looseObject({ id: string() });
|
|
16709
|
+
const ProjectSchema = object({
|
|
16710
|
+
id: string(),
|
|
16711
|
+
name: string(),
|
|
16712
|
+
userDescription: string().optional(),
|
|
16713
|
+
isManagedSourceCode: boolean().optional()
|
|
16714
|
+
});
|
|
16715
|
+
const ProjectsResponseSchema = array(ProjectSchema);
|
|
16722
16716
|
|
|
16723
16717
|
//#endregion
|
|
16724
16718
|
//#region src/core/project/config.ts
|
|
@@ -16803,6 +16797,10 @@ async function createProject(projectName, description) {
|
|
|
16803
16797
|
} });
|
|
16804
16798
|
return { projectId: CreateProjectResponseSchema.parse(await response.json()).id };
|
|
16805
16799
|
}
|
|
16800
|
+
async function listProjects() {
|
|
16801
|
+
const response = await base44Client.get(`api/apps?sort=-updated_date&fields=id,name,user_description,is_managed_source_code`);
|
|
16802
|
+
return ProjectsResponseSchema.parse(await response.json());
|
|
16803
|
+
}
|
|
16806
16804
|
|
|
16807
16805
|
//#endregion
|
|
16808
16806
|
//#region node_modules/ejs/lib/utils.js
|
|
@@ -31005,10 +31003,7 @@ const theme = {
|
|
|
31005
31003
|
base44OrangeBackground: source_default.bgHex("#E86B3C"),
|
|
31006
31004
|
shinyOrange: source_default.hex("#FFD700"),
|
|
31007
31005
|
links: source_default.hex("#00D4FF"),
|
|
31008
|
-
white: source_default.white
|
|
31009
|
-
success: source_default.green,
|
|
31010
|
-
warning: source_default.yellow,
|
|
31011
|
-
error: source_default.red
|
|
31006
|
+
white: source_default.white
|
|
31012
31007
|
},
|
|
31013
31008
|
styles: {
|
|
31014
31009
|
header: source_default.dim,
|
|
@@ -38033,6 +38028,13 @@ var require_lodash = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
38033
38028
|
//#region src/cli/commands/project/create.ts
|
|
38034
38029
|
var import_lodash = /* @__PURE__ */ __toESM(require_lodash(), 1);
|
|
38035
38030
|
const DEFAULT_TEMPLATE_ID = "backend-only";
|
|
38031
|
+
const SUPPORTED_AGENTS = [{
|
|
38032
|
+
value: "cursor",
|
|
38033
|
+
label: "Cursor"
|
|
38034
|
+
}, {
|
|
38035
|
+
value: "claude-code",
|
|
38036
|
+
label: "Claude Code"
|
|
38037
|
+
}];
|
|
38036
38038
|
async function getTemplateById(templateId) {
|
|
38037
38039
|
const templates = await listTemplates();
|
|
38038
38040
|
const template = templates.find((t) => t.id === templateId);
|
|
@@ -38095,6 +38097,7 @@ async function createInteractive(options) {
|
|
|
38095
38097
|
description: result.description || void 0,
|
|
38096
38098
|
projectPath: result.projectPath,
|
|
38097
38099
|
deploy: options.deploy,
|
|
38100
|
+
skills: options.skills,
|
|
38098
38101
|
isInteractive: true
|
|
38099
38102
|
});
|
|
38100
38103
|
}
|
|
@@ -38105,10 +38108,11 @@ async function createNonInteractive(options) {
|
|
|
38105
38108
|
description: options.description,
|
|
38106
38109
|
projectPath: options.path,
|
|
38107
38110
|
deploy: options.deploy,
|
|
38111
|
+
skills: options.skills,
|
|
38108
38112
|
isInteractive: false
|
|
38109
38113
|
});
|
|
38110
38114
|
}
|
|
38111
|
-
async function executeCreate({ template, name: rawName, description, projectPath, deploy, isInteractive }) {
|
|
38115
|
+
async function executeCreate({ template, name: rawName, description, projectPath, deploy, skills, isInteractive }) {
|
|
38112
38116
|
const name$1 = rawName.trim();
|
|
38113
38117
|
const resolvedPath = resolve(projectPath);
|
|
38114
38118
|
const { projectId } = await runTask("Setting up your project...", async () => {
|
|
@@ -38168,12 +38172,45 @@ async function executeCreate({ template, name: rawName, description, projectPath
|
|
|
38168
38172
|
finalAppUrl = appUrl;
|
|
38169
38173
|
}
|
|
38170
38174
|
}
|
|
38175
|
+
let selectedAgents = [];
|
|
38176
|
+
if (isInteractive) {
|
|
38177
|
+
const result = await fe({
|
|
38178
|
+
message: "Add AI agent skills? (Select agents to configure)",
|
|
38179
|
+
options: SUPPORTED_AGENTS,
|
|
38180
|
+
initialValues: SUPPORTED_AGENTS.map((agent) => agent.value),
|
|
38181
|
+
required: false
|
|
38182
|
+
});
|
|
38183
|
+
if (!pD(result)) selectedAgents = result;
|
|
38184
|
+
} else if (skills) selectedAgents = SUPPORTED_AGENTS.map((agent) => agent.value);
|
|
38185
|
+
if (selectedAgents.length > 0) {
|
|
38186
|
+
const agentArgs = selectedAgents.flatMap((agent) => ["-a", agent]);
|
|
38187
|
+
M.step("Installing skills for: " + selectedAgents.join(", "));
|
|
38188
|
+
await runTask("Installing skills for: " + selectedAgents.join(", "), async () => {
|
|
38189
|
+
await execa("npx", [
|
|
38190
|
+
"-y",
|
|
38191
|
+
"add-skill",
|
|
38192
|
+
"base44/skills",
|
|
38193
|
+
"-y",
|
|
38194
|
+
"-s",
|
|
38195
|
+
"base44-cli",
|
|
38196
|
+
"-s",
|
|
38197
|
+
"base44-sdk",
|
|
38198
|
+
...agentArgs
|
|
38199
|
+
], {
|
|
38200
|
+
cwd: resolvedPath,
|
|
38201
|
+
stdio: "inherit"
|
|
38202
|
+
});
|
|
38203
|
+
}, {
|
|
38204
|
+
successMessage: theme.colors.base44Orange("AI agent skills added successfully"),
|
|
38205
|
+
errorMessage: "Failed to add AI agent skills - you can add them later with: npx add-skill base44/skills"
|
|
38206
|
+
});
|
|
38207
|
+
}
|
|
38171
38208
|
M.message(`${theme.styles.header("Project")}: ${theme.colors.base44Orange(name$1)}`);
|
|
38172
38209
|
M.message(`${theme.styles.header("Dashboard")}: ${theme.colors.links(getDashboardUrl(projectId))}`);
|
|
38173
38210
|
if (finalAppUrl) M.message(`${theme.styles.header("Site")}: ${theme.colors.links(finalAppUrl)}`);
|
|
38174
38211
|
return { outroMessage: "Your project is set up and ready to use" };
|
|
38175
38212
|
}
|
|
38176
|
-
const createCommand = new Command("create").description("Create a new Base44 project").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").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").hook("preAction", validateNonInteractiveFlags$1).action(async (options) => {
|
|
38213
|
+
const createCommand = new Command("create").description("Create a new Base44 project").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").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 (Cursor, Claude Code)").hook("preAction", validateNonInteractiveFlags$1).action(async (options) => {
|
|
38177
38214
|
await chooseCreate(options);
|
|
38178
38215
|
});
|
|
38179
38216
|
|
|
@@ -38759,20 +38796,34 @@ const deployCommand = new Command("deploy").description("Deploy all project reso
|
|
|
38759
38796
|
//#endregion
|
|
38760
38797
|
//#region src/cli/commands/project/link.ts
|
|
38761
38798
|
function validateNonInteractiveFlags(command) {
|
|
38762
|
-
const { create: create$1, name: name$1 } = command.opts();
|
|
38799
|
+
const { create: create$1, name: name$1, existing, projectId } = command.opts();
|
|
38800
|
+
if (create$1 && existing) command.error("--create and --existing cannot be used together");
|
|
38801
|
+
if (existing && !projectId) command.error("--projectId is required when using --existing");
|
|
38763
38802
|
if (create$1 && !name$1) command.error("--name is required when using --create");
|
|
38764
38803
|
}
|
|
38765
|
-
async function
|
|
38804
|
+
async function promptForLinkAction() {
|
|
38766
38805
|
const actionOptions = [{
|
|
38767
38806
|
value: "create",
|
|
38768
38807
|
label: "Create a new project",
|
|
38769
38808
|
hint: "Create a new Base44 project and link it"
|
|
38770
38809
|
}];
|
|
38810
|
+
actionOptions.push({
|
|
38811
|
+
value: "choose",
|
|
38812
|
+
label: "Link an existing project",
|
|
38813
|
+
hint: `Choose from one of your available projects previously created by the Base44 CLI`
|
|
38814
|
+
});
|
|
38815
|
+
const action = await ve({
|
|
38816
|
+
message: "How would you like to link this project?",
|
|
38817
|
+
options: actionOptions
|
|
38818
|
+
});
|
|
38819
|
+
if (pD(action)) {
|
|
38820
|
+
xe("Operation cancelled.");
|
|
38821
|
+
process.exit(0);
|
|
38822
|
+
}
|
|
38823
|
+
return action;
|
|
38824
|
+
}
|
|
38825
|
+
async function promptForNewProjectDetails() {
|
|
38771
38826
|
const result = await Ce({
|
|
38772
|
-
action: () => ve({
|
|
38773
|
-
message: "How would you like to link this project?",
|
|
38774
|
-
options: actionOptions
|
|
38775
|
-
}),
|
|
38776
38827
|
name: () => {
|
|
38777
38828
|
return he({
|
|
38778
38829
|
message: "What is the name of your project?",
|
|
@@ -38792,29 +38843,67 @@ async function promptForProjectDetails() {
|
|
|
38792
38843
|
description: result.description ? result.description.trim() : void 0
|
|
38793
38844
|
};
|
|
38794
38845
|
}
|
|
38846
|
+
async function promptForExistingProject(linkableProjects) {
|
|
38847
|
+
const selectedProject = await ve({
|
|
38848
|
+
message: "Choose a project to link",
|
|
38849
|
+
options: linkableProjects.map((project) => ({
|
|
38850
|
+
value: project,
|
|
38851
|
+
label: project.name
|
|
38852
|
+
}))
|
|
38853
|
+
});
|
|
38854
|
+
if (pD(selectedProject)) {
|
|
38855
|
+
xe("Operation cancelled.");
|
|
38856
|
+
process.exit(0);
|
|
38857
|
+
}
|
|
38858
|
+
return selectedProject;
|
|
38859
|
+
}
|
|
38795
38860
|
async function link(options) {
|
|
38796
38861
|
const projectRoot = await findProjectRoot();
|
|
38797
38862
|
if (!projectRoot) throw new Error("No Base44 project found. Run this command from a project directory with a config.jsonc file.");
|
|
38798
38863
|
if (await appConfigExists(projectRoot.root)) throw new Error("Project is already linked. An .app.jsonc file with the appId already exists.");
|
|
38799
|
-
|
|
38800
|
-
|
|
38801
|
-
|
|
38802
|
-
|
|
38803
|
-
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
|
|
38807
|
-
|
|
38808
|
-
|
|
38809
|
-
|
|
38810
|
-
|
|
38811
|
-
|
|
38812
|
-
|
|
38813
|
-
|
|
38814
|
-
|
|
38864
|
+
let finalProjectId;
|
|
38865
|
+
const action = options.existing ? "choose" : options.create ? "create" : await promptForLinkAction();
|
|
38866
|
+
if (action === "choose") {
|
|
38867
|
+
const linkableProjects = (await runTask("Fetching projects...", async () => listProjects(), {
|
|
38868
|
+
successMessage: "Projects fetched",
|
|
38869
|
+
errorMessage: "Failed to fetch projects"
|
|
38870
|
+
})).filter((p$1) => p$1.isManagedSourceCode !== true);
|
|
38871
|
+
if (!linkableProjects.length) return { outroMessage: "No projects available for linking" };
|
|
38872
|
+
const { id: projectId } = options.existing ? { id: options.projectId } : await promptForExistingProject(linkableProjects);
|
|
38873
|
+
await runTask("Linking project...", async () => {
|
|
38874
|
+
await writeAppConfig(projectRoot.root, projectId);
|
|
38875
|
+
setAppConfig({
|
|
38876
|
+
id: projectId,
|
|
38877
|
+
projectRoot: projectRoot.root
|
|
38878
|
+
});
|
|
38879
|
+
}, {
|
|
38880
|
+
successMessage: "Project linked successfully",
|
|
38881
|
+
errorMessage: "Failed to link project"
|
|
38882
|
+
});
|
|
38883
|
+
finalProjectId = projectId;
|
|
38884
|
+
}
|
|
38885
|
+
if (action === "create") {
|
|
38886
|
+
const { name: name$1, description } = options.create ? {
|
|
38887
|
+
name: options.name.trim(),
|
|
38888
|
+
description: options.description?.trim()
|
|
38889
|
+
} : await promptForNewProjectDetails();
|
|
38890
|
+
const { projectId } = await runTask("Creating project on Base44...", async () => {
|
|
38891
|
+
return await createProject(name$1, description);
|
|
38892
|
+
}, {
|
|
38893
|
+
successMessage: "Project created successfully",
|
|
38894
|
+
errorMessage: "Failed to create project"
|
|
38895
|
+
});
|
|
38896
|
+
await writeAppConfig(projectRoot.root, projectId);
|
|
38897
|
+
setAppConfig({
|
|
38898
|
+
id: projectId,
|
|
38899
|
+
projectRoot: projectRoot.root
|
|
38900
|
+
});
|
|
38901
|
+
finalProjectId = projectId;
|
|
38902
|
+
}
|
|
38903
|
+
M.message(`${theme.styles.header("Dashboard")}: ${theme.colors.links(getDashboardUrl(finalProjectId))}`);
|
|
38815
38904
|
return { outroMessage: "Project linked" };
|
|
38816
38905
|
}
|
|
38817
|
-
const linkCommand = new Command("link").description("Link a local project to a Base44 project").option("-c, --create", "Create a new project (skip selection prompt)").option("-n, --name <name>", "Project name (required when --create is used)").option("-d, --description <description>", "Project description").hook("preAction", validateNonInteractiveFlags).action(async (options) => {
|
|
38906
|
+
const linkCommand = new Command("link").description("Link a local project to a Base44 project (create new or link existing)").option("-c, --create", "Create a new project (skip selection prompt)").option("-n, --name <name>", "Project name (required when --create is used)").option("-d, --description <description>", "Project description").option("-e, --existing", "Link to an existing project (skip selection prompt)").option("-p, --projectId <id>", "Project ID (required when --existing is used)").hook("preAction", validateNonInteractiveFlags).action(async (options) => {
|
|
38818
38907
|
await runCommand(() => link(options), {
|
|
38819
38908
|
requireAuth: true,
|
|
38820
38909
|
requireAppConfig: false
|
|
@@ -38842,393 +38931,9 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
|
|
|
38842
38931
|
await runCommand(() => deployAction(options), { requireAuth: true });
|
|
38843
38932
|
}));
|
|
38844
38933
|
|
|
38845
|
-
//#endregion
|
|
38846
|
-
//#region src/core/connectors/schema.ts
|
|
38847
|
-
/**
|
|
38848
|
-
* Response from POST /api/apps/{app_id}/external-auth/initiate
|
|
38849
|
-
*/
|
|
38850
|
-
const InitiateResponseSchema = object({
|
|
38851
|
-
redirect_url: string().optional(),
|
|
38852
|
-
connection_id: string().optional(),
|
|
38853
|
-
already_authorized: boolean().optional(),
|
|
38854
|
-
other_user_email: string().optional(),
|
|
38855
|
-
error: string().optional()
|
|
38856
|
-
});
|
|
38857
|
-
/**
|
|
38858
|
-
* Response from GET /api/apps/{app_id}/external-auth/status
|
|
38859
|
-
*/
|
|
38860
|
-
const StatusResponseSchema = object({
|
|
38861
|
-
status: _enum([
|
|
38862
|
-
"ACTIVE",
|
|
38863
|
-
"PENDING",
|
|
38864
|
-
"FAILED"
|
|
38865
|
-
]),
|
|
38866
|
-
account_email: string().optional(),
|
|
38867
|
-
error: string().optional()
|
|
38868
|
-
});
|
|
38869
|
-
/**
|
|
38870
|
-
* A connected integration from the list endpoint
|
|
38871
|
-
*/
|
|
38872
|
-
const ConnectorSchema = object({
|
|
38873
|
-
integration_type: string(),
|
|
38874
|
-
status: string(),
|
|
38875
|
-
connected_at: string().optional(),
|
|
38876
|
-
account_info: object({
|
|
38877
|
-
email: string().optional(),
|
|
38878
|
-
name: string().optional()
|
|
38879
|
-
}).optional()
|
|
38880
|
-
}).transform((data) => ({
|
|
38881
|
-
integrationType: data.integration_type,
|
|
38882
|
-
status: data.status,
|
|
38883
|
-
connectedAt: data.connected_at,
|
|
38884
|
-
accountInfo: data.account_info
|
|
38885
|
-
}));
|
|
38886
|
-
/**
|
|
38887
|
-
* Response from GET /api/apps/{app_id}/external-auth/list
|
|
38888
|
-
*/
|
|
38889
|
-
const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
|
|
38890
|
-
/**
|
|
38891
|
-
* Generic API error response
|
|
38892
|
-
*/
|
|
38893
|
-
const ApiErrorSchema = object({
|
|
38894
|
-
error: string(),
|
|
38895
|
-
detail: string().optional()
|
|
38896
|
-
});
|
|
38897
|
-
|
|
38898
|
-
//#endregion
|
|
38899
|
-
//#region src/core/connectors/api.ts
|
|
38900
|
-
/**
|
|
38901
|
-
* Initiates OAuth flow for a connector integration.
|
|
38902
|
-
* Returns a redirect URL to open in the browser.
|
|
38903
|
-
*/
|
|
38904
|
-
async function initiateOAuth(integrationType, scopes = null) {
|
|
38905
|
-
const response = await getAppClient().post("external-auth/initiate", {
|
|
38906
|
-
json: {
|
|
38907
|
-
integration_type: integrationType,
|
|
38908
|
-
scopes
|
|
38909
|
-
},
|
|
38910
|
-
throwHttpErrors: false
|
|
38911
|
-
});
|
|
38912
|
-
const json = await response.json();
|
|
38913
|
-
if (!response.ok) {
|
|
38914
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
38915
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
38916
|
-
throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
|
|
38917
|
-
}
|
|
38918
|
-
const result = InitiateResponseSchema.safeParse(json);
|
|
38919
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
|
|
38920
|
-
return result.data;
|
|
38921
|
-
}
|
|
38922
|
-
/**
|
|
38923
|
-
* Checks the status of an OAuth connection attempt.
|
|
38924
|
-
*/
|
|
38925
|
-
async function checkOAuthStatus(integrationType, connectionId) {
|
|
38926
|
-
const response = await getAppClient().get("external-auth/status", {
|
|
38927
|
-
searchParams: {
|
|
38928
|
-
integration_type: integrationType,
|
|
38929
|
-
connection_id: connectionId
|
|
38930
|
-
},
|
|
38931
|
-
throwHttpErrors: false
|
|
38932
|
-
});
|
|
38933
|
-
const json = await response.json();
|
|
38934
|
-
if (!response.ok) {
|
|
38935
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
38936
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
38937
|
-
throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
|
|
38938
|
-
}
|
|
38939
|
-
const result = StatusResponseSchema.safeParse(json);
|
|
38940
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
|
|
38941
|
-
return result.data;
|
|
38942
|
-
}
|
|
38943
|
-
/**
|
|
38944
|
-
* Lists all connected integrations for the current app.
|
|
38945
|
-
*/
|
|
38946
|
-
async function listConnectors() {
|
|
38947
|
-
const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
|
|
38948
|
-
const json = await response.json();
|
|
38949
|
-
if (!response.ok) {
|
|
38950
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
38951
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
38952
|
-
throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
|
|
38953
|
-
}
|
|
38954
|
-
const result = ListResponseSchema.safeParse(json);
|
|
38955
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
|
|
38956
|
-
return result.data.integrations;
|
|
38957
|
-
}
|
|
38958
|
-
/**
|
|
38959
|
-
* Disconnects (soft delete) a connector integration.
|
|
38960
|
-
*/
|
|
38961
|
-
async function disconnectConnector(integrationType) {
|
|
38962
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
|
|
38963
|
-
if (!response.ok) {
|
|
38964
|
-
const json = await response.json();
|
|
38965
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
38966
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
38967
|
-
throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
|
|
38968
|
-
}
|
|
38969
|
-
}
|
|
38970
|
-
|
|
38971
|
-
//#endregion
|
|
38972
|
-
//#region src/core/connectors/constants.ts
|
|
38973
|
-
/**
|
|
38974
|
-
* Supported OAuth connector integrations.
|
|
38975
|
-
* Based on apper/backend/app/external_auth/models/constants.py
|
|
38976
|
-
*/
|
|
38977
|
-
const SUPPORTED_INTEGRATIONS = [
|
|
38978
|
-
"googlecalendar",
|
|
38979
|
-
"googledrive",
|
|
38980
|
-
"gmail",
|
|
38981
|
-
"googlesheets",
|
|
38982
|
-
"googledocs",
|
|
38983
|
-
"googleslides",
|
|
38984
|
-
"slack",
|
|
38985
|
-
"notion",
|
|
38986
|
-
"salesforce",
|
|
38987
|
-
"hubspot",
|
|
38988
|
-
"linkedin",
|
|
38989
|
-
"tiktok"
|
|
38990
|
-
];
|
|
38991
|
-
/**
|
|
38992
|
-
* Display names for integrations (for CLI output)
|
|
38993
|
-
*/
|
|
38994
|
-
const INTEGRATION_DISPLAY_NAMES = {
|
|
38995
|
-
googlecalendar: "Google Calendar",
|
|
38996
|
-
googledrive: "Google Drive",
|
|
38997
|
-
gmail: "Gmail",
|
|
38998
|
-
googlesheets: "Google Sheets",
|
|
38999
|
-
googledocs: "Google Docs",
|
|
39000
|
-
googleslides: "Google Slides",
|
|
39001
|
-
slack: "Slack",
|
|
39002
|
-
notion: "Notion",
|
|
39003
|
-
salesforce: "Salesforce",
|
|
39004
|
-
hubspot: "HubSpot",
|
|
39005
|
-
linkedin: "LinkedIn",
|
|
39006
|
-
tiktok: "TikTok"
|
|
39007
|
-
};
|
|
39008
|
-
function isValidIntegration(type) {
|
|
39009
|
-
return SUPPORTED_INTEGRATIONS.includes(type);
|
|
39010
|
-
}
|
|
39011
|
-
function getIntegrationDisplayName(type) {
|
|
39012
|
-
if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
|
|
39013
|
-
return type;
|
|
39014
|
-
}
|
|
39015
|
-
|
|
39016
|
-
//#endregion
|
|
39017
|
-
//#region src/cli/commands/connectors/add.ts
|
|
39018
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
39019
|
-
const POLL_TIMEOUT_MS = 300 * 1e3;
|
|
39020
|
-
async function promptForIntegrationType() {
|
|
39021
|
-
const selected = await ve({
|
|
39022
|
-
message: "Select an integration to connect:",
|
|
39023
|
-
options: SUPPORTED_INTEGRATIONS.map((type) => ({
|
|
39024
|
-
value: type,
|
|
39025
|
-
label: getIntegrationDisplayName(type)
|
|
39026
|
-
}))
|
|
39027
|
-
});
|
|
39028
|
-
if (pD(selected)) return null;
|
|
39029
|
-
return selected;
|
|
39030
|
-
}
|
|
39031
|
-
async function waitForOAuthCompletion(integrationType, connectionId) {
|
|
39032
|
-
let accountEmail;
|
|
39033
|
-
let error;
|
|
39034
|
-
try {
|
|
39035
|
-
await runTask("Waiting for authorization...", async (updateMessage) => {
|
|
39036
|
-
await pWaitFor(async () => {
|
|
39037
|
-
const status = await checkOAuthStatus(integrationType, connectionId);
|
|
39038
|
-
if (status.status === "ACTIVE") {
|
|
39039
|
-
accountEmail = status.account_email;
|
|
39040
|
-
return true;
|
|
39041
|
-
}
|
|
39042
|
-
if (status.status === "FAILED") {
|
|
39043
|
-
error = status.error || "Authorization failed";
|
|
39044
|
-
throw new Error(error);
|
|
39045
|
-
}
|
|
39046
|
-
updateMessage("Waiting for authorization in browser...");
|
|
39047
|
-
return false;
|
|
39048
|
-
}, {
|
|
39049
|
-
interval: POLL_INTERVAL_MS,
|
|
39050
|
-
timeout: POLL_TIMEOUT_MS
|
|
39051
|
-
});
|
|
39052
|
-
}, {
|
|
39053
|
-
successMessage: "Authorization completed!",
|
|
39054
|
-
errorMessage: "Authorization failed"
|
|
39055
|
-
});
|
|
39056
|
-
return {
|
|
39057
|
-
success: true,
|
|
39058
|
-
accountEmail
|
|
39059
|
-
};
|
|
39060
|
-
} catch (err) {
|
|
39061
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39062
|
-
success: false,
|
|
39063
|
-
error: "Authorization timed out. Please try again."
|
|
39064
|
-
};
|
|
39065
|
-
return {
|
|
39066
|
-
success: false,
|
|
39067
|
-
error: error || (err instanceof Error ? err.message : "Unknown error")
|
|
39068
|
-
};
|
|
39069
|
-
}
|
|
39070
|
-
}
|
|
39071
|
-
async function addConnector(integrationType) {
|
|
39072
|
-
let selectedType;
|
|
39073
|
-
if (!integrationType) {
|
|
39074
|
-
const prompted = await promptForIntegrationType();
|
|
39075
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39076
|
-
selectedType = prompted;
|
|
39077
|
-
} else {
|
|
39078
|
-
if (!isValidIntegration(integrationType)) {
|
|
39079
|
-
const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
|
|
39080
|
-
throw new Error(`Unsupported connector: ${integrationType}\nSupported connectors: ${supportedList}`);
|
|
39081
|
-
}
|
|
39082
|
-
selectedType = integrationType;
|
|
39083
|
-
}
|
|
39084
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39085
|
-
const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
|
|
39086
|
-
return await initiateOAuth(selectedType);
|
|
39087
|
-
}, {
|
|
39088
|
-
successMessage: `${displayName} OAuth initiated`,
|
|
39089
|
-
errorMessage: `Failed to initiate ${displayName} connection`
|
|
39090
|
-
});
|
|
39091
|
-
if (initiateResponse.already_authorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)}` };
|
|
39092
|
-
if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
|
|
39093
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
|
|
39094
|
-
M.info(`Opening browser for ${displayName} authorization...`);
|
|
39095
|
-
await open_default(initiateResponse.redirect_url);
|
|
39096
|
-
const result = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
|
|
39097
|
-
if (!result.success) throw new Error(result.error || "Authorization failed");
|
|
39098
|
-
const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
|
|
39099
|
-
return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
|
|
39100
|
-
}
|
|
39101
|
-
const connectorsAddCommand = new Command("connectors:add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
|
|
39102
|
-
await runCommand(() => addConnector(type), {
|
|
39103
|
-
requireAuth: true,
|
|
39104
|
-
requireAppConfig: true
|
|
39105
|
-
});
|
|
39106
|
-
});
|
|
39107
|
-
|
|
39108
|
-
//#endregion
|
|
39109
|
-
//#region src/cli/commands/connectors/list.ts
|
|
39110
|
-
function formatDate(dateString) {
|
|
39111
|
-
if (!dateString) return "-";
|
|
39112
|
-
try {
|
|
39113
|
-
return new Date(dateString).toLocaleDateString("en-US", {
|
|
39114
|
-
year: "numeric",
|
|
39115
|
-
month: "short",
|
|
39116
|
-
day: "numeric"
|
|
39117
|
-
});
|
|
39118
|
-
} catch {
|
|
39119
|
-
return dateString;
|
|
39120
|
-
}
|
|
39121
|
-
}
|
|
39122
|
-
function formatStatus(status) {
|
|
39123
|
-
const normalized = status.toLowerCase();
|
|
39124
|
-
if (normalized === "active" || normalized === "connected") return theme.colors.success("● active");
|
|
39125
|
-
if (normalized === "expired") return theme.colors.warning("● expired");
|
|
39126
|
-
if (normalized === "failed" || normalized === "disconnected") return theme.colors.error("● disconnected");
|
|
39127
|
-
return status;
|
|
39128
|
-
}
|
|
39129
|
-
async function listConnectorsCommand() {
|
|
39130
|
-
const connectors = await runTask("Fetching connectors...", async () => {
|
|
39131
|
-
return await listConnectors();
|
|
39132
|
-
}, {
|
|
39133
|
-
successMessage: "Connectors loaded",
|
|
39134
|
-
errorMessage: "Failed to fetch connectors"
|
|
39135
|
-
});
|
|
39136
|
-
if (connectors.length === 0) {
|
|
39137
|
-
M.info("No connectors configured for this app.");
|
|
39138
|
-
M.info(`Run ${theme.styles.bold("base44 connectors:add")} to connect an integration.`);
|
|
39139
|
-
return { outroMessage: "" };
|
|
39140
|
-
}
|
|
39141
|
-
console.log();
|
|
39142
|
-
console.log(theme.styles.bold("Connected Integrations:"));
|
|
39143
|
-
console.log();
|
|
39144
|
-
const headers = [
|
|
39145
|
-
"Type",
|
|
39146
|
-
"Account",
|
|
39147
|
-
"Status",
|
|
39148
|
-
"Connected"
|
|
39149
|
-
];
|
|
39150
|
-
const colWidths = [
|
|
39151
|
-
20,
|
|
39152
|
-
30,
|
|
39153
|
-
15,
|
|
39154
|
-
15
|
|
39155
|
-
];
|
|
39156
|
-
const headerRow = headers.map((h$2, i$1) => h$2.padEnd(colWidths[i$1])).join(" ");
|
|
39157
|
-
console.log(theme.styles.dim(headerRow));
|
|
39158
|
-
console.log(theme.styles.dim("─".repeat(headerRow.length)));
|
|
39159
|
-
for (const connector of connectors) {
|
|
39160
|
-
const type = getIntegrationDisplayName(connector.integrationType).padEnd(colWidths[0]);
|
|
39161
|
-
const account = (connector.accountInfo?.email || connector.accountInfo?.name || "-").padEnd(colWidths[1]);
|
|
39162
|
-
const status = formatStatus(connector.status);
|
|
39163
|
-
const connected = formatDate(connector.connectedAt).padEnd(colWidths[3]);
|
|
39164
|
-
console.log(`${type} ${account} ${status.padEnd(colWidths[2] + 10)} ${connected}`);
|
|
39165
|
-
}
|
|
39166
|
-
console.log();
|
|
39167
|
-
return { outroMessage: `${connectors.length} connector${connectors.length === 1 ? "" : "s"} configured` };
|
|
39168
|
-
}
|
|
39169
|
-
const connectorsListCommand = new Command("connectors:list").description("List all connected OAuth integrations").action(async () => {
|
|
39170
|
-
await runCommand(listConnectorsCommand, {
|
|
39171
|
-
requireAuth: true,
|
|
39172
|
-
requireAppConfig: true
|
|
39173
|
-
});
|
|
39174
|
-
});
|
|
39175
|
-
|
|
39176
|
-
//#endregion
|
|
39177
|
-
//#region src/cli/commands/connectors/remove.ts
|
|
39178
|
-
async function promptForConnectorToRemove(connectors) {
|
|
39179
|
-
const selected = await ve({
|
|
39180
|
-
message: "Select a connector to remove:",
|
|
39181
|
-
options: connectors.map((c$1) => ({
|
|
39182
|
-
value: c$1.integrationType,
|
|
39183
|
-
label: `${getIntegrationDisplayName(c$1.integrationType)}${c$1.accountInfo?.email ? ` (${c$1.accountInfo.email})` : ""}`
|
|
39184
|
-
}))
|
|
39185
|
-
});
|
|
39186
|
-
if (pD(selected)) return null;
|
|
39187
|
-
return selected;
|
|
39188
|
-
}
|
|
39189
|
-
async function removeConnectorCommand(integrationType) {
|
|
39190
|
-
const connectors = await runTask("Fetching connectors...", async () => {
|
|
39191
|
-
return await listConnectors();
|
|
39192
|
-
}, {
|
|
39193
|
-
successMessage: "Connectors loaded",
|
|
39194
|
-
errorMessage: "Failed to fetch connectors"
|
|
39195
|
-
});
|
|
39196
|
-
if (connectors.length === 0) return { outroMessage: "No connectors to remove" };
|
|
39197
|
-
let selectedType;
|
|
39198
|
-
if (!integrationType) {
|
|
39199
|
-
const prompted = await promptForConnectorToRemove(connectors);
|
|
39200
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39201
|
-
selectedType = prompted;
|
|
39202
|
-
} else {
|
|
39203
|
-
if (!isValidIntegration(integrationType)) throw new Error(`Invalid connector type: ${integrationType}`);
|
|
39204
|
-
if (!connectors.some((c$1) => c$1.integrationType === integrationType)) throw new Error(`No ${getIntegrationDisplayName(integrationType)} connector found for this app`);
|
|
39205
|
-
selectedType = integrationType;
|
|
39206
|
-
}
|
|
39207
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39208
|
-
const connector = connectors.find((c$1) => c$1.integrationType === selectedType);
|
|
39209
|
-
const shouldRemove = await ye({
|
|
39210
|
-
message: `Disconnect ${displayName}${connector?.accountInfo?.email ? ` (${connector.accountInfo.email})` : ""}?`,
|
|
39211
|
-
initialValue: false
|
|
39212
|
-
});
|
|
39213
|
-
if (pD(shouldRemove) || !shouldRemove) return { outroMessage: "Cancelled" };
|
|
39214
|
-
await runTask(`Disconnecting ${displayName}...`, async () => {
|
|
39215
|
-
await disconnectConnector(selectedType);
|
|
39216
|
-
}, {
|
|
39217
|
-
successMessage: `${displayName} disconnected`,
|
|
39218
|
-
errorMessage: `Failed to disconnect ${displayName}`
|
|
39219
|
-
});
|
|
39220
|
-
return { outroMessage: `Successfully disconnected ${theme.styles.bold(displayName)}` };
|
|
39221
|
-
}
|
|
39222
|
-
const connectorsRemoveCommand = new Command("connectors:remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").description("Disconnect an OAuth integration").action(async (type) => {
|
|
39223
|
-
await runCommand(() => removeConnectorCommand(type), {
|
|
39224
|
-
requireAuth: true,
|
|
39225
|
-
requireAppConfig: true
|
|
39226
|
-
});
|
|
39227
|
-
});
|
|
39228
|
-
|
|
39229
38934
|
//#endregion
|
|
39230
38935
|
//#region package.json
|
|
39231
|
-
var version = "0.0.
|
|
38936
|
+
var version = "0.0.16";
|
|
39232
38937
|
|
|
39233
38938
|
//#endregion
|
|
39234
38939
|
//#region src/cli/index.ts
|
|
@@ -39245,9 +38950,6 @@ program.addCommand(linkCommand);
|
|
|
39245
38950
|
program.addCommand(entitiesPushCommand);
|
|
39246
38951
|
program.addCommand(functionsDeployCommand);
|
|
39247
38952
|
program.addCommand(siteDeployCommand);
|
|
39248
|
-
program.addCommand(connectorsAddCommand);
|
|
39249
|
-
program.addCommand(connectorsListCommand);
|
|
39250
|
-
program.addCommand(connectorsRemoveCommand);
|
|
39251
38953
|
program.parse();
|
|
39252
38954
|
|
|
39253
38955
|
//#endregion
|
package/package.json
CHANGED