@flowkode/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ import { Command } from "commander";
2
+ import { printTable, printDetail, printSuccess, printError, printJson, } from "../output.js";
3
+ export function domainsCommand(api) {
4
+ const cmd = new Command("domains").description("Gerer les domaines");
5
+ cmd
6
+ .command("list")
7
+ .description("Lister les domaines")
8
+ .option("--registrar <registrar>", "Filtrer (internetbs, ovh, dynadot)")
9
+ .option("--provider <provider>", "Filtrer par provider de deploiement")
10
+ .option("--project <id>", "Filtrer par projet")
11
+ .option("--folder <id>", "Filtrer par dossier")
12
+ .action(async (opts) => {
13
+ try {
14
+ const res = (await api.get("/domains", {
15
+ registrar: opts.registrar,
16
+ deployment_provider: opts.provider,
17
+ project_id: opts.project,
18
+ folder_id: opts.folder,
19
+ }));
20
+ printTable(res.data, [
21
+ { key: "id", label: "ID", width: 36 },
22
+ { key: "name", label: "Domaine", width: 30 },
23
+ { key: "registrar", label: "Registrar", width: 12 },
24
+ { key: "status", label: "Statut", width: 10 },
25
+ { key: "availability", label: "Dispo", width: 10 },
26
+ { key: "project_name", label: "Projet", width: 20 },
27
+ ]);
28
+ }
29
+ catch (e) {
30
+ printError(e);
31
+ }
32
+ });
33
+ cmd
34
+ .command("get <id>")
35
+ .description("Detail d'un domaine")
36
+ .action(async (id) => {
37
+ try {
38
+ const res = (await api.get(`/domains/${id}`));
39
+ printDetail(res.data, [
40
+ { key: "id", label: "ID" },
41
+ { key: "name", label: "Domaine" },
42
+ { key: "registrar", label: "Registrar" },
43
+ { key: "status", label: "Statut" },
44
+ { key: "availability", label: "Disponibilite" },
45
+ { key: "dns_status", label: "DNS" },
46
+ { key: "project_name", label: "Projet" },
47
+ { key: "expires_at", label: "Expiration" },
48
+ ]);
49
+ }
50
+ catch (e) {
51
+ printError(e);
52
+ }
53
+ });
54
+ cmd
55
+ .command("check <domain>")
56
+ .description("Verifier la disponibilite d'un domaine")
57
+ .action(async (domain) => {
58
+ try {
59
+ const res = (await api.post("/domains/check", {
60
+ domain,
61
+ }));
62
+ printTable(res.data, [
63
+ { key: "registrar", label: "Registrar", width: 12 },
64
+ { key: "available", label: "Disponible", width: 12 },
65
+ { key: "price", label: "Prix", width: 10 },
66
+ { key: "currency", label: "Devise", width: 8 },
67
+ ]);
68
+ }
69
+ catch (e) {
70
+ printError(e);
71
+ }
72
+ });
73
+ cmd
74
+ .command("purchase <domain>")
75
+ .description("Acheter un domaine")
76
+ .requiredOption("-r, --registrar <registrar>", "Registrar (internetbs, ovh, dynadot)")
77
+ .option("-y, --years <years>", "Duree en annees", "1")
78
+ .action(async (domain, opts) => {
79
+ try {
80
+ await api.post("/domains/purchase", {
81
+ domain,
82
+ registrar: opts.registrar,
83
+ years: parseInt(opts.years),
84
+ });
85
+ printSuccess(`Domaine ${domain} achete via ${opts.registrar}.`);
86
+ }
87
+ catch (e) {
88
+ printError(e);
89
+ }
90
+ });
91
+ cmd
92
+ .command("sync")
93
+ .description("Synchroniser les domaines depuis les registrars")
94
+ .action(async () => {
95
+ try {
96
+ await api.post("/domains/sync");
97
+ printSuccess("Domaines synchronises.");
98
+ }
99
+ catch (e) {
100
+ printError(e);
101
+ }
102
+ });
103
+ cmd
104
+ .command("associate <domainId>")
105
+ .description("Associer un domaine a un projet")
106
+ .requiredOption("--project <id>", "ID du projet")
107
+ .action(async (domainId, opts) => {
108
+ try {
109
+ const res = await api.post(`/domains/${domainId}/associate`, {
110
+ project_id: opts.project,
111
+ });
112
+ printJson(res);
113
+ printSuccess("Domaine associe.");
114
+ }
115
+ catch (e) {
116
+ printError(e);
117
+ }
118
+ });
119
+ cmd
120
+ .command("dissociate <domainId>")
121
+ .description("Dissocier un domaine de son projet")
122
+ .action(async (domainId) => {
123
+ try {
124
+ await api.delete(`/domains/${domainId}/associate`);
125
+ printSuccess("Domaine dissocie.");
126
+ }
127
+ catch (e) {
128
+ printError(e);
129
+ }
130
+ });
131
+ cmd
132
+ .command("dns <domainId>")
133
+ .description("Configurer les DNS du domaine")
134
+ .action(async (domainId) => {
135
+ try {
136
+ const res = await api.post(`/domains/${domainId}/configure-dns`);
137
+ printJson(res);
138
+ }
139
+ catch (e) {
140
+ printError(e);
141
+ }
142
+ });
143
+ cmd
144
+ .command("propagation <domainId>")
145
+ .description("Verifier la propagation DNS")
146
+ .action(async (domainId) => {
147
+ try {
148
+ const res = await api.post(`/domains/${domainId}/check-propagation`);
149
+ printJson(res);
150
+ }
151
+ catch (e) {
152
+ printError(e);
153
+ }
154
+ });
155
+ cmd
156
+ .command("delete <id>")
157
+ .description("Supprimer un domaine")
158
+ .action(async (id) => {
159
+ try {
160
+ await api.delete(`/domains/${id}`);
161
+ printSuccess("Domaine supprime.");
162
+ }
163
+ catch (e) {
164
+ printError(e);
165
+ }
166
+ });
167
+ cmd
168
+ .command("update <id>")
169
+ .description("Modifier un domaine")
170
+ .option("--folder <id>", "ID du dossier")
171
+ .action(async (id, opts) => {
172
+ try {
173
+ const body = {};
174
+ if (opts.folder !== undefined)
175
+ body.folder_id = opts.folder || null;
176
+ const res = (await api.patch(`/domains/${id}`, body));
177
+ printDetail(res.data, [
178
+ { key: "id", label: "ID" },
179
+ { key: "name", label: "Domaine" },
180
+ { key: "folder_id", label: "Dossier" },
181
+ ]);
182
+ printSuccess("Domaine mis a jour.");
183
+ }
184
+ catch (e) {
185
+ printError(e);
186
+ }
187
+ });
188
+ return cmd;
189
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { FlowkodeApiClient } from "../api-client.js";
3
+ export declare function foldersCommand(api: FlowkodeApiClient): Command;
@@ -0,0 +1,103 @@
1
+ import { Command } from "commander";
2
+ import { printTable, printDetail, printSuccess, printError, } from "../output.js";
3
+ export function foldersCommand(api) {
4
+ const cmd = new Command("folders").description("Gerer les dossiers");
5
+ cmd
6
+ .command("list")
7
+ .description("Lister les dossiers")
8
+ .action(async () => {
9
+ try {
10
+ const res = (await api.get("/folders"));
11
+ printTable(res.data, [
12
+ { key: "id", label: "ID", width: 36 },
13
+ { key: "name", label: "Nom", width: 25 },
14
+ { key: "color", label: "Couleur", width: 10 },
15
+ { key: "position", label: "Pos", width: 5 },
16
+ ]);
17
+ }
18
+ catch (e) {
19
+ printError(e);
20
+ }
21
+ });
22
+ cmd
23
+ .command("get <id>")
24
+ .description("Detail d'un dossier")
25
+ .action(async (id) => {
26
+ try {
27
+ const res = (await api.get(`/folders/${id}`));
28
+ printDetail(res.data, [
29
+ { key: "id", label: "ID" },
30
+ { key: "name", label: "Nom" },
31
+ { key: "color", label: "Couleur" },
32
+ { key: "position", label: "Position" },
33
+ { key: "created_at", label: "Cree le" },
34
+ ]);
35
+ }
36
+ catch (e) {
37
+ printError(e);
38
+ }
39
+ });
40
+ cmd
41
+ .command("create")
42
+ .description("Creer un dossier")
43
+ .requiredOption("-n, --name <name>", "Nom du dossier")
44
+ .option("-c, --color <color>", "Couleur (gray, red, orange, amber, green, blue, violet, pink)", "gray")
45
+ .action(async (opts) => {
46
+ try {
47
+ const res = (await api.post("/folders", {
48
+ name: opts.name,
49
+ color: opts.color,
50
+ }));
51
+ printDetail(res.data, [
52
+ { key: "id", label: "ID" },
53
+ { key: "name", label: "Nom" },
54
+ { key: "color", label: "Couleur" },
55
+ ]);
56
+ printSuccess("Dossier cree.");
57
+ }
58
+ catch (e) {
59
+ printError(e);
60
+ }
61
+ });
62
+ cmd
63
+ .command("update <id>")
64
+ .description("Modifier un dossier")
65
+ .option("-n, --name <name>", "Nouveau nom")
66
+ .option("-c, --color <color>", "Nouvelle couleur")
67
+ .option("-p, --position <pos>", "Nouvelle position")
68
+ .action(async (id, opts) => {
69
+ try {
70
+ const body = {};
71
+ if (opts.name)
72
+ body.name = opts.name;
73
+ if (opts.color)
74
+ body.color = opts.color;
75
+ if (opts.position !== undefined)
76
+ body.position = parseInt(opts.position);
77
+ const res = (await api.patch(`/folders/${id}`, body));
78
+ printDetail(res.data, [
79
+ { key: "id", label: "ID" },
80
+ { key: "name", label: "Nom" },
81
+ { key: "color", label: "Couleur" },
82
+ { key: "position", label: "Position" },
83
+ ]);
84
+ printSuccess("Dossier mis a jour.");
85
+ }
86
+ catch (e) {
87
+ printError(e);
88
+ }
89
+ });
90
+ cmd
91
+ .command("delete <id>")
92
+ .description("Supprimer un dossier")
93
+ .action(async (id) => {
94
+ try {
95
+ await api.delete(`/folders/${id}`);
96
+ printSuccess("Dossier supprime. Projets et domaines deplaces a la racine.");
97
+ }
98
+ catch (e) {
99
+ printError(e);
100
+ }
101
+ });
102
+ return cmd;
103
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { FlowkodeApiClient } from "../api-client.js";
3
+ export declare function jobsCommand(api: FlowkodeApiClient): Command;
@@ -0,0 +1,45 @@
1
+ import { Command } from "commander";
2
+ import { printTable, printDetail, printError } from "../output.js";
3
+ export function jobsCommand(api) {
4
+ const cmd = new Command("jobs").description("Suivre les jobs de generation");
5
+ cmd
6
+ .command("list")
7
+ .description("Lister les jobs")
8
+ .option("-s, --status <status>", "Filtrer (pending, running, completed, failed)")
9
+ .action(async (opts) => {
10
+ try {
11
+ const res = (await api.get("/jobs", {
12
+ status: opts.status,
13
+ }));
14
+ printTable(res.data, [
15
+ { key: "id", label: "ID", width: 36 },
16
+ { key: "status", label: "Statut", width: 12 },
17
+ { key: "project_id", label: "Projet", width: 36 },
18
+ { key: "created_at", label: "Cree le", width: 20 },
19
+ ]);
20
+ }
21
+ catch (e) {
22
+ printError(e);
23
+ }
24
+ });
25
+ cmd
26
+ .command("get <id>")
27
+ .description("Detail d'un job")
28
+ .action(async (id) => {
29
+ try {
30
+ const res = (await api.get(`/jobs/${id}`));
31
+ printDetail(res.data, [
32
+ { key: "id", label: "ID" },
33
+ { key: "status", label: "Statut" },
34
+ { key: "project_id", label: "Projet" },
35
+ { key: "error_message", label: "Erreur" },
36
+ { key: "created_at", label: "Cree le" },
37
+ { key: "updated_at", label: "Mis a jour" },
38
+ ]);
39
+ }
40
+ catch (e) {
41
+ printError(e);
42
+ }
43
+ });
44
+ return cmd;
45
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function loginCommand(): Command;
3
+ export declare function logoutCommand(): Command;
@@ -0,0 +1,133 @@
1
+ import { Command } from "commander";
2
+ import { createServer } from "node:http";
3
+ import { randomBytes } from "node:crypto";
4
+ import { exec } from "node:child_process";
5
+ import chalk from "chalk";
6
+ import { loadConfig, saveConfig, getBaseUrl } from "../config.js";
7
+ import { printSuccess, printError } from "../output.js";
8
+ function openBrowser(url) {
9
+ try {
10
+ const cmd = process.platform === "darwin"
11
+ ? "open"
12
+ : process.platform === "win32"
13
+ ? "start"
14
+ : "xdg-open";
15
+ exec(`${cmd} "${url}"`);
16
+ }
17
+ catch {
18
+ // Silent fail
19
+ }
20
+ }
21
+ export function loginCommand() {
22
+ const cmd = new Command("login")
23
+ .description("Se connecter a Flowkode via le navigateur")
24
+ .option("--no-browser", "Ne pas ouvrir le navigateur automatiquement")
25
+ .action(async (opts) => {
26
+ const baseUrl = getBaseUrl();
27
+ const code = randomBytes(32).toString("hex");
28
+ console.log();
29
+ console.log(chalk.bold(" Flowkode Login"));
30
+ console.log();
31
+ // Get a random port
32
+ const tempServer = createServer();
33
+ await new Promise((resolve) => tempServer.listen(0, "127.0.0.1", resolve));
34
+ const port = tempServer.address().port;
35
+ tempServer.close();
36
+ // Now start the real server on that port
37
+ const resultPromise = new Promise((resolve, reject) => {
38
+ const server = createServer((req, res) => {
39
+ const url = new URL(req.url ?? "/", "http://localhost");
40
+ if (url.pathname !== "/callback") {
41
+ res.writeHead(404);
42
+ res.end("Not found");
43
+ return;
44
+ }
45
+ const receivedCode = url.searchParams.get("code");
46
+ const error = url.searchParams.get("error");
47
+ const key = url.searchParams.get("key");
48
+ const email = url.searchParams.get("email") ?? "";
49
+ if (receivedCode !== code) {
50
+ res.writeHead(400);
51
+ res.end("Invalid code");
52
+ return;
53
+ }
54
+ if (error) {
55
+ res.writeHead(200, {
56
+ "Content-Type": "text/html; charset=utf-8",
57
+ });
58
+ res.end(htmlPage("Autorisation refusee", "Vous pouvez fermer cette page.", "#dc2626"));
59
+ server.close();
60
+ reject(new Error("Autorisation refusee par l'utilisateur."));
61
+ return;
62
+ }
63
+ if (!key) {
64
+ res.writeHead(400);
65
+ res.end("Missing key");
66
+ return;
67
+ }
68
+ res.writeHead(200, {
69
+ "Content-Type": "text/html; charset=utf-8",
70
+ });
71
+ res.end(htmlPage("Connecte !", "Vous pouvez fermer cette page et retourner dans votre terminal.", "#16a34a"));
72
+ server.close();
73
+ resolve({ key, email });
74
+ });
75
+ server.listen(port, "127.0.0.1");
76
+ setTimeout(() => {
77
+ server.close();
78
+ reject(new Error("Timeout — aucune reponse recue du navigateur (5 min)."));
79
+ }, 5 * 60 * 1000);
80
+ });
81
+ const authorizeUrl = `${baseUrl}/cli/authorize?port=${port}&code=${code}`;
82
+ if (opts.browser !== false) {
83
+ console.log(chalk.dim(" Ouverture du navigateur..."));
84
+ openBrowser(authorizeUrl);
85
+ }
86
+ else {
87
+ console.log(` Ouvrez ce lien dans votre navigateur :\n\n ${chalk.cyan(authorizeUrl)}`);
88
+ }
89
+ console.log();
90
+ console.log(chalk.dim(" En attente d'autorisation..."));
91
+ try {
92
+ const { key, email } = await resultPromise;
93
+ const config = loadConfig();
94
+ config.api_key = key;
95
+ saveConfig(config);
96
+ console.log();
97
+ printSuccess(`Connecte${email ? ` en tant que ${chalk.bold(email)}` : ""} !`);
98
+ console.log(chalk.dim(" Cle sauvegardee dans ~/.flowkode/config.json"));
99
+ console.log();
100
+ console.log(chalk.dim(" Essayez : flowkode projects list"));
101
+ console.log();
102
+ }
103
+ catch (e) {
104
+ console.log();
105
+ printError(e);
106
+ }
107
+ });
108
+ return cmd;
109
+ }
110
+ export function logoutCommand() {
111
+ return new Command("logout")
112
+ .description("Se deconnecter (supprime la cle API locale)")
113
+ .action(() => {
114
+ const config = loadConfig();
115
+ if (!config.api_key) {
116
+ console.log(chalk.dim("Pas de cle API configuree."));
117
+ return;
118
+ }
119
+ delete config.api_key;
120
+ saveConfig(config);
121
+ printSuccess("Deconnecte. Cle API supprimee de ~/.flowkode/config.json");
122
+ });
123
+ }
124
+ function htmlPage(title, message, color) {
125
+ return `<!DOCTYPE html>
126
+ <html><head><meta charset="utf-8"><title>Flowkode CLI</title></head>
127
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#fafafa">
128
+ <div style="text-align:center">
129
+ <h2 style="color:${color}">${title}</h2>
130
+ <p style="color:#666">${message}</p>
131
+ </div>
132
+ </body></html>`;
133
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { FlowkodeApiClient } from "../api-client.js";
3
+ export declare function projectsCommand(api: FlowkodeApiClient): Command;
@@ -0,0 +1,146 @@
1
+ import { Command } from "commander";
2
+ import { printTable, printDetail, printSuccess, printError, printJson, } from "../output.js";
3
+ export function projectsCommand(api) {
4
+ const cmd = new Command("projects").description("Gerer les projets");
5
+ cmd
6
+ .command("list")
7
+ .description("Lister les projets")
8
+ .option("--folder <id>", "Filtrer par dossier")
9
+ .action(async (opts) => {
10
+ try {
11
+ const res = (await api.get("/projects", {
12
+ folder_id: opts.folder,
13
+ }));
14
+ printTable(res.data, [
15
+ { key: "id", label: "ID", width: 36 },
16
+ { key: "name", label: "Nom", width: 30 },
17
+ { key: "description", label: "Description", width: 40 },
18
+ { key: "created_at", label: "Cree le", width: 20 },
19
+ ]);
20
+ }
21
+ catch (e) {
22
+ printError(e);
23
+ }
24
+ });
25
+ cmd
26
+ .command("get <id>")
27
+ .description("Detail d'un projet")
28
+ .action(async (id) => {
29
+ try {
30
+ const res = (await api.get(`/projects/${id}`));
31
+ printDetail(res.data, [
32
+ { key: "id", label: "ID" },
33
+ { key: "name", label: "Nom" },
34
+ { key: "description", label: "Description" },
35
+ { key: "site_type", label: "Type" },
36
+ { key: "keywords", label: "Mots-cles" },
37
+ { key: "folder_id", label: "Dossier" },
38
+ { key: "created_at", label: "Cree le" },
39
+ ]);
40
+ }
41
+ catch (e) {
42
+ printError(e);
43
+ }
44
+ });
45
+ cmd
46
+ .command("create")
47
+ .description("Creer un projet (mode rapide)")
48
+ .requiredOption("-n, --name <name>", "Nom du projet")
49
+ .requiredOption("-t, --type <type>", "Type de site (vitrine, affiliation)")
50
+ .requiredOption("-k, --keywords <keywords>", "Mots-cles SEO")
51
+ .option("-l, --language <lang>", "Langue", "fr")
52
+ .option("-d, --description <desc>", "Description du site")
53
+ .option("--ai-model <model>", "ID du modele IA")
54
+ .action(async (opts) => {
55
+ try {
56
+ const res = await api.post("/projects", {
57
+ mode: "quick",
58
+ name: opts.name,
59
+ siteType: opts.type,
60
+ keywords: opts.keywords,
61
+ language: opts.language,
62
+ description: opts.description,
63
+ aiModel: opts.aiModel,
64
+ });
65
+ printJson(res);
66
+ printSuccess("Projet en cours de generation. Suivez avec: flowkode jobs get <jobId>");
67
+ }
68
+ catch (e) {
69
+ printError(e);
70
+ }
71
+ });
72
+ cmd
73
+ .command("update <id>")
74
+ .description("Modifier un projet")
75
+ .option("-n, --name <name>", "Nouveau nom")
76
+ .option("-d, --description <desc>", "Nouvelle description")
77
+ .option("--folder <id>", "ID du dossier (vide pour racine)")
78
+ .action(async (id, opts) => {
79
+ try {
80
+ const body = {};
81
+ if (opts.name)
82
+ body.name = opts.name;
83
+ if (opts.description)
84
+ body.description = opts.description;
85
+ if (opts.folder !== undefined)
86
+ body.folder_id = opts.folder || null;
87
+ const res = (await api.patch(`/projects/${id}`, body));
88
+ printDetail(res.data, [
89
+ { key: "id", label: "ID" },
90
+ { key: "name", label: "Nom" },
91
+ { key: "description", label: "Description" },
92
+ { key: "folder_id", label: "Dossier" },
93
+ ]);
94
+ printSuccess("Projet mis a jour.");
95
+ }
96
+ catch (e) {
97
+ printError(e);
98
+ }
99
+ });
100
+ cmd
101
+ .command("deploy <id>")
102
+ .description("Deployer un projet")
103
+ .requiredOption("-p, --provider <provider>", "Provider (github, vercel, cloudflare)")
104
+ .option("--domain <id>", "ID du domaine a associer")
105
+ .option("--configure-ns", "Configurer les nameservers automatiquement")
106
+ .action(async (id, opts) => {
107
+ try {
108
+ const res = await api.post(`/projects/${id}/deploy`, {
109
+ provider: opts.provider,
110
+ domain_id: opts.domain,
111
+ configure_ns: opts.configureNs,
112
+ });
113
+ printJson(res);
114
+ printSuccess("Deploiement lance.");
115
+ }
116
+ catch (e) {
117
+ printError(e);
118
+ }
119
+ });
120
+ cmd
121
+ .command("redeploy <id>")
122
+ .description("Re-deployer sur le dernier provider")
123
+ .action(async (id) => {
124
+ try {
125
+ const res = await api.post(`/projects/${id}/redeploy`);
126
+ printJson(res);
127
+ printSuccess("Re-deploiement lance.");
128
+ }
129
+ catch (e) {
130
+ printError(e);
131
+ }
132
+ });
133
+ cmd
134
+ .command("indexing <id>")
135
+ .description("Statut d'indexation Google du projet")
136
+ .action(async (id) => {
137
+ try {
138
+ const res = await api.get(`/projects/${id}/indexing`);
139
+ printJson(res);
140
+ }
141
+ catch (e) {
142
+ printError(e);
143
+ }
144
+ });
145
+ return cmd;
146
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { FlowkodeApiClient } from "../api-client.js";
3
+ export declare function utilitiesCommands(api: FlowkodeApiClient): Command[];