freeturtle 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -2,15 +2,58 @@
2
2
 
3
3
  An open-source framework for deploying autonomous AI CEOs that run onchain businesses.
4
4
 
5
- ## Quick Start
5
+ ## Getting Started
6
+
7
+ ### 1. Set Up a Server
8
+
9
+ You need a Linux server that runs 24/7. We recommend [Oracle Cloud's free ARM instance](docs/oracle-cloud-setup.md) — 2 CPUs, 12 GB RAM, always free.
10
+
11
+ > **New to servers?** The [Oracle Cloud setup guide](docs/oracle-cloud-setup.md) walks through everything from account creation to SSH. Paste it into an AI chat (ChatGPT, Claude, etc.) and ask it to guide you step by step.
12
+
13
+ ### 2. Create Accounts for Your CEO
14
+
15
+ Before running init, create a separate identity for your CEO:
16
+
17
+ - **Google account** — use it to sign up for everything below
18
+ - **Farcaster** — the account your CEO will post from
19
+ - **Neynar** — API access for Farcaster (sign up at [dev.neynar.com](https://dev.neynar.com))
20
+ - **GitHub** (optional) — if your CEO will manage repos, give it its own account
21
+
22
+ The CEO is effectively a team member who needs its own accounts.
23
+
24
+ ### 3. Install and Run
6
25
 
7
26
  ```bash
8
- pnpm install -g freeturtle
27
+ # Install Node.js and pnpm (if not already installed)
28
+ curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
29
+ sudo apt-get install -y nodejs
30
+ sudo npm install -g pnpm
31
+
32
+ # Install FreeTurtle
33
+ sudo pnpm install -g freeturtle
34
+
35
+ # Set up your CEO (interactive wizard)
9
36
  freeturtle init
37
+
38
+ # Start the daemon
10
39
  freeturtle start
40
+
41
+ # Keep it running after reboot (Linux)
42
+ freeturtle install-service
11
43
  ```
12
44
 
13
- The setup wizard walks you through everything: naming your AI CEO, connecting Farcaster, Telegram, GitHub, a database, and onchain data.
45
+ The setup wizard walks you through naming your AI CEO, picking an LLM provider, connecting Farcaster, Telegram, GitHub, and more.
46
+
47
+ ### 4. Set Up Webhooks (optional)
48
+
49
+ If you want your CEO to auto-respond to Farcaster mentions and replies, you need webhooks. This requires HTTPS, which means a domain and a reverse proxy.
50
+
51
+ The quickest path:
52
+ 1. Get a free subdomain at [duckdns.org](https://www.duckdns.org)
53
+ 2. Install Caddy (`sudo apt install -y caddy`) — it handles HTTPS automatically
54
+ 3. Run `freeturtle webhooks` to register with Neynar
55
+
56
+ See the [Oracle Cloud setup guide](docs/oracle-cloud-setup.md#setting-up-webhooks-farcaster-mentionsreplies) for full instructions.
14
57
 
15
58
  ## What It Does
16
59
 
@@ -35,7 +78,7 @@ FreeTurtle is a Node.js daemon that mostly sleeps and wakes up when:
35
78
  3. The **founder sends a message** via Terminal or Telegram
36
79
  4. A **webhook event** arrives (e.g. someone mentions the CEO on Farcaster)
37
80
 
38
- All three route to the same **task runner**, which:
81
+ All four route to the same **task runner**, which:
39
82
 
40
83
  1. Loads `soul.md` (the CEO's identity and voice)
41
84
  2. Loads recent memory (posting log, post queue)
@@ -80,6 +123,24 @@ All three route to the same **task runner**, which:
80
123
  └──────────────────────────────────────────────────────┘
81
124
  ```
82
125
 
126
+ ## CLI Commands
127
+
128
+ ```bash
129
+ freeturtle init # Set up a new AI CEO
130
+ freeturtle start # Start the daemon
131
+ freeturtle start --chat # Start with interactive terminal chat
132
+ freeturtle status # Show daemon status
133
+ freeturtle send "message" # Send a message to the running CEO
134
+ freeturtle setup # Reconfigure LLM provider
135
+ freeturtle connect farcaster # Set up Farcaster signer
136
+ freeturtle webhooks # Set up Neynar webhooks (mentions, replies, watched users/channels)
137
+ freeturtle approvals # List pending approval requests
138
+ freeturtle approve <id> # Approve a pending action
139
+ freeturtle reject <id> # Reject a pending action
140
+ freeturtle update # Update to the latest version
141
+ freeturtle install-service # Install as a systemd service (Linux)
142
+ ```
143
+
83
144
  ## Modules
84
145
 
85
146
  ### Workspace (always loaded)
@@ -189,7 +250,7 @@ Controls the daemon. Markdown format:
189
250
  ```markdown
190
251
  ## LLM
191
252
  - provider: claude_api
192
- - model: claude-sonnet-4-5-20250514
253
+ - model: claude-sonnet-4-5
193
254
  - max_tokens: 4096
194
255
 
195
256
  ## Cron
@@ -236,46 +297,19 @@ DATABASE_URL=postgres://...
236
297
  RPC_URL=https://mainnet.base.org
237
298
  ```
238
299
 
239
- ## CLI Commands
240
-
241
- ```bash
242
- freeturtle init # Set up a new AI CEO
243
- freeturtle start # Start the daemon
244
- freeturtle start --chat # Start with interactive terminal chat
245
- freeturtle status # Show daemon status
246
- freeturtle send "message" # Send a message to the running CEO
247
- freeturtle setup # Reconfigure LLM provider
248
- freeturtle connect farcaster # Set up Farcaster signer
249
- freeturtle webhooks # Set up Neynar webhooks (mentions, replies, watched users/channels)
250
- freeturtle approvals # List pending approval requests
251
- freeturtle approve <id> # Approve a pending action
252
- freeturtle reject <id> # Reject a pending action
253
- freeturtle update # Update to the latest version
254
- freeturtle install-service # Install as a systemd service (Linux)
255
- ```
256
-
257
- ## Hosting
258
-
259
- We recommend [Oracle Cloud's free ARM instance](docs/oracle-cloud-setup.md) — 4 CPUs, 24 GB RAM, always free. The setup guide walks through account creation, instance setup, networking, and installing FreeTurtle as a system service.
260
-
261
- ```bash
262
- # On your server
263
- pnpm install -g freeturtle
264
- freeturtle init
265
- freeturtle start
266
- freeturtle install-service # auto-restart on reboot
267
- ```
300
+ ## Self-Modification
268
301
 
269
- ## Before You Begin
302
+ FreeTurtle CEOs can modify their own behavior at runtime. Everything that defines the CEO — identity, voice, goals, config, memory — is a file in the workspace, and the CEO has tools to read and write those files.
270
303
 
271
- **Create a separate account for your CEO.** Start with a Google account, then use it to sign up for:
304
+ Examples of what you can tell your CEO:
272
305
 
273
- - Farcaster (the account your CEO will post from)
274
- - Neynar (API access for Farcaster)
275
- - GitHub (if your CEO will manage repos)
276
- - Any other services your CEO needs
306
+ - **"Be more direct and honest"** — CEO edits the Voice section of `soul.md` (requires your approval)
307
+ - **"Remember that @rish posts interesting stuff"** — CEO writes a note to `workspace/memory/notes.md`
308
+ - **"Change posting to every 4 hours"** — CEO edits the cron schedule in `config.md` (requires approval, takes effect on restart)
309
+ - **"Add a goal about growing the Discord"** — CEO edits the Goals section of `soul.md` (requires approval)
310
+ - **"Write a brief on this week's engagement"** — CEO writes to `workspace/strategy/`
277
311
 
278
- The CEO is effectively a team member who needs its own accounts. Identity separation keeps things clean.
312
+ Changes to core files (`soul.md`, `config.md`, `.env`) always require founder approval. Memory and notes writes go through freely.
279
313
 
280
314
  ## Policy & Approvals
281
315
 
@@ -305,9 +339,9 @@ Add a `## Policy` section to `config.md`:
305
339
  ```
306
340
 
307
341
  **Allowlist rules:**
308
- - Not set allow all (no restriction)
309
- - Empty list deny everything
310
- - Populated list only allow listed values
342
+ - Not set allow all (no restriction)
343
+ - Empty list deny everything
344
+ - Populated list only allow listed values
311
345
 
312
346
  ### Approval Flow
313
347
 
@@ -392,20 +426,6 @@ Your `.env` file contains API keys and tokens. FreeTurtle automatically sets it
392
426
 
393
427
  Your cloud provider (Oracle, AWS, GCP) has full access to the underlying infrastructure — they can technically read any file on your VM. This is true of all cloud computing and is covered by their terms of service. For most use cases this is fine. If this is unacceptable for your threat model, run FreeTurtle on hardware you physically control.
394
428
 
395
- ## Self-Modification
396
-
397
- FreeTurtle CEOs can modify their own behavior at runtime. Everything that defines the CEO — identity, voice, goals, config, memory — is a file in the workspace, and the CEO has tools to read and write those files.
398
-
399
- Examples of what you can tell your CEO:
400
-
401
- - **"Be more direct and honest"** → CEO edits the Voice section of `soul.md` (requires your approval)
402
- - **"Remember that @rish posts interesting stuff"** → CEO writes a note to `workspace/memory/notes.md`
403
- - **"Change posting to every 4 hours"** → CEO edits the cron schedule in `config.md` (requires approval, takes effect on restart)
404
- - **"Add a goal about growing the Discord"** → CEO edits the Goals section of `soul.md` (requires approval)
405
- - **"Write a brief on this week's engagement"** → CEO writes to `workspace/strategy/`
406
-
407
- Changes to core files (`soul.md`, `config.md`, `.env`) always require founder approval. Memory and notes writes go through freely.
408
-
409
429
  ## The Two-Turtle Vision (v0.2)
410
430
 
411
431
  The current v0.1 is a single-process CEO. v0.2 will split it into two:
@@ -15,14 +15,14 @@ const program = new Command();
15
15
  program
16
16
  .name("freeturtle")
17
17
  .description("An open-source framework for deploying autonomous AI CEOs that run onchain businesses.")
18
- .version("0.1.13");
18
+ .version("0.1.15");
19
19
  program
20
20
  .command("hello")
21
21
  .description("Verify the CLI is working")
22
22
  .action(() => {
23
23
  console.log(" \x1b[38;2;94;255;164m _____ ____\x1b[0m");
24
24
  console.log(" \x1b[38;2;94;255;164m/ \\ | o |\x1b[0m");
25
- console.log(" \x1b[38;2;94;255;164m| |/ ___\\|\x1b[0m FreeTurtle v0.1.13");
25
+ console.log(" \x1b[38;2;94;255;164m| |/ ___\\|\x1b[0m FreeTurtle v0.1.15");
26
26
  console.log(" \x1b[38;2;94;255;164m|_________/\x1b[0m");
27
27
  console.log(" \x1b[38;2;94;255;164m|_|_| |_|_|\x1b[0m");
28
28
  });
@@ -125,6 +125,14 @@ connect
125
125
  const { connectFarcaster } = await import("../src/cli/connect-farcaster.js");
126
126
  await connectFarcaster(opts.dir);
127
127
  });
128
+ connect
129
+ .command("gmail")
130
+ .description("Connect Gmail for reading and sending emails")
131
+ .option("--dir <path>", "Workspace directory", DEFAULT_DIR)
132
+ .action(async (opts) => {
133
+ const { connectGmail } = await import("../src/cli/connect-gmail.js");
134
+ await connectGmail(opts.dir);
135
+ });
128
136
  program
129
137
  .command("webhooks")
130
138
  .description("Set up Neynar webhooks for Farcaster mentions")
@@ -1 +1 @@
1
- {"version":3,"file":"freeturtle.js","sourceRoot":"","sources":["../../bin/freeturtle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"freeturtle.js","sourceRoot":"","sources":["../../bin/freeturtle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface GmailConnectResult {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ email: string;
5
+ }
6
+ export declare function connectGmail(dir: string): Promise<GmailConnectResult | null>;
@@ -0,0 +1,128 @@
1
+ import * as p from "@clack/prompts";
2
+ import { readFile, writeFile, chmod, mkdir } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { runGoogleOAuthFlow, createGoogleOAuth2Client, } from "../oauth/google.js";
5
+ import { GmailClient } from "../modules/gmail/client.js";
6
+ export async function connectGmail(dir) {
7
+ p.intro("Gmail Setup");
8
+ p.note([
9
+ "We recommend creating a dedicated Google account for your",
10
+ "CEO so emails come from its own identity. You can use this",
11
+ "same Google account to create a GitHub account for the CEO.",
12
+ ].join("\n"), "Recommendation");
13
+ const email = await p.text({
14
+ message: "Gmail address for the CEO",
15
+ placeholder: "ceo@gmail.com",
16
+ validate: (v) => {
17
+ if (!v?.trim())
18
+ return "Required";
19
+ if (!v.includes("@"))
20
+ return "Must be a valid email address";
21
+ return undefined;
22
+ },
23
+ });
24
+ if (p.isCancel(email)) {
25
+ p.cancel("Cancelled.");
26
+ return null;
27
+ }
28
+ p.note([
29
+ "To connect Gmail, you need a Google Cloud OAuth client:",
30
+ "",
31
+ "1. Go to console.cloud.google.com/apis/credentials",
32
+ "2. Create a project (or use an existing one)",
33
+ "3. Enable the Gmail API:",
34
+ " APIs & Services > Library > search 'Gmail API' > Enable",
35
+ "4. Create OAuth 2.0 Client ID:",
36
+ " Credentials > Create Credentials > OAuth client ID",
37
+ " Application type: Desktop app",
38
+ "5. Copy the Client ID and Client Secret",
39
+ "",
40
+ "Tip: paste these instructions into an AI chat and ask it",
41
+ "to walk you through step by step.",
42
+ ].join("\n"), "Google Cloud Setup");
43
+ const clientId = await p.text({
44
+ message: "OAuth Client ID",
45
+ validate: (v) => (v?.trim() ? undefined : "Required"),
46
+ });
47
+ if (p.isCancel(clientId)) {
48
+ p.cancel("Cancelled.");
49
+ return null;
50
+ }
51
+ const clientSecret = await p.text({
52
+ message: "OAuth Client Secret",
53
+ validate: (v) => (v?.trim() ? undefined : "Required"),
54
+ });
55
+ if (p.isCancel(clientSecret)) {
56
+ p.cancel("Cancelled.");
57
+ return null;
58
+ }
59
+ p.note("A browser window will open for Gmail authorization.\nSign in with the CEO's Google account and grant access.", "OAuth Flow");
60
+ const s = p.spinner();
61
+ s.start("Waiting for browser authorization...");
62
+ let refreshToken;
63
+ try {
64
+ refreshToken = await runGoogleOAuthFlow(clientId.trim(), clientSecret.trim());
65
+ s.stop("Authorization successful!");
66
+ }
67
+ catch (err) {
68
+ s.stop("Authorization failed");
69
+ p.log.error(`OAuth failed: ${err instanceof Error ? err.message : "Unknown error"}`);
70
+ return null;
71
+ }
72
+ // Test connection
73
+ s.start("Testing Gmail connection...");
74
+ try {
75
+ const auth = createGoogleOAuth2Client({
76
+ clientId: clientId.trim(),
77
+ clientSecret: clientSecret.trim(),
78
+ refreshToken,
79
+ });
80
+ const client = new GmailClient(auth);
81
+ const sendAs = await client.getSendAs();
82
+ const primary = sendAs.find((a) => a.isPrimary) || sendAs[0];
83
+ const displayName = primary?.displayName;
84
+ s.stop(displayName
85
+ ? `Connected as ${displayName} (${primary?.email})`
86
+ : `Connected as ${primary?.email || email}`);
87
+ }
88
+ catch (err) {
89
+ s.stop("Connection test failed");
90
+ p.log.warn(`Could not verify Gmail access: ${err instanceof Error ? err.message : "Unknown error"}`);
91
+ p.log.warn("The credentials were saved — you can test later with: freeturtle status");
92
+ }
93
+ // Save to .env
94
+ const envPath = join(dir, ".env");
95
+ let envContent = "";
96
+ try {
97
+ envContent = await readFile(envPath, "utf-8");
98
+ }
99
+ catch {
100
+ // No existing .env
101
+ }
102
+ const envVars = {
103
+ GOOGLE_CLIENT_ID: clientId.trim(),
104
+ GOOGLE_CLIENT_SECRET: clientSecret.trim(),
105
+ GOOGLE_GMAIL_REFRESH_TOKEN: refreshToken,
106
+ };
107
+ for (const [key, value] of Object.entries(envVars)) {
108
+ const regex = new RegExp(`^${key}=.*$`, "m");
109
+ if (regex.test(envContent)) {
110
+ envContent = envContent.replace(regex, `${key}=${value}`);
111
+ }
112
+ else {
113
+ envContent +=
114
+ `${envContent.endsWith("\n") || envContent === "" ? "" : "\n"}${key}=${value}\n`;
115
+ }
116
+ }
117
+ await mkdir(dirname(envPath), { recursive: true });
118
+ await writeFile(envPath, envContent, "utf-8");
119
+ await chmod(envPath, 0o600);
120
+ p.log.success("Gmail credentials saved to .env");
121
+ p.outro("Gmail connected!");
122
+ return {
123
+ clientId: clientId.trim(),
124
+ clientSecret: clientSecret.trim(),
125
+ email: email.trim(),
126
+ };
127
+ }
128
+ //# sourceMappingURL=connect-gmail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-gmail.js","sourceRoot":"","sources":["../../../src/cli/connect-gmail.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,10 @@ export declare function testGitHub(token: string): Promise<void>;
10
10
  * Test a Postgres connection URL by connecting and immediately disconnecting.
11
11
  */
12
12
  export declare function testDatabase(url: string): Promise<void>;
13
+ /**
14
+ * Test Gmail OAuth credentials by calling the profile endpoint.
15
+ */
16
+ export declare function testGmail(clientId: string, clientSecret: string, refreshToken: string): Promise<void>;
13
17
  /**
14
18
  * Test an EVM RPC URL by calling eth_blockNumber.
15
19
  */
@@ -40,6 +40,16 @@ export async function testDatabase(url) {
40
40
  await client.end().catch(() => { });
41
41
  }
42
42
  }
43
+ /**
44
+ * Test Gmail OAuth credentials by calling the profile endpoint.
45
+ */
46
+ export async function testGmail(clientId, clientSecret, refreshToken) {
47
+ const { createGoogleOAuth2Client } = await import("../oauth/google.js");
48
+ const { GmailClient } = await import("../modules/gmail/client.js");
49
+ const auth = createGoogleOAuth2Client({ clientId, clientSecret, refreshToken });
50
+ const client = new GmailClient(auth);
51
+ await client.getProfile();
52
+ }
43
53
  /**
44
54
  * Test an EVM RPC URL by calling eth_blockNumber.
45
55
  */
@@ -1 +1 @@
1
- {"version":3,"file":"connection-tests.js","sourceRoot":"","sources":["../../../src/cli/connection-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa;IAC9C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,+BAA+B,KAAK,QAAQ,CAAC,CAAC;IACtE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0C,CAAC;IACzE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,WAAW,IAAI,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAa;IAC5C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,6BAA6B,EAAE;QACrD,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,KAAK,EAAE;YAChC,MAAM,EAAE,6BAA6B;SACtC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc;IAC9C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;QAC9B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,iBAAiB;YACzB,MAAM,EAAE,EAAE;YACV,EAAE,EAAE,CAAC;SACN,CAAC;KACH,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoC,CAAC;IACnE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"connection-tests.js","sourceRoot":"","sources":["../../../src/cli/connection-tests.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { createInterface } from "node:readline";
5
5
  import { runSetup } from "../setup.js";
6
6
  import { LLMClient } from "../llm.js";
7
7
  import { connectFarcaster } from "./connect-farcaster.js";
8
+ import { connectGmail } from "./connect-gmail.js";
8
9
  import { scanForSecrets, redactSecrets, condenseDocs } from "./intake.js";
9
10
  import { testTelegram, testGitHub, testDatabase, testOnchain } from "./connection-tests.js";
10
11
  import { createWebhook, channelToUrl } from "../webhooks/neynar.js";
@@ -15,7 +16,7 @@ const TURTLE = `
15
16
  \x1b[38;2;94;255;164m|_________/\x1b[0m
16
17
  \x1b[38;2;94;255;164m|_|_| |_|_|\x1b[0m
17
18
 
18
- \x1b[1mFreeTurtle\x1b[0m \x1b[2mv0.1.13\x1b[0m
19
+ \x1b[1mFreeTurtle\x1b[0m \x1b[2mv0.1.15\x1b[0m
19
20
  `;
20
21
  const HATCH_FRAMES = [
21
22
  " 🥚",
@@ -172,6 +173,11 @@ export async function runInit(dir) {
172
173
  telegram: false,
173
174
  telegramToken: "",
174
175
  telegramOwner: "",
176
+ gmail: false,
177
+ googleClientId: "",
178
+ googleClientSecret: "",
179
+ googleRefreshToken: "",
180
+ gmailEmail: "",
175
181
  github: false,
176
182
  githubToken: "",
177
183
  database: false,
@@ -346,19 +352,42 @@ export async function runInit(dir) {
346
352
  "How it works:",
347
353
  " 1. You pick what to listen for",
348
354
  " 2. FreeTurtle runs a webhook server on your machine",
349
- " 3. Neynar sends matching events to your server",
355
+ " 3. Neynar sends matching events to your server via HTTPS",
350
356
  "",
351
- "You'll need:",
352
- " - Your server's public IP address",
353
- " Find it: run `curl ifconfig.me` on your server",
354
- " Or: Oracle Cloud Console > Compute > Instances > Public IP",
357
+ "Neynar requires an HTTPS URL, so you need a domain or",
358
+ "free subdomain with a reverse proxy. Here's the easiest setup:",
355
359
  "",
356
- " - Port 3456 open in BOTH firewalls (if on Oracle Cloud):",
360
+ "Step 1: Get a free subdomain (skip if you have a domain)",
361
+ " - Go to duckdns.org and sign in with Google/GitHub",
362
+ " - Create a subdomain (e.g. myturtle.duckdns.org)",
363
+ " - Set it to your server's public IP",
364
+ " (find your IP: run `curl ifconfig.me` on the server)",
365
+ "",
366
+ "Step 2: Open ports 80 and 443 for HTTPS",
367
+ " On Oracle Cloud (BOTH firewalls):",
357
368
  " 1. Oracle Cloud Console: Networking > VCN > Subnet >",
358
- " Security List > Add Ingress Rule (TCP port 3456)",
359
- " 2. On the server: sudo iptables -I INPUT -p tcp --dport 3456 -j ACCEPT",
369
+ " Security List > Add TWO Ingress Rules:",
370
+ " Rule 1: Source CIDR 0.0.0.0/0, TCP, Dest Port 80",
371
+ " Rule 2: Source CIDR 0.0.0.0/0, TCP, Dest Port 443",
372
+ " 2. On the server:",
373
+ " sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT",
374
+ " sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT",
375
+ "",
376
+ "Step 3: Install Caddy (auto-HTTPS reverse proxy)",
377
+ " sudo apt install -y caddy",
378
+ "",
379
+ "Step 4: Configure Caddy",
380
+ " sudo tee /etc/caddy/Caddyfile <<EOF",
381
+ " yourname.duckdns.org {",
382
+ " reverse_proxy localhost:3456",
383
+ " }",
384
+ " EOF",
385
+ " sudo systemctl restart caddy",
360
386
  "",
361
- "Your webhook URL will be: http://<YOUR_PUBLIC_IP>:3456/webhook",
387
+ "Your webhook URL will be: https://yourname.duckdns.org/webhook",
388
+ "",
389
+ "Tip: paste these instructions into an AI chat (ChatGPT, Claude,",
390
+ "etc.) and ask it to walk you through step by step.",
362
391
  ].join("\n"), "Webhooks");
363
392
  const enable = await p.confirm({
364
393
  message: "Set up webhooks?",
@@ -370,6 +399,7 @@ export async function runInit(dir) {
370
399
  return true;
371
400
  state.webhookEnabled = true;
372
401
  // What to listen for
402
+ p.log.info("Use spacebar to toggle options, enter to confirm.");
373
403
  const listeners = await p.multiselect({
374
404
  message: "What should your CEO listen for?",
375
405
  options: [
@@ -445,24 +475,16 @@ export async function runInit(dir) {
445
475
  return true;
446
476
  }
447
477
  state.webhookPort = port;
448
- p.note([
449
- "Neynar needs a public URL to send events to.",
450
- "This is your server's public IP + the port you chose + /webhook",
451
- "",
452
- "To find your public IP:",
453
- " Oracle Cloud Console: Compute > Instances > Public IP Address",
454
- " From the server: curl ifconfig.me",
455
- "",
456
- `Example: http://<YOUR_PUBLIC_IP>:${port}/webhook`,
457
- ].join("\n"), "Webhook URL");
458
478
  const url = await p.text({
459
- message: "Your server's public webhook URL",
460
- placeholder: `http://<YOUR_PUBLIC_IP>:${port}/webhook`,
479
+ message: "Your HTTPS webhook URL",
480
+ placeholder: "https://yourname.duckdns.org/webhook",
461
481
  validate: (v) => {
462
482
  if (!v?.trim())
463
483
  return "Required";
464
484
  if (!v.includes("/webhook"))
465
485
  return "URL should end with /webhook";
486
+ if (!v.startsWith("https://") && !v.startsWith("http://localhost"))
487
+ return "Neynar requires HTTPS (or http://localhost)";
466
488
  return undefined;
467
489
  },
468
490
  });
@@ -494,13 +516,7 @@ export async function runInit(dir) {
494
516
  state.webhookEnabled = false;
495
517
  }
496
518
  if (state.webhookEnabled) {
497
- p.note([
498
- "Make sure port " + state.webhookPort + " is open in BOTH firewalls (if on Oracle Cloud):",
499
- "",
500
- " 1. Oracle Cloud Console: Networking > VCN > Subnet >",
501
- " Security List > Add Ingress Rule (TCP port " + state.webhookPort + ")",
502
- " 2. On the server: sudo iptables -I INPUT -p tcp --dport " + state.webhookPort + " -j ACCEPT",
503
- ].join("\n"), "Firewall reminder");
519
+ p.log.success("Make sure Caddy is running and your domain points to this server.");
504
520
  }
505
521
  return true;
506
522
  },
@@ -539,7 +555,37 @@ export async function runInit(dir) {
539
555
  }
540
556
  return true;
541
557
  },
542
- // 9. GitHub
558
+ // Gmail
559
+ async () => {
560
+ const enable = await p.confirm({
561
+ message: "Connect Gmail? (read and send emails)",
562
+ initialValue: state.gmail,
563
+ });
564
+ if (p.isCancel(enable))
565
+ return false;
566
+ state.gmail = enable;
567
+ if (enable) {
568
+ const result = await connectGmail(dir);
569
+ if (result) {
570
+ state.googleClientId = result.clientId;
571
+ state.googleClientSecret = result.clientSecret;
572
+ state.gmailEmail = result.email;
573
+ // Read the refresh token that connectGmail saved to .env
574
+ try {
575
+ const envContent = await readFile(join(dir, ".env"), "utf-8");
576
+ const match = envContent.match(/^GOOGLE_GMAIL_REFRESH_TOKEN=(.+)$/m);
577
+ if (match)
578
+ state.googleRefreshToken = match[1];
579
+ }
580
+ catch { /* ignore */ }
581
+ }
582
+ else {
583
+ state.gmail = false;
584
+ }
585
+ }
586
+ return true;
587
+ },
588
+ // GitHub
543
589
  async () => {
544
590
  const enable = await p.confirm({
545
591
  message: "Connect GitHub? (issues and file commits)",
@@ -761,6 +807,7 @@ export async function runInit(dir) {
761
807
  const modules = [
762
808
  state.farcaster && "Farcaster",
763
809
  state.telegram && "Telegram",
810
+ state.gmail && "Gmail",
764
811
  state.github && "GitHub",
765
812
  state.database && "Database",
766
813
  state.onchain && "Onchain",
@@ -854,6 +901,9 @@ ${state.founderName}.
854
901
  "### database",
855
902
  `- enabled: ${state.database}`,
856
903
  "",
904
+ "### gmail",
905
+ `- enabled: ${state.gmail}`,
906
+ "",
857
907
  "### github",
858
908
  `- enabled: ${state.github}`,
859
909
  "",
@@ -888,6 +938,12 @@ ${state.founderName}.
888
938
  envLines.push(`TELEGRAM_BOT_TOKEN=${state.telegramToken}`);
889
939
  if (state.telegramOwner)
890
940
  envLines.push(`TELEGRAM_OWNER_ID=${state.telegramOwner}`);
941
+ if (state.googleClientId)
942
+ envLines.push(`GOOGLE_CLIENT_ID=${state.googleClientId}`);
943
+ if (state.googleClientSecret)
944
+ envLines.push(`GOOGLE_CLIENT_SECRET=${state.googleClientSecret}`);
945
+ if (state.googleRefreshToken)
946
+ envLines.push(`GOOGLE_GMAIL_REFRESH_TOKEN=${state.googleRefreshToken}`);
891
947
  if (state.githubToken)
892
948
  envLines.push(`GITHUB_TOKEN=${state.githubToken}`);
893
949
  if (state.dbUrl)