phewsh 0.2.1 → 0.3.0
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/phewsh.js +50 -25
- package/commands/intent.js +10 -0
- package/commands/login.js +80 -31
- package/commands/sync.js +168 -0
- package/lib/supabase.js +80 -0
- package/package.json +2 -1
package/bin/phewsh.js
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
const args = process.argv.slice(2);
|
|
4
4
|
const command = args[0];
|
|
5
5
|
|
|
6
|
+
// ── ANSI helpers (no chalk dependency)
|
|
7
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`; // bold
|
|
8
|
+
const d = (s) => `\x1b[2m${s}\x1b[0m`; // dim
|
|
9
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`; // bright white
|
|
10
|
+
const g = (s) => `\x1b[90m${s}\x1b[0m`; // dark gray
|
|
11
|
+
|
|
12
|
+
function showBrand() {
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const hasIntent = fs.existsSync(path.join(process.cwd(), '.intent', 'vision.md'));
|
|
17
|
+
const configPath = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
18
|
+
let hint = g(' run "phewsh intent --init" to start');
|
|
19
|
+
if (hasIntent) {
|
|
20
|
+
hint = g(' .intent/ loaded · run "phewsh ai run \\"...\\"" to execute');
|
|
21
|
+
} else {
|
|
22
|
+
try {
|
|
23
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
24
|
+
if (config?.email) hint = g(` logged in as ${config.email} · run "phewsh intent --init" to start`);
|
|
25
|
+
} catch { /* no config */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(` ${d('😮\u200d💨')} ${d('🤫')}`);
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(` ${b(w('P H E W S H'))}`);
|
|
32
|
+
console.log(` ${g('Build with clarity. Execute without drift.')}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(hint);
|
|
35
|
+
console.log('');
|
|
36
|
+
}
|
|
37
|
+
|
|
6
38
|
const COMMANDS = {
|
|
7
39
|
intent: () => require('../commands/intent'),
|
|
8
40
|
login: () => require('../commands/login'),
|
|
@@ -19,28 +51,21 @@ function showVersion() {
|
|
|
19
51
|
|
|
20
52
|
function showHelp() {
|
|
21
53
|
const pkg = require('../package.json');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
phewsh login Set up identity and API key
|
|
38
|
-
phewsh intent --init Create .intent/ in any project
|
|
39
|
-
phewsh ai run "what's next?" AI with your project context
|
|
40
|
-
phewsh ai run "write a release checklist"
|
|
41
|
-
|
|
42
|
-
Learn more: https://phewsh.com
|
|
43
|
-
`);
|
|
54
|
+
showBrand();
|
|
55
|
+
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com')}\n`);
|
|
56
|
+
console.log(` ${b('Commands')}`);
|
|
57
|
+
console.log(` ${w('login')} Set up identity, API key, and cloud sync`);
|
|
58
|
+
console.log(` ${w('intent')} Structure thinking → vision, plan, next actions`);
|
|
59
|
+
console.log(` ${w('ai')} Run context-aware AI prompts (reads .intent/)`);
|
|
60
|
+
console.log(` ${w('sap')} Sustainable AI Protocol — usage and accountability`);
|
|
61
|
+
console.log(` ${w('music')} MBHD music engine`);
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(` ${b('Quick start')}`);
|
|
64
|
+
console.log(` ${g('phewsh login')} Set up identity + API key`);
|
|
65
|
+
console.log(` ${g('phewsh intent --init')} Create .intent/ in any project`);
|
|
66
|
+
console.log(` ${g('phewsh intent --sync')} Push .intent/ to cloud`);
|
|
67
|
+
console.log(` ${g('phewsh ai run "what\'s next?"')} AI with your project context`);
|
|
68
|
+
console.log('');
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
// Non-blocking update check
|
|
@@ -54,12 +79,12 @@ function checkForUpdates() {
|
|
|
54
79
|
const current = pkg.version.split('.').map(Number);
|
|
55
80
|
const isNewer = newer[0] > current[0] || newer[1] > current[1] || newer[2] > current[2];
|
|
56
81
|
if (isNewer) {
|
|
57
|
-
console.log(`\n Update available: ${pkg.version} → ${data.version}`);
|
|
58
|
-
console.log(`
|
|
82
|
+
console.log(g(`\n Update available: ${pkg.version} → ${data.version}`));
|
|
83
|
+
console.log(g(` npm install -g phewsh\n`));
|
|
59
84
|
}
|
|
60
85
|
}
|
|
61
86
|
})
|
|
62
|
-
.catch(() => {});
|
|
87
|
+
.catch(() => {});
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
package/commands/intent.js
CHANGED
|
@@ -13,6 +13,8 @@ const flags = {
|
|
|
13
13
|
status: args.includes('--status') || args.includes('-s'),
|
|
14
14
|
init: args.includes('--init') || args.includes('-i'),
|
|
15
15
|
help: args.includes('--help') || args.includes('-h'),
|
|
16
|
+
sync: args.includes('--sync'),
|
|
17
|
+
pull: args.includes('--pull'),
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
function hasExistingArtifacts() {
|
|
@@ -215,6 +217,8 @@ function showHelp() {
|
|
|
215
217
|
phewsh intent Show status (or prompt to init if new)
|
|
216
218
|
phewsh intent --init Create .intent/ with structured artifacts
|
|
217
219
|
phewsh intent --status Show artifact state and next actions
|
|
220
|
+
phewsh intent --sync Push .intent/ to cloud (requires login)
|
|
221
|
+
phewsh intent --pull Pull .intent/ from cloud
|
|
218
222
|
phewsh intent --open Open the web compass at phewsh.com/intent
|
|
219
223
|
phewsh intent --evolve Open compass to update existing artifacts
|
|
220
224
|
|
|
@@ -237,6 +241,12 @@ async function main() {
|
|
|
237
241
|
await initIntent();
|
|
238
242
|
} else if (flags.status) {
|
|
239
243
|
showStatus();
|
|
244
|
+
} else if (flags.sync) {
|
|
245
|
+
const { main: sync } = require('./sync');
|
|
246
|
+
await sync('push');
|
|
247
|
+
} else if (flags.pull) {
|
|
248
|
+
const { main: sync } = require('./sync');
|
|
249
|
+
await sync('pull');
|
|
240
250
|
} else if (flags.evolve) {
|
|
241
251
|
if (!hasExistingArtifacts()) {
|
|
242
252
|
console.log('\n No .intent/ found. Run `phewsh intent --init` first.\n');
|
package/commands/login.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
+
const { sendOtp, verifyOtp, refreshSession } = require('../lib/supabase');
|
|
6
7
|
|
|
7
8
|
const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
8
9
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
@@ -32,11 +33,11 @@ async function main() {
|
|
|
32
33
|
if (!config) {
|
|
33
34
|
console.log('\n Not logged in. Run `phewsh login` to get started.\n');
|
|
34
35
|
} else {
|
|
35
|
-
console.log('\n
|
|
36
|
-
console.log(` Email
|
|
37
|
-
console.log(`
|
|
38
|
-
console.log(`
|
|
39
|
-
console.log(`
|
|
36
|
+
console.log('\n phewsh — identity\n');
|
|
37
|
+
console.log(` Email ${config.email || '(not set)'}`);
|
|
38
|
+
console.log(` Synced ${config.supabaseUserId ? '✓ cloud sync enabled' : '✗ local only'}`);
|
|
39
|
+
console.log(` API key ${config.apiKey ? config.apiKey.slice(0, 8) + '...' : '(not set)'}`);
|
|
40
|
+
console.log(` Provider ${config.defaultProvider || 'anthropic'}`);
|
|
40
41
|
console.log('');
|
|
41
42
|
}
|
|
42
43
|
return;
|
|
@@ -45,57 +46,105 @@ async function main() {
|
|
|
45
46
|
if (args.includes('--logout')) {
|
|
46
47
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
47
48
|
fs.unlinkSync(CONFIG_PATH);
|
|
48
|
-
console.log('\n Logged out
|
|
49
|
+
console.log('\n Logged out.\n');
|
|
49
50
|
} else {
|
|
50
51
|
console.log('\n Not logged in.\n');
|
|
51
52
|
}
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (args.includes('--set-key')) {
|
|
57
|
+
const config = loadConfig() || {};
|
|
58
|
+
const { ask, close } = createPrompter();
|
|
59
|
+
const apiKey = await ask('\n Anthropic API key\n > ');
|
|
60
|
+
close();
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
saveConfig({ ...config, apiKey });
|
|
63
|
+
console.log('\n API key saved.\n');
|
|
64
|
+
}
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
// --refresh: try to refresh the Supabase token silently
|
|
69
|
+
if (args.includes('--refresh')) {
|
|
70
|
+
const config = loadConfig();
|
|
71
|
+
if (!config?.supabaseRefreshToken) {
|
|
72
|
+
console.log('\n No session to refresh. Run `phewsh login`.\n');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const session = await refreshSession(config.supabaseRefreshToken);
|
|
77
|
+
if (session?.access_token) {
|
|
78
|
+
saveConfig({
|
|
79
|
+
...config,
|
|
80
|
+
supabaseAccessToken: session.access_token,
|
|
81
|
+
supabaseRefreshToken: session.refresh_token,
|
|
82
|
+
});
|
|
83
|
+
console.log('\n Session refreshed.\n');
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error('\n Refresh failed:', err.message, '\n');
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const existing = loadConfig();
|
|
92
|
+
if (existing?.supabaseUserId) {
|
|
93
|
+
console.log(`\n Already logged in as ${existing.email || existing.supabaseUserId}`);
|
|
94
|
+
console.log(' Run `phewsh login --logout` to reset or `phewsh login --status` to view.\n');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
64
97
|
|
|
98
|
+
console.log('\n 😮\u200d💨🤫 phewsh login\n');
|
|
65
99
|
const { ask, close } = createPrompter();
|
|
66
100
|
|
|
67
101
|
const email = await ask(' Email address\n > ');
|
|
68
|
-
console.log('');
|
|
102
|
+
if (!email) { close(); console.log('\n Cancelled.\n'); return; }
|
|
103
|
+
|
|
104
|
+
console.log('\n Sending verification code...');
|
|
105
|
+
const sent = await sendOtp(email);
|
|
106
|
+
if (!sent) {
|
|
107
|
+
close();
|
|
108
|
+
console.error('\n Failed to send code. Check your email address.\n');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
69
111
|
|
|
70
|
-
console.log(
|
|
71
|
-
|
|
72
|
-
console.log(' (Leave blank to skip for now)\n');
|
|
73
|
-
const apiKey = await ask(' Anthropic API key\n > ');
|
|
112
|
+
console.log(` Check ${email} for a 6-digit code.\n`);
|
|
113
|
+
const token = await ask(' Verification code\n > ');
|
|
74
114
|
console.log('');
|
|
75
115
|
|
|
76
|
-
|
|
116
|
+
let session;
|
|
117
|
+
try {
|
|
118
|
+
session = await verifyOtp(email, token);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
close();
|
|
121
|
+
console.error('\n Verification failed:', err.message, '\n');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
77
124
|
|
|
78
|
-
|
|
125
|
+
console.log('\n To use `phewsh ai`, you need an Anthropic API key.');
|
|
126
|
+
console.log(' Get one at console.anthropic.com/settings/keys');
|
|
127
|
+
console.log(' (Leave blank to skip)\n');
|
|
128
|
+
const apiKey = await ask(' Anthropic API key (optional)\n > ');
|
|
129
|
+
close();
|
|
130
|
+
console.log('');
|
|
79
131
|
|
|
80
132
|
const config = {
|
|
81
|
-
userId,
|
|
82
|
-
email: email
|
|
83
|
-
|
|
133
|
+
userId: session.user.id,
|
|
134
|
+
email: session.user.email,
|
|
135
|
+
supabaseUserId: session.user.id,
|
|
136
|
+
supabaseAccessToken: session.access_token,
|
|
137
|
+
supabaseRefreshToken: session.refresh_token,
|
|
84
138
|
defaultProvider: 'anthropic',
|
|
85
139
|
createdAt: new Date().toISOString(),
|
|
86
140
|
};
|
|
87
|
-
|
|
88
|
-
// Clean up undefined fields
|
|
89
|
-
Object.keys(config).forEach(k => config[k] === undefined && delete config[k]);
|
|
141
|
+
if (apiKey) config.apiKey = apiKey;
|
|
90
142
|
|
|
91
143
|
saveConfig(config);
|
|
92
144
|
|
|
93
|
-
console.log(` Logged in as ${email
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} else {
|
|
97
|
-
console.log(' No API key set. Add one later with `phewsh login --set-key`.\n');
|
|
98
|
-
}
|
|
145
|
+
console.log(` ✓ Logged in as ${session.user.email}`);
|
|
146
|
+
console.log(' ✓ Cloud sync enabled — run `phewsh intent --sync` in any project\n');
|
|
147
|
+
if (!apiKey) console.log(' Add an API key any time with `phewsh login --set-key`\n');
|
|
99
148
|
}
|
|
100
149
|
|
|
101
150
|
main().catch(err => {
|
package/commands/sync.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// phewsh intent --sync
|
|
2
|
+
// Syncs .intent/ artifacts to/from Supabase — same tables used by phewsh.com/intent
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const { select, upsert, refreshSession } = require('../lib/supabase');
|
|
9
|
+
|
|
10
|
+
const CONFIG_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
11
|
+
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
12
|
+
|
|
13
|
+
const FILE_TO_KIND = { 'vision.md': 'vision', 'plan.md': 'plan', 'next.md': 'next' };
|
|
14
|
+
const KIND_TO_FILE = { vision: 'vision.md', plan: 'plan.md', next: 'next.md' };
|
|
15
|
+
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
18
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return null; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveConfig(config) {
|
|
22
|
+
const dir = path.dirname(CONFIG_PATH);
|
|
23
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function genProjectId() {
|
|
28
|
+
return `p_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getProjectName() {
|
|
32
|
+
// Try to read entity from vision.md frontmatter, fall back to directory name
|
|
33
|
+
const visionPath = path.join(INTENT_DIR, 'vision.md');
|
|
34
|
+
if (fs.existsSync(visionPath)) {
|
|
35
|
+
const content = fs.readFileSync(visionPath, 'utf-8');
|
|
36
|
+
const match = content.match(/^entity:\s*(.+)$/m);
|
|
37
|
+
if (match) return match[1].trim();
|
|
38
|
+
}
|
|
39
|
+
return path.basename(process.cwd());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function ensureValidToken(config) {
|
|
43
|
+
// Try to refresh if we have a refresh token
|
|
44
|
+
if (!config.supabaseAccessToken && config.supabaseRefreshToken) {
|
|
45
|
+
const session = await refreshSession(config.supabaseRefreshToken);
|
|
46
|
+
if (session?.access_token) {
|
|
47
|
+
config.supabaseAccessToken = session.access_token;
|
|
48
|
+
config.supabaseRefreshToken = session.refresh_token;
|
|
49
|
+
saveConfig(config);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return config.supabaseAccessToken;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function push(config, token) {
|
|
56
|
+
if (!fs.existsSync(INTENT_DIR)) {
|
|
57
|
+
console.log('\n No .intent/ found. Run `phewsh intent --init` first.\n');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const projectName = getProjectName();
|
|
62
|
+
const userId = config.supabaseUserId;
|
|
63
|
+
|
|
64
|
+
// Find or create the project
|
|
65
|
+
let project;
|
|
66
|
+
const existing = await select(
|
|
67
|
+
'projects',
|
|
68
|
+
`name=eq.${encodeURIComponent(projectName)}&user_id=eq.${userId}&select=id,name`,
|
|
69
|
+
token
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (existing.length > 0) {
|
|
73
|
+
project = existing[0];
|
|
74
|
+
} else {
|
|
75
|
+
const created = await upsert('projects', {
|
|
76
|
+
id: genProjectId(),
|
|
77
|
+
user_id: userId,
|
|
78
|
+
name: projectName,
|
|
79
|
+
archetype: 'product',
|
|
80
|
+
freeform_text: '',
|
|
81
|
+
}, token);
|
|
82
|
+
project = Array.isArray(created) ? created[0] : created;
|
|
83
|
+
console.log(` Created project: ${projectName}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Push each artifact file
|
|
87
|
+
const pushed = [];
|
|
88
|
+
for (const [file, kind] of Object.entries(FILE_TO_KIND)) {
|
|
89
|
+
const filePath = path.join(INTENT_DIR, file);
|
|
90
|
+
if (!fs.existsSync(filePath)) continue;
|
|
91
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
92
|
+
await upsert('artifacts', {
|
|
93
|
+
project_id: project.id,
|
|
94
|
+
user_id: userId,
|
|
95
|
+
kind,
|
|
96
|
+
content,
|
|
97
|
+
}, token);
|
|
98
|
+
pushed.push(file);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`\n ✓ Synced to cloud — ${projectName}`);
|
|
102
|
+
pushed.forEach(f => console.log(` ${f}`));
|
|
103
|
+
console.log('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function pull(config, token) {
|
|
107
|
+
const projectName = getProjectName();
|
|
108
|
+
const userId = config.supabaseUserId;
|
|
109
|
+
|
|
110
|
+
const projects = await select(
|
|
111
|
+
'projects',
|
|
112
|
+
`name=eq.${encodeURIComponent(projectName)}&user_id=eq.${userId}&select=id,name`,
|
|
113
|
+
token
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (projects.length === 0) {
|
|
117
|
+
console.log(`\n No cloud project found for "${projectName}".\n Push first with: phewsh intent --sync\n`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const project = projects[0];
|
|
122
|
+
const artifacts = await select(
|
|
123
|
+
'artifacts',
|
|
124
|
+
`project_id=eq.${project.id}&user_id=eq.${userId}&select=kind,content,updated_at`,
|
|
125
|
+
token
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (artifacts.length === 0) {
|
|
129
|
+
console.log('\n No artifacts found in cloud for this project.\n');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fs.mkdirSync(INTENT_DIR, { recursive: true });
|
|
134
|
+
|
|
135
|
+
const pulled = [];
|
|
136
|
+
for (const artifact of artifacts) {
|
|
137
|
+
const file = KIND_TO_FILE[artifact.kind];
|
|
138
|
+
if (!file) continue;
|
|
139
|
+
fs.writeFileSync(path.join(INTENT_DIR, file), artifact.content);
|
|
140
|
+
pulled.push(file);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(`\n ✓ Pulled from cloud — ${projectName}`);
|
|
144
|
+
pulled.forEach(f => console.log(` ${f}`));
|
|
145
|
+
console.log('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function main(direction = 'push') {
|
|
149
|
+
const config = loadConfig();
|
|
150
|
+
if (!config?.supabaseUserId) {
|
|
151
|
+
console.log('\n Not logged in. Run `phewsh login` first.\n');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const token = await ensureValidToken(config);
|
|
156
|
+
if (!token) {
|
|
157
|
+
console.log('\n Session expired. Run `phewsh login` to re-authenticate.\n');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (direction === 'pull') {
|
|
162
|
+
await pull(config, token);
|
|
163
|
+
} else {
|
|
164
|
+
await push(config, token);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { main };
|
package/lib/supabase.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Supabase REST client for the CLI — no SDK, just fetch (Node 18+ built-in)
|
|
2
|
+
|
|
3
|
+
const SUPABASE_URL = 'https://fpnpfnahwaztdlxuayyv.supabase.co';
|
|
4
|
+
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZwbnBmbmFod2F6dGRseHVheXl2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA1NDY2NTcsImV4cCI6MjA3NjEyMjY1N30.Q6mn8RIvXujBXbd10aFkeY7yGHVsAQPEHM5OzoPMsFQ';
|
|
5
|
+
|
|
6
|
+
async function req(path, options = {}, accessToken = null) {
|
|
7
|
+
const headers = {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
10
|
+
...options.headers,
|
|
11
|
+
};
|
|
12
|
+
if (accessToken) headers['Authorization'] = `Bearer ${accessToken}`;
|
|
13
|
+
|
|
14
|
+
const res = await fetch(`${SUPABASE_URL}${path}`, {
|
|
15
|
+
...options,
|
|
16
|
+
headers,
|
|
17
|
+
});
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Send magic link / OTP to email
|
|
22
|
+
async function sendOtp(email) {
|
|
23
|
+
const res = await req('/auth/v1/otp', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
body: JSON.stringify({ email, create_user: true }),
|
|
26
|
+
});
|
|
27
|
+
return res.ok;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Verify OTP token and return session
|
|
31
|
+
async function verifyOtp(email, token) {
|
|
32
|
+
const res = await req('/auth/v1/verify', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
body: JSON.stringify({ type: 'email', email, token }),
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const err = await res.json().catch(() => ({}));
|
|
38
|
+
throw new Error(err.error_description || err.msg || 'OTP verification failed');
|
|
39
|
+
}
|
|
40
|
+
return res.json(); // { access_token, refresh_token, user }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Refresh a Supabase session
|
|
44
|
+
async function refreshSession(refreshToken) {
|
|
45
|
+
const res = await req('/auth/v1/token?grant_type=refresh_token', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) return null;
|
|
50
|
+
return res.json();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// REST: select rows
|
|
54
|
+
async function select(table, query = '', accessToken) {
|
|
55
|
+
const res = await req(`/rest/v1/${table}?${query}`, {
|
|
56
|
+
method: 'GET',
|
|
57
|
+
headers: { 'Prefer': 'return=representation' },
|
|
58
|
+
}, accessToken);
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const err = await res.json().catch(() => ({}));
|
|
61
|
+
throw new Error(err.message || `SELECT ${table} failed`);
|
|
62
|
+
}
|
|
63
|
+
return res.json();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// REST: upsert (insert or update)
|
|
67
|
+
async function upsert(table, data, accessToken) {
|
|
68
|
+
const res = await req(`/rest/v1/${table}`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Prefer': 'resolution=merge-duplicates,return=representation' },
|
|
71
|
+
body: JSON.stringify(data),
|
|
72
|
+
}, accessToken);
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const err = await res.json().catch(() => ({}));
|
|
75
|
+
throw new Error(err.message || `UPSERT ${table} failed`);
|
|
76
|
+
}
|
|
77
|
+
return res.json();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { sendOtp, verifyOtp, refreshSession, select, upsert, SUPABASE_URL, SUPABASE_ANON_KEY };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phewsh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Turn intent into action. Structure your thinking, execute your next step.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"phewsh": "bin/phewsh.js"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
10
|
"commands/",
|
|
11
|
+
"lib/",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|