@meshxdata/fops 0.0.6 → 0.0.8
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 -3
- package/src/auth/coda.js +4 -4
- package/src/auth/login.js +9 -6
- package/src/commands/index.js +74 -9
- package/src/doctor.js +333 -71
- package/src/feature-flags.js +3 -3
- package/src/lazy.js +12 -0
- package/src/plugins/bundled/coda/auth.js +8 -2
- package/src/plugins/bundled/cursor/index.js +3 -2
- package/src/plugins/bundled/fops-plugin-1password/index.js +6 -4
- package/src/plugins/bundled/fops-plugin-1password/lib/op.js +10 -2
- package/src/plugins/bundled/fops-plugin-1password/lib/setup.js +3 -3
- package/src/plugins/bundled/fops-plugin-1password/lib/sync.js +7 -2
- package/src/plugins/bundled/fops-plugin-ecr/lib/aws.js +5 -6
- package/src/plugins/bundled/fops-plugin-ecr/lib/setup.js +5 -5
- package/src/plugins/loader.js +3 -3
- package/src/plugins/skills.js +1 -1
- package/src/setup/aws.js +74 -46
- package/src/setup/setup.js +2 -2
- package/src/setup/wizard.js +16 -32
- package/src/shell.js +7 -1
- package/src/ui/confirm.js +10 -1
- package/src/wsl.js +82 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meshxdata/fops",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "CLI to install and manage Foundation data mesh platforms",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foundation",
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
"test:watch": "vitest"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@anthropic-ai/claude-code": "^1.
|
|
29
|
-
"@meshxdata/fops": "^0.0.4",
|
|
28
|
+
"@anthropic-ai/claude-code": "^2.1.39",
|
|
30
29
|
"boxen": "^8.0.1",
|
|
31
30
|
"chalk": "^5.3.0",
|
|
32
31
|
"commander": "^12.0.0",
|
package/src/auth/coda.js
CHANGED
|
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
5
|
import { openBrowser } from "./login.js";
|
|
6
|
+
import { getInquirer } from "../lazy.js";
|
|
7
7
|
|
|
8
8
|
const CODA_ACCOUNT_URL = "https://coda.io/account";
|
|
9
9
|
const FOPS_CONFIG_PATH = path.join(os.homedir(), ".fops.json");
|
|
@@ -60,7 +60,7 @@ export async function runCodaLogin() {
|
|
|
60
60
|
const existing = resolveCodaApiToken();
|
|
61
61
|
if (existing) {
|
|
62
62
|
const masked = existing.slice(0, 8) + "..." + existing.slice(-4);
|
|
63
|
-
const { overwrite } = await
|
|
63
|
+
const { overwrite } = await (await getInquirer()).prompt([{
|
|
64
64
|
type: "confirm",
|
|
65
65
|
name: "overwrite",
|
|
66
66
|
message: `Already have a Coda API token (${masked}). Replace it?`,
|
|
@@ -84,7 +84,7 @@ export async function runCodaLogin() {
|
|
|
84
84
|
console.log(chalk.dim(" 5. Copy the token and paste it below"));
|
|
85
85
|
console.log("");
|
|
86
86
|
|
|
87
|
-
const { openIt } = await
|
|
87
|
+
const { openIt } = await (await getInquirer()).prompt([{
|
|
88
88
|
type: "confirm",
|
|
89
89
|
name: "openIt",
|
|
90
90
|
message: "Open Coda account page in your browser?",
|
|
@@ -100,7 +100,7 @@ export async function runCodaLogin() {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const { token } = await
|
|
103
|
+
const { token } = await (await getInquirer()).prompt([{
|
|
104
104
|
type: "password",
|
|
105
105
|
name: "token",
|
|
106
106
|
message: "Paste your Coda API token:",
|
package/src/auth/login.js
CHANGED
|
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execaSync } from "execa";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
5
|
import { CLAUDE_DIR, CLAUDE_CREDENTIALS, resolveAnthropicApiKey } from "./resolve.js";
|
|
6
|
+
import { getInquirer } from "../lazy.js";
|
|
7
7
|
|
|
8
8
|
const ANTHROPIC_KEYS_URL = "https://console.anthropic.com/settings/keys";
|
|
9
9
|
|
|
@@ -17,9 +17,12 @@ export function authHelp() {
|
|
|
17
17
|
|
|
18
18
|
export function openBrowser(url) {
|
|
19
19
|
const platform = process.platform;
|
|
20
|
-
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
21
20
|
try {
|
|
22
|
-
|
|
21
|
+
if (platform === "win32") {
|
|
22
|
+
execaSync("cmd", ["/c", "start", "", url], { reject: false });
|
|
23
|
+
} else {
|
|
24
|
+
execaSync(platform === "darwin" ? "open" : "xdg-open", [url], { reject: false });
|
|
25
|
+
}
|
|
23
26
|
return true;
|
|
24
27
|
} catch {
|
|
25
28
|
return false;
|
|
@@ -51,7 +54,7 @@ export function saveApiKey(apiKey) {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export async function offerClaudeLogin() {
|
|
54
|
-
const { startLogin } = await
|
|
57
|
+
const { startLogin } = await (await getInquirer()).prompt([
|
|
55
58
|
{
|
|
56
59
|
type: "confirm",
|
|
57
60
|
name: "startLogin",
|
|
@@ -301,7 +304,7 @@ export async function runLogin(options = {}) {
|
|
|
301
304
|
const existingKey = resolveAnthropicApiKey();
|
|
302
305
|
if (existingKey) {
|
|
303
306
|
const masked = existingKey.slice(0, 15) + "..." + existingKey.slice(-4);
|
|
304
|
-
const { overwrite } = await
|
|
307
|
+
const { overwrite } = await (await getInquirer()).prompt([
|
|
305
308
|
{
|
|
306
309
|
type: "confirm",
|
|
307
310
|
name: "overwrite",
|
|
@@ -323,7 +326,7 @@ export async function runLogin(options = {}) {
|
|
|
323
326
|
// Terminal-only flow
|
|
324
327
|
console.log(chalk.blue("\nGet an API key from: " + ANTHROPIC_KEYS_URL + "\n"));
|
|
325
328
|
|
|
326
|
-
const { apiKey } = await
|
|
329
|
+
const { apiKey } = await (await getInquirer()).prompt([
|
|
327
330
|
{
|
|
328
331
|
type: "password",
|
|
329
332
|
name: "apiKey",
|
package/src/commands/index.js
CHANGED
|
@@ -3,17 +3,9 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { Command } from "commander";
|
|
7
6
|
import { PKG } from "../config.js";
|
|
8
7
|
import { rootDir, requireRoot, hasComposeInDir, isFoundationRoot, findComposeRootUp, checkInitState } from "../project.js";
|
|
9
|
-
import { execa } from "execa";
|
|
10
8
|
import { make, dockerCompose } from "../shell.js";
|
|
11
|
-
import { runSetup, runInitWizard } from "../setup/index.js";
|
|
12
|
-
import { ensureEcrAuth } from "../setup/aws.js";
|
|
13
|
-
import { runAgentSingleTurn, runAgentInteractive } from "../agent/index.js";
|
|
14
|
-
import { runDoctor } from "../doctor.js";
|
|
15
|
-
import { runFeatureFlags } from "../feature-flags.js";
|
|
16
|
-
import { runLogin, runCodaLogin } from "../auth/index.js";
|
|
17
9
|
import { runHook, loadSkills } from "../plugins/index.js";
|
|
18
10
|
|
|
19
11
|
export function registerCommands(program, registry) {
|
|
@@ -25,6 +17,7 @@ export function registerCommands(program, registry) {
|
|
|
25
17
|
.argument("[service]", "Service to login to: claude (default) or coda")
|
|
26
18
|
.option("--no-browser", "Paste API key in terminal instead of OAuth")
|
|
27
19
|
.action(async (service, opts) => {
|
|
20
|
+
const { runLogin, runCodaLogin } = await import("../auth/index.js");
|
|
28
21
|
const target = (service || "claude").toLowerCase();
|
|
29
22
|
if (target === "coda") {
|
|
30
23
|
await runCodaLogin();
|
|
@@ -46,6 +39,7 @@ export function registerCommands(program, registry) {
|
|
|
46
39
|
.option("--download", "Run make download after submodules", false)
|
|
47
40
|
.option("--yes", "Use defaults without prompting")
|
|
48
41
|
.action(async (opts) => {
|
|
42
|
+
const { runSetup } = await import("../setup/index.js");
|
|
49
43
|
const dir = opts.dir ? path.resolve(opts.dir) : (rootDir() || process.cwd());
|
|
50
44
|
if (!fs.existsSync(path.join(dir, "docker-compose.yaml")) && !fs.existsSync(path.join(dir, "docker-compose.yml"))) {
|
|
51
45
|
console.error(chalk.red("No docker-compose in %s. Run from foundation-compose root."), dir);
|
|
@@ -68,6 +62,7 @@ export function registerCommands(program, registry) {
|
|
|
68
62
|
.option("--no-submodules", "Skip git submodule init/update")
|
|
69
63
|
.option("--yes", "Use defaults without prompting; fail if no docker-compose in cwd")
|
|
70
64
|
.action(async (opts) => {
|
|
65
|
+
const { runSetup, runInitWizard } = await import("../setup/index.js");
|
|
71
66
|
const dir = opts.dir ? path.resolve(opts.dir) : process.cwd();
|
|
72
67
|
const hasCompose = hasComposeInDir(dir);
|
|
73
68
|
if (opts.yes) {
|
|
@@ -92,6 +87,7 @@ export function registerCommands(program, registry) {
|
|
|
92
87
|
.option("--no-run", "Do not offer to run suggested commands (single-turn only)", false)
|
|
93
88
|
.option("--model <id>", "Model override (e.g. claude-sonnet-4-20250514, gpt-4o-mini)")
|
|
94
89
|
.action(async (opts) => {
|
|
90
|
+
const { runAgentSingleTurn, runAgentInteractive } = await import("../agent/index.js");
|
|
95
91
|
const root = requireRoot(program);
|
|
96
92
|
if (opts.message) {
|
|
97
93
|
await runAgentSingleTurn(root, opts.message, { runSuggestions: opts.run !== false, model: opts.model, registry });
|
|
@@ -106,6 +102,8 @@ export function registerCommands(program, registry) {
|
|
|
106
102
|
.option("-d, --detach", "Run in background", true)
|
|
107
103
|
.option("--no-chat", "Skip interactive AI assistant after startup")
|
|
108
104
|
.action(async (opts) => {
|
|
105
|
+
const { execa } = await import("execa");
|
|
106
|
+
const { ensureEcrAuth } = await import("../setup/aws.js");
|
|
109
107
|
const root = requireRoot(program);
|
|
110
108
|
|
|
111
109
|
// Pre-flight: check if project is initialised
|
|
@@ -116,6 +114,19 @@ export function registerCommands(program, registry) {
|
|
|
116
114
|
process.exit(1);
|
|
117
115
|
}
|
|
118
116
|
|
|
117
|
+
// Pre-flight: check Docker daemon is reachable
|
|
118
|
+
try {
|
|
119
|
+
await execa("docker", ["info"], { timeout: 10000, reject: true });
|
|
120
|
+
} catch {
|
|
121
|
+
console.error(chalk.red("\n Docker daemon is not running or not reachable."));
|
|
122
|
+
if (process.platform === "linux") {
|
|
123
|
+
console.error(chalk.dim(" Try: sudo service docker start"));
|
|
124
|
+
} else {
|
|
125
|
+
console.error(chalk.dim(" Start Docker Desktop and try again."));
|
|
126
|
+
}
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
await ensureEcrAuth(root);
|
|
120
131
|
await runHook(registry, "before:up", { root });
|
|
121
132
|
|
|
@@ -152,16 +163,21 @@ export function registerCommands(program, registry) {
|
|
|
152
163
|
if (result.exitCode !== 0) {
|
|
153
164
|
console.error(chalk.red(`\n Some services failed to start (exit code ${result.exitCode}).`));
|
|
154
165
|
console.error(chalk.dim(" Dropping into debug agent to diagnose...\n"));
|
|
166
|
+
const { runAgentInteractive } = await import("../agent/index.js");
|
|
155
167
|
await runAgentInteractive(root, { registry, initialAgent: "debug" });
|
|
156
168
|
return;
|
|
157
169
|
}
|
|
158
|
-
if (opts.chat !== false)
|
|
170
|
+
if (opts.chat !== false) {
|
|
171
|
+
const { runAgentInteractive } = await import("../agent/index.js");
|
|
172
|
+
await runAgentInteractive(root, { registry });
|
|
173
|
+
}
|
|
159
174
|
});
|
|
160
175
|
|
|
161
176
|
program
|
|
162
177
|
.command("chat")
|
|
163
178
|
.description("Interactive AI assistant (same as foundation agent with no -m)")
|
|
164
179
|
.action(async () => {
|
|
180
|
+
const { runAgentInteractive } = await import("../agent/index.js");
|
|
165
181
|
const root = requireRoot(program);
|
|
166
182
|
await runAgentInteractive(root, { registry });
|
|
167
183
|
});
|
|
@@ -201,6 +217,7 @@ export function registerCommands(program, registry) {
|
|
|
201
217
|
.description("Check environment and Foundation setup (Docker, git, .env, submodules)")
|
|
202
218
|
.option("--fix", "Apply suggested fixes where possible", false)
|
|
203
219
|
.action(async (opts) => {
|
|
220
|
+
const { runDoctor } = await import("../doctor.js");
|
|
204
221
|
await runDoctor(opts, registry);
|
|
205
222
|
});
|
|
206
223
|
|
|
@@ -208,6 +225,7 @@ export function registerCommands(program, registry) {
|
|
|
208
225
|
.command("config")
|
|
209
226
|
.description("Toggle MX_FF_* feature flags and restart affected services")
|
|
210
227
|
.action(async () => {
|
|
228
|
+
const { runFeatureFlags } = await import("../feature-flags.js");
|
|
211
229
|
const root = requireRoot(program);
|
|
212
230
|
await runFeatureFlags(root);
|
|
213
231
|
});
|
|
@@ -216,6 +234,7 @@ export function registerCommands(program, registry) {
|
|
|
216
234
|
.command("build")
|
|
217
235
|
.description("Build all Foundation service images from source")
|
|
218
236
|
.action(async () => {
|
|
237
|
+
const { ensureEcrAuth } = await import("../setup/aws.js");
|
|
219
238
|
const root = requireRoot(program);
|
|
220
239
|
await ensureEcrAuth(root);
|
|
221
240
|
await make(root, "build");
|
|
@@ -225,11 +244,56 @@ export function registerCommands(program, registry) {
|
|
|
225
244
|
.command("download")
|
|
226
245
|
.description("Pull all container images from registry (requires ECR auth)")
|
|
227
246
|
.action(async () => {
|
|
247
|
+
const { ensureEcrAuth } = await import("../setup/aws.js");
|
|
228
248
|
const root = requireRoot(program);
|
|
229
249
|
await ensureEcrAuth(root);
|
|
230
250
|
await make(root, "download");
|
|
231
251
|
});
|
|
232
252
|
|
|
253
|
+
program
|
|
254
|
+
.command("update")
|
|
255
|
+
.description("Update fops CLI to the latest version")
|
|
256
|
+
.option("--check", "Only check for updates, don't install")
|
|
257
|
+
.action(async (opts) => {
|
|
258
|
+
const { execa } = await import("execa");
|
|
259
|
+
const currentVersion = PKG.version;
|
|
260
|
+
console.log(chalk.dim(` Current version: ${currentVersion}`));
|
|
261
|
+
|
|
262
|
+
// Fetch latest version from npm registry
|
|
263
|
+
console.log(chalk.dim(" Checking for updates..."));
|
|
264
|
+
let latestVersion;
|
|
265
|
+
try {
|
|
266
|
+
const { stdout } = await execa("npm", ["view", PKG.name, "version"], { timeout: 15000 });
|
|
267
|
+
latestVersion = stdout.trim();
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error(chalk.red(` Failed to check npm registry: ${err.message}`));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (latestVersion === currentVersion) {
|
|
274
|
+
console.log(chalk.green(` ✓ Already on the latest version (${currentVersion})`));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(chalk.yellow(` Update available: ${currentVersion} → ${latestVersion}`));
|
|
279
|
+
|
|
280
|
+
if (opts.check) {
|
|
281
|
+
console.log(chalk.dim(` Run 'fops update' to install the update.`));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Install the latest version globally
|
|
286
|
+
console.log(chalk.cyan(` ▶ npm install -g ${PKG.name}@latest`));
|
|
287
|
+
try {
|
|
288
|
+
await execa("npm", ["install", "-g", `${PKG.name}@latest`], { stdio: "inherit", timeout: 300_000 });
|
|
289
|
+
console.log(chalk.green(` ✓ Updated to ${latestVersion}`));
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error(chalk.red(` Update failed: ${err.message}`));
|
|
292
|
+
console.log(chalk.dim(" Try running with sudo: sudo fops update"));
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
233
297
|
program
|
|
234
298
|
.command("bootstrap")
|
|
235
299
|
.description("Create demo data mesh (make bootstrap)")
|
|
@@ -396,6 +460,7 @@ export function registerCommands(program, registry) {
|
|
|
396
460
|
.command("add <repo>")
|
|
397
461
|
.description("Install a plugin from GitHub (owner/repo)")
|
|
398
462
|
.action(async (repo) => {
|
|
463
|
+
const { execa } = await import("execa");
|
|
399
464
|
const parts = repo.split("/");
|
|
400
465
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
401
466
|
console.error(chalk.red(' Usage: fops plugin marketplace add <owner/repo>'));
|