moflo 4.9.17 → 4.9.19
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/commands/simplify.md +11 -13
- package/.claude/helpers/gate.cjs +140 -5
- package/.claude/helpers/simplify-classify.cjs +4 -1
- package/.claude/skills/simplify/SKILL.md +13 -11
- package/bin/gate.cjs +140 -5
- package/bin/simplify-classify.cjs +4 -1
- package/dist/src/cli/commands/doctor-fixes.js +62 -16
- package/dist/src/cli/commands/spell-credentials.js +248 -0
- package/dist/src/cli/commands/spell.js +5 -3
- package/dist/src/cli/services/moflo-paths.js +5 -0
- package/dist/src/cli/spells/commands/prompt-command.js +45 -0
- package/dist/src/cli/spells/core/prerequisite-checker.js +89 -26
- package/dist/src/cli/spells/core/runner.js +4 -3
- package/dist/src/cli/spells/credentials/credential-store.js +20 -4
- package/dist/src/cli/spells/credentials/default-store.js +126 -0
- package/dist/src/cli/spells/credentials/index.js +6 -0
- package/dist/src/cli/spells/factory/runner-bridge.js +5 -2
- package/dist/src/cli/spells/factory/runner-factory.js +4 -2
- package/dist/src/cli/spells/index.js +1 -1
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Credential Store
|
|
3
|
+
*
|
|
4
|
+
* Singleton `CredentialAccessor` wired into the runner factory, MCP bridge,
|
|
5
|
+
* and daemon executor so spells persist secrets between casts.
|
|
6
|
+
*
|
|
7
|
+
* Passphrase resolution: `MOFLO_CREDENTIALS_PASSPHRASE` env var, else an
|
|
8
|
+
* auto-generated `~/.moflo/credentials.key` (32 random bytes hex, mode
|
|
9
|
+
* 0o600) created on first need. The auto-key gives zero-config at-rest
|
|
10
|
+
* encryption — the threat model matches `~/.aws/credentials`: filesystem
|
|
11
|
+
* read by an attacker compromises the store either way.
|
|
12
|
+
*
|
|
13
|
+
* Failures degrade to `lockedNoopAccessor` (returns undefined / no-op
|
|
14
|
+
* persists) so the spell engine never crashes on a missing or unreadable
|
|
15
|
+
* credentials path.
|
|
16
|
+
*/
|
|
17
|
+
import { randomBytes } from 'node:crypto';
|
|
18
|
+
import { mkdirSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { CredentialStore, MIN_PASSPHRASE_LENGTH } from './credential-store.js';
|
|
21
|
+
import { mofloHomeDir } from '../../services/moflo-paths.js';
|
|
22
|
+
const KEY_BYTES = 32;
|
|
23
|
+
/** 64 hex chars from `KEY_BYTES`; floor of 16 catches truncation/corruption. */
|
|
24
|
+
const KEY_FILE_MIN_HEX_LENGTH = 16;
|
|
25
|
+
export const DEFAULT_CREDENTIALS_FILE = join(mofloHomeDir(), 'credentials.json');
|
|
26
|
+
export const DEFAULT_CREDENTIALS_KEY_FILE = join(mofloHomeDir(), 'credentials.key');
|
|
27
|
+
let cached = null;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the singleton default credential store. Returns an unlocked
|
|
30
|
+
* `CredentialStore` when a passphrase is available, otherwise a locked
|
|
31
|
+
* no-op accessor. Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export function getDefaultCredentialStore(options = {}) {
|
|
34
|
+
if (cached)
|
|
35
|
+
return cached;
|
|
36
|
+
const filePath = resolveCredentialFilePath(options.filePath);
|
|
37
|
+
const passphrase = resolvePassphrase(options.passphrase, options.keyFilePath);
|
|
38
|
+
let accessor;
|
|
39
|
+
if (passphrase) {
|
|
40
|
+
try {
|
|
41
|
+
accessor = new CredentialStore({ filePath, passphrase });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.warn(`[moflo:credentials] store unavailable: ${err.message}`);
|
|
45
|
+
accessor = lockedNoopAccessor;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
accessor = lockedNoopAccessor;
|
|
50
|
+
}
|
|
51
|
+
cached = accessor;
|
|
52
|
+
return accessor;
|
|
53
|
+
}
|
|
54
|
+
/** Reset the cached singleton — used by tests and CLI subcommands. */
|
|
55
|
+
export function resetDefaultCredentialStore() {
|
|
56
|
+
cached = null;
|
|
57
|
+
}
|
|
58
|
+
/** Resolve the credentials JSON path (override → env → default). */
|
|
59
|
+
export function resolveCredentialFilePath(explicit) {
|
|
60
|
+
return explicit ?? process.env.MOFLO_CREDENTIALS_FILE ?? DEFAULT_CREDENTIALS_FILE;
|
|
61
|
+
}
|
|
62
|
+
/** Resolve the key file path (override → env → default). */
|
|
63
|
+
export function resolveKeyFilePath(explicit) {
|
|
64
|
+
return explicit ?? process.env.MOFLO_CREDENTIALS_KEY_FILE ?? DEFAULT_CREDENTIALS_KEY_FILE;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a passphrase from explicit override, `MOFLO_CREDENTIALS_PASSPHRASE`,
|
|
68
|
+
* or the auto-generated key file. Returns `undefined` when no path yields a
|
|
69
|
+
* usable value (locked-store mode).
|
|
70
|
+
*/
|
|
71
|
+
export function resolvePassphrase(explicit, keyFilePathOverride) {
|
|
72
|
+
if (explicit)
|
|
73
|
+
return explicit;
|
|
74
|
+
const envPass = process.env.MOFLO_CREDENTIALS_PASSPHRASE;
|
|
75
|
+
if (envPass && envPass.length >= MIN_PASSPHRASE_LENGTH)
|
|
76
|
+
return envPass;
|
|
77
|
+
return resolveKeyFilePassphrase(keyFilePathOverride);
|
|
78
|
+
}
|
|
79
|
+
function resolveKeyFilePassphrase(override) {
|
|
80
|
+
const keyPath = resolveKeyFilePath(override);
|
|
81
|
+
try {
|
|
82
|
+
let content = null;
|
|
83
|
+
try {
|
|
84
|
+
content = readFileSync(keyPath, 'utf-8').trim();
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err.code !== 'ENOENT')
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
if (content !== null) {
|
|
91
|
+
if (content.length >= KEY_FILE_MIN_HEX_LENGTH)
|
|
92
|
+
return content;
|
|
93
|
+
// Refuse to regenerate over a corrupt key when credentials exist —
|
|
94
|
+
// a fresh key would silently invalidate every stored secret.
|
|
95
|
+
const credPath = join(dirname(keyPath), 'credentials.json');
|
|
96
|
+
try {
|
|
97
|
+
readFileSync(credPath);
|
|
98
|
+
console.warn(`[moflo:credentials] key file at ${keyPath} is corrupt; refusing to regenerate while ${credPath} exists`);
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.code !== 'ENOENT')
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
mkdirSync(dirname(keyPath), { recursive: true });
|
|
107
|
+
const generated = randomBytes(KEY_BYTES).toString('hex');
|
|
108
|
+
writeFileSync(keyPath, generated, { encoding: 'utf-8', mode: 0o600 });
|
|
109
|
+
// chmod after write in case the create mode was masked by umask.
|
|
110
|
+
try {
|
|
111
|
+
chmodSync(keyPath, 0o600);
|
|
112
|
+
}
|
|
113
|
+
catch { /* best-effort on Windows */ }
|
|
114
|
+
return generated;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.warn(`[moflo:credentials] could not read/create key file: ${err.message}`);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export const lockedNoopAccessor = {
|
|
122
|
+
async get() { return undefined; },
|
|
123
|
+
async has() { return false; },
|
|
124
|
+
async store() { },
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=default-store.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials barrel — public surface of the credential subsystem.
|
|
3
|
+
*/
|
|
4
|
+
export { CredentialStore, CredentialStoreError, MIN_PASSPHRASE_LENGTH, } from './credential-store.js';
|
|
5
|
+
export { getDefaultCredentialStore, resetDefaultCredentialStore, resolveCredentialFilePath, resolveKeyFilePath, resolvePassphrase, lockedNoopAccessor, DEFAULT_CREDENTIALS_FILE, DEFAULT_CREDENTIALS_KEY_FILE, } from './default-store.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { loadSandboxConfigFromProject } from '../core/platform-sandbox.js';
|
|
12
12
|
import { createRunner, runSpellFromContent } from './runner-factory.js';
|
|
13
|
+
import { getDefaultCredentialStore } from '../credentials/default-store.js';
|
|
13
14
|
/**
|
|
14
15
|
* Resolve sandbox config: prefer caller-supplied; fall back to auto-loading
|
|
15
16
|
* from moflo.yaml at projectRoot. Returns undefined when neither is available
|
|
@@ -36,13 +37,14 @@ export async function bridgeRunSpell(content, sourceFile, args, options = {}) {
|
|
|
36
37
|
activeSpells.set(spellId, controller);
|
|
37
38
|
try {
|
|
38
39
|
const sandboxConfig = await resolveSandbox(options.sandboxConfig, options.projectRoot);
|
|
40
|
+
const credentials = options.credentials ?? getDefaultCredentialStore();
|
|
39
41
|
const result = await runSpellFromContent(content, sourceFile, {
|
|
40
42
|
spellId,
|
|
41
43
|
args,
|
|
42
44
|
dryRun: options.dryRun,
|
|
43
45
|
signal: controller.signal,
|
|
44
46
|
memory: options.memory,
|
|
45
|
-
credentials
|
|
47
|
+
credentials,
|
|
46
48
|
...(options.projectRoot ? { projectRoot: options.projectRoot } : {}),
|
|
47
49
|
...(sandboxConfig ? { sandboxConfig } : {}),
|
|
48
50
|
});
|
|
@@ -61,7 +63,8 @@ export async function bridgeExecuteSpell(definition, args, options = {}) {
|
|
|
61
63
|
activeSpells.set(spellId, controller);
|
|
62
64
|
try {
|
|
63
65
|
const sandboxConfig = await resolveSandbox(options.sandboxConfig, options.projectRoot);
|
|
64
|
-
const
|
|
66
|
+
const credentials = options.credentials ?? getDefaultCredentialStore();
|
|
67
|
+
const runner = createRunner({ memory: options.memory, credentials });
|
|
65
68
|
return await runner.run(definition, args, {
|
|
66
69
|
spellId,
|
|
67
70
|
signal: controller.signal,
|
|
@@ -14,6 +14,7 @@ import { validateSpellDefinition } from '../schema/validator.js';
|
|
|
14
14
|
import { SpellConnectorRegistry } from '../registry/connector-registry.js';
|
|
15
15
|
import { loadSandboxConfigFromProject } from '../core/platform-sandbox.js';
|
|
16
16
|
import { errorDetail } from '../../shared/utils/error-detail.js';
|
|
17
|
+
import { getDefaultCredentialStore } from '../credentials/default-store.js';
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// Factory
|
|
19
20
|
// ============================================================================
|
|
@@ -33,7 +34,7 @@ export function createRunner(options = {}) {
|
|
|
33
34
|
if (options.stepDirs?.length) {
|
|
34
35
|
registry.loadFromDirectories(options.stepDirs);
|
|
35
36
|
}
|
|
36
|
-
const credentials = options.credentials ??
|
|
37
|
+
const credentials = options.credentials ?? getDefaultCredentialStore();
|
|
37
38
|
const memory = options.memory ?? noopMemory;
|
|
38
39
|
// Auto-register shipped connectors into the connector registry
|
|
39
40
|
const connectorRegistry = options.connectorRegistry ?? new SpellConnectorRegistry();
|
|
@@ -92,9 +93,10 @@ export async function runSpellFromContent(content, sourceFile, options = {}) {
|
|
|
92
93
|
// ============================================================================
|
|
93
94
|
// Noop Accessors (for standalone usage without full CLI context)
|
|
94
95
|
// ============================================================================
|
|
95
|
-
const noopCredentials = {
|
|
96
|
+
export const noopCredentials = {
|
|
96
97
|
async get() { return undefined; },
|
|
97
98
|
async has() { return false; },
|
|
99
|
+
async store() { },
|
|
98
100
|
};
|
|
99
101
|
export const noopMemory = {
|
|
100
102
|
async read() { return null; },
|
|
@@ -49,7 +49,7 @@ export { buildPausedState, persistPausedState, resumeSpell, cleanupStalePaused,
|
|
|
49
49
|
// ============================================================================
|
|
50
50
|
// Credential Store
|
|
51
51
|
// ============================================================================
|
|
52
|
-
export { CredentialStore, CredentialStoreError, } from './credentials/
|
|
52
|
+
export { CredentialStore, CredentialStoreError, getDefaultCredentialStore, resetDefaultCredentialStore, lockedNoopAccessor, } from './credentials/index.js';
|
|
53
53
|
// ============================================================================
|
|
54
54
|
// Spell Registry (abbreviation lookup + list/info)
|
|
55
55
|
// ============================================================================
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.19",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
82
82
|
"@typescript-eslint/parser": "^7.18.0",
|
|
83
83
|
"eslint": "^8.0.0",
|
|
84
|
-
"moflo": "^4.9.
|
|
84
|
+
"moflo": "^4.9.18",
|
|
85
85
|
"tsx": "^4.21.0",
|
|
86
86
|
"typescript": "^5.9.3",
|
|
87
87
|
"vitest": "^4.0.0"
|