odoo-forge 0.1.9 → 0.1.11

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.9",
3
+ "version": "0.1.11",
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.9"
7
+ "odoo-forge-bundle": "0.1.11"
8
8
  },
9
9
  "bin": {
10
10
  "odoo-forge": "bin/odoo-forge.js"
package/src/index.js CHANGED
@@ -1,16 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import readline from "node:readline/promises";
5
- import { spawn } from "node:child_process";
6
4
  import { fileURLToPath } from "node:url";
7
-
8
- import { installClaudeWiring, readClaudeManagedToken } from "./claude.js";
9
- import { installCodexWiring, readCodexManagedToken } from "./codex.js";
10
5
  import {
11
6
  getAgentsSkillsRoot,
12
- getClaudeConfigPath,
13
- getCodexConfigPath,
14
7
  getInstalledSkillsPath,
15
8
  getLegacyInstalledSkillsPath,
16
9
  } from "./paths.js";
@@ -21,9 +14,7 @@ function printHelp() {
21
14
  Usage:
22
15
  odoo-forge install
23
16
  odoo-forge update
24
- odoo-forge doctor
25
- odoo-forge login flowus
26
- odoo-forge mcp flowus`);
17
+ odoo-forge doctor`);
27
18
  }
28
19
 
29
20
  async function resolveBundleRoot(bundleRootOverride) {
@@ -42,29 +33,6 @@ async function resolveBundleRoot(bundleRootOverride) {
42
33
  }
43
34
  }
44
35
 
45
- async function defaultPromptForSecret(message) {
46
- const rl = readline.createInterface({
47
- input: process.stdin,
48
- output: process.stdout,
49
- });
50
- try {
51
- return await rl.question(message);
52
- } finally {
53
- rl.close();
54
- }
55
- }
56
-
57
- async function defaultSpawnProcess(command, args, options = {}) {
58
- return await new Promise((resolve, reject) => {
59
- const child = spawn(command, args, {
60
- stdio: "inherit",
61
- ...options,
62
- });
63
- child.on("close", (code) => resolve(code ?? 0));
64
- child.on("error", reject);
65
- });
66
- }
67
-
68
36
  function installSkills({ bundleRoot, homeDir }) {
69
37
  const sourceSkillsDir = path.join(bundleRoot, "skills");
70
38
  const targetSkillsDir = getInstalledSkillsPath({ homeDir });
@@ -84,120 +52,43 @@ function installSkills({ bundleRoot, homeDir }) {
84
52
  return targetSkillsDir;
85
53
  }
86
54
 
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 });
93
-
94
- if (currentToken) {
95
- return currentToken;
55
+ function countInstalledSkills(skillsPath) {
56
+ if (!fs.existsSync(skillsPath)) {
57
+ return 0;
96
58
  }
97
59
 
98
- const prompted = await ctx.promptForSecret("FlowUS token is required.\nPaste your FlowUS token: ");
99
- const token = prompted.trim();
100
- if (!token) {
101
- throw new Error("FlowUS token is required.");
102
- }
103
- return token;
60
+ return fs
61
+ .readdirSync(skillsPath, { withFileTypes: true })
62
+ .filter((entry) => entry.isDirectory())
63
+ .length;
104
64
  }
105
65
 
106
66
  function runDoctor(ctx) {
107
67
  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 });
112
68
 
113
69
  ctx.output.log("Odoo Forge doctor");
114
70
  ctx.output.log(`Skills root: ${skillsPath}`);
115
71
  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"}`);
72
+ ctx.output.log(`Installed skills count: ${countInstalledSkills(skillsPath)}`);
121
73
  }
122
74
 
