memoir-cli 3.2.2 → 3.3.1

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.
@@ -2,9 +2,9 @@ import chalk from 'chalk';
2
2
  import boxen from 'boxen';
3
3
  import gradient from 'gradient-string';
4
4
  import inquirer from 'inquirer';
5
- import { signIn, signUp, saveSession, getSession, logout, getSubscription } from '../cloud/auth.js';
5
+ import { signIn, signUp, saveSession, getSession, logout, getSubscription, resetPassword, deleteAccount } from '../cloud/auth.js';
6
6
 
7
- export async function loginCommand() {
7
+ export async function loginCommand(options = {}) {
8
8
  // Check if already logged in
9
9
  const existing = await getSession();
10
10
  if (existing) {
@@ -18,32 +18,44 @@ export async function loginCommand() {
18
18
  return;
19
19
  }
20
20
 
21
- console.log();
22
-
23
- const { action } = await inquirer.prompt([{
24
- type: 'list',
25
- name: 'action',
26
- message: 'Sign in or create account?',
27
- choices: [
28
- { name: 'Sign in (existing account)', value: 'signin' },
29
- { name: 'Create account', value: 'signup' },
30
- ],
31
- }]);
32
-
33
- const { email } = await inquirer.prompt([{
34
- type: 'input',
35
- name: 'email',
36
- message: 'Email:',
37
- validate: v => v.includes('@') ? true : 'Enter a valid email',
38
- }]);
39
-
40
- const { password } = await inquirer.prompt([{
41
- type: 'password',
42
- name: 'password',
43
- message: 'Password:',
44
- mask: '*',
45
- validate: v => v.length >= 6 ? true : 'Password must be at least 6 characters',
46
- }]);
21
+ let action, email, password;
22
+
23
+ // Support non-interactive login via flags
24
+ if (options.email && options.password) {
25
+ action = options.signup ? 'signup' : 'signin';
26
+ email = options.email;
27
+ password = options.password;
28
+ } else {
29
+ console.log();
30
+
31
+ const actionAnswer = await inquirer.prompt([{
32
+ type: 'list',
33
+ name: 'action',
34
+ message: 'Sign in or create account?',
35
+ choices: [
36
+ { name: 'Sign in (existing account)', value: 'signin' },
37
+ { name: 'Create account', value: 'signup' },
38
+ ],
39
+ }]);
40
+ action = actionAnswer.action;
41
+
42
+ const emailAnswer = await inquirer.prompt([{
43
+ type: 'input',
44
+ name: 'email',
45
+ message: 'Email:',
46
+ validate: v => v.includes('@') ? true : 'Enter a valid email',
47
+ }]);
48
+ email = emailAnswer.email;
49
+
50
+ const passwordAnswer = await inquirer.prompt([{
51
+ type: 'password',
52
+ name: 'password',
53
+ message: 'Password:',
54
+ mask: '*',
55
+ validate: v => v.length >= 6 ? true : 'Password must be at least 6 characters',
56
+ }]);
57
+ password = passwordAnswer.password;
58
+ }
47
59
 
48
60
  try {
49
61
  let session;
@@ -84,6 +96,34 @@ export async function loginCommand() {
84
96
  }
85
97
  }
86
98
 
99
+ export async function forgotPasswordCommand(options = {}) {
100
+ let email = options.email;
101
+
102
+ if (!email) {
103
+ const emailAnswer = await inquirer.prompt([{
104
+ type: 'input',
105
+ name: 'email',
106
+ message: 'Email:',
107
+ validate: v => v.includes('@') ? true : 'Enter a valid email',
108
+ }]);
109
+ email = emailAnswer.email;
110
+ }
111
+
112
+ try {
113
+ await resetPassword(email);
114
+ console.log('\n' + boxen(
115
+ chalk.green('✔ Password reset email sent!') + '\n\n' +
116
+ chalk.white('Check ') + chalk.cyan(email) + chalk.white(' for a reset link.'),
117
+ { padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
118
+ ) + '\n');
119
+ } catch (error) {
120
+ console.log('\n' + boxen(
121
+ chalk.red('✖ ' + error.message),
122
+ { padding: 1, borderStyle: 'round', borderColor: 'red' }
123
+ ) + '\n');
124
+ }
125
+ }
126
+
87
127
  export async function logoutCommand() {
88
128
  await logout();
89
129
  console.log('\n' + boxen(
@@ -91,3 +131,43 @@ export async function logoutCommand() {
91
131
  { padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
92
132
  ) + '\n');
93
133
  }
134
+
135
+ export async function deleteAccountCommand(options = {}) {
136
+ const session = await getSession();
137
+ if (!session) {
138
+ console.log('\n' + boxen(
139
+ chalk.red('✖ Not logged in. Run ') + chalk.cyan('memoir login') + chalk.red(' first.'),
140
+ { padding: 1, borderStyle: 'round', borderColor: 'red' }
141
+ ) + '\n');
142
+ return;
143
+ }
144
+
145
+ if (!options.confirm) {
146
+ const answer = await inquirer.prompt([{
147
+ type: 'input',
148
+ name: 'confirmation',
149
+ message: 'Type DELETE to confirm account deletion:',
150
+ }]);
151
+
152
+ if (answer.confirmation !== 'DELETE') {
153
+ console.log('\n' + chalk.gray(' Account deletion cancelled.') + '\n');
154
+ return;
155
+ }
156
+ }
157
+
158
+ try {
159
+ await deleteAccount(session);
160
+
161
+ console.log('\n' + boxen(
162
+ gradient.pastel(' memoir cloud ') + '\n\n' +
163
+ chalk.green('✔ Account deleted.') + '\n' +
164
+ chalk.gray('All backups, shared links, and data have been removed.'),
165
+ { padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
166
+ ) + '\n');
167
+ } catch (error) {
168
+ console.log('\n' + boxen(
169
+ chalk.red('✖ ' + error.message),
170
+ { padding: 1, borderStyle: 'round', borderColor: 'red' }
171
+ ) + '\n');
172
+ }
173
+ }
@@ -1,7 +1,30 @@
1
1
  import chalk from 'chalk';
2
2
  import boxen from 'boxen';
3
3
  import gradient from 'gradient-string';
4
- import { getSession, getSubscription } from '../cloud/auth.js';
4
+ import ora from 'ora';
5
+ import { getSession, getSubscription, supaFetch } from '../cloud/auth.js';
6
+ import { SUPABASE_URL } from '../cloud/constants.js';
7
+
8
+ async function createCheckoutSession(session) {
9
+ const res = await fetch(`${SUPABASE_URL}/functions/v1/stripe-checkout`, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Authorization': `Bearer ${session.access_token}`,
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ });
16
+ const data = await res.json();
17
+ if (!res.ok) throw new Error(data.error || 'Failed to create checkout session');
18
+ return data.url;
19
+ }
20
+
21
+ function openUrl(url) {
22
+ const { exec } = require('child_process');
23
+ const platform = process.platform;
24
+ if (platform === 'darwin') exec(`open "${url}"`);
25
+ else if (platform === 'win32') exec(`start "" "${url}"`);
26
+ else exec(`xdg-open "${url}"`);
27
+ }
5
28
 
6
29
  export async function upgradeCommand() {
7
30
  const session = await getSession();
@@ -23,7 +46,6 @@ export async function upgradeCommand() {
23
46
  const col2 = 22;
24
47
 
25
48
  const pad = (str, width) => {
26
- // Strip ANSI for length calculation
27
49
  const stripped = str.replace(/\u001b\[[0-9;]*m/g, '');
28
50
  const diff = width - stripped.length;
29
51
  return diff > 0 ? str + ' '.repeat(diff) : str;
@@ -75,32 +97,32 @@ export async function upgradeCommand() {
75
97
  { padding: 1, borderStyle: 'round', borderColor: 'cyan', dimBorder: true }
76
98
  ));
77
99
 
78
- // If free and logged in, open pricing page
100
+ // If free and logged in, open Stripe checkout
79
101
  if (session && currentPlan === 'free') {
80
- console.log('\n' + chalk.cyan(' Opening pricing page...') + '\n');
81
-
82
- const { exec } = await import('child_process');
83
- const url = 'https://memoir.sh/pricing';
84
-
85
- const platform = process.platform;
86
- let cmd;
87
- if (platform === 'darwin') {
88
- cmd = `open "${url}"`;
89
- } else if (platform === 'win32') {
90
- cmd = `start "${url}"`;
91
- } else {
92
- cmd = `xdg-open "${url}"`;
93
- }
102
+ const spinner = ora(chalk.cyan(' Creating checkout session...')).start();
94
103
 
95
- exec(cmd, () => {});
96
-
97
- console.log(
98
- chalk.gray(' Once you\'ve completed payment, run ') +
99
- chalk.cyan('memoir login') +
100
- chalk.gray(' to refresh your plan.') + '\n'
101
- );
104
+ try {
105
+ const url = await createCheckoutSession(session);
106
+ spinner.succeed(chalk.green(' Opening Stripe checkout...'));
107
+
108
+ const { exec } = await import('child_process');
109
+ const platform = process.platform;
110
+ if (platform === 'darwin') exec(`open "${url}"`);
111
+ else if (platform === 'win32') exec(`start "" "${url}"`);
112
+ else exec(`xdg-open "${url}"`);
113
+
114
+ console.log(
115
+ '\n' + chalk.gray(' Complete payment in your browser.') + '\n' +
116
+ chalk.gray(' Your plan updates automatically — run ') +
117
+ chalk.cyan('memoir upgrade') +
118
+ chalk.gray(' again to verify.') + '\n'
119
+ );
120
+ } catch (err) {
121
+ spinner.fail(chalk.red(' ' + err.message));
122
+ console.log(chalk.gray('\n Fallback: visit ') + chalk.cyan('https://memoir.sh/pricing') + '\n');
123
+ }
102
124
  } else if (!session) {
103
- console.log('\n' + chalk.gray(' Sign up at ') + chalk.cyan('memoir.sh/pricing') + chalk.gray(' or run ') + chalk.cyan('memoir login') + chalk.gray(' to get started.') + '\n');
125
+ console.log('\n' + chalk.gray(' Run ') + chalk.cyan('memoir login') + chalk.gray(' to create an account, then ') + chalk.cyan('memoir upgrade') + chalk.gray(' to subscribe.') + '\n');
104
126
  } else {
105
127
  console.log();
106
128
  }
package/GAMEPLAN.md DELETED
@@ -1,235 +0,0 @@
1
- # Memoir Business Game Plan
2
- **Date:** March 25, 2026 | **Status:** Pre-revenue | **Author:** Boardroom consensus + CEO directive
3
-
4
- ---
5
-
6
- ## Current State
7
-
8
- | Metric | Value |
9
- |--------|-------|
10
- | Version | 3.1.2 (npm) |
11
- | Revenue | $0 |
12
- | Users | ~50 npm downloads |
13
- | AI Tools Supported | 11 (Claude, Gemini, Cursor, Copilot, Windsurf, Zed, Cline, Continue, Aider, Codex, ChatGPT) |
14
- | Cloud Backend | Supabase (auth + storage + PostgreSQL) |
15
- | Landing Page | memoir.sh (Vercel, 13 blog posts, no email capture) |
16
- | Blog Posts | 13 SEO-targeted articles |
17
- | Analytics | None (CLI or website) |
18
- | Payment Processing | None |
19
- | Team Features | None |
20
- | Shareable Links | None |
21
-
22
- ---
23
-
24
- ## What's Already Built (Don't Rebuild)
25
-
26
- - **CLI core** — push, restore, snapshot, resume, migrate, diff, profiles, doctor
27
- - **Cloud sync** — Supabase auth (email/password), gzipped bundles in Storage, PostgreSQL metadata
28
- - **Free/Pro tiers** — enforced in code (Free: 3 backups, Pro: 50) — but no way to pay for Pro
29
- - **Version history** — cloud backups versioned, restore from any version
30
- - **E2E encryption** — AES-256-GCM, async scrypt (just fixed), client-side before upload
31
- - **Workspace sync** — clones git repos, bundles non-git projects, applies uncommitted patches
32
- - **Session handoff** — snapshot current session, resume on another machine
33
- - **Landing page** — memoir.sh with animated terminal demo, tool marquee, FAQ, competitor comparison
34
- - **13 SEO blog posts** — sync guides for each tool, comparisons, setup guides
35
-
36
- ---
37
-
38
- ## The Plan: 4 Phases
39
-
40
- ### Phase 1: Monetization Foundation (Week 1-2)
41
- > **Goal:** Accept money. Capture emails. Track usage.
42
-
43
- | Task | Priority | Effort | Details |
44
- |------|----------|--------|---------|
45
- | Add Stripe integration | P0 | 1 day | Connect to Pro tier ($15/mo individual, already priced in competitor comparison) |
46
- | `memoir upgrade` command | P0 | 0.5 day | Opens Stripe checkout from CLI, activates Pro |
47
- | Add pricing page to memoir.sh | P0 | 0.5 day | Free vs Pro vs Teams (coming soon) |
48
- | Add email capture / waitlist | P0 | 0.5 day | "Get notified for Teams" — collect emails on memoir.sh |
49
- | Add PostHog analytics to CLI | P1 | 0.5 day | Anonymous: commands used, tool count, machine OS, cloud vs local |
50
- | Add PostHog to memoir.sh | P1 | 0.5 day | Page views, blog reads, install clicks |
51
- | `memoir doctor` completion | P2 | 0.5 day | Finish the stubbed diagnostics command |
52
-
53
- **Phase 1 deliverable:** People can pay. We know who's using what.
54
-
55
- ---
56
-
57
- ### Phase 2: Viral Loop + Shareability (Week 3-4)
58
- > **Goal:** Every user brings one more user.
59
-
60
- | Task | Priority | Effort | Details |
61
- |------|----------|--------|---------|
62
- | Shareable context links | P0 | 2 days | `memoir share` → generates encrypted link (Supabase signed URL, 24hr expiry). Recipient runs `memoir restore --from <link>` |
63
- | Share landing page | P0 | 0.5 day | When link is opened in browser (not CLI), show "Install memoir to restore this context" with one-click copy |
64
- | Team invite flow | P1 | 1 day | `memoir team create`, `memoir team invite <email>` — shared backup namespace in Supabase |
65
- | Onboarding context | P1 | 0.5 day | `memoir push --share` generates a restore link printed to terminal. Copy-paste to Slack |
66
- | "Synced with memoir" badge | P2 | 0.5 day | Auto-append to CLAUDE.md / .cursorrules when synced — passive discovery |
67
-
68
- **Phase 2 deliverable:** Sharing is the viral loop. Every shared link = a new install prompt.
69
-
70
- ---
71
-
72
- ### Phase 3: Teams Tier (Week 5-8)
73
- > **Goal:** $29/seat/month revenue from dev teams.
74
-
75
- | Task | Priority | Effort | Details |
76
- |------|----------|--------|---------|
77
- | Organizations in Supabase | P0 | 2 days | `organizations` table, `org_members` table, role-based (admin/member) |
78
- | Shared team backups | P0 | 2 days | `memoir push --team` syncs to org namespace. All members can restore team context |
79
- | Team dashboard (web) | P1 | 3 days | memoir.sh/dashboard — see team members, backup history, storage usage |
80
- | Seat-based billing | P0 | 1 day | Stripe per-seat subscription, enforce in CLI |
81
- | Context inheritance | P1 | 1 day | `memoir restore --from <teammate>` — pull specific teammate's context with permission |
82
- | Audit log | P2 | 1 day | Who pushed, who restored, when — enterprise compliance checkbox |
83
- | SSO (Google/GitHub) | P2 | 1 day | Enterprise teams expect OAuth, not email/password |
84
-
85
- **Phase 3 deliverable:** Teams can buy seats, share context, see a dashboard.
86
-
87
- ---
88
-
89
- ### Phase 4: Enterprise + Moat (Week 9-12)
90
- > **Goal:** First $5k MRR. Lock-in through history depth.
91
-
92
- | Task | Priority | Effort | Details |
93
- |------|----------|--------|---------|
94
- | Context time-travel | P1 | 3 days | `memoir restore --version 5 --tool claude` — restore any tool to any point in history |
95
- | Diff between versions | P1 | 1 day | `memoir diff v3 v7` — show what changed in AI context between versions |
96
- | Context quality scoring | P2 | 2 days | Auto-tag backups: files changed, decisions made, session length. Surface "important" snapshots |
97
- | Enterprise pricing page | P1 | 0.5 day | $99/seat/month with SLA, priority support, SSO, audit logs |
98
- | SOC2 narrative | P2 | 1 day | Document E2E encryption, zero-knowledge architecture, audit trail for compliance conversations |
99
- | API for integrations | P2 | 3 days | REST API: programmatic backup/restore for CI/CD pipelines, onboarding scripts |
100
-
101
- **Phase 4 deliverable:** Enterprise-ready product with deep history moat.
102
-
103
- ---
104
-
105
- ## Pricing Structure
106
-
107
- | Tier | Price | Limits | Target |
108
- |------|-------|--------|--------|
109
- | **Free** | $0/forever | 3 cloud backups, local unlimited, 1 machine | Solo devs trying it out |
110
- | **Pro** | $15/month | 50 cloud backups, unlimited machines, version history | Power users, multi-machine devs |
111
- | **Teams** | $29/seat/month | Shared team context, dashboard, audit log, 200 backups/team | Dev teams (5-20 people) |
112
- | **Enterprise** | $99/seat/month | SSO, SLA, API access, compliance docs, unlimited backups | Companies (20+) |
113
-
114
- ---
115
-
116
- ## Positioning
117
-
118
- | Audience | Message |
119
- |----------|---------|
120
- | Individual dev | "Never lose your AI context again" |
121
- | Dev team lead | "Onboard devs in 60 seconds with full AI context" |
122
- | Enterprise buyer | "SOC2-ready AI workflow continuity" |
123
- | SEO / content | "Git for your AI setup" |
124
-
125
- **One-liner:** memoir syncs your AI memory across every machine and every teammate.
126
-
127
- **Anti-positioning (what we're NOT):** Not a dotfiles manager. Not VS Code Sync. Not a cloud IDE. We sync the AI layer — the conversations, decisions, and context that make your tools smart.
128
-
129
- ---
130
-
131
- ## First 100 Users Plan
132
-
133
- | Week | Action | Target |
134
- |------|--------|--------|
135
- | 1 | Ship Stripe + pricing page | Accept payments |
136
- | 2 | Post on r/programming, r/neovim, r/cursor, Hacker News | 500 page views |
137
- | 3 | Cold DM 50 developers who tweet about Claude/Cursor context loss | 10 installs |
138
- | 4 | Ship shareable links, post demo video on X/Twitter | 20 shares |
139
- | 5-6 | Reach out to 10 companies using Cursor (check their GitHub for .cursorrules) | 3 team pilots |
140
- | 7-8 | Ship Teams tier, convert pilots to paid | First $290 MRR |
141
- | 9-12 | Content marketing: "How Company X onboards devs in 60s" case study | 100 users, $1k+ MRR |
142
-
143
- ---
144
-
145
- ## Supabase Schema Changes Needed
146
-
147
- ```sql
148
- -- Organizations
149
- CREATE TABLE organizations (
150
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
151
- name TEXT NOT NULL,
152
- slug TEXT UNIQUE NOT NULL,
153
- owner_id UUID REFERENCES auth.users(id),
154
- plan TEXT DEFAULT 'teams',
155
- stripe_customer_id TEXT,
156
- stripe_subscription_id TEXT,
157
- created_at TIMESTAMPTZ DEFAULT now()
158
- );
159
-
160
- -- Org members
161
- CREATE TABLE org_members (
162
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
163
- org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
164
- user_id UUID REFERENCES auth.users(id),
165
- role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'member')),
166
- invited_by UUID REFERENCES auth.users(id),
167
- joined_at TIMESTAMPTZ DEFAULT now(),
168
- UNIQUE(org_id, user_id)
169
- );
170
-
171
- -- Shared links
172
- CREATE TABLE shared_links (
173
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
174
- backup_id UUID REFERENCES backups(id),
175
- created_by UUID REFERENCES auth.users(id),
176
- token TEXT UNIQUE NOT NULL,
177
- expires_at TIMESTAMPTZ NOT NULL,
178
- max_uses INT DEFAULT 1,
179
- use_count INT DEFAULT 0,
180
- created_at TIMESTAMPTZ DEFAULT now()
181
- );
182
-
183
- -- Add org_id to backups for team backups
184
- ALTER TABLE backups ADD COLUMN org_id UUID REFERENCES organizations(id);
185
-
186
- -- Add stripe fields to subscriptions
187
- ALTER TABLE subscriptions ADD COLUMN stripe_customer_id TEXT;
188
- ALTER TABLE subscriptions ADD COLUMN stripe_subscription_id TEXT;
189
- ALTER TABLE subscriptions ADD COLUMN seats INT DEFAULT 1;
190
- ```
191
-
192
- ---
193
-
194
- ## New CLI Commands Summary
195
-
196
- ```
197
- memoir upgrade # Open Stripe checkout, activate Pro
198
- memoir share # Generate encrypted shareable link
199
- memoir team create # Create a team organization
200
- memoir team invite # Invite teammate by email
201
- memoir team list # List team members
202
- memoir push --team # Push to team namespace
203
- memoir restore --from # Restore from shared link or teammate
204
- memoir history --diff # Diff between backup versions
205
- memoir analytics # Show your usage stats (local)
206
- ```
207
-
208
- ---
209
-
210
- ## Risk Register
211
-
212
- | Risk | Likelihood | Impact | Mitigation |
213
- |------|-----------|--------|------------|
214
- | AI tools change config paths | High | Medium | Version-gated path detection, community PRs for new tools |
215
- | Supabase costs spike with users | Medium | Medium | Gzip compression (already done), enforce tier limits, monitor |
216
- | Nobody pays for Pro | High | High | Validate with shareable links first (free viral), watch conversion |
217
- | Enterprise competitor (JetBrains, GitHub) builds this | Medium | High | Move fast, own the CLI mindshare, community moat |
218
- | ToS violations syncing AI configs | Low | High | Only sync user-owned files (configs, not conversations). Document clearly |
219
- | Data breach of cloud backups | Low | Critical | E2E encryption means we literally can't read user data. Zero-knowledge by design |
220
-
221
- ---
222
-
223
- ## This Week's Checklist
224
-
225
- - [ ] Stripe account setup + integration in CLI
226
- - [ ] `memoir upgrade` command
227
- - [ ] Pricing page on memoir.sh
228
- - [ ] Email capture for Teams waitlist on memoir.sh
229
- - [ ] PostHog analytics on CLI (anonymous, opt-out available)
230
- - [ ] PostHog analytics on memoir.sh
231
- - [ ] Post in 3 subreddits about memoir
232
-
233
- ---
234
-
235
- *"The CLI is the funnel. Cloud is the product. Teams is the business."*