claude-flow 3.32.2 → 3.32.9

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.
Files changed (50) hide show
  1. package/.claude/helpers/statusline.cjs +93 -39
  2. package/.claude-plugin/hooks/hooks.json +3 -1
  3. package/.claude-plugin/scripts/ruflo-hook.cjs +2 -2
  4. package/.claude-plugin/scripts/ruflo-hook.sh +17 -17
  5. package/package.json +9 -3
  6. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  7. package/v3/@claude-flow/cli/dist/src/auth/client.d.ts +89 -0
  8. package/v3/@claude-flow/cli/dist/src/auth/client.js +242 -0
  9. package/v3/@claude-flow/cli/dist/src/auth/constants.d.ts +7 -0
  10. package/v3/@claude-flow/cli/dist/src/auth/constants.js +7 -0
  11. package/v3/@claude-flow/cli/dist/src/auth/scopes.d.ts +14 -0
  12. package/v3/@claude-flow/cli/dist/src/auth/scopes.js +21 -0
  13. package/v3/@claude-flow/cli/dist/src/auth/security-bridge.d.ts +36 -0
  14. package/v3/@claude-flow/cli/dist/src/auth/security-bridge.js +42 -0
  15. package/v3/@claude-flow/cli/dist/src/auth/session.d.ts +20 -0
  16. package/v3/@claude-flow/cli/dist/src/auth/session.js +32 -0
  17. package/v3/@claude-flow/cli/dist/src/auth/state.d.ts +19 -0
  18. package/v3/@claude-flow/cli/dist/src/auth/state.js +53 -0
  19. package/v3/@claude-flow/cli/dist/src/auth/types.d.ts +27 -0
  20. package/v3/@claude-flow/cli/dist/src/auth/types.js +11 -0
  21. package/v3/@claude-flow/cli/dist/src/commands/auth.d.ts +15 -0
  22. package/v3/@claude-flow/cli/dist/src/commands/auth.js +244 -0
  23. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +478 -24
  24. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +43 -1
  25. package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
  26. package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.d.ts +20 -0
  27. package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.js +277 -0
  28. package/v3/@claude-flow/cli/dist/src/commands/proxy.js +97 -4
  29. package/v3/@claude-flow/cli/dist/src/funnel/message-transport.d.ts +11 -4
  30. package/v3/@claude-flow/cli/dist/src/funnel/message-transport.js +11 -4
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +11 -3
  32. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +9 -0
  33. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +13 -0
  34. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +32 -5
  35. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +72 -0
  36. package/v3/@claude-flow/cli/dist/src/proxy/install.d.ts +29 -0
  37. package/v3/@claude-flow/cli/dist/src/proxy/install.js +135 -0
  38. package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.d.ts +61 -0
  39. package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.js +249 -0
  40. package/v3/@claude-flow/cli/dist/src/proxy/paths.d.ts +34 -0
  41. package/v3/@claude-flow/cli/dist/src/proxy/paths.js +70 -0
  42. package/v3/@claude-flow/cli/dist/src/proxy/release.d.ts +47 -0
  43. package/v3/@claude-flow/cli/dist/src/proxy/release.js +138 -0
  44. package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.d.ts +5 -0
  45. package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.js +61 -0
  46. package/v3/@claude-flow/cli/dist/src/proxy/verify.d.ts +44 -0
  47. package/v3/@claude-flow/cli/dist/src/proxy/verify.js +68 -0
  48. package/v3/@claude-flow/cli/package.json +6 -4
  49. package/.claude/.proven-config-version +0 -1
  50. package/.claude/proven-config.json +0 -42
