@meshxdata/fops 0.0.3 → 0.0.4
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 +1 -1
- package/src/agent/agent.js +139 -38
- package/src/agent/agents.js +224 -0
- package/src/agent/context.js +146 -12
- package/src/agent/index.js +1 -0
- package/src/agent/llm.js +84 -13
- package/src/auth/coda.js +10 -10
- package/src/auth/login.js +13 -13
- package/src/auth/oauth.js +4 -4
- package/src/commands/index.js +74 -15
- package/src/config.js +2 -2
- package/src/doctor.js +67 -9
- package/src/feature-flags.js +197 -0
- package/src/plugins/api.js +14 -0
- package/src/plugins/builtins/stack-api.js +36 -0
- package/src/plugins/loader.js +67 -0
- package/src/plugins/registry.js +2 -0
- package/src/project.js +20 -1
- package/src/setup/aws.js +7 -7
- package/src/setup/setup.js +10 -9
- package/src/setup/wizard.js +73 -6
- package/src/ui/confirm.js +3 -2
- package/src/ui/input.js +2 -2
- package/src/ui/spinner.js +4 -4
- package/src/ui/streaming.js +2 -2
package/src/setup/wizard.js
CHANGED
|
@@ -5,6 +5,8 @@ import chalk from "chalk";
|
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
import inquirer from "inquirer";
|
|
7
7
|
import { isFoundationRoot, findComposeRootUp } from "../project.js";
|
|
8
|
+
import { discoverPlugins } from "../plugins/discovery.js";
|
|
9
|
+
import { validateManifest } from "../plugins/manifest.js";
|
|
8
10
|
import { runSetup, CLONE_BRANCH } from "./setup.js";
|
|
9
11
|
|
|
10
12
|
export async function runInitWizard() {
|
|
@@ -13,10 +15,10 @@ export async function runInitWizard() {
|
|
|
13
15
|
let projectRoot = null;
|
|
14
16
|
if (envRoot && fs.existsSync(envRoot) && isFoundationRoot(envRoot)) {
|
|
15
17
|
projectRoot = path.resolve(envRoot);
|
|
16
|
-
console.log(chalk.
|
|
18
|
+
console.log(chalk.dim(`Using FOUNDATION_ROOT: ${projectRoot}\n`));
|
|
17
19
|
} else if (isFoundationRoot(cwd)) {
|
|
18
20
|
projectRoot = cwd;
|
|
19
|
-
console.log(chalk.
|
|
21
|
+
console.log(chalk.dim("Using current directory as project root.\n"));
|
|
20
22
|
} else {
|
|
21
23
|
const foundUp = findComposeRootUp(cwd);
|
|
22
24
|
if (foundUp && foundUp !== cwd) {
|
|
@@ -41,6 +43,18 @@ export async function runInitWizard() {
|
|
|
41
43
|
const netrcPath = path.join(os.homedir(), ".netrc");
|
|
42
44
|
const hasNetrc = fs.existsSync(netrcPath) && fs.readFileSync(netrcPath, "utf8").includes("machine github.com");
|
|
43
45
|
console.log(hasNetrc ? chalk.green(" ✓ GitHub credentials (~/.netrc)") : chalk.yellow(" ⚠ GitHub credentials — add to ~/.netrc (needed for private submodules)"));
|
|
46
|
+
// Cursor IDE (only when cursor plugin is installed)
|
|
47
|
+
const cursorPluginDir = path.join(os.homedir(), ".fops", "plugins", "cursor");
|
|
48
|
+
if (fs.existsSync(cursorPluginDir)) {
|
|
49
|
+
let cursorVer = null;
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execa("cursor", ["--version"]);
|
|
52
|
+
cursorVer = (stdout || "").split("\n")[0].trim();
|
|
53
|
+
} catch {}
|
|
54
|
+
console.log(cursorVer
|
|
55
|
+
? chalk.green(" ✓ Cursor IDE") + chalk.dim(` — ${cursorVer}`)
|
|
56
|
+
: chalk.yellow(" ⚠ Cursor IDE — install from cursor.com, then: Cmd+Shift+P → 'Install cursor command'"));
|
|
57
|
+
}
|
|
44
58
|
console.log("");
|
|
45
59
|
if (!hasGit || !hasDocker || !hasClaude) {
|
|
46
60
|
console.log(chalk.red("Fix the missing prerequisites above, then run fops init again.\n"));
|
|
@@ -80,18 +94,19 @@ export async function runInitWizard() {
|
|
|
80
94
|
}
|
|
81
95
|
console.log(chalk.blue(`\nInitializing submodules (checking out ${CLONE_BRANCH})...\n`));
|
|
82
96
|
try {
|
|
83
|
-
await execa("git", ["submodule", "update", "--init", "--remote", "--recursive"], { cwd: resolved, stdio: "inherit" });
|
|
97
|
+
await execa("git", ["submodule", "update", "--init", "--force", "--remote", "--recursive"], { cwd: resolved, stdio: "inherit" });
|
|
84
98
|
await execa("git", ["submodule", "foreach", `git fetch origin && git checkout origin/${CLONE_BRANCH} 2>/dev/null || git checkout origin/main`], { cwd: resolved, stdio: "inherit" });
|
|
85
99
|
console.log(chalk.green(`\n Cloned successfully — submodules on ${CLONE_BRANCH} (falling back to main).\n`));
|
|
86
100
|
} catch {
|
|
87
|
-
console.log(chalk.yellow(`\n ⚠ Some submodules had issues. Attempting to
|
|
101
|
+
console.log(chalk.yellow(`\n ⚠ Some submodules had issues. Attempting to recover...\n`));
|
|
88
102
|
try {
|
|
89
|
-
await execa("git", ["submodule", "
|
|
103
|
+
await execa("git", ["submodule", "absorbgitdirs"], { cwd: resolved, stdio: "inherit" });
|
|
104
|
+
await execa("git", ["submodule", "update", "--init", "--force", "--recursive"], { cwd: resolved, stdio: "inherit" });
|
|
90
105
|
await execa("git", ["submodule", "foreach", `git fetch origin && git checkout origin/${CLONE_BRANCH} 2>/dev/null || git checkout origin/main`], { cwd: resolved, stdio: "inherit" });
|
|
91
106
|
console.log(chalk.green(" Submodules recovered.\n"));
|
|
92
107
|
} catch {
|
|
93
108
|
console.log(chalk.yellow(" Some submodules still failed. Fix manually with:"));
|
|
94
|
-
console.log(chalk.
|
|
109
|
+
console.log(chalk.dim(` cd ${resolved} && git submodule foreach 'git checkout ${CLONE_BRANCH} || git checkout main && git pull'\n`));
|
|
95
110
|
}
|
|
96
111
|
}
|
|
97
112
|
projectRoot = resolved;
|
|
@@ -118,6 +133,58 @@ export async function runInitWizard() {
|
|
|
118
133
|
{ type: "confirm", name: "env", message: "Create .env from .env.example (if missing)?", default: true },
|
|
119
134
|
{ type: "confirm", name: "download", message: "Download container images now (make download)?", default: false },
|
|
120
135
|
]);
|
|
136
|
+
|
|
137
|
+
// ── Plugin selection ───────────────────────────────
|
|
138
|
+
const candidates = discoverPlugins();
|
|
139
|
+
const plugins = candidates
|
|
140
|
+
.map((c) => {
|
|
141
|
+
const manifest = validateManifest(c.path);
|
|
142
|
+
if (!manifest) return null;
|
|
143
|
+
return { id: manifest.id, name: manifest.name, description: manifest.description || "", path: c.path };
|
|
144
|
+
})
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
|
|
147
|
+
if (plugins.length > 0) {
|
|
148
|
+
console.log(chalk.cyan("\n Plugins\n"));
|
|
149
|
+
console.log(chalk.dim(" Select which plugins to enable:\n"));
|
|
150
|
+
|
|
151
|
+
// Read existing config to preserve current enabled state
|
|
152
|
+
const fopsConfigPath = path.join(os.homedir(), ".fops.json");
|
|
153
|
+
let fopsConfig = {};
|
|
154
|
+
try {
|
|
155
|
+
if (fs.existsSync(fopsConfigPath)) {
|
|
156
|
+
fopsConfig = JSON.parse(fs.readFileSync(fopsConfigPath, "utf8"));
|
|
157
|
+
}
|
|
158
|
+
} catch {}
|
|
159
|
+
|
|
160
|
+
const currentEntries = fopsConfig?.plugins?.entries || {};
|
|
161
|
+
const choices = plugins.map((p) => {
|
|
162
|
+
const isEnabled = currentEntries[p.id]?.enabled !== false;
|
|
163
|
+
return {
|
|
164
|
+
name: `${p.name}${p.description ? chalk.dim(` — ${p.description}`) : ""}`,
|
|
165
|
+
value: p.id,
|
|
166
|
+
checked: isEnabled,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const { enabledPlugins } = await inquirer.prompt([{
|
|
171
|
+
type: "checkbox",
|
|
172
|
+
name: "enabledPlugins",
|
|
173
|
+
message: "Plugins:",
|
|
174
|
+
choices,
|
|
175
|
+
}]);
|
|
176
|
+
|
|
177
|
+
// Save enabled/disabled state
|
|
178
|
+
if (!fopsConfig.plugins) fopsConfig.plugins = {};
|
|
179
|
+
if (!fopsConfig.plugins.entries) fopsConfig.plugins.entries = {};
|
|
180
|
+
for (const p of plugins) {
|
|
181
|
+
if (!fopsConfig.plugins.entries[p.id]) fopsConfig.plugins.entries[p.id] = {};
|
|
182
|
+
fopsConfig.plugins.entries[p.id].enabled = enabledPlugins.includes(p.id);
|
|
183
|
+
}
|
|
184
|
+
fs.writeFileSync(fopsConfigPath, JSON.stringify(fopsConfig, null, 2) + "\n");
|
|
185
|
+
console.log(chalk.green(` ✓ ${enabledPlugins.length}/${plugins.length} plugin(s) enabled`));
|
|
186
|
+
}
|
|
187
|
+
|
|
121
188
|
console.log("");
|
|
122
189
|
await runSetup(projectRoot, { submodules, env, download, netrcCheck: true });
|
|
123
190
|
}
|
package/src/ui/confirm.js
CHANGED
|
@@ -50,8 +50,9 @@ export function SelectPrompt({ message, options, onResult }) {
|
|
|
50
50
|
),
|
|
51
51
|
...options.map((opt, i) =>
|
|
52
52
|
h(Box, { key: i },
|
|
53
|
-
h(Text, { color: i === cursor ? "
|
|
54
|
-
|
|
53
|
+
h(Text, { color: "cyan" }, i === cursor ? " ❯ " : " "),
|
|
54
|
+
h(Text, { color: i === cursor ? "white" : undefined, dimColor: i !== cursor },
|
|
55
|
+
opt.label
|
|
55
56
|
)
|
|
56
57
|
)
|
|
57
58
|
)
|
package/src/ui/input.js
CHANGED
|
@@ -84,7 +84,7 @@ export function InputBox({ onSubmit, onExit, history = [], statusText }) {
|
|
|
84
84
|
h(Text, { color: "cyan" }, cursor)
|
|
85
85
|
),
|
|
86
86
|
h(Separator),
|
|
87
|
-
statusText && h(Text, {
|
|
87
|
+
statusText && h(Text, { color: "#888888" }, " " + statusText)
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -161,7 +161,7 @@ export function StandaloneInput({ onResult, statusText }) {
|
|
|
161
161
|
h(Text, { color: "cyan" }, cursor)
|
|
162
162
|
),
|
|
163
163
|
h(Separator),
|
|
164
|
-
statusText && h(Text, {
|
|
164
|
+
statusText && h(Text, { color: "#888888" }, " " + statusText)
|
|
165
165
|
);
|
|
166
166
|
}
|
|
167
167
|
|
package/src/ui/spinner.js
CHANGED
|
@@ -6,7 +6,7 @@ const h = React.createElement;
|
|
|
6
6
|
|
|
7
7
|
const SPARKLE_FRAMES = ["✻", "✼", "✻", "✦"];
|
|
8
8
|
const SPARKLE_INTERVAL = 120;
|
|
9
|
-
const INTENT_LINE = chalk.cyan("⏺") + chalk.
|
|
9
|
+
const INTENT_LINE = chalk.cyan("⏺") + chalk.dim(" Thinking...");
|
|
10
10
|
|
|
11
11
|
// Claude-style verbs for the spinner
|
|
12
12
|
export const VERBS = [
|
|
@@ -55,11 +55,11 @@ export function ThinkingSpinner({ message }) {
|
|
|
55
55
|
return h(Box, { flexDirection: "column" },
|
|
56
56
|
h(Box, null,
|
|
57
57
|
h(Text, { color: "cyan" }, "⏺"),
|
|
58
|
-
h(Text, {
|
|
58
|
+
h(Text, { dimColor: true }, " Thinking...")
|
|
59
59
|
),
|
|
60
60
|
h(Box, null,
|
|
61
61
|
h(Text, { color: "magenta" }, SPARKLE_FRAMES[frame]),
|
|
62
|
-
h(Text, {
|
|
62
|
+
h(Text, { dimColor: true }, ` ${message || `${verb}…`} `),
|
|
63
63
|
h(Text, { dimColor: true }, "(esc to interrupt)")
|
|
64
64
|
)
|
|
65
65
|
);
|
|
@@ -134,7 +134,7 @@ function ThinkingDisplay({ status, detail, thinking, content }) {
|
|
|
134
134
|
),
|
|
135
135
|
// Response content preview
|
|
136
136
|
contentPreview && h(Box, { marginLeft: 2 },
|
|
137
|
-
h(Text, {
|
|
137
|
+
h(Text, { dimColor: true }, contentPreview)
|
|
138
138
|
)
|
|
139
139
|
);
|
|
140
140
|
}
|
package/src/ui/streaming.js
CHANGED
|
@@ -11,7 +11,7 @@ export function ResponseBox({ content, title = "Claude" }) {
|
|
|
11
11
|
return h(Box, {
|
|
12
12
|
flexDirection: "column",
|
|
13
13
|
borderStyle: "round",
|
|
14
|
-
borderColor: "
|
|
14
|
+
borderColor: "white",
|
|
15
15
|
paddingX: 1,
|
|
16
16
|
marginY: 1,
|
|
17
17
|
},
|
|
@@ -64,7 +64,7 @@ export function StreamingResponse({ title = "Claude" }) {
|
|
|
64
64
|
return h(Box, {
|
|
65
65
|
flexDirection: "column",
|
|
66
66
|
borderStyle: "round",
|
|
67
|
-
borderColor: "
|
|
67
|
+
borderColor: "white",
|
|
68
68
|
paddingX: 1,
|
|
69
69
|
marginTop: 1,
|
|
70
70
|
},
|