123
75
  async function runInstallLike(ctx, mode) {
124
76
  const bundleRoot = await resolveBundleRoot(ctx.bundleRoot);
125
- const token = await ensureFlowusToken({ ctx });
126
77
  const skillsPath = installSkills({
127
78
  bundleRoot,
128
79
  homeDir: ctx.homeDir,
129
80
  });
130
81
 
131
- const codexResult = installCodexWiring({
132
- homeDir: ctx.homeDir,
133
- token,
134
- });
135
- const claudeResult = installClaudeWiring({
136
- homeDir: ctx.homeDir,
137
- token,
138
- });
139
-
140
82
  ctx.output.log(`${mode} complete.`);
141
83
  ctx.output.log(`Skills root: ${skillsPath}`);
142
- ctx.output.log(`Codex config: ${codexResult.configPath}`);
143
- ctx.output.log(`Claude config: ${claudeResult.configPath}`);
144
- }
145
-
146
- async function runLogin(ctx, provider) {
147
- if (provider !== "flowus") {
148
- throw new Error(`Unsupported provider: ${provider}`);
149
- }
150
-
151
- const token =
152
- ctx.env.ODOO_FORGE_FLOWUS_TOKEN ??
153
- ctx.env.FLOWUS_TOKEN ??
154
- (await ctx.promptForSecret("Paste your FlowUS token: ")).trim();
155
-
156
- if (!token) {
157
- throw new Error("FlowUS token is required.");
158
- }
159
-
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}`);
171
- }
172
-
173
- async function runMcp(ctx, provider) {
174
- if (provider !== "flowus") {
175
- throw new Error(`Unsupported MCP provider: ${provider}`);
176
- }
177
-
178
- const token = ctx.env.FLOWUS_TOKEN ?? ctx.env.ODOO_FORGE_FLOWUS_TOKEN;
179
-
180
- if (!token) {
181
- throw new Error("Missing FlowUS token. Set FLOWUS_TOKEN before running `odoo-forge mcp flowus`.");
182
- }
183
-
184
- return await ctx.spawnProcess("npx", ["-y", "flowus-mcp-server@latest"], {
185
- env: {
186
- ...ctx.env,
187
- FLOWUS_TOKEN: token,
188
- },
189
- });
84
+ ctx.output.log(`Installed skills count: ${countInstalledSkills(skillsPath)}`);
190
85
  }
191
86
 
192
87
  function normalizeContext(overrides = {}) {
193
88
  return {
194
89
  homeDir: overrides.homeDir ?? os.homedir(),
195
- platform: overrides.platform ?? process.platform,
196
- env: overrides.env ?? process.env,
197
90
  bundleRoot: overrides.bundleRoot,
198
91
  output: overrides.output ?? console,
199
- promptForSecret: overrides.promptForSecret ?? defaultPromptForSecret,
200
- spawnProcess: overrides.spawnProcess ?? defaultSpawnProcess,
201
92
  };
202
93
  }
203
94
 
@@ -215,12 +106,6 @@ export async function main(argv, overrides = {}) {
215
106
  case "doctor":
216
107
  runDoctor(ctx);
217
108
  break;
218
- case "login":
219
- await runLogin(ctx, argv[1]);
220
- break;
221
- case "mcp":
222
- await runMcp(ctx, argv[1]);
223
- break;
224
109
  case "help":
225
110
  case "--help":
226
111
  case "-h":
package/src/paths.js CHANGED
@@ -1,14 +1,6 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
 
4
- export function getCodexConfigPath({ homeDir = os.homedir() } = {}) {
5
- return path.join(homeDir, ".codex", "config.toml");
6
- }
7
-
8
- export function getClaudeConfigPath({ homeDir = os.homedir() } = {}) {
9
- return path.join(homeDir, ".claude.json");
10
- }
11
-
12
4
  export function getAgentsSkillsRoot({ homeDir = os.homedir() } = {}) {
13
5
  return path.join(homeDir, ".agents", "skills");
14
6
  }
package/src/claude.js DELETED
@@ -1,50 +0,0 @@
1
- import fs from "node:fs";
2
-
3
- import { getClaudeConfigPath } from "./paths.js";
4
-
5
- export function readClaudeConfig({ homeDir }) {
6
- const configPath = getClaudeConfigPath({ homeDir });
7
- if (!fs.existsSync(configPath)) {
8
- return {};
9
- }
10
-
11
- return JSON.parse(fs.readFileSync(configPath, "utf8"));
12
- }
13
-
14
- export function writeClaudeConfig({ homeDir, config }) {
15
- const configPath = getClaudeConfigPath({ homeDir });
16
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
17
- return configPath;
18
- }
19
-
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,
27
- },
28
- };
29
- }
30
-
31
- export function installClaudeWiring({ homeDir, token }) {
32
- const currentConfig = readClaudeConfig({ homeDir });
33
- const nextConfig = {
34
- ...currentConfig,
35
- mcpServers: {
36
- ...(currentConfig.mcpServers ?? {}),
37
- flowus: buildClaudeFlowusServer({ token }),
38
- },
39
- };
40
- const configPath = writeClaudeConfig({ homeDir, config: nextConfig });
41
-
42
- return {
43
- configPath,
44
- };
45
- }
46
-
47
- export function readClaudeManagedToken({ homeDir }) {
48
- const config = readClaudeConfig({ homeDir });
49
- return config?.mcpServers?.flowus?.env?.FLOWUS_TOKEN ?? null;
50
- }
package/src/codex.js DELETED
@@ -1,158 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- import { getCodexConfigPath } from "./paths.js";
5
-
6
- export const CODEX_START_MARKER = "# BEGIN ODOO FORGE FLOWUS";
7
- export const CODEX_END_MARKER = "# END ODOO FORGE FLOWUS";
8
-
9
- export function buildCodexManagedBlock({ token }) {
10
- return `${CODEX_START_MARKER}
11
- [mcp_servers.flowus]
12
- type = "stdio"
13
- command = "npx"
14
- args = ["-y", "flowus-mcp-server@latest"]
15
-
16
- [mcp_servers.flowus.env]
17
- FLOWUS_TOKEN = "${token}"
18
- ${CODEX_END_MARKER}`;
19
- }
20
-
21
- function stripManagedBlock({
22
- originalContent,
23
- startMarker,
24
- endMarker,
25
- }) {
26
- const startIndex = originalContent.indexOf(startMarker);
27
- const endIndex = originalContent.indexOf(endMarker);
28
-
29
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
30
- return originalContent;
31
- }
32
-
33
- const blockEnd = endIndex + endMarker.length;
34
- return `${originalContent.slice(0, startIndex)}${originalContent.slice(blockEnd)}`;
35
- }
36
-
37
- function isFlowusSectionHeader(headerName) {
38
- return headerName === "mcp_servers.flowus" || headerName.startsWith("mcp_servers.flowus.");
39
- }
40
-
41
- function removeFlowusSections(originalContent) {
42
- const lines = originalContent.split("\n");
43
- const keptLines = [];
44
- let skipCurrentSection = false;
45
-
46
- for (const line of lines) {
47
- const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
48
- if (sectionMatch) {
49
- skipCurrentSection = isFlowusSectionHeader(sectionMatch[1]);
50
- }
51
-
52
- if (!skipCurrentSection) {
53
- keptLines.push(line);
54
- }
55
- }
56
-
57
- return keptLines.join("\n");
58
- }
59
-
60
- function readSectionContent({ originalContent, sectionName }) {
61
- const lines = originalContent.split("\n");
62
- const collected = [];
63
- let inSection = false;
64
-
65
- for (const line of lines) {
66
- const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
67
- if (sectionMatch) {
68
- if (inSection) {
69
- break;
70
- }
71
- inSection = sectionMatch[1] === sectionName;
72
- continue;
73
- }
74
-
75
- if (inSection) {
76
- collected.push(line);
77
- }
78
- }
79
-
80
- return inSection ? collected.join("\n") : null;
81
- }
82
-
83
- function extractTokenFromContent(originalContent) {
84
- const flowusSection = readSectionContent({
85
- originalContent,
86
- sectionName: "mcp_servers.flowus",
87
- });
88
- const envSection = readSectionContent({
89
- originalContent,
90
- sectionName: "mcp_servers.flowus.env",
91
- });
92
-
93
- const candidates = [envSection, flowusSection, originalContent];
94
- for (const candidate of candidates) {
95
- if (!candidate) {
96
- continue;
97
- }
98
-
99
- const inlineMatch = candidate.match(/FLOWUS_TOKEN\s*=\s*"([^"]+)"/);
100
- if (inlineMatch) {
101
- return inlineMatch[1];
102
- }
103
- }
104
-
105
- return null;
106
- }
107
-
108
- export function upsertManagedBlock({
109
- originalContent,
110
- startMarker,
111
- endMarker,
112
- block,
113
- }) {
114
- const withoutManagedBlock = stripManagedBlock({
115
- originalContent,
116
- startMarker,
117
- endMarker,
118
- });
119
- const withoutFlowusSections = removeFlowusSections(withoutManagedBlock);
120
- const trimmed = withoutFlowusSections.trimEnd();
121
- return trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`;
122
- }
123
-
124
- export function readCodexManagedToken({ homeDir }) {
125
- const configPath = getCodexConfigPath({ homeDir });
126
- if (!fs.existsSync(configPath)) {
127
- return null;
128
- }
129
-
130
- const content = fs.readFileSync(configPath, "utf8");
131
- const startIndex = content.indexOf(CODEX_START_MARKER);
132
- const endIndex = content.indexOf(CODEX_END_MARKER);
133
- if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
134
- const managedBlock = content.slice(startIndex, endIndex + CODEX_END_MARKER.length);
135
- return extractTokenFromContent(managedBlock);
136
- }
137
-
138
- return extractTokenFromContent(content);
139
- }
140
-
141
- export function installCodexWiring({ homeDir, token }) {
142
- const configPath = getCodexConfigPath({ homeDir });
143
- const codexDir = path.dirname(configPath);
144
-
145
- fs.mkdirSync(codexDir, { recursive: true });
146
- const currentConfig = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
147
- const nextConfig = upsertManagedBlock({
148
- originalContent: currentConfig,
149
- startMarker: CODEX_START_MARKER,
150
- endMarker: CODEX_END_MARKER,
151
- block: buildCodexManagedBlock({ token }),
152
- });
153
- fs.writeFileSync(configPath, nextConfig);
154
-
155
- return {
156
- configPath,
157
- };
158
- }