add auto updates (#172)
This commit is contained in:
22
README.md
22
README.md
@@ -393,6 +393,28 @@ Permissions are also configured in `.letta/settings.json`:
|
||||
}
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
Letta Code automatically checks for updates on startup and installs them in the background.
|
||||
|
||||
### Auto updates
|
||||
|
||||
* **Update checks**: Performed on startup
|
||||
* **Update process**: Downloads and installs automatically in the background
|
||||
* **Applying updates**: Updates take effect the next time you start Letta Code
|
||||
|
||||
**Disable auto-updates:**
|
||||
Set the `DISABLE_AUTOUPDATER` environment variable in your shell:
|
||||
```bash
|
||||
export DISABLE_AUTOUPDATER=1
|
||||
```
|
||||
|
||||
### Update manually
|
||||
|
||||
```bash
|
||||
letta update
|
||||
```
|
||||
|
||||
## Self-hosting
|
||||
|
||||
To use Letta Code with a self-hosted server, set `LETTA_BASE_URL` to your server IP, e.g. `export LETTA_BASE_URL="http://localhost:8283"`.
|
||||
|
||||
19
src/index.ts
19
src/index.ts
@@ -26,6 +26,9 @@ USAGE
|
||||
# headless
|
||||
letta -p "..." One-off prompt in headless mode (no TTY UI)
|
||||
|
||||
# maintenance
|
||||
letta update Manually check for updates and install if available
|
||||
|
||||
OPTIONS
|
||||
-h, --help Show this help and exit
|
||||
-v, --version Print version and exit
|
||||
@@ -105,6 +108,12 @@ async function main() {
|
||||
await settingsManager.initialize();
|
||||
const settings = settingsManager.getSettings();
|
||||
|
||||
// Check for updates on startup (non-blocking)
|
||||
const { checkAndAutoUpdate } = await import("./updater/auto-update");
|
||||
checkAndAutoUpdate().catch(() => {
|
||||
// Silently ignore update failures
|
||||
});
|
||||
|
||||
// set LETTA_API_KEY from environment if available
|
||||
if (process.env.LETTA_API_KEY && !settings.env?.LETTA_API_KEY) {
|
||||
settings.env = settings.env || {};
|
||||
@@ -165,7 +174,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// Check for subcommands
|
||||
const _command = positionals[2]; // First positional after node and script
|
||||
const command = positionals[2]; // First positional after node and script
|
||||
|
||||
// Handle help flag first
|
||||
if (values.help) {
|
||||
@@ -180,6 +189,14 @@ async function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle update command
|
||||
if (command === "update") {
|
||||
const { manualUpdate } = await import("./updater/auto-update");
|
||||
const result = await manualUpdate();
|
||||
console.log(result.message);
|
||||
process.exit(result.success ? 0 : 1);
|
||||
}
|
||||
|
||||
const shouldContinue = (values.continue as boolean | undefined) ?? false;
|
||||
const forceNew = (values.new as boolean | undefined) ?? false;
|
||||
const freshBlocks = (values["fresh-blocks"] as boolean | undefined) ?? false;
|
||||
|
||||
115
src/updater/auto-update.ts
Normal file
115
src/updater/auto-update.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { getVersion } from "../version";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface UpdateCheckResult {
|
||||
updateAvailable: boolean;
|
||||
latestVersion?: string;
|
||||
currentVersion: string;
|
||||
}
|
||||
|
||||
function isAutoUpdateEnabled(): boolean {
|
||||
return process.env.DISABLE_AUTOUPDATER !== "1";
|
||||
}
|
||||
|
||||
function isRunningLocally(): boolean {
|
||||
const argv = process.argv[1] || "";
|
||||
|
||||
// If running from node_modules, it's npm installed (should auto-update)
|
||||
// Otherwise it's local dev (source or built locally)
|
||||
return !argv.includes("node_modules");
|
||||
}
|
||||
|
||||
async function checkForUpdate(): Promise<UpdateCheckResult> {
|
||||
const currentVersion = getVersion();
|
||||
|
||||
try {
|
||||
const { stdout } = await execAsync(
|
||||
"npm view @letta-ai/letta-code version",
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
const latestVersion = stdout.trim();
|
||||
|
||||
if (latestVersion !== currentVersion) {
|
||||
return {
|
||||
updateAvailable: true,
|
||||
latestVersion,
|
||||
currentVersion,
|
||||
};
|
||||
}
|
||||
} catch (_error) {
|
||||
// Silently fail
|
||||
}
|
||||
|
||||
return {
|
||||
updateAvailable: false,
|
||||
currentVersion,
|
||||
};
|
||||
}
|
||||
|
||||
async function performUpdate(): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
await execAsync("npm install -g @letta-ai/letta-code@latest", {
|
||||
timeout: 60000,
|
||||
});
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAndAutoUpdate() {
|
||||
if (!isAutoUpdateEnabled() || isRunningLocally()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await checkForUpdate();
|
||||
|
||||
if (result.updateAvailable) {
|
||||
await performUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export async function manualUpdate(): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}> {
|
||||
if (isRunningLocally()) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Manual updates are disabled in development mode",
|
||||
};
|
||||
}
|
||||
|
||||
const result = await checkForUpdate();
|
||||
|
||||
if (!result.updateAvailable) {
|
||||
return {
|
||||
success: true,
|
||||
message: `Already on latest version (${result.currentVersion})`,
|
||||
};
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Updating from ${result.currentVersion} to ${result.latestVersion}...`,
|
||||
);
|
||||
|
||||
const updateResult = await performUpdate();
|
||||
|
||||
if (updateResult.success) {
|
||||
return {
|
||||
success: true,
|
||||
message: `Updated to ${result.latestVersion}. Restart Letta Code to use the new version.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: `Update failed: ${updateResult.error}`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user