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.
@@ -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: options.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 runner = createRunner({ memory: options.memory, credentials: options.credentials });
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 ?? noopCredentials;
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/credential-store.js';
52
+ export { CredentialStore, CredentialStoreError, getDefaultCredentialStore, resetDefaultCredentialStore, lockedNoopAccessor, } from './credentials/index.js';
53
53
  // ============================================================================
54
54
  // Spell Registry (abbreviation lookup + list/info)
55
55
  // ============================================================================
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.9.17';
5
+ export const VERSION = '4.9.19';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.9.17",
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.16",
84
+ "moflo": "^4.9.18",
85
85
  "tsx": "^4.21.0",
86
86
  "typescript": "^5.9.3",
87
87
  "vitest": "^4.0.0"