odoo-forge 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "odoo-forge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI installer and updater for Odoo Forge internal 1.0.",
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "odoo-forge-bundle": "0.1.0"
7
+ "odoo-forge-bundle": "0.1.1"
8
8
  },
9
9
  "bin": {
10
10
  "odoo-forge": "bin/odoo-forge.js"
package/src/claude.js CHANGED
@@ -1,92 +1,50 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
2
 
4
- const MARKETPLACE_NAME = "odoo-forge-marketplace";
5
- const PLUGIN_NAME = "odoo-forge";
3
+ import { getClaudeConfigPath } from "./paths.js";
6
4
 
7
- function writeJson(filePath, value) {
8
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
9
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
10
- }
11
-
12
- export function renderClaudeMarketplace({
13
- bundleRoot,
14
- destinationRoot,
15
- version,
16
- }) {
17
- const marketplaceDir = path.join(destinationRoot, "claude-marketplace");
18
- const pluginDir = path.join(marketplaceDir, "plugins", PLUGIN_NAME);
19
- const skillsSource = path.join(bundleRoot, "skills");
5
+ export function readClaudeConfig({ homeDir }) {
6
+ const configPath = getClaudeConfigPath({ homeDir });
7
+ if (!fs.existsSync(configPath)) {
8
+ return {};
9
+ }
20
10
 
21
- fs.rmSync(marketplaceDir, { force: true, recursive: true });
22
- fs.mkdirSync(pluginDir, { recursive: true });
11
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
12
+ }
23
13
 
24
- writeJson(path.join(marketplaceDir, ".claude-plugin", "marketplace.json"), {
25
- name: MARKETPLACE_NAME,
26
- owner: {
27
- name: "Odoo Forge Team",
28
- },
29
- metadata: {
30
- description: "Internal marketplace for Odoo Forge.",
31
- version,
32
- pluginRoot: "./plugins",
33
- },
34
- plugins: [
35
- {
36
- name: PLUGIN_NAME,
37
- description: "Internal Odoo Forge skill suite.",
38
- source: "./plugins/odoo-forge",
39
- category: "development",
40
- },
41
- ],
42
- });
14
+ export function writeClaudeConfig({ homeDir, config }) {
15
+ const configPath = getClaudeConfigPath({ homeDir });
16
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
17
+ return configPath;
18
+ }
43
19
 
44
- writeJson(path.join(pluginDir, ".claude-plugin", "plugin.json"), {
45
- name: PLUGIN_NAME,
46
- description: "Internal Odoo Forge plugin for Claude Code.",
47
- version,
48
- author: {
49
- name: "Internal Team",
20
+ export function buildClaudeFlowusServer({ token }) {
21
+ return {
22
+ type: "stdio",
23
+ command: "npx",
24
+ args: ["-y", "flowus-mcp-server@latest"],
25
+ env: {
26
+ FLOWUS_TOKEN: token,
50
27
  },
51
- });
28
+ };
29
+ }
52
30
 
53
- writeJson(path.join(pluginDir, ".mcp.json"), {
31
+ export function installClaudeWiring({ homeDir, token }) {
32
+ const currentConfig = readClaudeConfig({ homeDir });
33
+ const nextConfig = {
34
+ ...currentConfig,
54
35
  mcpServers: {
55
- flowus: {
56
- type: "stdio",
57
- command: "odoo-forge",
58
- args: ["mcp", "flowus"],
59
- },
36
+ ...(currentConfig.mcpServers ?? {}),
37
+ flowus: buildClaudeFlowusServer({ token }),
60
38
  },
61
- });
62
-
63
- fs.cpSync(skillsSource, path.join(pluginDir, "skills"), { recursive: true });
39
+ };
40
+ const configPath = writeClaudeConfig({ homeDir, config: nextConfig });
64
41
 
65
- return marketplaceDir;
42
+ return {
43
+ configPath,
44
+ };
66
45
  }
67
46
 
68
- export function buildClaudeCommandPlan({ marketplaceDir, mode }) {
69
- if (mode === "update") {
70
- return [
71
- {
72
- command: "claude",
73
- args: ["plugin", "marketplace", "update", MARKETPLACE_NAME],
74
- },
75
- {
76
- command: "claude",
77
- args: ["plugin", "update", `${PLUGIN_NAME}@${MARKETPLACE_NAME}`, "--scope", "user"],
78
- },
79
- ];
80
- }
81
-
82
- return [
83
- {
84
- command: "claude",
85
- args: ["plugin", "marketplace", "add", marketplaceDir],
86
- },
87
- {
88
- command: "claude",
89
- args: ["plugin", "install", `${PLUGIN_NAME}@${MARKETPLACE_NAME}`, "--scope", "user"],
90
- },
91
- ];
47
+ export function readClaudeManagedToken({ homeDir }) {
48
+ const config = readClaudeConfig({ homeDir });
49
+ return config?.mcpServers?.flowus?.env?.FLOWUS_TOKEN ?? null;
92
50
  }
package/src/codex.js CHANGED
@@ -1,17 +1,18 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { getCodexConfigPath, getCodexSkillsLinkPath } from "./paths.js";
4
+ import { getCodexConfigPath } from "./paths.js";
5
5
 
6
6
  export const CODEX_START_MARKER = "# BEGIN ODOO FORGE FLOWUS";
7
7
  export const CODEX_END_MARKER = "# END ODOO FORGE FLOWUS";
8
8
 
9
- export function buildCodexManagedBlock() {
9
+ export function buildCodexManagedBlock({ token }) {
10
10
  return `${CODEX_START_MARKER}
11
11
  [mcp_servers.flowus]
12
12
  type = "stdio"
13
- command = "odoo-forge"
14
- args = ["mcp", "flowus"]
13
+ command = "npx"
14
+ args = ["-y", "flowus-mcp-server@latest"]
15
+ env = { FLOWUS_TOKEN = "${token}" }
15
16
  ${CODEX_END_MARKER}`;
16
17
  }
17
18
 
@@ -33,33 +34,27 @@ export function upsertManagedBlock({
33
34
  return trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`;
34
35
  }
35
36
 
36
- function ensureDirectoryLink({ linkPath, targetPath, platform = process.platform }) {
37
- fs.mkdirSync(path.dirname(linkPath), { recursive: true });
37
+ export function readCodexManagedToken({ homeDir }) {
38
+ const configPath = getCodexConfigPath({ homeDir });
39
+ if (!fs.existsSync(configPath)) {
40
+ return null;
41
+ }
38
42
 
39
- if (fs.existsSync(linkPath) || fs.lstatSync(path.dirname(linkPath)).isDirectory()) {
40
- try {
41
- const existingTarget = fs.readlinkSync(linkPath);
42
- if (path.resolve(path.dirname(linkPath), existingTarget) === targetPath) {
43
- return;
44
- }
45
- } catch {
46
- fs.rmSync(linkPath, { force: true, recursive: true });
47
- }
43
+ const content = fs.readFileSync(configPath, "utf8");
44
+ const startIndex = content.indexOf(CODEX_START_MARKER);
45
+ const endIndex = content.indexOf(CODEX_END_MARKER);
46
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
47
+ return null;
48
48
  }
49
49
 
50
- fs.rmSync(linkPath, { force: true, recursive: true });
51
- fs.symlinkSync(targetPath, linkPath, platform === "win32" ? "junction" : "dir");
50
+ const managedBlock = content.slice(startIndex, endIndex + CODEX_END_MARKER.length);
51
+ const match = managedBlock.match(/FLOWUS_TOKEN\s*=\s*"([^"]+)"/);
52
+ return match?.[1] ?? null;
52
53
  }
53
54
 
54
- export function installCodexWiring({
55
- homeDir,
56
- installRoot,
57
- platform = process.platform,
58
- }) {
55
+ export function installCodexWiring({ homeDir, token }) {
59
56
  const configPath = getCodexConfigPath({ homeDir });
60
- const linkPath = getCodexSkillsLinkPath({ homeDir });
61
57
  const codexDir = path.dirname(configPath);
62
- const skillsTarget = path.join(installRoot, "current", "skills");
63
58
 
64
59
  fs.mkdirSync(codexDir, { recursive: true });
65
60
  const currentConfig = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
@@ -67,14 +62,11 @@ export function installCodexWiring({
67
62
  originalContent: currentConfig,
68
63
  startMarker: CODEX_START_MARKER,
69
64
  endMarker: CODEX_END_MARKER,
70
- block: buildCodexManagedBlock(),
65
+ block: buildCodexManagedBlock({ token }),
71
66
  });
72
67
  fs.writeFileSync(configPath, nextConfig);
73
68
 
74
- ensureDirectoryLink({ linkPath, targetPath: skillsTarget, platform });
75
-
76
69
  return {
77
70
  configPath,
78
- linkPath,
79
71
  };
80
72
  }
package/src/index.js CHANGED
@@ -2,20 +2,17 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import readline from "node:readline/promises";
5
- import { spawn, spawnSync } from "node:child_process";
5
+ import { spawn } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
+ import { installClaudeWiring, readClaudeManagedToken } from "./claude.js";
9
+ import { installCodexWiring, readCodexManagedToken } from "./codex.js";
8
10
  import {
9
- getConfigPath,
10
- getFlowusToken,
11
- readUserConfig,
12
- writeFlowusToken,
13
- writeState,
14
- } from "./config.js";
15
- import { buildClaudeCommandPlan, renderClaudeMarketplace } from "./claude.js";
16
- import { installCodexWiring } from "./codex.js";
17
- import { getCurrentRoot, getInstallRoot, getStatePath } from "./paths.js";
18
- import { installRuntime } from "./runtime.js";
11
+ getAgentsSkillsRoot,
12
+ getClaudeConfigPath,
13
+ getCodexConfigPath,
14
+ getInstalledSkillsPath,
15
+ } from "./paths.js";
19
16
 
20
17
  function printHelp() {
21
18
  console.log(`Odoo Forge CLI
@@ -56,17 +53,6 @@ async function defaultPromptForSecret(message) {
56
53
  }
57
54
  }
58
55
 
59
- async function defaultRunCommand(command, args, options = {}) {
60
- return await new Promise((resolve) => {
61
- const child = spawn(command, args, {
62
- stdio: "inherit",
63
- ...options,
64
- });
65
- child.on("close", (code) => resolve({ code: code ?? 0 }));
66
- child.on("error", () => resolve({ code: 1 }));
67
- });
68
- }
69
-
70
56
  async function defaultSpawnProcess(command, args, options = {}) {
71
57
  return await new Promise((resolve, reject) => {
72
58
  const child = spawn(command, args, {
@@ -78,22 +64,25 @@ async function defaultSpawnProcess(command, args, options = {}) {
78
64
  });
79
65
  }
80
66
 
81
- async function defaultHasCommand(command) {
82
- const result = spawnSync(command, ["--version"], { stdio: "ignore" });
83
- return result.status === 0 || result.status === 1;
84
- }
67
+ function installSkills({ bundleRoot, homeDir }) {
68
+ const sourceSkillsDir = path.join(bundleRoot, "skills");
69
+ const targetSkillsDir = getInstalledSkillsPath({ homeDir });
85
70
 
86
- async function readProduct(bundleRoot) {
87
- return JSON.parse(fs.readFileSync(path.join(bundleRoot, "config", "product.json"), "utf8"));
71
+ fs.mkdirSync(getAgentsSkillsRoot({ homeDir }), { recursive: true });
72
+ fs.rmSync(targetSkillsDir, { recursive: true, force: true });
73
+ fs.cpSync(sourceSkillsDir, targetSkillsDir, { recursive: true });
74
+
75
+ return targetSkillsDir;
88
76
  }
89
77
 
90
- async function ensureFlowusToken({ ctx, configPath }) {
91
- const existingConfig = readUserConfig({ configPath });
92
- const envToken = ctx.env.ODOO_FORGE_FLOWUS_TOKEN;
93
- const currentToken = envToken || getFlowusToken({ config: existingConfig });
78
+ async function ensureFlowusToken({ ctx }) {
79
+ const envToken = ctx.env.ODOO_FORGE_FLOWUS_TOKEN ?? ctx.env.FLOWUS_TOKEN;
80
+ const currentToken =
81
+ envToken ??
82
+ readCodexManagedToken({ homeDir: ctx.homeDir }) ??
83
+ readClaudeManagedToken({ homeDir: ctx.homeDir });
94
84
 
95
85
  if (currentToken) {
96
- writeFlowusToken({ configPath, token: currentToken });
97
86
  return currentToken;
98
87
  }
99
88
 
@@ -102,103 +91,47 @@ async function ensureFlowusToken({ ctx, configPath }) {
102
91
  if (!token) {
103
92
  throw new Error("FlowUS token is required.");
104
93
  }
105
- writeFlowusToken({ configPath, token });
106
94
  return token;
107
95
  }
108
96
 
109
- async function installClaude({ ctx, currentRoot, version, mode }) {
110
- if (!(await ctx.hasCommand("claude"))) {
111
- ctx.output.log("Claude CLI not found. Skipping Claude installation.");
112
- return { enabled: false };
113
- }
114
-
115
- const marketplaceDir = renderClaudeMarketplace({
116
- bundleRoot: currentRoot,
117
- destinationRoot: currentRoot,
118
- version,
119
- });
120
- const commandPlan = buildClaudeCommandPlan({ marketplaceDir, mode });
121
-
122
- for (const step of commandPlan) {
123
- const result = await ctx.runCommand(step.command, step.args);
124
- if (result.code !== 0) {
125
- throw new Error(
126
- `Claude command failed: ${step.command} ${step.args.join(" ")}`,
127
- );
128
- }
129
- }
130
-
131
- return {
132
- enabled: true,
133
- marketplaceDir,
134
- };
135
- }
136
-
137
97
  function runDoctor(ctx) {
138
- const installRoot = getInstallRoot({ homeDir: ctx.homeDir });
139
- const statePath = getStatePath({ homeDir: ctx.homeDir, installRoot });
140
- const configPath = getConfigPath({
141
- homeDir: ctx.homeDir,
142
- platform: ctx.platform,
143
- env: ctx.env,
144
- });
145
- const config = readUserConfig({ configPath });
146
- const currentRoot = getCurrentRoot({ homeDir: ctx.homeDir, installRoot });
98
+ const skillsPath = getInstalledSkillsPath({ homeDir: ctx.homeDir });
99
+ const codexConfigPath = getCodexConfigPath({ homeDir: ctx.homeDir });
100
+ const claudeConfigPath = getClaudeConfigPath({ homeDir: ctx.homeDir });
101
+ const codexToken = readCodexManagedToken({ homeDir: ctx.homeDir });
102
+ const claudeToken = readClaudeManagedToken({ homeDir: ctx.homeDir });
147
103
 
148
104
  ctx.output.log("Odoo Forge doctor");
149
- ctx.output.log(`Install root: ${installRoot}`);
150
- ctx.output.log(`Install root exists: ${fs.existsSync(installRoot) ? "yes" : "no"}`);
151
- ctx.output.log(`State file exists: ${fs.existsSync(statePath) ? "yes" : "no"}`);
152
- ctx.output.log(`Config file exists: ${fs.existsSync(configPath) ? "yes" : "no"}`);
153
- ctx.output.log(`FlowUS token exists: ${getFlowusToken({ config }) ? "yes" : "no"}`);
154
- ctx.output.log(`Current runtime exists: ${fs.existsSync(currentRoot) ? "yes" : "no"}`);
155
- ctx.output.log(
156
- `Claude marketplace exists: ${fs.existsSync(path.join(currentRoot, "claude-marketplace")) ? "yes" : "no"}`,
157
- );
105
+ ctx.output.log(`Skills root: ${skillsPath}`);
106
+ ctx.output.log(`Skills installed: ${fs.existsSync(skillsPath) ? "yes" : "no"}`);
107
+ ctx.output.log(`Codex config exists: ${fs.existsSync(codexConfigPath) ? "yes" : "no"}`);
108
+ ctx.output.log(`Codex FlowUS MCP exists: ${codexToken ? "yes" : "no"}`);
109
+ ctx.output.log(`Claude config exists: ${fs.existsSync(claudeConfigPath) ? "yes" : "no"}`);
110
+ ctx.output.log(`Claude FlowUS MCP exists: ${claudeToken ? "yes" : "no"}`);
111
+ ctx.output.log(`FlowUS token synchronized: ${codexToken && claudeToken && codexToken === claudeToken ? "yes" : "no"}`);
158
112
  }
159
113
 
160
114
  async function runInstallLike(ctx, mode) {
161
- const installRoot = getInstallRoot({ homeDir: ctx.homeDir });
162
- const configPath = getConfigPath({
163
- homeDir: ctx.homeDir,
164
- platform: ctx.platform,
165
- env: ctx.env,
166
- });
167
115
  const bundleRoot = await resolveBundleRoot(ctx.bundleRoot);
168
- const product = await readProduct(bundleRoot);
169
-
170
- await ensureFlowusToken({ ctx, configPath });
171
- const { currentRoot } = installRuntime({
116
+ const token = await ensureFlowusToken({ ctx });
117
+ const skillsPath = installSkills({
172
118
  bundleRoot,
173
- installRoot,
174
- version: product.version,
175
119
  homeDir: ctx.homeDir,
176
120
  });
177
121
 
178
- installCodexWiring({
122
+ const codexResult = installCodexWiring({
179
123
  homeDir: ctx.homeDir,
180
- installRoot,
181
- platform: ctx.platform,
124
+ token,
182
125
  });
183
-
184
- const claudeResult = await installClaude({
185
- ctx,
186
- currentRoot,
187
- version: product.version,
188
- mode,
189
- });
190
-
191
- writeState({
192
- statePath: getStatePath({ homeDir: ctx.homeDir, installRoot }),
193
- state: {
194
- version: product.version,
195
- installedAt: new Date().toISOString(),
196
- claudeEnabled: claudeResult.enabled,
197
- },
126
+ const claudeResult = installClaudeWiring({
127
+ homeDir: ctx.homeDir,
128
+ token,
198
129
  });
199
130
 
200
131
  ctx.output.log(`${mode} complete.`);
201
- ctx.output.log(`Install root: ${installRoot}`);
132
+ ctx.output.log(`Skills root: ${skillsPath}`);
133
+ ctx.output.log(`Codex config: ${codexResult.configPath}`);
134
+ ctx.output.log(`Claude config: ${claudeResult.configPath}`);
202
135
  }
203
136
 
204
137
  async function runLogin(ctx, provider) {
@@ -206,21 +139,26 @@ async function runLogin(ctx, provider) {
206
139
  throw new Error(`Unsupported provider: ${provider}`);
207
140
  }
208
141
 
209
- const configPath = getConfigPath({
210
- homeDir: ctx.homeDir,
211
- platform: ctx.platform,
212
- env: ctx.env,
213
- });
214
142
  const token =
215
143
  ctx.env.ODOO_FORGE_FLOWUS_TOKEN ??
144
+ ctx.env.FLOWUS_TOKEN ??
216
145
  (await ctx.promptForSecret("Paste your FlowUS token: ")).trim();
217
146
 
218
147
  if (!token) {
219
148
  throw new Error("FlowUS token is required.");
220
149
  }
221
150
 
222
- writeFlowusToken({ configPath, token });
223
- ctx.output.log(`Saved FlowUS token to ${configPath}`);
151
+ const codexResult = installCodexWiring({
152
+ homeDir: ctx.homeDir,
153
+ token,
154
+ });
155
+ const claudeResult = installClaudeWiring({
156
+ homeDir: ctx.homeDir,
157
+ token,
158
+ });
159
+
160
+ ctx.output.log(`Saved FlowUS token to ${codexResult.configPath}`);
161
+ ctx.output.log(`Saved FlowUS token to ${claudeResult.configPath}`);
224
162
  }
225
163
 
226
164
  async function runMcp(ctx, provider) {
@@ -228,16 +166,10 @@ async function runMcp(ctx, provider) {
228
166
  throw new Error(`Unsupported MCP provider: ${provider}`);
229
167
  }
230
168
 
231
- const configPath = getConfigPath({
232
- homeDir: ctx.homeDir,
233
- platform: ctx.platform,
234
- env: ctx.env,
235
- });
236
- const config = readUserConfig({ configPath });
237
- const token = getFlowusToken({ config });
169
+ const token = ctx.env.FLOWUS_TOKEN ?? ctx.env.ODOO_FORGE_FLOWUS_TOKEN;
238
170
 
239
171
  if (!token) {
240
- throw new Error("Missing FlowUS token. Run `odoo-forge login flowus` first.");
172
+ throw new Error("Missing FlowUS token. Set FLOWUS_TOKEN before running `odoo-forge mcp flowus`.");
241
173
  }
242
174
 
243
175
  return await ctx.spawnProcess("npx", ["-y", "flowus-mcp-server@latest"], {
@@ -256,9 +188,7 @@ function normalizeContext(overrides = {}) {
256
188
  bundleRoot: overrides.bundleRoot,
257
189
  output: overrides.output ?? console,
258
190
  promptForSecret: overrides.promptForSecret ?? defaultPromptForSecret,
259
- runCommand: overrides.runCommand ?? defaultRunCommand,
260
191
  spawnProcess: overrides.spawnProcess ?? defaultSpawnProcess,
261
- hasCommand: overrides.hasCommand ?? defaultHasCommand,
262
192
  };
263
193
  }
264
194
 
package/src/paths.js CHANGED
@@ -1,26 +1,18 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
 
4
- export function getInstallRoot({ homeDir = os.homedir() } = {}) {
5
- return path.join(homeDir, ".odoo-forge");
6
- }
7
-
8
- export function getCurrentRoot({ homeDir = os.homedir(), installRoot } = {}) {
9
- return path.join(installRoot ?? getInstallRoot({ homeDir }), "current");
10
- }
11
-
12
- export function getVersionsRoot({ homeDir = os.homedir(), installRoot } = {}) {
13
- return path.join(installRoot ?? getInstallRoot({ homeDir }), "versions");
4
+ export function getCodexConfigPath({ homeDir = os.homedir() } = {}) {
5
+ return path.join(homeDir, ".codex", "config.toml");
14
6
  }
15
7
 
16
- export function getStatePath({ homeDir = os.homedir(), installRoot } = {}) {
17
- return path.join(installRoot ?? getInstallRoot({ homeDir }), "state.json");
8
+ export function getClaudeConfigPath({ homeDir = os.homedir() } = {}) {
9
+ return path.join(homeDir, ".claude.json");
18
10
  }
19
11
 
20
- export function getCodexConfigPath({ homeDir = os.homedir() } = {}) {
21
- return path.join(homeDir, ".codex", "config.toml");
12
+ export function getAgentsSkillsRoot({ homeDir = os.homedir() } = {}) {
13
+ return path.join(homeDir, ".agents", "skills");
22
14
  }
23
15
 
24
- export function getCodexSkillsLinkPath({ homeDir = os.homedir() } = {}) {
16
+ export function getInstalledSkillsPath({ homeDir = os.homedir() } = {}) {
25
17
  return path.join(homeDir, ".agents", "skills", "odoo-forge");
26
18
  }
package/src/config.js DELETED
@@ -1,70 +0,0 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
-
5
- function readJsonFile(filePath) {
6
- if (!fs.existsSync(filePath)) {
7
- return {};
8
- }
9
-
10
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
11
- }
12
-
13
- function writeJsonFile(filePath, value) {
14
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
15
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
16
- }
17
-
18
- export function getConfigPath({
19
- homeDir = os.homedir(),
20
- platform = process.platform,
21
- env = process.env,
22
- } = {}) {
23
- if (platform === "darwin") {
24
- return path.join(homeDir, "Library", "Application Support", "Odoo Forge", "config.json");
25
- }
26
-
27
- if (platform === "win32") {
28
- const appData = env.APPDATA ?? path.join(homeDir, "AppData", "Roaming");
29
- return path.win32.join(appData, "Odoo Forge", "config.json");
30
- }
31
-
32
- return path.join(homeDir, ".config", "Odoo Forge", "config.json");
33
- }
34
-
35
- export function readUserConfig({ configPath }) {
36
- return readJsonFile(configPath);
37
- }
38
-
39
- export function writeUserConfig({ configPath, config }) {
40
- writeJsonFile(configPath, config);
41
- }
42
-
43
- export function writeFlowusToken({ configPath, token }) {
44
- const current = readUserConfig({ configPath });
45
- const next = {
46
- ...current,
47
- mcp: {
48
- ...(current.mcp ?? {}),
49
- flowus: {
50
- ...((current.mcp ?? {}).flowus ?? {}),
51
- FLOWUS_TOKEN: token,
52
- },
53
- },
54
- };
55
-
56
- writeUserConfig({ configPath, config: next });
57
- return next;
58
- }
59
-
60
- export function getFlowusToken({ config }) {
61
- return config?.mcp?.flowus?.FLOWUS_TOKEN;
62
- }
63
-
64
- export function readState({ statePath }) {
65
- return readJsonFile(statePath);
66
- }
67
-
68
- export function writeState({ statePath, state }) {
69
- writeJsonFile(statePath, state);
70
- }
package/src/runtime.js DELETED
@@ -1,27 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- import { getCurrentRoot, getVersionsRoot } from "./paths.js";
5
-
6
- export function installRuntime({
7
- bundleRoot,
8
- installRoot,
9
- version,
10
- homeDir,
11
- }) {
12
- const versionsRoot = getVersionsRoot({ homeDir, installRoot });
13
- const currentRoot = getCurrentRoot({ homeDir, installRoot });
14
- const versionRoot = path.join(versionsRoot, version);
15
-
16
- fs.mkdirSync(versionsRoot, { recursive: true });
17
- fs.rmSync(versionRoot, { force: true, recursive: true });
18
- fs.cpSync(bundleRoot, versionRoot, { recursive: true });
19
-
20
- fs.rmSync(currentRoot, { force: true, recursive: true });
21
- fs.cpSync(versionRoot, currentRoot, { recursive: true });
22
-
23
- return {
24
- currentRoot,
25
- versionRoot,
26
- };
27
- }