@axiomatic-labs/claudeflow 2.0.26 → 2.0.28

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/bin/cli.js CHANGED
@@ -12,6 +12,16 @@ switch (command) {
12
12
  case 'version':
13
13
  require('../lib/version.js')();
14
14
  break;
15
+ case 'logout': {
16
+ const ui = require('../lib/ui.js');
17
+ const { clearCachedToken } = require('../lib/auth.js');
18
+ if (clearCachedToken()) {
19
+ ui.success('Token removed from ~/.claudeflow/config.json');
20
+ } else {
21
+ ui.info('No cached token found.');
22
+ }
23
+ break;
24
+ }
15
25
  case '--version':
16
26
  case '-v':
17
27
  console.log(`claudeflow v${require('../package.json').version}`);
@@ -25,6 +35,7 @@ switch (command) {
25
35
  console.log(` claudeflow ${ui.CYAN}init${ui.RESET} Install the latest template files`);
26
36
  console.log(` claudeflow ${ui.CYAN}update${ui.RESET} Update templates to latest version`);
27
37
  console.log(` claudeflow ${ui.CYAN}version${ui.RESET} Show version info`);
38
+ console.log(` claudeflow ${ui.CYAN}logout${ui.RESET} Remove cached GitHub token`);
28
39
  console.log('');
29
40
  console.log(` ${ui.BOLD}Options:${ui.RESET}`);
30
41
  console.log(` --version, -v Show CLI version`);
package/lib/auth.js CHANGED
@@ -1,4 +1,8 @@
1
1
  const { execSync } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const readline = require('readline');
5
+ const https = require('https');
2
6
 
3
7
  const VALID_TOKEN_PATTERNS = [
4
8
  /^ghp_[A-Za-z0-9_]+$/, // Personal access token
@@ -9,6 +13,9 @@ const VALID_TOKEN_PATTERNS = [
9
13
  /^[0-9a-f]{40}$/, // Classic 40-char hex token
10
14
  ];
11
15
 
16
+ const CONFIG_DIR = path.join(require('os').homedir(), '.claudeflow');
17
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
18
+
12
19
  function sanitizeToken(raw) {
13
20
  if (!raw || typeof raw !== 'string') return null;
14
21
  const clean = raw.replace(/[\x00-\x1F\x7F]/g, '').trim();
@@ -17,6 +24,95 @@ function sanitizeToken(raw) {
17
24
  return clean;
18
25
  }
19
26
 
27
+ function readCachedToken() {
28
+ try {
29
+ const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
30
+ return sanitizeToken(data.githubToken);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function cacheToken(token) {
37
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
38
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify({ githubToken: token }, null, 2) + '\n', {
39
+ mode: 0o600,
40
+ });
41
+ }
42
+
43
+ function clearCachedToken() {
44
+ try {
45
+ fs.unlinkSync(CONFIG_PATH);
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function validateToken(token) {
53
+ return new Promise((resolve) => {
54
+ const req = https.get(
55
+ 'https://api.github.com/repos/axiomatic-labs/axiomatic-cli',
56
+ {
57
+ headers: {
58
+ Authorization: `token ${token}`,
59
+ 'User-Agent': 'claudeflow-cli',
60
+ Accept: 'application/vnd.github.v3+json',
61
+ },
62
+ },
63
+ (res) => {
64
+ // Drain the response body so the socket can close
65
+ res.resume();
66
+ resolve(res.statusCode === 200);
67
+ }
68
+ );
69
+ req.on('error', () => resolve(false));
70
+ req.setTimeout(10000, () => {
71
+ req.destroy();
72
+ resolve(false);
73
+ });
74
+ });
75
+ }
76
+
77
+ function promptForToken() {
78
+ return new Promise((resolve) => {
79
+ const ui = require('./ui.js');
80
+
81
+ console.log('');
82
+ console.log(` ${ui.YELLOW}No GitHub token found.${ui.RESET}`);
83
+ console.log('');
84
+ console.log(` To use Claudeflow, you need a GitHub Personal Access Token`);
85
+ console.log(` with access to the ${ui.BOLD}axiomatic-labs/axiomatic-cli${ui.RESET} repository.`);
86
+ console.log('');
87
+ console.log(` Create one at: ${ui.CYAN}https://github.com/settings/tokens/new${ui.RESET}`);
88
+ console.log(` Required scope: ${ui.BOLD}repo${ui.RESET} (Full control of private repositories)`);
89
+ console.log('');
90
+
91
+ const rl = readline.createInterface({
92
+ input: process.stdin,
93
+ output: process.stdout,
94
+ });
95
+
96
+ let resolved = false;
97
+
98
+ rl.question(` Paste your token: `, (answer) => {
99
+ if (!resolved) {
100
+ resolved = true;
101
+ rl.close();
102
+ resolve(answer || null);
103
+ }
104
+ });
105
+
106
+ // Handle Ctrl+C / closed stream
107
+ rl.on('close', () => {
108
+ if (!resolved) {
109
+ resolved = true;
110
+ resolve(null);
111
+ }
112
+ });
113
+ });
114
+ }
115
+
20
116
  function getGitHubToken() {
21
117
  // Try gh CLI first
22
118
  try {
@@ -31,21 +127,49 @@ function getGitHubToken() {
31
127
  if (token) return token;
32
128
  }
33
129
 
130
+ // Try cached token
131
+ const cached = readCachedToken();
132
+ if (cached) return cached;
133
+
34
134
  return null;
35
135
  }
36
136
 
37
- function requireAuth() {
137
+ async function requireAuth() {
138
+ const ui = require('./ui.js');
139
+
140
+ // Fast path: gh CLI, env var, or cached token
38
141
  const token = getGitHubToken();
39
- if (!token) {
40
- console.error('GitHub authentication required.\n');
41
- console.error('Option 1: Install the GitHub CLI and authenticate:');
42
- console.error(' brew install gh && gh auth login\n');
43
- console.error('Option 2: Set a GitHub Personal Access Token:');
44
- console.error(' export GITHUB_TOKEN=ghp_your_token_here\n');
45
- console.error('The token needs read access to axiomatic-labs/axiomatic-cli releases.');
46
- process.exit(1);
142
+ if (token) return token;
143
+
144
+ // Interactive prompt with validation loop
145
+ for (let attempts = 0; attempts < 3; attempts++) {
146
+ const raw = await promptForToken();
147
+ if (!raw) {
148
+ // User cancelled (Ctrl+C or empty input)
149
+ ui.error('Authentication cancelled.');
150
+ process.exit(1);
151
+ }
152
+
153
+ const token = sanitizeToken(raw);
154
+ if (!token) {
155
+ ui.error('Invalid token format. Expected a GitHub PAT (ghp_..., github_pat_..., etc.).');
156
+ continue;
157
+ }
158
+
159
+ ui.step('Validating token...');
160
+ const valid = await validateToken(token);
161
+ if (!valid) {
162
+ ui.error('Token invalid or no access to the repository.');
163
+ continue;
164
+ }
165
+
166
+ cacheToken(token);
167
+ ui.success(`Token saved to ~/.claudeflow/config.json`);
168
+ return token;
47
169
  }
48
- return token;
170
+
171
+ ui.error('Too many failed attempts.');
172
+ process.exit(1);
49
173
  }
50
174
 
51
- module.exports = { getGitHubToken, requireAuth };
175
+ module.exports = { getGitHubToken, requireAuth, clearCachedToken };
package/lib/init.js CHANGED
@@ -21,7 +21,7 @@ async function run() {
21
21
  ui.banner();
22
22
 
23
23
  ui.step('Authenticating with GitHub...');
24
- const token = requireAuth();
24
+ const token = await requireAuth();
25
25
 
26
26
  ui.step('Fetching latest release...');
27
27
  const release = await getLatestRelease(token);
package/lib/update.js CHANGED
@@ -19,7 +19,7 @@ async function run() {
19
19
  ui.banner(current);
20
20
 
21
21
  ui.step('Authenticating with GitHub...');
22
- const token = requireAuth();
22
+ const token = await requireAuth();
23
23
 
24
24
  ui.step('Checking for updates...');
25
25
  const release = await getLatestRelease(token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.0.26",
3
+ "version": "2.0.28",
4
4
  "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"