moflo 4.12.3 → 4.12.4-rc.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/.claude/agents/core/coder.md +2 -3
- package/.claude/agents/core/researcher.md +3 -4
- package/.claude/agents/core/reviewer.md +6 -8
- package/.claude/guidance/shipped/moflo-spell-engine.md +13 -10
- package/.claude/helpers/gate-hook.mjs +67 -2
- package/.claude/helpers/gate.cjs +173 -12
- package/.claude/helpers/hook-handler.cjs +0 -0
- package/.claude/helpers/prompt-hook.mjs +0 -0
- package/.claude/skills/connector-builder/SKILL.md +5 -4
- package/.claude/skills/fl/phases.md +1 -1
- package/.claude/skills/fl/sdd.md +1 -1
- package/.claude/skills/spell-builder/SKILL.md +5 -4
- package/.claude/skills/spell-builder/architecture.md +4 -4
- package/.claude/skills/spell-builder/connectors/github-cli/README.md +22 -17
- package/.claude/skills/spell-builder/connectors/http/README.md +18 -11
- package/.claude/skills/spell-builder/connectors/local-outlook/README.md +8 -7
- package/.claude/skills/spell-builder/connectors/playwright/README.md +10 -8
- package/.claude/skills/spell-builder/steps/agent/README.md +25 -21
- package/.claude/skills/verify/SKILL.md +24 -3
- package/bin/gate-hook.mjs +67 -2
- package/bin/gate.cjs +173 -12
- package/bin/generate-code-map.mjs +70 -17
- package/bin/index-all.mjs +38 -0
- package/bin/index-guidance.mjs +8 -41
- package/bin/index-patterns.mjs +2 -2
- package/bin/index-tests.mjs +6 -4
- package/bin/lib/guidance-config.mjs +119 -0
- package/bin/lib/index-fingerprint.mjs +0 -0
- package/dist/src/cli/commands/agent.js +1 -89
- package/dist/src/cli/commands/analyze.js +1 -1
- package/dist/src/cli/commands/benchmark.js +11 -8
- package/dist/src/cli/commands/claims.js +2 -2
- package/dist/src/cli/commands/completions.js +1 -1
- package/dist/src/cli/commands/config.js +256 -96
- package/dist/src/cli/commands/deployment.js +6 -6
- package/dist/src/cli/commands/diagnose.js +1 -1
- package/dist/src/cli/commands/doctor-checks-config.js +18 -22
- package/dist/src/cli/commands/doctor-checks-deep.js +1 -1
- package/dist/src/cli/commands/doctor-fixes.js +36 -11
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/embeddings.js +64 -23
- package/dist/src/cli/commands/github.js +1 -1
- package/dist/src/cli/commands/guidance.js +10 -10
- package/dist/src/cli/commands/hive-mind.js +8 -70
- package/dist/src/cli/commands/hooks.js +32 -219
- package/dist/src/cli/commands/mcp.js +5 -2
- package/dist/src/cli/commands/memory.js +59 -46
- package/dist/src/cli/commands/neural.js +21 -21
- package/dist/src/cli/commands/performance.js +14 -7
- package/dist/src/cli/commands/plugins.js +2 -2
- package/dist/src/cli/commands/providers.js +2 -2
- package/dist/src/cli/commands/retire.js +1 -1
- package/dist/src/cli/commands/route.js +1 -1
- package/dist/src/cli/commands/security.js +4 -4
- package/dist/src/cli/commands/status.js +18 -13
- package/dist/src/cli/commands/task.js +8 -0
- package/dist/src/cli/config/cli-config-store.js +326 -0
- package/dist/src/cli/hooks/workers/index.js +15 -9
- package/dist/src/cli/index.js +43 -33
- package/dist/src/cli/init/claudemd-generator.js +3 -3
- package/dist/src/cli/init/helpers-generator.js +203 -20
- package/dist/src/cli/init/moflo-yaml-template.js +11 -3
- package/dist/src/cli/init/settings-generator.js +10 -2
- package/dist/src/cli/mcp-client.js +12 -2
- package/dist/src/cli/mcp-server.js +5 -23
- package/dist/src/cli/mcp-tools/analysis-tools.js +103 -0
- package/dist/src/cli/mcp-tools/coverage-tools.js +206 -0
- package/dist/src/cli/mcp-tools/hive-mind-tools.js +71 -0
- package/dist/src/cli/mcp-tools/hooks-tools.js +25 -9
- package/dist/src/cli/mcp-tools/memory-admin-tools.js +538 -0
- package/dist/src/cli/mcp-tools/memory-tools.js +26 -5
- package/dist/src/cli/mcp-tools/neural-tools.js +56 -87
- package/dist/src/cli/mcp-tools/performance-tools.js +76 -39
- package/dist/src/cli/mcp-tools/progress-tools.js +142 -0
- package/dist/src/cli/mcp-tools/security-tools.js +1 -1
- package/dist/src/cli/mcp-tools/session-tools.js +116 -0
- package/dist/src/cli/mcp-tools/synthetic.js +74 -0
- package/dist/src/cli/mcp-tools/system-tools.js +126 -78
- package/dist/src/cli/mcp-tools/task-tools.js +101 -1
- package/dist/src/cli/movector/coverage-router.js +48 -6
- package/dist/src/cli/movector/diff-classifier.js +8 -6
- package/dist/src/cli/movector/flash-attention.js +1 -1
- package/dist/src/cli/movector/vector-db.js +1 -1
- package/dist/src/cli/parser.js +81 -12
- package/dist/src/cli/services/daemon-lock.js +5 -2
- package/dist/src/cli/services/hook-block-hash.js +3 -1
- package/dist/src/cli/services/moflo-version.js +62 -0
- package/dist/src/cli/services/movector-training.js +1 -1
- package/dist/src/cli/services/spell-gate.js +6 -4
- package/dist/src/cli/services/worker-daemon.js +37 -7
- package/dist/src/cli/shared/utils/load-average.js +59 -0
- package/dist/src/cli/spells/commands/agent-command.js +55 -25
- package/dist/src/cli/suggest.js +1 -1
- package/dist/src/cli/swarm/swarm-persistence.js +112 -0
- package/dist/src/cli/swarm/unified-coordinator.js +118 -3
- package/dist/src/cli/version.js +1 -1
- package/package.json +5 -3
- package/dist/src/cli/mcp-tools/github-tools.js +0 -318
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* V3 CLI Config Command
|
|
3
3
|
* Configuration management
|
|
4
|
+
*
|
|
5
|
+
* Every subcommand here reads and writes a real file — see
|
|
6
|
+
* `../config/cli-config-store.ts` for the store and for why that is worth
|
|
7
|
+
* stating: this family used to print "Creating ..." and "Configuration
|
|
8
|
+
* updated" without touching the filesystem, which made `flo doctor --fix`
|
|
9
|
+
* report the `Config File` warning as fixed forever.
|
|
10
|
+
*
|
|
11
|
+
* `show` and `generate` are the odd pair out: they operate on **moflo.yaml**
|
|
12
|
+
* via `../config/moflo-config.ts`, not on the JSON config.
|
|
4
13
|
*/
|
|
14
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
5
16
|
import { output } from '../output.js';
|
|
6
17
|
import { confirm } from '../prompt.js';
|
|
18
|
+
import { resolveStateRoot } from '../services/project-root.js';
|
|
19
|
+
import { CliConfigParseError, RESETTABLE_SECTIONS, cliConfigPath, defaultCliConfig, findCliConfigFile, flattenConfig, getConfigValue, loadCliConfig, resetSection, saveCliConfig, setConfigValue, writeJsonFile, writeTextFile, } from '../config/cli-config-store.js';
|
|
20
|
+
/**
|
|
21
|
+
* Anchor config reads/writes at the state root — `resolveStateRoot`, not
|
|
22
|
+
* `findProjectRoot`, because this command CREATES `.moflo/` content and must
|
|
23
|
+
* land on the monorepo's canonical anchor rather than minting an island in a
|
|
24
|
+
* sub-workspace (#1315).
|
|
25
|
+
*/
|
|
26
|
+
function configRoot(ctx) {
|
|
27
|
+
return resolveStateRoot({ cwd: ctx.cwd || process.cwd() });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Path relative to the project root, for display. Builds the `./` prefix from
|
|
31
|
+
* `path.sep` so Windows shows `.\.moflo\config.json` rather than a mixed
|
|
32
|
+
* `./.moflo\config.json`.
|
|
33
|
+
*/
|
|
34
|
+
function displayPath(root, target) {
|
|
35
|
+
const rel = relative(root, target);
|
|
36
|
+
return rel && !rel.startsWith('..') ? `.${sep}${rel}` : target;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load config, printing the parse error and returning `null` when the file on
|
|
40
|
+
* disk is corrupt. Callers exit 1 on `null` rather than silently falling back
|
|
41
|
+
* to defaults — a config that cannot be read is a failure, not a default.
|
|
42
|
+
*/
|
|
43
|
+
function loadOrReport(root) {
|
|
44
|
+
try {
|
|
45
|
+
return loadCliConfig(root);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
if (err instanceof CliConfigParseError) {
|
|
49
|
+
output.printError(err.message);
|
|
50
|
+
output.writeln(output.dim(' Fix the JSON syntax, or run `flo config init --force` to rewrite it.'));
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
7
56
|
// Init configuration
|
|
8
57
|
const initCommand = {
|
|
9
58
|
name: 'init',
|
|
@@ -32,54 +81,27 @@ const initCommand = {
|
|
|
32
81
|
action: async (ctx) => {
|
|
33
82
|
const sparc = ctx.flags.sparc;
|
|
34
83
|
const v3 = ctx.flags.v3;
|
|
84
|
+
const force = ctx.flags.force;
|
|
85
|
+
const root = configRoot(ctx);
|
|
86
|
+
const existing = findCliConfigFile(root);
|
|
87
|
+
if (existing && !force) {
|
|
88
|
+
output.printError(`Configuration already exists: ${displayPath(root, existing)}`);
|
|
89
|
+
output.writeln(output.dim(' Re-run with --force to overwrite it.'));
|
|
90
|
+
return { success: false, exitCode: 1 };
|
|
91
|
+
}
|
|
35
92
|
output.writeln();
|
|
36
93
|
output.printInfo('Initializing MoFlo configuration...');
|
|
37
94
|
output.writeln();
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
sparc: sparc,
|
|
43
|
-
agents: {
|
|
44
|
-
defaultType: 'coder',
|
|
45
|
-
maxConcurrent: 15,
|
|
46
|
-
autoSpawn: true,
|
|
47
|
-
timeout: 300
|
|
48
|
-
},
|
|
49
|
-
swarm: {
|
|
50
|
-
topology: 'hybrid',
|
|
51
|
-
maxAgents: 15,
|
|
52
|
-
autoScale: true,
|
|
53
|
-
coordinationStrategy: 'consensus'
|
|
54
|
-
},
|
|
55
|
-
memory: {
|
|
56
|
-
backend: 'hybrid',
|
|
57
|
-
path: './data/memory',
|
|
58
|
-
cacheSize: 256,
|
|
59
|
-
enableHNSW: true
|
|
60
|
-
},
|
|
61
|
-
mcp: {
|
|
62
|
-
transport: 'stdio',
|
|
63
|
-
autoStart: true,
|
|
64
|
-
tools: 'all'
|
|
65
|
-
},
|
|
66
|
-
providers: [
|
|
67
|
-
{ name: 'anthropic', priority: 1, enabled: true },
|
|
68
|
-
{ name: 'openrouter', priority: 2, enabled: false },
|
|
69
|
-
{ name: 'ollama', priority: 3, enabled: false }
|
|
70
|
-
]
|
|
71
|
-
};
|
|
72
|
-
output.writeln(output.dim(' Creating claude-flow.config.json...'));
|
|
73
|
-
output.writeln(output.dim(' Creating .moflo/ directory...'));
|
|
74
|
-
if (sparc) {
|
|
75
|
-
output.writeln(output.dim(' Initializing SPARC methodology...'));
|
|
76
|
-
output.writeln(output.dim(' Creating SPARC workflow files...'));
|
|
77
|
-
}
|
|
78
|
-
if (v3) {
|
|
79
|
-
output.writeln(output.dim(' Enabling V3 15-agent coordination...'));
|
|
80
|
-
output.writeln(output.dim(' Configuring AgentDB integration...'));
|
|
81
|
-
output.writeln(output.dim(' Setting up Flash Attention optimization...'));
|
|
95
|
+
const config = defaultCliConfig({ v3, sparc });
|
|
96
|
+
const target = existing ?? cliConfigPath(root);
|
|
97
|
+
try {
|
|
98
|
+
saveCliConfig(root, config, { path: target });
|
|
82
99
|
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
output.printError(`Failed to write ${displayPath(root, target)}: ${err.message}`);
|
|
102
|
+
return { success: false, exitCode: 1 };
|
|
103
|
+
}
|
|
104
|
+
output.writeln(output.dim(` Wrote ${displayPath(root, target)}`));
|
|
83
105
|
output.writeln();
|
|
84
106
|
output.printTable({
|
|
85
107
|
columns: [
|
|
@@ -98,7 +120,7 @@ const initCommand = {
|
|
|
98
120
|
});
|
|
99
121
|
output.writeln();
|
|
100
122
|
output.printSuccess('Configuration initialized');
|
|
101
|
-
output.writeln(output.dim(
|
|
123
|
+
output.writeln(output.dim(` Config file: ${displayPath(root, target)}`));
|
|
102
124
|
return { success: true, data: config };
|
|
103
125
|
}
|
|
104
126
|
};
|
|
@@ -120,39 +142,32 @@ const getCommand = {
|
|
|
120
142
|
],
|
|
121
143
|
action: async (ctx) => {
|
|
122
144
|
const key = ctx.flags.key || ctx.args[0];
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'swarm.maxAgents': 15,
|
|
129
|
-
'swarm.autoScale': true,
|
|
130
|
-
'memory.backend': 'hybrid',
|
|
131
|
-
'memory.cacheSize': 256,
|
|
132
|
-
'mcp.transport': 'stdio',
|
|
133
|
-
'agents.defaultType': 'coder',
|
|
134
|
-
'agents.maxConcurrent': 15
|
|
135
|
-
};
|
|
145
|
+
const root = configRoot(ctx);
|
|
146
|
+
const loaded = loadOrReport(root);
|
|
147
|
+
if (!loaded)
|
|
148
|
+
return { success: false, exitCode: 1 };
|
|
149
|
+
const { config, path } = loaded;
|
|
136
150
|
if (!key) {
|
|
137
|
-
|
|
151
|
+
const configValues = flattenConfig(config);
|
|
138
152
|
if (ctx.flags.format === 'json') {
|
|
139
153
|
output.printJson(configValues);
|
|
140
154
|
return { success: true, data: configValues };
|
|
141
155
|
}
|
|
142
156
|
output.writeln();
|
|
143
157
|
output.writeln(output.bold('Current Configuration'));
|
|
158
|
+
output.writeln(output.dim(path ? ` ${displayPath(root, path)}` : ' (no config file — showing defaults)'));
|
|
144
159
|
output.writeln();
|
|
145
160
|
output.printTable({
|
|
146
161
|
columns: [
|
|
147
|
-
{ key: 'key', header: 'Key', width:
|
|
162
|
+
{ key: 'key', header: 'Key', width: 30 },
|
|
148
163
|
{ key: 'value', header: 'Value', width: 30 }
|
|
149
164
|
],
|
|
150
165
|
data: Object.entries(configValues).map(([k, v]) => ({ key: k, value: String(v) }))
|
|
151
166
|
});
|
|
152
167
|
return { success: true, data: configValues };
|
|
153
168
|
}
|
|
154
|
-
const value =
|
|
155
|
-
if (
|
|
169
|
+
const { found, value } = getConfigValue(config, key);
|
|
170
|
+
if (!found) {
|
|
156
171
|
output.printError(`Configuration key not found: ${key}`);
|
|
157
172
|
return { success: false, exitCode: 1 };
|
|
158
173
|
}
|
|
@@ -160,7 +175,7 @@ const getCommand = {
|
|
|
160
175
|
output.printJson({ key, value });
|
|
161
176
|
}
|
|
162
177
|
else {
|
|
163
|
-
output.writeln(`${key} = ${value}`);
|
|
178
|
+
output.writeln(`${key} = ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`);
|
|
164
179
|
}
|
|
165
180
|
return { success: true, data: { key, value } };
|
|
166
181
|
}
|
|
@@ -170,19 +185,21 @@ const setCommand = {
|
|
|
170
185
|
name: 'set',
|
|
171
186
|
description: 'Set configuration value',
|
|
172
187
|
options: [
|
|
188
|
+
// Not `required` at the parser level: these are also accepted
|
|
189
|
+
// positionally (`flo config set swarm.maxAgents 20`, this command's own
|
|
190
|
+
// documented example), and a parser-level requirement rejects that form
|
|
191
|
+
// before the action can read ctx.args. The action validates instead.
|
|
173
192
|
{
|
|
174
193
|
name: 'key',
|
|
175
194
|
short: 'k',
|
|
176
195
|
description: 'Configuration key',
|
|
177
|
-
type: 'string'
|
|
178
|
-
required: true
|
|
196
|
+
type: 'string'
|
|
179
197
|
},
|
|
180
198
|
{
|
|
181
199
|
name: 'value',
|
|
182
200
|
short: 'v',
|
|
183
201
|
description: 'Configuration value',
|
|
184
|
-
type: 'string'
|
|
185
|
-
required: true
|
|
202
|
+
type: 'string'
|
|
186
203
|
}
|
|
187
204
|
],
|
|
188
205
|
examples: [
|
|
@@ -196,9 +213,27 @@ const setCommand = {
|
|
|
196
213
|
output.printError('Both key and value are required');
|
|
197
214
|
return { success: false, exitCode: 1 };
|
|
198
215
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
216
|
+
const root = configRoot(ctx);
|
|
217
|
+
const loaded = loadOrReport(root);
|
|
218
|
+
if (!loaded)
|
|
219
|
+
return { success: false, exitCode: 1 };
|
|
220
|
+
const { config } = loaded;
|
|
221
|
+
const result = setConfigValue(config, key, String(value));
|
|
222
|
+
if (!result.ok) {
|
|
223
|
+
output.printError(`Cannot set ${key}: ${result.error}`);
|
|
224
|
+
return { success: false, exitCode: 1 };
|
|
225
|
+
}
|
|
226
|
+
let target;
|
|
227
|
+
try {
|
|
228
|
+
target = saveCliConfig(root, config, { path: loaded.path ?? undefined });
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
output.printError(`Failed to write configuration: ${err.message}`);
|
|
232
|
+
return { success: false, exitCode: 1 };
|
|
233
|
+
}
|
|
234
|
+
output.printInfo(`Setting ${key} = ${String(result.value)}`);
|
|
235
|
+
output.printSuccess(`Configuration updated (${displayPath(root, target)})`);
|
|
236
|
+
return { success: true, data: { key, value: result.value } };
|
|
202
237
|
}
|
|
203
238
|
};
|
|
204
239
|
// List providers
|
|
@@ -230,12 +265,58 @@ const providersCommand = {
|
|
|
230
265
|
}
|
|
231
266
|
],
|
|
232
267
|
action: async (ctx) => {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
{
|
|
237
|
-
|
|
238
|
-
|
|
268
|
+
const root = configRoot(ctx);
|
|
269
|
+
const loaded = loadOrReport(root);
|
|
270
|
+
if (!loaded)
|
|
271
|
+
return { success: false, exitCode: 1 };
|
|
272
|
+
const { config } = loaded;
|
|
273
|
+
const add = ctx.flags.add;
|
|
274
|
+
const remove = ctx.flags.remove;
|
|
275
|
+
const enable = ctx.flags.enable;
|
|
276
|
+
const disable = ctx.flags.disable;
|
|
277
|
+
const find = (name) => config.providers.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
278
|
+
let mutated = false;
|
|
279
|
+
if (add) {
|
|
280
|
+
if (find(add)) {
|
|
281
|
+
output.printError(`Provider already exists: ${add}`);
|
|
282
|
+
return { success: false, exitCode: 1 };
|
|
283
|
+
}
|
|
284
|
+
const priority = config.providers.reduce((max, p) => Math.max(max, p.priority), 0) + 1;
|
|
285
|
+
config.providers.push({ name: add, priority, enabled: false });
|
|
286
|
+
mutated = true;
|
|
287
|
+
}
|
|
288
|
+
for (const [name, action] of [[remove, 'remove'], [enable, 'enable'], [disable, 'disable']]) {
|
|
289
|
+
if (!name)
|
|
290
|
+
continue;
|
|
291
|
+
const provider = find(name);
|
|
292
|
+
if (!provider) {
|
|
293
|
+
output.printError(`Provider not found: ${name}`);
|
|
294
|
+
return { success: false, exitCode: 1 };
|
|
295
|
+
}
|
|
296
|
+
if (action === 'remove') {
|
|
297
|
+
config.providers = config.providers.filter((p) => p !== provider);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
provider.enabled = action === 'enable';
|
|
301
|
+
}
|
|
302
|
+
mutated = true;
|
|
303
|
+
}
|
|
304
|
+
if (mutated) {
|
|
305
|
+
let target;
|
|
306
|
+
try {
|
|
307
|
+
target = saveCliConfig(root, config, { path: loaded.path ?? undefined });
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
output.printError(`Failed to write configuration: ${err.message}`);
|
|
311
|
+
return { success: false, exitCode: 1 };
|
|
312
|
+
}
|
|
313
|
+
output.printSuccess(`Providers updated (${displayPath(root, target)})`);
|
|
314
|
+
}
|
|
315
|
+
const providers = config.providers.map((p) => ({
|
|
316
|
+
...p,
|
|
317
|
+
model: p.model ?? '',
|
|
318
|
+
status: p.enabled ? 'Active' : 'Disabled'
|
|
319
|
+
}));
|
|
239
320
|
if (ctx.flags.format === 'json') {
|
|
240
321
|
output.printJson(providers);
|
|
241
322
|
return { success: true, data: providers };
|
|
@@ -277,12 +358,17 @@ const resetCommand = {
|
|
|
277
358
|
name: 'section',
|
|
278
359
|
description: 'Reset specific section only',
|
|
279
360
|
type: 'string',
|
|
280
|
-
choices: [
|
|
361
|
+
choices: [...RESETTABLE_SECTIONS]
|
|
281
362
|
}
|
|
282
363
|
],
|
|
283
364
|
action: async (ctx) => {
|
|
284
365
|
const force = ctx.flags.force;
|
|
285
|
-
const section = ctx.flags.section || 'all';
|
|
366
|
+
const section = (ctx.flags.section || 'all');
|
|
367
|
+
if (!RESETTABLE_SECTIONS.includes(section)) {
|
|
368
|
+
output.printError(`Unknown section: ${section}`);
|
|
369
|
+
output.writeln(output.dim(` Valid sections: ${RESETTABLE_SECTIONS.join(', ')}`));
|
|
370
|
+
return { success: false, exitCode: 1 };
|
|
371
|
+
}
|
|
286
372
|
if (!force && ctx.interactive) {
|
|
287
373
|
const confirmed = await confirm({
|
|
288
374
|
message: `Reset ${section} configuration to defaults?`,
|
|
@@ -293,9 +379,21 @@ const resetCommand = {
|
|
|
293
379
|
return { success: true };
|
|
294
380
|
}
|
|
295
381
|
}
|
|
382
|
+
const root = configRoot(ctx);
|
|
383
|
+
const loaded = loadOrReport(root);
|
|
384
|
+
if (!loaded)
|
|
385
|
+
return { success: false, exitCode: 1 };
|
|
296
386
|
output.printInfo(`Resetting ${section} configuration...`);
|
|
297
|
-
|
|
298
|
-
|
|
387
|
+
let target;
|
|
388
|
+
try {
|
|
389
|
+
target = saveCliConfig(root, resetSection(loaded.config, section), { path: loaded.path ?? undefined });
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
output.printError(`Failed to write configuration: ${err.message}`);
|
|
393
|
+
return { success: false, exitCode: 1 };
|
|
394
|
+
}
|
|
395
|
+
output.printSuccess(`Configuration reset to defaults (${displayPath(root, target)})`);
|
|
396
|
+
return { success: true, data: { section, reset: true, path: target } };
|
|
299
397
|
}
|
|
300
398
|
};
|
|
301
399
|
// Export configuration
|
|
@@ -319,20 +417,39 @@ const exportCommand = {
|
|
|
319
417
|
}
|
|
320
418
|
],
|
|
321
419
|
action: async (ctx) => {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
output
|
|
420
|
+
const format = ctx.flags.format || 'json';
|
|
421
|
+
if (format !== 'json' && format !== 'yaml') {
|
|
422
|
+
output.printError(`Unsupported export format: ${format}`);
|
|
423
|
+
output.writeln(output.dim(' Valid formats: json, yaml'));
|
|
424
|
+
return { success: false, exitCode: 1 };
|
|
425
|
+
}
|
|
426
|
+
const root = configRoot(ctx);
|
|
427
|
+
const loaded = loadOrReport(root);
|
|
428
|
+
if (!loaded)
|
|
429
|
+
return { success: false, exitCode: 1 };
|
|
430
|
+
const requested = ctx.flags.output;
|
|
431
|
+
const defaultName = `moflo.config.export.${format === 'yaml' ? 'yaml' : 'json'}`;
|
|
432
|
+
const outputPath = requested
|
|
433
|
+
? (isAbsolute(requested) ? requested : resolve(root, requested))
|
|
434
|
+
: resolve(root, defaultName);
|
|
435
|
+
const config = { ...loaded.config, exportedAt: new Date().toISOString() };
|
|
436
|
+
output.printInfo(`Exporting configuration to ${displayPath(root, outputPath)}...`);
|
|
437
|
+
try {
|
|
438
|
+
if (format === 'yaml') {
|
|
439
|
+
const { dump } = await import('js-yaml');
|
|
440
|
+
writeTextFile(outputPath, dump(config));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
writeJsonFile(outputPath, config);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
output.printError(`Failed to write ${displayPath(root, outputPath)}: ${err.message}`);
|
|
448
|
+
return { success: false, exitCode: 1 };
|
|
449
|
+
}
|
|
333
450
|
output.writeln();
|
|
334
451
|
output.printSuccess('Configuration exported');
|
|
335
|
-
return { success: true, data: { path: outputPath, config } };
|
|
452
|
+
return { success: true, data: { path: outputPath, format, config } };
|
|
336
453
|
}
|
|
337
454
|
};
|
|
338
455
|
// Import configuration
|
|
@@ -340,12 +457,13 @@ const importCommand = {
|
|
|
340
457
|
name: 'import',
|
|
341
458
|
description: 'Import configuration',
|
|
342
459
|
options: [
|
|
460
|
+
// Also accepted positionally (`flo config import ./cfg.json`) — see the
|
|
461
|
+
// note on `set`.
|
|
343
462
|
{
|
|
344
463
|
name: 'file',
|
|
345
464
|
short: 'f',
|
|
346
465
|
description: 'Configuration file path',
|
|
347
|
-
type: 'string'
|
|
348
|
-
required: true
|
|
466
|
+
type: 'string'
|
|
349
467
|
},
|
|
350
468
|
{
|
|
351
469
|
name: 'merge',
|
|
@@ -361,15 +479,57 @@ const importCommand = {
|
|
|
361
479
|
output.printError('File path is required');
|
|
362
480
|
return { success: false, exitCode: 1 };
|
|
363
481
|
}
|
|
482
|
+
const root = configRoot(ctx);
|
|
483
|
+
const source = isAbsolute(file) ? file : resolve(root, file);
|
|
484
|
+
if (!existsSync(source)) {
|
|
485
|
+
output.printError(`Configuration file not found: ${file}`);
|
|
486
|
+
return { success: false, exitCode: 1 };
|
|
487
|
+
}
|
|
488
|
+
let incoming;
|
|
489
|
+
try {
|
|
490
|
+
const raw = readFileSync(source, 'utf8');
|
|
491
|
+
if (extname(source).toLowerCase() === '.yaml' || extname(source).toLowerCase() === '.yml') {
|
|
492
|
+
const { load } = await import('js-yaml');
|
|
493
|
+
incoming = load(raw);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
incoming = JSON.parse(raw);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
output.printError(`Could not parse ${file}: ${err.message}`);
|
|
501
|
+
return { success: false, exitCode: 1 };
|
|
502
|
+
}
|
|
503
|
+
if (typeof incoming !== 'object' || incoming === null || Array.isArray(incoming)) {
|
|
504
|
+
output.printError(`Not a configuration object: ${file}`);
|
|
505
|
+
return { success: false, exitCode: 1 };
|
|
506
|
+
}
|
|
364
507
|
output.printInfo(`Importing configuration from ${file}...`);
|
|
508
|
+
// Merge mode layers the file over what is on disk; replace mode layers it
|
|
509
|
+
// over defaults, so an incomplete file still yields a complete config.
|
|
510
|
+
let base;
|
|
365
511
|
if (merge) {
|
|
512
|
+
const loaded = loadOrReport(root);
|
|
513
|
+
if (!loaded)
|
|
514
|
+
return { success: false, exitCode: 1 };
|
|
515
|
+
base = loaded.config;
|
|
366
516
|
output.writeln(output.dim(' Merging with existing configuration...'));
|
|
367
517
|
}
|
|
368
518
|
else {
|
|
519
|
+
base = defaultCliConfig();
|
|
369
520
|
output.writeln(output.dim(' Replacing existing configuration...'));
|
|
370
521
|
}
|
|
371
|
-
|
|
372
|
-
|
|
522
|
+
const imported = { ...base, ...incoming };
|
|
523
|
+
let target;
|
|
524
|
+
try {
|
|
525
|
+
target = saveCliConfig(root, imported);
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
output.printError(`Failed to write configuration: ${err.message}`);
|
|
529
|
+
return { success: false, exitCode: 1 };
|
|
530
|
+
}
|
|
531
|
+
output.printSuccess(`Configuration imported (${displayPath(root, target)})`);
|
|
532
|
+
return { success: true, data: { file, merge, imported: true, path: target } };
|
|
373
533
|
}
|
|
374
534
|
};
|
|
375
535
|
// Show moflo project config (merged with defaults)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* V3 CLI Deployment Command
|
|
3
3
|
* Deployment management, environments, rollbacks
|
|
4
4
|
*
|
|
5
|
-
* Created with ❤️ by
|
|
5
|
+
* Created with ❤️ by cielolimitada.com
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
// Deploy subcommand
|
|
@@ -14,7 +14,7 @@ const deployCommand = {
|
|
|
14
14
|
{ name: 'version', short: 'v', type: 'string', description: 'Version to deploy', default: 'latest' },
|
|
15
15
|
{ name: 'dry-run', short: 'd', type: 'boolean', description: 'Simulate deployment without changes' },
|
|
16
16
|
{ name: 'force', short: 'f', type: 'boolean', description: 'Force deployment without checks' },
|
|
17
|
-
{ name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default:
|
|
17
|
+
{ name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default: true },
|
|
18
18
|
],
|
|
19
19
|
examples: [
|
|
20
20
|
{ command: 'flo deployment deploy -e prod', description: 'Deploy to production' },
|
|
@@ -100,7 +100,7 @@ const rollbackCommand = {
|
|
|
100
100
|
options: [
|
|
101
101
|
{ name: 'env', short: 'e', type: 'string', description: 'Environment to rollback', required: true },
|
|
102
102
|
{ name: 'version', short: 'v', type: 'string', description: 'Specific version to rollback to' },
|
|
103
|
-
{ name: 'steps', short: 's', type: 'number', description: 'Number of versions to rollback', default:
|
|
103
|
+
{ name: 'steps', short: 's', type: 'number', description: 'Number of versions to rollback', default: 1 },
|
|
104
104
|
],
|
|
105
105
|
examples: [
|
|
106
106
|
{ command: 'flo deployment rollback -e prod', description: 'Rollback production' },
|
|
@@ -141,7 +141,7 @@ const historyCommand = {
|
|
|
141
141
|
description: 'View deployment history',
|
|
142
142
|
options: [
|
|
143
143
|
{ name: 'env', short: 'e', type: 'string', description: 'Filter by environment' },
|
|
144
|
-
{ name: 'limit', short: 'l', type: 'number', description: 'Number of entries', default:
|
|
144
|
+
{ name: 'limit', short: 'l', type: 'number', description: 'Number of entries', default: 10 },
|
|
145
145
|
],
|
|
146
146
|
examples: [
|
|
147
147
|
{ command: 'flo deployment history', description: 'Show all history' },
|
|
@@ -214,7 +214,7 @@ const logsCommand = {
|
|
|
214
214
|
{ name: 'deployment', short: 'd', type: 'string', description: 'Deployment ID' },
|
|
215
215
|
{ name: 'env', short: 'e', type: 'string', description: 'Environment' },
|
|
216
216
|
{ name: 'follow', short: 'f', type: 'boolean', description: 'Follow log output' },
|
|
217
|
-
{ name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default:
|
|
217
|
+
{ name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: 50 },
|
|
218
218
|
],
|
|
219
219
|
examples: [
|
|
220
220
|
{ command: 'flo deployment logs -e prod', description: 'View production logs' },
|
|
@@ -281,7 +281,7 @@ export const deploymentCommand = {
|
|
|
281
281
|
'Deployment previews for PRs',
|
|
282
282
|
]);
|
|
283
283
|
output.writeln();
|
|
284
|
-
output.writeln(output.dim('Created with ❤️ by
|
|
284
|
+
output.writeln(output.dim('Created with ❤️ by cielolimitada.com'));
|
|
285
285
|
return { success: true };
|
|
286
286
|
},
|
|
287
287
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* All test data is cleaned up after each test — no code or state is left behind.
|
|
10
10
|
*
|
|
11
|
-
* Created with
|
|
11
|
+
* Created with cielolimitada.com
|
|
12
12
|
*/
|
|
13
13
|
import { output } from '../output.js';
|
|
14
14
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
@@ -10,41 +10,37 @@ import { findProjectDaemonPids, getDaemonLockHolder, getDaemonLockPayload, } fro
|
|
|
10
10
|
import { resolveClientPort, LEGACY_DEFAULT_PORT, probeDaemonHealthWithRetry as probeDaemonHealthIdentity, normalizeProjectRoot, } from '../services/daemon-port.js';
|
|
11
11
|
import { COMMON_WALK_SKIP_NAMES, LEGACY_SWARM_DIR, memoryDbCandidatePaths, memoryDbPath, } from '../services/moflo-paths.js';
|
|
12
12
|
import { probeDbIntegrity } from '../services/memory-db-integrity-repair.js';
|
|
13
|
+
import { CLI_CONFIG_CANDIDATES, CLI_CONFIG_YAML_CANDIDATES } from '../config/cli-config-store.js';
|
|
13
14
|
import { findProjectRoot, resolveStateRoot } from '../services/project-root.js';
|
|
14
15
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
15
16
|
export async function checkConfigFile() {
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (const
|
|
17
|
+
// Resolve against the state root, not the bare cwd: running `flo doctor`
|
|
18
|
+
// from a subdirectory used to report "no config file" for a project that has
|
|
19
|
+
// one, and the auto-fix (which writes at the state root) could never clear
|
|
20
|
+
// the warning it raised. Same anchor as `flo config` (#1315).
|
|
21
|
+
const root = resolveStateRoot();
|
|
22
|
+
// JSON configs (parse-validated). The candidate list lives in the config
|
|
23
|
+
// store so this check, `flo config`, and the doctor auto-fix all agree on
|
|
24
|
+
// which files count. LEGACY-CONFIG: `claude-flow.*` names are still
|
|
25
|
+
// recognised there so consumers upgrading from pre-#699 moflo builds keep
|
|
26
|
+
// working without a manual rename.
|
|
27
|
+
for (const candidate of CLI_CONFIG_CANDIDATES) {
|
|
28
|
+
const configPath = join(root, candidate);
|
|
27
29
|
if (existsSync(configPath)) {
|
|
28
30
|
try {
|
|
29
31
|
const content = readFileSync(configPath, 'utf8');
|
|
30
32
|
JSON.parse(content);
|
|
31
|
-
return { name: 'Config File', status: 'pass', message: `Found: ${
|
|
33
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${candidate}` };
|
|
32
34
|
}
|
|
33
35
|
catch {
|
|
34
|
-
return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${
|
|
36
|
+
return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${candidate}`, fix: 'Fix JSON syntax in config file' };
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
// YAML configs (existence-checked only — no heavy yaml parser dependency).
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'moflo.config.yaml',
|
|
43
|
-
'claude-flow.config.yaml', // LEGACY-CONFIG: pre-#699 fallback
|
|
44
|
-
];
|
|
45
|
-
for (const configPath of yamlPaths) {
|
|
46
|
-
if (existsSync(configPath)) {
|
|
47
|
-
return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
|
|
41
|
+
for (const candidate of CLI_CONFIG_YAML_CANDIDATES) {
|
|
42
|
+
if (existsSync(join(root, candidate))) {
|
|
43
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${candidate}` };
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'npx moflo config init' };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* so these checks work both in the dev repo AND in consumer projects where
|
|
12
12
|
* moflo is installed under node_modules/moflo/.
|
|
13
13
|
*
|
|
14
|
-
* Created with
|
|
14
|
+
* Created with cielolimitada.com
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync, readFileSync } from 'fs';
|
|
17
17
|
import { join } from 'path';
|