odoo-forge 0.1.0 → 0.1.2
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 +2 -2
- package/src/claude.js +36 -78
- package/src/codex.js +20 -28
- package/src/index.js +67 -128
- package/src/paths.js +9 -13
- package/src/config.js +0 -70
- package/src/runtime.js +0 -27
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odoo-forge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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.
|
|
7
|
+
"odoo-forge-bundle": "0.1.2"
|
|
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
|
-
|
|
5
|
-
const PLUGIN_NAME = "odoo-forge";
|
|
3
|
+
import { getClaudeConfigPath } from "./paths.js";
|
|
6
4
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
fs.
|
|
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.
|
|
22
|
-
|
|
11
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
12
|
+
}
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
31
|
+
export function installClaudeWiring({ homeDir, token }) {
|
|
32
|
+
const currentConfig = readClaudeConfig({ homeDir });
|
|
33
|
+
const nextConfig = {
|
|
34
|
+
...currentConfig,
|
|
54
35
|
mcpServers: {
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
42
|
+
return {
|
|
43
|
+
configPath,
|
|
44
|
+
};
|
|
66
45
|
}
|
|
67
46
|
|
|
68
|
-
export function
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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 = "
|
|
14
|
-
args = ["
|
|
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
|
|
37
|
-
|
|
37
|
+
export function readCodexManagedToken({ homeDir }) {
|
|
38
|
+
const configPath = getCodexConfigPath({ homeDir });
|
|
39
|
+
if (!fs.existsSync(configPath)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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,18 @@ 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
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./
|
|
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
|
+
getLegacyInstalledSkillsPath,
|
|
16
|
+
} from "./paths.js";
|
|
19
17
|
|
|
20
18
|
function printHelp() {
|
|
21
19
|
console.log(`Odoo Forge CLI
|
|
@@ -56,17 +54,6 @@ async function defaultPromptForSecret(message) {
|
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
|
|
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
57
|
async function defaultSpawnProcess(command, args, options = {}) {
|
|
71
58
|
return await new Promise((resolve, reject) => {
|
|
72
59
|
const child = spawn(command, args, {
|
|
@@ -78,22 +65,33 @@ async function defaultSpawnProcess(command, args, options = {}) {
|
|
|
78
65
|
});
|
|
79
66
|
}
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
}
|
|
68
|
+
function installSkills({ bundleRoot, homeDir }) {
|
|
69
|
+
const sourceSkillsDir = path.join(bundleRoot, "skills");
|
|
70
|
+
const targetSkillsDir = getInstalledSkillsPath({ homeDir });
|
|
71
|
+
const legacySkillsDir = getLegacyInstalledSkillsPath({ homeDir });
|
|
72
|
+
|
|
73
|
+
fs.mkdirSync(getAgentsSkillsRoot({ homeDir }), { recursive: true });
|
|
74
|
+
fs.rmSync(legacySkillsDir, { recursive: true, force: true });
|
|
75
|
+
|
|
76
|
+
for (const entry of fs.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
77
|
+
const sourceEntryPath = path.join(sourceSkillsDir, entry.name);
|
|
78
|
+
const targetEntryPath = path.join(targetSkillsDir, entry.name);
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
fs.rmSync(targetEntryPath, { recursive: true, force: true });
|
|
81
|
+
fs.cpSync(sourceEntryPath, targetEntryPath, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return targetSkillsDir;
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
async function ensureFlowusToken({ ctx
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
87
|
+
async function ensureFlowusToken({ ctx }) {
|
|
88
|
+
const envToken = ctx.env.ODOO_FORGE_FLOWUS_TOKEN ?? ctx.env.FLOWUS_TOKEN;
|
|
89
|
+
const currentToken =
|
|
90
|
+
envToken ??
|
|
91
|
+
readCodexManagedToken({ homeDir: ctx.homeDir }) ??
|
|
92
|
+
readClaudeManagedToken({ homeDir: ctx.homeDir });
|
|
94
93
|
|
|
95
94
|
if (currentToken) {
|
|
96
|
-
writeFlowusToken({ configPath, token: currentToken });
|
|
97
95
|
return currentToken;
|
|
98
96
|
}
|
|
99
97
|
|
|
@@ -102,103 +100,47 @@ async function ensureFlowusToken({ ctx, configPath }) {
|
|
|
102
100
|
if (!token) {
|
|
103
101
|
throw new Error("FlowUS token is required.");
|
|
104
102
|
}
|
|
105
|
-
writeFlowusToken({ configPath, token });
|
|
106
103
|
return token;
|
|
107
104
|
}
|
|
108
105
|
|
|
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
106
|
function runDoctor(ctx) {
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
env: ctx.env,
|
|
144
|
-
});
|
|
145
|
-
const config = readUserConfig({ configPath });
|
|
146
|
-
const currentRoot = getCurrentRoot({ homeDir: ctx.homeDir, installRoot });
|
|
107
|
+
const skillsPath = getInstalledSkillsPath({ homeDir: ctx.homeDir });
|
|
108
|
+
const codexConfigPath = getCodexConfigPath({ homeDir: ctx.homeDir });
|
|
109
|
+
const claudeConfigPath = getClaudeConfigPath({ homeDir: ctx.homeDir });
|
|
110
|
+
const codexToken = readCodexManagedToken({ homeDir: ctx.homeDir });
|
|
111
|
+
const claudeToken = readClaudeManagedToken({ homeDir: ctx.homeDir });
|
|
147
112
|
|
|
148
113
|
ctx.output.log("Odoo Forge doctor");
|
|
149
|
-
ctx.output.log(`
|
|
150
|
-
ctx.output.log(`
|
|
151
|
-
ctx.output.log(`
|
|
152
|
-
ctx.output.log(`
|
|
153
|
-
ctx.output.log(`
|
|
154
|
-
ctx.output.log(`
|
|
155
|
-
ctx.output.log(
|
|
156
|
-
`Claude marketplace exists: ${fs.existsSync(path.join(currentRoot, "claude-marketplace")) ? "yes" : "no"}`,
|
|
157
|
-
);
|
|
114
|
+
ctx.output.log(`Skills root: ${skillsPath}`);
|
|
115
|
+
ctx.output.log(`Skills installed: ${fs.existsSync(skillsPath) ? "yes" : "no"}`);
|
|
116
|
+
ctx.output.log(`Codex config exists: ${fs.existsSync(codexConfigPath) ? "yes" : "no"}`);
|
|
117
|
+
ctx.output.log(`Codex FlowUS MCP exists: ${codexToken ? "yes" : "no"}`);
|
|
118
|
+
ctx.output.log(`Claude config exists: ${fs.existsSync(claudeConfigPath) ? "yes" : "no"}`);
|
|
119
|
+
ctx.output.log(`Claude FlowUS MCP exists: ${claudeToken ? "yes" : "no"}`);
|
|
120
|
+
ctx.output.log(`FlowUS token synchronized: ${codexToken && claudeToken && codexToken === claudeToken ? "yes" : "no"}`);
|
|
158
121
|
}
|
|
159
122
|
|
|
160
123
|
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
124
|
const bundleRoot = await resolveBundleRoot(ctx.bundleRoot);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
await ensureFlowusToken({ ctx, configPath });
|
|
171
|
-
const { currentRoot } = installRuntime({
|
|
125
|
+
const token = await ensureFlowusToken({ ctx });
|
|
126
|
+
const skillsPath = installSkills({
|
|
172
127
|
bundleRoot,
|
|
173
|
-
installRoot,
|
|
174
|
-
version: product.version,
|
|
175
128
|
homeDir: ctx.homeDir,
|
|
176
129
|
});
|
|
177
130
|
|
|
178
|
-
installCodexWiring({
|
|
131
|
+
const codexResult = installCodexWiring({
|
|
179
132
|
homeDir: ctx.homeDir,
|
|
180
|
-
|
|
181
|
-
platform: ctx.platform,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const claudeResult = await installClaude({
|
|
185
|
-
ctx,
|
|
186
|
-
currentRoot,
|
|
187
|
-
version: product.version,
|
|
188
|
-
mode,
|
|
133
|
+
token,
|
|
189
134
|
});
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
state: {
|
|
194
|
-
version: product.version,
|
|
195
|
-
installedAt: new Date().toISOString(),
|
|
196
|
-
claudeEnabled: claudeResult.enabled,
|
|
197
|
-
},
|
|
135
|
+
const claudeResult = installClaudeWiring({
|
|
136
|
+
homeDir: ctx.homeDir,
|
|
137
|
+
token,
|
|
198
138
|
});
|
|
199
139
|
|
|
200
140
|
ctx.output.log(`${mode} complete.`);
|
|
201
|
-
ctx.output.log(`
|
|
141
|
+
ctx.output.log(`Skills root: ${skillsPath}`);
|
|
142
|
+
ctx.output.log(`Codex config: ${codexResult.configPath}`);
|
|
143
|
+
ctx.output.log(`Claude config: ${claudeResult.configPath}`);
|
|
202
144
|
}
|
|
203
145
|
|
|
204
146
|
async function runLogin(ctx, provider) {
|
|
@@ -206,21 +148,26 @@ async function runLogin(ctx, provider) {
|
|
|
206
148
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
207
149
|
}
|
|
208
150
|
|
|
209
|
-
const configPath = getConfigPath({
|
|
210
|
-
homeDir: ctx.homeDir,
|
|
211
|
-
platform: ctx.platform,
|
|
212
|
-
env: ctx.env,
|
|
213
|
-
});
|
|
214
151
|
const token =
|
|
215
152
|
ctx.env.ODOO_FORGE_FLOWUS_TOKEN ??
|
|
153
|
+
ctx.env.FLOWUS_TOKEN ??
|
|
216
154
|
(await ctx.promptForSecret("Paste your FlowUS token: ")).trim();
|
|
217
155
|
|
|
218
156
|
if (!token) {
|
|
219
157
|
throw new Error("FlowUS token is required.");
|
|
220
158
|
}
|
|
221
159
|
|
|
222
|
-
|
|
223
|
-
|
|
160
|
+
const codexResult = installCodexWiring({
|
|
161
|
+
homeDir: ctx.homeDir,
|
|
162
|
+
token,
|
|
163
|
+
});
|
|
164
|
+
const claudeResult = installClaudeWiring({
|
|
165
|
+
homeDir: ctx.homeDir,
|
|
166
|
+
token,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
ctx.output.log(`Saved FlowUS token to ${codexResult.configPath}`);
|
|
170
|
+
ctx.output.log(`Saved FlowUS token to ${claudeResult.configPath}`);
|
|
224
171
|
}
|
|
225
172
|
|
|
226
173
|
async function runMcp(ctx, provider) {
|
|
@@ -228,16 +175,10 @@ async function runMcp(ctx, provider) {
|
|
|
228
175
|
throw new Error(`Unsupported MCP provider: ${provider}`);
|
|
229
176
|
}
|
|
230
177
|
|
|
231
|
-
const
|
|
232
|
-
homeDir: ctx.homeDir,
|
|
233
|
-
platform: ctx.platform,
|
|
234
|
-
env: ctx.env,
|
|
235
|
-
});
|
|
236
|
-
const config = readUserConfig({ configPath });
|
|
237
|
-
const token = getFlowusToken({ config });
|
|
178
|
+
const token = ctx.env.FLOWUS_TOKEN ?? ctx.env.ODOO_FORGE_FLOWUS_TOKEN;
|
|
238
179
|
|
|
239
180
|
if (!token) {
|
|
240
|
-
throw new Error("Missing FlowUS token.
|
|
181
|
+
throw new Error("Missing FlowUS token. Set FLOWUS_TOKEN before running `odoo-forge mcp flowus`.");
|
|
241
182
|
}
|
|
242
183
|
|
|
243
184
|
return await ctx.spawnProcess("npx", ["-y", "flowus-mcp-server@latest"], {
|
|
@@ -256,9 +197,7 @@ function normalizeContext(overrides = {}) {
|
|
|
256
197
|
bundleRoot: overrides.bundleRoot,
|
|
257
198
|
output: overrides.output ?? console,
|
|
258
199
|
promptForSecret: overrides.promptForSecret ?? defaultPromptForSecret,
|
|
259
|
-
runCommand: overrides.runCommand ?? defaultRunCommand,
|
|
260
200
|
spawnProcess: overrides.spawnProcess ?? defaultSpawnProcess,
|
|
261
|
-
hasCommand: overrides.hasCommand ?? defaultHasCommand,
|
|
262
201
|
};
|
|
263
202
|
}
|
|
264
203
|
|
package/src/paths.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
return path.join(homeDir, ".
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function getCurrentRoot({ homeDir = os.homedir(), installRoot } = {}) {
|
|
9
|
-
return path.join(installRoot ?? getInstallRoot({ homeDir }), "current");
|
|
4
|
+
export function getCodexConfigPath({ homeDir = os.homedir() } = {}) {
|
|
5
|
+
return path.join(homeDir, ".codex", "config.toml");
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
export function
|
|
13
|
-
return path.join(
|
|
8
|
+
export function getClaudeConfigPath({ homeDir = os.homedir() } = {}) {
|
|
9
|
+
return path.join(homeDir, ".claude.json");
|
|
14
10
|
}
|
|
15
11
|
|
|
16
|
-
export function
|
|
17
|
-
return path.join(
|
|
12
|
+
export function getAgentsSkillsRoot({ homeDir = os.homedir() } = {}) {
|
|
13
|
+
return path.join(homeDir, ".agents", "skills");
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
export function
|
|
21
|
-
return
|
|
16
|
+
export function getInstalledSkillsPath({ homeDir = os.homedir() } = {}) {
|
|
17
|
+
return getAgentsSkillsRoot({ homeDir });
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
export function
|
|
20
|
+
export function getLegacyInstalledSkillsPath({ homeDir = os.homedir() } = {}) {
|
|
25
21
|
return path.join(homeDir, ".agents", "skills", "odoo-forge");
|
|
26
22
|
}
|
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
|
-
}
|