@@ -0,0 +1,244 @@
1
+ /**
2
+ * `ruflo auth` — Cognitum identity (ADR-306).
3
+ *
4
+ * `login` obtains OAuth tokens via the loopback PKCE flow (default,
5
+ * interactive desktop), the OOB manual-paste flow (`--no-browser`, or
6
+ * auto-detected headless environments), or `--token-stdin` (CI/enterprise
7
+ * automation — refuses interactively otherwise, per ADR-306). The refresh
8
+ * token goes to the OS keychain when reachable, or nowhere (session-only —
9
+ * a deliberate usability cost, not a bug) when it isn't. The access token
10
+ * itself is NEVER written to disk; see src/auth/session.ts.
11
+ */
12
+ import { output } from '../output.js';
13
+ import { isCI, isInteractive, hasConsent, recordConsent, revokeConsent } from '../funnel/index.js';
14
+ import { browserLogin, manualLogin, tokenStdinLogin, isProbablyHeadless, LoginCancelledError, LoginDeniedError, StateMismatchError, } from '../auth/client.js';
15
+ import { loadSecurityOAuth, SecurityPackageMissingError } from '../auth/security-bridge.js';
16
+ import { setProfile, removeProfile, listProfiles, clearAllProfiles, DEFAULT_PROFILE } from '../auth/state.js';
17
+ import { setSessionToken, clearSessionToken } from '../auth/session.js';
18
+ import { INITIAL_SCOPE, domainForScope } from '../auth/scopes.js';
19
+ import { KEYCHAIN_SERVICE } from '../auth/constants.js';
20
+ import { getValidAccessToken, NotLoggedInError, SessionOnlyExpiredError } from '../auth/client.js';
21
+ import { removeInjectedToken } from '../proxy/token-bridge.js';
22
+ function nowIso() {
23
+ return new Date().toISOString();
24
+ }
25
+ async function persistLogin(profileName, tokens, method) {
26
+ const expiresInSec = tokens.expires_in ?? 0;
27
+ const expiresAtMs = Date.now() + expiresInSec * 1000;
28
+ setSessionToken(profileName, tokens.access_token, expiresAtMs);
29
+ let keychainRef = null;
30
+ if (tokens.refresh_token) {
31
+ const sec = await loadSecurityOAuth();
32
+ const keychain = await sec.createKeychainAdapter();
33
+ if (await keychain.isAvailable()) {
34
+ await keychain.setSecret(KEYCHAIN_SERVICE, profileName, tokens.refresh_token);
35
+ keychainRef = profileName;
36
+ }
37
+ // else: session-only — the refresh token is simply not persisted anywhere.
38
+ }
39
+ const state = {
40
+ accountId: tokens.account_email ?? 'unknown',
41
+ scopes: [INITIAL_SCOPE],
42
+ accessTokenExpiresAt: new Date(expiresAtMs).toISOString(),
43
+ keychainRef,
44
+ profile: profileName,
45
+ loginMethod: method,
46
+ linkedAt: nowIso(),
47
+ };
48
+ setProfile(profileName, state);
49
+ recordConsent('account', true, 'auth-login');
50
+ return state;
51
+ }
52
+ function refuseNonInteractive(hasTokenStdin) {
53
+ if (hasTokenStdin)
54
+ return null;
55
+ if (isInteractive() && !isCI())
56
+ return null;
57
+ // The CLI harness only acts on exitCode — it never auto-prints
58
+ // CommandResult.message, so this path must print for itself.
59
+ const message = 'ruflo auth login refuses to run interactively in a non-TTY/CI environment. ' +
60
+ 'Use --token-stdin for automation (reads {"access_token",...} JSON from stdin).';
61
+ output.printError(message);
62
+ return { success: false, message, exitCode: 1 };
63
+ }
64
+ const loginCommand = {
65
+ name: 'login',
66
+ description: 'Sign in to Cognitum (PKCE browser flow, OOB manual flow, or --token-stdin)',
67
+ options: [
68
+ { name: 'profile', description: 'Named profile to store this login under', type: 'string', default: DEFAULT_PROFILE },
69
+ { name: 'no-browser', description: 'Force the headless OOB copy-paste flow', type: 'boolean', default: false },
70
+ { name: 'token-stdin', description: 'Read a pre-obtained token as JSON from stdin (CI/automation)', type: 'boolean', default: false },
71
+ ],
72
+ action: async (ctx) => {
73
+ const profileName = typeof ctx.flags.profile === 'string' ? ctx.flags.profile : DEFAULT_PROFILE;
74
+ // Parser camelCases kebab-case flag names — read via tokenStdin/noBrowser,
75
+ // not ['token-stdin']/['no-browser'] (see doctor.ts's fixHandles comment).
76
+ const tokenStdin = Boolean(ctx.flags.tokenStdin ?? ctx.flags['token-stdin']);
77
+ const noBrowser = Boolean(ctx.flags.noBrowser ?? ctx.flags['no-browser']);
78
+ const refusal = refuseNonInteractive(tokenStdin);
79
+ if (refusal)
80
+ return refusal;
81
+ try {
82
+ const result = tokenStdin
83
+ ? await tokenStdinLogin()
84
+ : noBrowser || isProbablyHeadless()
85
+ ? await manualLogin((line) => output.writeln(line))
86
+ : await browserLogin((line) => output.writeln(line));
87
+ const state = await persistLogin(profileName, result.tokens, result.method);
88
+ output.printSuccess(`Logged in as ${state.accountId} (profile: ${profileName})`);
89
+ output.writeln(state.keychainRef
90
+ ? ' refresh token: stored in the OS keychain'
91
+ : ' refresh token: NOT stored (no keychain backend reachable) — session-only, you will need to log in again next time');
92
+ return { success: true, data: { profile: profileName, accountId: state.accountId } };
93
+ }
94
+ catch (e) {
95
+ if (e instanceof SecurityPackageMissingError || e instanceof LoginCancelledError || e instanceof LoginDeniedError || e instanceof StateMismatchError) {
96
+ output.printError(e.message);
97
+ return { success: false, message: e.message, exitCode: 1 };
98
+ }
99
+ const message = e instanceof Error ? e.message : String(e);
100
+ output.printError('Sign-in failed', message);
101
+ return { success: false, message, exitCode: 1 };
102
+ }
103
+ },
104
+ };
105
+ const logoutCommand = {
106
+ name: 'logout',
107
+ description: 'Sign out — clears the local session, keychain entry, and account consent',
108
+ options: [
109
+ { name: 'profile', description: 'Profile to log out of', type: 'string', default: DEFAULT_PROFILE },
110
+ { name: 'all', description: 'Log out of every profile', type: 'boolean', default: false },
111
+ ],
112
+ action: async (ctx) => {
113
+ removeInjectedToken();
114
+ const all = ctx.flags.all === true;
115
+ const profileName = typeof ctx.flags.profile === 'string' ? ctx.flags.profile : DEFAULT_PROFILE;
116
+ const { profiles } = listProfiles();
117
+ if (profiles.length === 0) {
118
+ output.writeln('Nothing to log out of — no profile is signed in.');
119
+ return { success: true, data: { hadSession: false } };
120
+ }
121
+ const toClear = all ? profiles : profiles.filter((p) => p.profile === profileName);
122
+ if (toClear.length === 0) {
123
+ output.writeln(`No such profile: ${profileName}`);
124
+ return { success: false, exitCode: 1 };
125
+ }
126
+ let sec;
127
+ try {
128
+ sec = await loadSecurityOAuth();
129
+ }
130
+ catch {
131
+ sec = null; // best-effort keychain cleanup — logout must still succeed locally
132
+ }
133
+ for (const p of toClear) {
134
+ clearSessionToken(p.profile);
135
+ if (p.keychainRef && sec) {
136
+ const keychain = await sec.createKeychainAdapter();
137
+ await keychain.deleteSecret(KEYCHAIN_SERVICE, p.keychainRef).catch(() => { });
138
+ }
139
+ removeProfile(p.profile);
140
+ }
141
+ if (all) {
142
+ clearAllProfiles();
143
+ revokeConsent('account', 'auth-logout');
144
+ }
145
+ else if (listProfiles().profiles.length === 0) {
146
+ revokeConsent('account', 'auth-logout');
147
+ }
148
+ output.printSuccess(all ? 'Logged out of all profiles.' : `Logged out of profile "${profileName}".`);
149
+ output.writeln(' Note: this only forgets the local copy. Cognitum does not currently expose a token ' +
150
+ 'revocation endpoint for this flow, matching the same known limitation meta-proxy documents ' +
151
+ 'for its own logout — revoke server-side access from the Cognitum dashboard if needed.');
152
+ return { success: true, data: { hadSession: true } };
153
+ },
154
+ };
155
+ const statusCommand = {
156
+ name: 'status',
157
+ description: 'Show signed-in profile(s), scopes, and expiry',
158
+ options: [
159
+ { name: 'profile', description: 'Show only this profile', type: 'string' },
160
+ { name: 'json', description: 'Machine-readable output', type: 'boolean', default: false },
161
+ {
162
+ name: 'check',
163
+ description: 'Validate credentials now; silently refresh from the OS keychain when needed',
164
+ type: 'boolean',
165
+ default: false,
166
+ },
167
+ ],
168
+ action: async (ctx) => {
169
+ const { defaultProfile, profiles } = listProfiles();
170
+ const filterName = typeof ctx.flags.profile === 'string' ? ctx.flags.profile : undefined;
171
+ const shown = filterName ? profiles.filter((p) => p.profile === filterName) : profiles;
172
+ if (shown.length === 0) {
173
+ const message = filterName ? `No such profile: ${filterName}` : 'Not logged in. Run: ruflo auth login';
174
+ if (ctx.flags.json) {
175
+ output.printJson({ profiles: [] });
176
+ }
177
+ else {
178
+ output.writeln(message);
179
+ }
180
+ return { success: true, data: { profiles: [] } };
181
+ }
182
+ const withConsistency = shown.map((p) => {
183
+ const missingConsent = p.scopes.filter((scope) => {
184
+ const domain = domainForScope(scope);
185
+ return domain !== undefined && !hasConsent(domain);
186
+ });
187
+ return { ...p, isDefault: p.profile === defaultProfile, missingConsent };
188
+ });
189
+ const checked = await Promise.all(withConsistency.map(async (p) => {
190
+ if (!ctx.flags.check)
191
+ return { ...p, credentialStatus: 'not-checked' };
192
+ try {
193
+ await getValidAccessToken(p.profile);
194
+ return { ...p, credentialStatus: 'valid' };
195
+ }
196
+ catch (e) {
197
+ const message = e instanceof Error ? e.message : String(e);
198
+ return {
199
+ ...p,
200
+ credentialStatus: e instanceof NotLoggedInError || e instanceof SessionOnlyExpiredError
201
+ ? 'login-required'
202
+ : 'unavailable',
203
+ credentialError: message,
204
+ };
205
+ }
206
+ }));
207
+ if (ctx.flags.json) {
208
+ output.printJson({ profiles: checked });
209
+ return { success: true, data: { profiles: checked } };
210
+ }
211
+ for (const p of checked) {
212
+ output.writeln(`Profile: ${p.profile}${p.isDefault ? ' (default)' : ''}`);
213
+ output.writeln(` account: ${p.accountId}`);
214
+ output.writeln(` scopes: ${p.scopes.join(', ')}`);
215
+ output.writeln(` access token expires: ${p.accessTokenExpiresAt}`);
216
+ output.writeln(` refresh token: ${p.keychainRef ? 'in OS keychain' : 'session-only (not persisted)'}`);
217
+ if (ctx.flags.check) {
218
+ output.writeln(` credential check: ${p.credentialStatus}`);
219
+ if ('credentialError' in p && p.credentialError)
220
+ output.writeln(` ${p.credentialError}`);
221
+ }
222
+ if (p.missingConsent.length > 0) {
223
+ output.printError(` scope-vs-consent mismatch: ${p.missingConsent.join(', ')} granted without a matching consent receipt`);
224
+ }
225
+ output.writeln('');
226
+ }
227
+ return { success: true, data: { profiles: checked } };
228
+ },
229
+ };
230
+ export const authCommand = {
231
+ name: 'auth',
232
+ description: 'Cognitum identity — login, logout, status (ADR-306)',
233
+ subcommands: [loginCommand, logoutCommand, statusCommand],
234
+ examples: [
235
+ { command: 'ruflo auth login', description: 'Sign in via the browser PKCE flow' },
236
+ { command: 'ruflo auth login --no-browser', description: 'Sign in via the headless OOB copy-paste flow' },
237
+ { command: 'ruflo auth status', description: 'Show signed-in profile(s)' },
238
+ { command: 'ruflo auth status --check', description: 'Validate or silently refresh credentials now' },
239
+ { command: 'ruflo auth logout', description: 'Sign out of the default profile' },
240
+ ],
241
+ action: statusCommand.action,
242
+ };
243
+ export default authCommand;
244
+ //# sourceMappingURL=auth.js.map