mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
feat(npx): added scaffolding for 'npx sim' command that relies on localStorage only, can be ran from anywhere with Nodejs
This commit is contained in:
@@ -16,6 +16,8 @@ Thank you for your interest in contributing to Sim Studio! Our goal is to provid
|
||||
- [Local Development Setup](#local-development-setup)
|
||||
- [License](#license)
|
||||
- [Adding New Blocks and Tools](#adding-new-blocks-and-tools)
|
||||
- [Local Storage Mode](#local-storage-mode)
|
||||
- [CLI](#cli)
|
||||
|
||||
---
|
||||
|
||||
@@ -391,4 +393,54 @@ Happy coding!
|
||||
|
||||
---
|
||||
|
||||
## Local Storage Mode
|
||||
|
||||
Sim Studio supports a local storage mode that uses the browser's localStorage instead of a database. This is particularly useful for:
|
||||
|
||||
- Quick demos and testing
|
||||
- Using the `npx sim` CLI
|
||||
- Development without setting up a database
|
||||
- Creating shareable examples
|
||||
|
||||
To enable local storage mode:
|
||||
|
||||
1. Set the environment variable: `USE_LOCAL_STORAGE=true`
|
||||
2. Start the application: `npm run dev`
|
||||
|
||||
All data will be stored in the browser's localStorage. This means:
|
||||
|
||||
- Data persists between browser sessions
|
||||
- Different browsers (Chrome vs. Firefox) will have separate data stores
|
||||
- Database migrations and schema changes won't affect local storage
|
||||
|
||||
### Developing the CLI
|
||||
|
||||
Sim Studio includes a CLI package that allows users to quickly start the application with `npx sim`. To develop the CLI:
|
||||
|
||||
1. Build the CLI: `npm run cli:build`
|
||||
2. Test the CLI: `npm run cli:start`
|
||||
3. Make changes in the `packages/@sim/cli` directory
|
||||
4. Publish new versions: `npm run cli:publish` (requires npm permissions)
|
||||
|
||||
The CLI automatically enables local storage mode when running.
|
||||
|
||||
### Building the Standalone Version
|
||||
|
||||
The `npx sim` command downloads and runs a pre-built standalone version of Sim Studio. To build this standalone version:
|
||||
|
||||
1. Run `npm run build:standalone` from the project root
|
||||
2. This creates a tarball (`sim-standalone.tar.gz`) containing:
|
||||
|
||||
- A pre-built static export of the Next.js application
|
||||
- A simple Express server to serve the static files
|
||||
- Configuration for localStorage mode
|
||||
|
||||
3. To release a new version:
|
||||
- Upload the tarball to a GitHub release
|
||||
- Update the `DOWNLOAD_URL` in `packages/@sim/cli/src/commands/start.ts`
|
||||
- Update the `STANDALONE_VERSION` constant if needed
|
||||
- Publish the CLI package: `npm run cli:publish`
|
||||
|
||||
---
|
||||
|
||||
Thank you for taking the time to contribute to Sim Studio. We truly appreciate your efforts and look forward to collaborating with you!
|
||||
|
||||
64
README.md
64
README.md
@@ -1,43 +1,65 @@
|
||||
# Sim Studio
|
||||
|
||||
[](https://x.com/simstudioai) [](https://opensource.org/licenses/MIT)
|
||||
|
||||
**Sim Studio** is a powerful, user-friendly platform that allows developers and agents to build, test, and optimize agentic workflows.
|
||||
<div align="center">
|
||||
<img src="https://imgur.com/a/QnSEYrN" alt="Sim Studio Banner" width="600px" />
|
||||
<p><b>Build, optimize, and test agent workflows with a powerful visual interface.</b></p>
|
||||
<p>
|
||||
<a href="https://github.com/simstudioai/sim/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/simstudioai/sim?style=flat-square" alt="Stars" />
|
||||
</a>
|
||||
<a href="https://github.com/simstudioai/sim/network/members">
|
||||
<img src="https://img.shields.io/github/forks/simstudioai/sim?style=flat-square" alt="Forks" />
|
||||
</a>
|
||||
<a href="https://github.com/simstudioai/sim/issues">
|
||||
<img src="https://img.shields.io/github/issues/simstudioai/sim?style=flat-square" alt="Issues" />
|
||||
</a>
|
||||
<a href="https://github.com/simstudioai/sim/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/simstudioai/sim?style=flat-square" alt="License" />
|
||||
</a>
|
||||
<a href="https://discord.gg/rTHJynCD">
|
||||
<img src="https://img.shields.io/discord/1234567890?style=flat-square&label=Discord" alt="Discord" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Run
|
||||
|
||||
1. Self-host
|
||||
2. [Join the Waitlist](https://simstudio.ai) for the cloud-hosted beta
|
||||
|
||||
## Quick Start
|
||||
## 🚀 Quick Start
|
||||
|
||||
The quickest way to get Sim Studio running locally:
|
||||
### Try Instantly with npx
|
||||
|
||||
1. **Clone the repository**
|
||||
Sim Studio now supports a quick start option with zero installation required:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/simstudioai/sim.git
|
||||
npx sim
|
||||
```
|
||||
|
||||
This downloads and runs Sim Studio with browser localStorage for data persistence. Visit http://localhost:3000 to start building workflows immediately!
|
||||
|
||||
### Docker Setup (For Development)
|
||||
|
||||
For a full development environment with database support:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/YOUR_USERNAME/sim.git
|
||||
cd sim
|
||||
```
|
||||
|
||||
2. **Start with Docker**
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Or use the convenience script for automatic setup:
|
||||
|
||||
```bash
|
||||
chmod +x start_simstudio_docker.sh
|
||||
# Start the Docker environment
|
||||
./start_simstudio_docker.sh
|
||||
```
|
||||
|
||||
This will start Sim Studio at http://localhost:3000 with a local PostgreSQL database.
|
||||
### VS Code Dev Container
|
||||
|
||||
3. **[Optional] Configure Your Environment**
|
||||
For the best development experience:
|
||||
|
||||
In `.env`, configure Authentication secrets (required for login functionality) and optionally a Resend API key (for authentication emails).
|
||||
1. Install the VS Code Remote - Containers extension
|
||||
2. Open the project in VS Code
|
||||
3. Click "Reopen in Container" when prompted
|
||||
4. Run `sim-start` in the terminal
|
||||
|
||||
## Development Options
|
||||
|
||||
|
||||
25
db/index.ts
25
db/index.ts
@@ -1,12 +1,23 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
|
||||
// In production, use the Vercel-generated POSTGRES_URL
|
||||
// In development, use the direct DATABASE_URL
|
||||
const connectionString = process.env.POSTGRES_URL || process.env.DATABASE_URL!
|
||||
// Check if we're in local storage mode (CLI usage with npx sim)
|
||||
const isLocalStorage = process.env.USE_LOCAL_STORAGE === 'true'
|
||||
|
||||
// Disable prefetch as it is not supported for "Transaction" pool mode
|
||||
const client = postgres(connectionString, {
|
||||
// Only connect to the database if we're not in local storage mode
|
||||
let db: ReturnType<typeof drizzle> | null = null
|
||||
|
||||
if (!isLocalStorage) {
|
||||
// In production, use the Vercel-generated POSTGRES_URL
|
||||
// In development, use the direct DATABASE_URL
|
||||
const connectionString = process.env.POSTGRES_URL || process.env.DATABASE_URL!
|
||||
|
||||
// Disable prefetch as it is not supported for "Transaction" pool mode
|
||||
const client = postgres(connectionString, {
|
||||
prepare: false,
|
||||
})
|
||||
export const db = drizzle(client)
|
||||
})
|
||||
db = drizzle(client)
|
||||
}
|
||||
|
||||
// Export the database client or a null value if in local storage mode
|
||||
export { db }
|
||||
|
||||
28
lib/storage.ts
Normal file
28
lib/storage.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Storage detection helper that determines if we should use local storage
|
||||
* This is used when running via the CLI with `npx sim`
|
||||
*/
|
||||
|
||||
// Check if we should use local storage based on environment variable
|
||||
export const useLocalStorage = () => {
|
||||
// In client components, check for the environment variable in localStorage
|
||||
// This is set by the CLI when running with `npx sim`
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('USE_LOCAL_STORAGE') === 'true'
|
||||
}
|
||||
|
||||
// In server components/API routes, check process.env
|
||||
return process.env.USE_LOCAL_STORAGE === 'true'
|
||||
}
|
||||
|
||||
// Export helpers for components to use
|
||||
export const storageMode = {
|
||||
get isLocal() {
|
||||
return useLocalStorage()
|
||||
},
|
||||
get isDatabase() {
|
||||
return !useLocalStorage()
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,21 @@
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
// Check if we're building for standalone distribution
|
||||
const isStandaloneBuild = process.env.USE_LOCAL_STORAGE === 'true'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
devIndicators: false,
|
||||
images: {
|
||||
domains: ['avatars.githubusercontent.com'],
|
||||
// Enable static image optimization for standalone export
|
||||
unoptimized: isStandaloneBuild,
|
||||
},
|
||||
// Use 'export' for standalone builds, 'standalone' for regular builds
|
||||
output: isStandaloneBuild ? 'export' : 'standalone',
|
||||
// Only include headers when not building for standalone export
|
||||
...(isStandaloneBuild
|
||||
? {}
|
||||
: {
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
@@ -37,7 +48,7 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
]
|
||||
},
|
||||
output: 'standalone',
|
||||
}),
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
||||
2345
package-lock.json
generated
2345
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -14,7 +14,13 @@
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage"
|
||||
"test:coverage": "jest --coverage",
|
||||
"cli:build": "npm run build -w @sim/cli",
|
||||
"cli:dev": "npm run build -w @sim/cli && cd packages/@sim/cli && node ./dist/index.js",
|
||||
"cli:publish": "cd packages/@sim/cli && npm publish --access public",
|
||||
"cli:start": "cd packages/@sim/cli && node ./dist/index.js start",
|
||||
"build:standalone": "node scripts/build-standalone.js",
|
||||
"build:cli": "npm run cli:build && npm run build:standalone"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.38.0",
|
||||
@@ -47,6 +53,7 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"next": "^15.2.0",
|
||||
"openai": "^4.85.4",
|
||||
"ora": "^8.2.0",
|
||||
"postgres": "^3.4.5",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -87,5 +94,8 @@
|
||||
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/@sim/*"
|
||||
]
|
||||
}
|
||||
|
||||
101
packages/@sim/cli/README.md
Normal file
101
packages/@sim/cli/README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Sim Studio CLI
|
||||
|
||||
The Sim Studio CLI provides a convenient way to run Sim Studio directly from your terminal without needing to set up a database or complex environment.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run Sim Studio with default settings
|
||||
npx sim
|
||||
|
||||
# Start with custom port
|
||||
npx sim start -p 8080
|
||||
|
||||
# Get help
|
||||
npx sim help
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Zero Configuration**: Get started immediately with `npx sim`
|
||||
- **Local Storage**: Works entirely in the browser, no database required
|
||||
- **Persistence**: Your workflows and data persist between sessions
|
||||
- **Familiar Experience**: All the power of Sim Studio in a simplified package
|
||||
|
||||
## Commands
|
||||
|
||||
- `sim` - Start Sim Studio with default settings
|
||||
- `sim start` - Start Sim Studio with options
|
||||
- `sim version` - Display version information
|
||||
- `sim help` - Show help and usage information
|
||||
|
||||
## Options
|
||||
|
||||
- `-p, --port <port>` - Specify port (default: 3000)
|
||||
- `-d, --debug` - Enable debug mode
|
||||
- `-v, --version` - Show version information
|
||||
- `-h, --help` - Show help information
|
||||
|
||||
## Local Storage Mode
|
||||
|
||||
When running Sim Studio via the CLI, all data is stored using the browser's localStorage. This means:
|
||||
|
||||
- Your workflows persist between browser sessions
|
||||
- No database configuration is required
|
||||
- Data is stored locally on your device
|
||||
- Multiple users can't share the same workflows (single-user mode)
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
If you need multi-user capabilities or want to store data in a database, consider:
|
||||
|
||||
1. Using the Docker setup in the main repository
|
||||
2. Setting up a full Sim Studio environment with PostgreSQL
|
||||
3. Deploying to Vercel with a database
|
||||
|
||||
## For Developers: Building & Publishing the CLI
|
||||
|
||||
### Release Checklist
|
||||
|
||||
1. ✅ Update the CLI code with your changes
|
||||
2. ✅ Bump the version in `package.json`
|
||||
3. ✅ Build the standalone version:
|
||||
```
|
||||
npm run build:cli
|
||||
```
|
||||
4. ✅ Upload the generated `sim-standalone.tar.gz` to GitHub releases
|
||||
5. ✅ Update the `DOWNLOAD_URL` constant in `packages/@sim/cli/src/commands/start.ts` to point to the new release URL
|
||||
6. ✅ Commit all changes
|
||||
7. ✅ Publish to npm:
|
||||
```
|
||||
npm run cli:publish
|
||||
```
|
||||
|
||||
### About the Standalone Version
|
||||
|
||||
The standalone version is a pre-built and bundled version of Sim Studio that can run without a database or complex setup. It includes:
|
||||
|
||||
- A pre-built static export of the Next.js application
|
||||
- A simple Express server to serve the static files
|
||||
- Configuration to use browser localStorage for data persistence
|
||||
|
||||
This allows users to quickly try Sim Studio with a simple `npx sim` command without installing anything else.
|
||||
|
||||
### Testing the CLI Locally
|
||||
|
||||
To test the CLI locally:
|
||||
|
||||
```bash
|
||||
# Build the CLI
|
||||
npm run cli:build
|
||||
|
||||
# Run the CLI directly
|
||||
npm run cli:start
|
||||
|
||||
# Or use the dev script
|
||||
npm run cli:dev
|
||||
```
|
||||
|
||||
## Need Help?
|
||||
|
||||
Visit our [documentation](https://github.com/yourusername/sim) or open an issue on GitHub.
|
||||
14
packages/@sim/cli/bin/sim.js
Executable file
14
packages/@sim/cli/bin/sim.js
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// This file is the entry point for the 'sim' command
|
||||
try {
|
||||
require('../dist/index.js')
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
console.error('Sim CLI has not been built. Please run npm run build first.')
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.error('An error occurred while starting Sim CLI:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
4
packages/@sim/cli/dist/commands/help.d.ts
vendored
Normal file
4
packages/@sim/cli/dist/commands/help.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Help command displays the logo and usage information
|
||||
*/
|
||||
export declare function help(): void;
|
||||
43
packages/@sim/cli/dist/commands/help.js
vendored
Normal file
43
packages/@sim/cli/dist/commands/help.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.help = help;
|
||||
const chalk_1 = __importDefault(require("chalk"));
|
||||
const logo_1 = require("../utils/logo");
|
||||
/**
|
||||
* Help command displays the logo and usage information
|
||||
*/
|
||||
function help() {
|
||||
// Display logo
|
||||
console.log(logo_1.logo);
|
||||
// Display help text
|
||||
console.log(`
|
||||
${chalk_1.default.bold('USAGE')}
|
||||
${chalk_1.default.cyan('sim')} Start Sim Studio with default settings
|
||||
${chalk_1.default.cyan('sim start')} Start Sim Studio with options
|
||||
${chalk_1.default.cyan('sim version')} Display version information
|
||||
${chalk_1.default.cyan('sim help')} Show this help information
|
||||
|
||||
${chalk_1.default.bold('OPTIONS')}
|
||||
${chalk_1.default.cyan('-p, --port <port>')} Specify port (default: 3000)
|
||||
${chalk_1.default.cyan('-d, --debug')} Enable debug mode
|
||||
${chalk_1.default.cyan('-v, --version')} Show version information
|
||||
${chalk_1.default.cyan('-h, --help')} Show help information
|
||||
|
||||
${chalk_1.default.bold('EXAMPLES')}
|
||||
${chalk_1.default.gray('# Start with default settings')}
|
||||
${chalk_1.default.cyan('$ sim')}
|
||||
|
||||
${chalk_1.default.gray('# Start on a specific port')}
|
||||
${chalk_1.default.cyan('$ sim start --port 8080')}
|
||||
|
||||
${chalk_1.default.gray('# Start with debug logging')}
|
||||
${chalk_1.default.cyan('$ sim start --debug')}
|
||||
|
||||
${chalk_1.default.bold('DOCUMENTATION')}
|
||||
${chalk_1.default.gray('For more information:')}
|
||||
https://github.com/simstudioai/sim
|
||||
`);
|
||||
}
|
||||
9
packages/@sim/cli/dist/commands/start.d.ts
vendored
Normal file
9
packages/@sim/cli/dist/commands/start.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
interface StartOptions {
|
||||
port: string;
|
||||
debug: boolean;
|
||||
}
|
||||
/**
|
||||
* Start command that launches Sim Studio using local storage
|
||||
*/
|
||||
export declare function start(options: StartOptions): Promise<import("child_process").ChildProcess>;
|
||||
export {};
|
||||
267
packages/@sim/cli/dist/commands/start.js
vendored
Normal file
267
packages/@sim/cli/dist/commands/start.js
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.start = start;
|
||||
const child_process_1 = require("child_process");
|
||||
const chalk_1 = __importDefault(require("chalk"));
|
||||
const config_1 = require("../utils/config");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const os_1 = __importDefault(require("os"));
|
||||
const tar_1 = require("tar");
|
||||
const https_1 = __importDefault(require("https"));
|
||||
const fs_2 = require("fs");
|
||||
const child_process_2 = require("child_process");
|
||||
// Constants for standalone app
|
||||
const SIM_HOME_DIR = path_1.default.join(os_1.default.homedir(), '.sim-studio');
|
||||
const SIM_STANDALONE_DIR = path_1.default.join(SIM_HOME_DIR, 'standalone');
|
||||
const SIM_VERSION_FILE = path_1.default.join(SIM_HOME_DIR, 'version.json');
|
||||
const DOWNLOAD_URL = 'https://github.com/simstudioai/sim/releases/download/v0.1.0/sim-standalone.tar.gz';
|
||||
const STANDALONE_VERSION = '0.1.0';
|
||||
/**
|
||||
* Start command that launches Sim Studio using local storage
|
||||
*/
|
||||
async function start(options) {
|
||||
// Update config with provided options
|
||||
config_1.config.set('port', options.port);
|
||||
config_1.config.set('debug', options.debug);
|
||||
config_1.config.set('lastRun', new Date().toISOString());
|
||||
const port = options.port || '3000';
|
||||
const debug = options.debug || false;
|
||||
// Dynamically import ora
|
||||
const oraModule = await Promise.resolve().then(() => __importStar(require('ora')));
|
||||
const ora = oraModule.default;
|
||||
// Show starting message
|
||||
const spinner = ora(`Starting Sim Studio on port ${port}...`).start();
|
||||
try {
|
||||
// Set environment variables for using local storage
|
||||
const env = {
|
||||
...process.env,
|
||||
PORT: port,
|
||||
USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage
|
||||
NODE_ENV: debug ? 'development' : 'production',
|
||||
DEBUG: debug ? '*' : undefined,
|
||||
};
|
||||
// Try to find the main package.json to determine if we're running from within the repo
|
||||
// or as an installed npm package
|
||||
const isInProjectDirectory = checkIfInProjectDirectory();
|
||||
let simProcess;
|
||||
if (isInProjectDirectory) {
|
||||
// Running from within the project directory - we'll use the existing
|
||||
// Next.js setup directly
|
||||
spinner.text = 'Detected Sim Studio project, starting with local configuration...';
|
||||
simProcess = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Running from outside the project via npx - we'll download and start a standalone version
|
||||
spinner.text = 'Setting up standalone Sim Studio...';
|
||||
// Create the .sim-studio directory if it doesn't exist
|
||||
if (!fs_1.default.existsSync(SIM_HOME_DIR)) {
|
||||
fs_1.default.mkdirSync(SIM_HOME_DIR, { recursive: true });
|
||||
}
|
||||
// Check if we already have the standalone version
|
||||
let needsDownload = true;
|
||||
if (fs_1.default.existsSync(SIM_VERSION_FILE)) {
|
||||
try {
|
||||
const versionInfo = JSON.parse(fs_1.default.readFileSync(SIM_VERSION_FILE, 'utf8'));
|
||||
if (versionInfo.version === STANDALONE_VERSION) {
|
||||
needsDownload = false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If there's an error reading the version file, download again
|
||||
needsDownload = true;
|
||||
}
|
||||
}
|
||||
// Download and extract if needed
|
||||
if (needsDownload) {
|
||||
try {
|
||||
await downloadStandaloneApp(spinner);
|
||||
}
|
||||
catch (error) {
|
||||
spinner.fail(`Failed to download Sim Studio: ${error instanceof Error ? error.message : String(error)}`);
|
||||
console.log(`\n${chalk_1.default.yellow('⚠️')} If you're having network issues, you can try:
|
||||
1. Check your internet connection
|
||||
2. Try again later
|
||||
3. Run Sim Studio directly from a cloned repository`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spinner.text = 'Using cached Sim Studio standalone version...';
|
||||
}
|
||||
// Start the standalone app
|
||||
spinner.text = 'Starting Sim Studio standalone...';
|
||||
// Make sure the standalone directory exists
|
||||
if (!fs_1.default.existsSync(SIM_STANDALONE_DIR) || !fs_1.default.existsSync(path_1.default.join(SIM_STANDALONE_DIR, 'server.js'))) {
|
||||
spinner.fail('Standalone app files are missing. Re-run to download again.');
|
||||
// Force a fresh download next time
|
||||
if (fs_1.default.existsSync(SIM_VERSION_FILE)) {
|
||||
fs_1.default.unlinkSync(SIM_VERSION_FILE);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
// Start the standalone Node.js server
|
||||
const standaloneEnv = {
|
||||
...env,
|
||||
SIM_STUDIO_PORT: port,
|
||||
};
|
||||
simProcess = (0, child_process_1.spawn)('node', ['server.js'], {
|
||||
cwd: SIM_STANDALONE_DIR,
|
||||
env: standaloneEnv,
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
// Successful start
|
||||
spinner.succeed(`Sim Studio is running on ${chalk_1.default.cyan(`http://localhost:${port}`)}`);
|
||||
console.log(`
|
||||
${chalk_1.default.green('✓')} Using local storage mode - your data will be stored in the browser
|
||||
${chalk_1.default.green('✓')} Any changes will be persisted between sessions through localStorage
|
||||
${chalk_1.default.yellow('i')} Press ${chalk_1.default.bold('Ctrl+C')} to stop the server
|
||||
`);
|
||||
// Handle process termination
|
||||
process.on('SIGINT', () => {
|
||||
console.log(`\n${chalk_1.default.yellow('⚠️')} Shutting down Sim Studio...`);
|
||||
simProcess.kill('SIGINT');
|
||||
process.exit(0);
|
||||
});
|
||||
// Return the process for testing purposes
|
||||
return simProcess;
|
||||
}
|
||||
catch (error) {
|
||||
spinner.fail('Failed to start Sim Studio');
|
||||
console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if we're running in a Sim Studio project directory
|
||||
*/
|
||||
function checkIfInProjectDirectory() {
|
||||
// Check if we have package.json that looks like a Sim Studio project
|
||||
try {
|
||||
const packageJsonPath = path_1.default.join(process.cwd(), 'package.json');
|
||||
if (fs_1.default.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
||||
// Check if it looks like our project
|
||||
if (packageJson.name === 'sim' ||
|
||||
packageJson.name === 'sim-studio' ||
|
||||
(packageJson.dependencies &&
|
||||
(packageJson.dependencies['next'] || packageJson.dependencies['@sim/cli']))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Also check for Next.js app files
|
||||
const nextConfigPath = path_1.default.join(process.cwd(), 'next.config.js');
|
||||
const nextTsConfigPath = path_1.default.join(process.cwd(), 'next.config.ts');
|
||||
if (fs_1.default.existsSync(nextConfigPath) || fs_1.default.existsSync(nextTsConfigPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we can't read/parse package.json, assume we're not in a project directory
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Downloads and extracts the standalone app
|
||||
*/
|
||||
async function downloadStandaloneApp(spinner) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create temp directory
|
||||
const tmpDir = path_1.default.join(os_1.default.tmpdir(), `sim-download-${Date.now()}`);
|
||||
fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
||||
const tarballPath = path_1.default.join(tmpDir, 'sim-standalone.tar.gz');
|
||||
const file = (0, fs_2.createWriteStream)(tarballPath);
|
||||
spinner.text = 'Downloading Sim Studio...';
|
||||
// Download the tarball
|
||||
https_1.default.get(DOWNLOAD_URL, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
spinner.fail(`Failed to download: ${response.statusCode}`);
|
||||
return reject(new Error(`Download failed with status code: ${response.statusCode}`));
|
||||
}
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
// Clear the standalone directory if it exists
|
||||
if (fs_1.default.existsSync(SIM_STANDALONE_DIR)) {
|
||||
fs_1.default.rmSync(SIM_STANDALONE_DIR, { recursive: true, force: true });
|
||||
}
|
||||
// Create the directory
|
||||
fs_1.default.mkdirSync(SIM_STANDALONE_DIR, { recursive: true });
|
||||
spinner.text = 'Extracting Sim Studio...';
|
||||
// Extract the tarball
|
||||
(0, tar_1.extract)({
|
||||
file: tarballPath,
|
||||
cwd: SIM_STANDALONE_DIR
|
||||
}).then(() => {
|
||||
// Clean up
|
||||
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
||||
// Install dependencies if needed
|
||||
if (fs_1.default.existsSync(path_1.default.join(SIM_STANDALONE_DIR, 'package.json'))) {
|
||||
spinner.text = 'Installing dependencies...';
|
||||
try {
|
||||
(0, child_process_2.execSync)('npm install --production', {
|
||||
cwd: SIM_STANDALONE_DIR,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
spinner.warn('Error installing dependencies, but trying to continue...');
|
||||
}
|
||||
}
|
||||
// Save version info
|
||||
fs_1.default.writeFileSync(SIM_VERSION_FILE, JSON.stringify({ version: STANDALONE_VERSION, installedAt: new Date().toISOString() }));
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
spinner.fail('Extraction failed');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
fs_1.default.unlink(tarballPath, () => { });
|
||||
spinner.fail('Download failed');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
4
packages/@sim/cli/dist/commands/version.d.ts
vendored
Normal file
4
packages/@sim/cli/dist/commands/version.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Version command displays the current version of the CLI
|
||||
*/
|
||||
export declare function version(): void;
|
||||
19
packages/@sim/cli/dist/commands/version.js
vendored
Normal file
19
packages/@sim/cli/dist/commands/version.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.version = version;
|
||||
const chalk_1 = __importDefault(require("chalk"));
|
||||
/**
|
||||
* Version command displays the current version of the CLI
|
||||
*/
|
||||
function version() {
|
||||
const pkg = require('../../package.json');
|
||||
console.log(`
|
||||
${chalk_1.default.bold('Sim Studio CLI')} ${chalk_1.default.green(`v${pkg.version}`)}
|
||||
${chalk_1.default.gray('Platform:')} ${process.platform}
|
||||
${chalk_1.default.gray('Node Version:')} ${process.version}
|
||||
${chalk_1.default.gray('CLI Path:')} ${__dirname}
|
||||
`);
|
||||
}
|
||||
2
packages/@sim/cli/dist/index.d.ts
vendored
Normal file
2
packages/@sim/cli/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
export {};
|
||||
68
packages/@sim/cli/dist/index.js
vendored
Normal file
68
packages/@sim/cli/dist/index.js
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const chalk_1 = __importDefault(require("chalk"));
|
||||
const commander_1 = require("commander");
|
||||
const update_notifier_1 = __importDefault(require("update-notifier"));
|
||||
const start_1 = require("./commands/start");
|
||||
const help_1 = require("./commands/help");
|
||||
const version_1 = require("./commands/version");
|
||||
const logo_1 = require("./utils/logo");
|
||||
const config_1 = require("./utils/config");
|
||||
// Package info for version checking
|
||||
const pkg = require('../package.json');
|
||||
// Check for updates
|
||||
(0, update_notifier_1.default)({ pkg }).notify();
|
||||
// Create program
|
||||
const program = new commander_1.Command();
|
||||
// Initialize CLI
|
||||
async function main() {
|
||||
// Configure the CLI
|
||||
program
|
||||
.name('sim')
|
||||
.description('Sim Studio CLI')
|
||||
.version(pkg.version, '-v, --version', 'Output the current version')
|
||||
.helpOption('-h, --help', 'Display help for command')
|
||||
.on('--help', () => (0, help_1.help)())
|
||||
.action(() => {
|
||||
// Default command (no args) runs start with default options
|
||||
(0, start_1.start)({ port: config_1.config.get('port'), debug: config_1.config.get('debug') });
|
||||
});
|
||||
// Start command
|
||||
program
|
||||
.command('start')
|
||||
.description('Start Sim Studio with local storage')
|
||||
.option('-p, --port <port>', 'Port to run on', config_1.config.get('port'))
|
||||
.option('-d, --debug', 'Enable debug mode', config_1.config.get('debug'))
|
||||
.action((options) => {
|
||||
(0, start_1.start)(options);
|
||||
});
|
||||
// Version command
|
||||
program
|
||||
.command('version')
|
||||
.description('Show detailed version information')
|
||||
.action(() => {
|
||||
(0, version_1.version)();
|
||||
});
|
||||
// Help command
|
||||
program
|
||||
.command('help')
|
||||
.description('Display help information')
|
||||
.action(() => {
|
||||
(0, help_1.help)();
|
||||
});
|
||||
// Display logo if not in help mode
|
||||
if (!process.argv.includes('--help') && !process.argv.includes('-h')) {
|
||||
console.log(logo_1.logo);
|
||||
}
|
||||
// Parse arguments
|
||||
program.parse(process.argv);
|
||||
}
|
||||
// Run the CLI
|
||||
main().catch((error) => {
|
||||
console.error(chalk_1.default.red('Error:'), error);
|
||||
process.exit(1);
|
||||
});
|
||||
8
packages/@sim/cli/dist/utils/config.d.ts
vendored
Normal file
8
packages/@sim/cli/dist/utils/config.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import Conf from 'conf';
|
||||
interface ConfigSchema {
|
||||
port: string;
|
||||
debug: boolean;
|
||||
lastRun: string;
|
||||
}
|
||||
export declare const config: Conf<ConfigSchema>;
|
||||
export {};
|
||||
16
packages/@sim/cli/dist/utils/config.js
vendored
Normal file
16
packages/@sim/cli/dist/utils/config.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.config = void 0;
|
||||
const conf_1 = __importDefault(require("conf"));
|
||||
// Create a config instance with default values
|
||||
exports.config = new conf_1.default({
|
||||
projectName: 'sim-studio',
|
||||
defaults: {
|
||||
port: '3000',
|
||||
debug: false,
|
||||
lastRun: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
4
packages/@sim/cli/dist/utils/logo.d.ts
vendored
Normal file
4
packages/@sim/cli/dist/utils/logo.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* ASCII art logo for Sim Studio
|
||||
*/
|
||||
export declare const logo: string;
|
||||
22
packages/@sim/cli/dist/utils/logo.js
vendored
Normal file
22
packages/@sim/cli/dist/utils/logo.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.logo = void 0;
|
||||
const chalk_1 = __importDefault(require("chalk"));
|
||||
/**
|
||||
* ASCII art logo for Sim Studio
|
||||
*/
|
||||
exports.logo = `
|
||||
${chalk_1.default.bold(chalk_1.default.magenta(`
|
||||
███████╗██╗███╗ ███╗ ███████╗████████╗██╗ ██╗██████╗ ██╗ ██████╗
|
||||
██╔════╝██║████╗ ████║ ██╔════╝╚══██╔══╝██║ ██║██╔══██╗██║██╔═══██╗
|
||||
███████╗██║██╔████╔██║ ███████╗ ██║ ██║ ██║██║ ██║██║██║ ██║
|
||||
╚════██║██║██║╚██╔╝██║ ╚════██║ ██║ ██║ ██║██║ ██║██║██║ ██║
|
||||
███████║██║██║ ╚═╝ ██║ ███████║ ██║ ╚██████╔╝██████╔╝██║╚██████╔╝
|
||||
╚══════╝╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝
|
||||
|
||||
`))}
|
||||
${chalk_1.default.cyan('Build, optimize, and test agent workflows with a powerful visual interface')}
|
||||
`;
|
||||
54
packages/@sim/cli/package.json
Normal file
54
packages/@sim/cli/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@sim/cli",
|
||||
"version": "0.1.0",
|
||||
"description": "CLI tool for Sim Studio - easily start, build and test agent workflows",
|
||||
"license": "MIT",
|
||||
"author": "Sim Studio Team",
|
||||
"main": "dist/index.js",
|
||||
"type": "commonjs",
|
||||
"bin": {
|
||||
"sim": "./bin/sim.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node bin/sim.js",
|
||||
"dev": "ts-node src/index.ts",
|
||||
"clean": "rimraf dist",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"sim",
|
||||
"sim-studio",
|
||||
"workflow",
|
||||
"automation",
|
||||
"cli",
|
||||
"agent"
|
||||
],
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.1.0",
|
||||
"conf": "^10.2.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"inquirer": "^8.2.6",
|
||||
"ora": "^8.2.0",
|
||||
"tar": "^6.2.1",
|
||||
"update-notifier": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^8.2.10",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/tar": "^6.1.11",
|
||||
"@types/update-notifier": "^5.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
39
packages/@sim/cli/src/commands/help.ts
Normal file
39
packages/@sim/cli/src/commands/help.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import chalk from 'chalk'
|
||||
import { logo } from '../utils/logo'
|
||||
|
||||
/**
|
||||
* Help command displays the logo and usage information
|
||||
*/
|
||||
export function help() {
|
||||
// Display logo
|
||||
console.log(logo)
|
||||
|
||||
// Display help text
|
||||
console.log(`
|
||||
${chalk.bold('USAGE')}
|
||||
${chalk.cyan('sim')} Start Sim Studio with default settings
|
||||
${chalk.cyan('sim start')} Start Sim Studio with options
|
||||
${chalk.cyan('sim version')} Display version information
|
||||
${chalk.cyan('sim help')} Show this help information
|
||||
|
||||
${chalk.bold('OPTIONS')}
|
||||
${chalk.cyan('-p, --port <port>')} Specify port (default: 3000)
|
||||
${chalk.cyan('-d, --debug')} Enable debug mode
|
||||
${chalk.cyan('-v, --version')} Show version information
|
||||
${chalk.cyan('-h, --help')} Show help information
|
||||
|
||||
${chalk.bold('EXAMPLES')}
|
||||
${chalk.gray('# Start with default settings')}
|
||||
${chalk.cyan('$ sim')}
|
||||
|
||||
${chalk.gray('# Start on a specific port')}
|
||||
${chalk.cyan('$ sim start --port 8080')}
|
||||
|
||||
${chalk.gray('# Start with debug logging')}
|
||||
${chalk.cyan('$ sim start --debug')}
|
||||
|
||||
${chalk.bold('DOCUMENTATION')}
|
||||
${chalk.gray('For more information:')}
|
||||
https://github.com/simstudioai/sim
|
||||
`)
|
||||
}
|
||||
286
packages/@sim/cli/src/commands/start.ts
Normal file
286
packages/@sim/cli/src/commands/start.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import chalk from 'chalk'
|
||||
import { spawn } from 'child_process'
|
||||
import { execSync } from 'child_process'
|
||||
import fs from 'fs'
|
||||
import { createWriteStream } from 'fs'
|
||||
import https from 'https'
|
||||
import type { Ora } from 'ora'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { extract } from 'tar'
|
||||
import { config } from '../utils/config'
|
||||
|
||||
interface StartOptions {
|
||||
port: string
|
||||
debug: boolean
|
||||
}
|
||||
|
||||
// Constants for standalone app
|
||||
const SIM_HOME_DIR = path.join(os.homedir(), '.sim-studio')
|
||||
const SIM_STANDALONE_DIR = path.join(SIM_HOME_DIR, 'standalone')
|
||||
const SIM_VERSION_FILE = path.join(SIM_HOME_DIR, 'version.json')
|
||||
const DOWNLOAD_URL =
|
||||
'https://github.com/simstudioai/sim/releases/download/v0.1.0/sim-standalone.tar.gz'
|
||||
const STANDALONE_VERSION = '0.1.0'
|
||||
|
||||
/**
|
||||
* Start command that launches Sim Studio using local storage
|
||||
*/
|
||||
export async function start(options: StartOptions) {
|
||||
// Update config with provided options
|
||||
config.set('port', options.port)
|
||||
config.set('debug', options.debug)
|
||||
config.set('lastRun', new Date().toISOString())
|
||||
|
||||
const port = options.port || '3000'
|
||||
const debug = options.debug || false
|
||||
|
||||
// Dynamically import ora
|
||||
const oraModule = await import('ora')
|
||||
const ora = oraModule.default
|
||||
|
||||
// Show starting message
|
||||
const spinner = ora(`Starting Sim Studio on port ${port}...`).start()
|
||||
|
||||
try {
|
||||
// Set environment variables for using local storage
|
||||
const env = {
|
||||
...process.env,
|
||||
PORT: port,
|
||||
USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage
|
||||
NODE_ENV: debug ? 'development' : 'production',
|
||||
DEBUG: debug ? '*' : undefined,
|
||||
}
|
||||
|
||||
// Try to find the main package.json to determine if we're running from within the repo
|
||||
// or as an installed npm package
|
||||
const isInProjectDirectory = checkIfInProjectDirectory()
|
||||
|
||||
let simProcess
|
||||
|
||||
if (isInProjectDirectory) {
|
||||
// Running from within the project directory - we'll use the existing
|
||||
// Next.js setup directly
|
||||
spinner.text = 'Detected Sim Studio project, starting with local configuration...'
|
||||
|
||||
simProcess = spawn('npm', ['run', 'dev'], {
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
})
|
||||
} else {
|
||||
// Running from outside the project via npx - we'll download and start a standalone version
|
||||
spinner.text = 'Setting up standalone Sim Studio...'
|
||||
|
||||
// Create the .sim-studio directory if it doesn't exist
|
||||
if (!fs.existsSync(SIM_HOME_DIR)) {
|
||||
fs.mkdirSync(SIM_HOME_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
// Check if we already have the standalone version
|
||||
let needsDownload = true
|
||||
|
||||
if (fs.existsSync(SIM_VERSION_FILE)) {
|
||||
try {
|
||||
const versionInfo = JSON.parse(fs.readFileSync(SIM_VERSION_FILE, 'utf8'))
|
||||
if (versionInfo.version === STANDALONE_VERSION) {
|
||||
needsDownload = false
|
||||
}
|
||||
} catch (error) {
|
||||
// If there's an error reading the version file, download again
|
||||
needsDownload = true
|
||||
}
|
||||
}
|
||||
|
||||
// Download and extract if needed
|
||||
if (needsDownload) {
|
||||
try {
|
||||
await downloadStandaloneApp(spinner)
|
||||
} catch (error) {
|
||||
spinner.fail(
|
||||
`Failed to download Sim Studio: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
console.log(`\n${chalk.yellow('⚠️')} If you're having network issues, you can try:
|
||||
1. Check your internet connection
|
||||
2. Try again later
|
||||
3. Run Sim Studio directly from a cloned repository`)
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
spinner.text = 'Using cached Sim Studio standalone version...'
|
||||
}
|
||||
|
||||
// Start the standalone app
|
||||
spinner.text = 'Starting Sim Studio standalone...'
|
||||
|
||||
// Make sure the standalone directory exists
|
||||
if (
|
||||
!fs.existsSync(SIM_STANDALONE_DIR) ||
|
||||
!fs.existsSync(path.join(SIM_STANDALONE_DIR, 'server.js'))
|
||||
) {
|
||||
spinner.fail('Standalone app files are missing. Re-run to download again.')
|
||||
// Force a fresh download next time
|
||||
if (fs.existsSync(SIM_VERSION_FILE)) {
|
||||
fs.unlinkSync(SIM_VERSION_FILE)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Start the standalone Node.js server
|
||||
const standaloneEnv = {
|
||||
...env,
|
||||
SIM_STUDIO_PORT: port,
|
||||
}
|
||||
|
||||
simProcess = spawn('node', ['server.js'], {
|
||||
cwd: SIM_STANDALONE_DIR,
|
||||
env: standaloneEnv,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Successful start
|
||||
spinner.succeed(`Sim Studio is running on ${chalk.cyan(`http://localhost:${port}`)}`)
|
||||
console.log(`
|
||||
${chalk.green('✓')} Using local storage mode - your data will be stored in the browser
|
||||
${chalk.green('✓')} Any changes will be persisted between sessions through localStorage
|
||||
${chalk.yellow('i')} Press ${chalk.bold('Ctrl+C')} to stop the server
|
||||
`)
|
||||
|
||||
// Handle process termination
|
||||
process.on('SIGINT', () => {
|
||||
console.log(`\n${chalk.yellow('⚠️')} Shutting down Sim Studio...`)
|
||||
simProcess.kill('SIGINT')
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
// Return the process for testing purposes
|
||||
return simProcess
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to start Sim Studio')
|
||||
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running in a Sim Studio project directory
|
||||
*/
|
||||
function checkIfInProjectDirectory(): boolean {
|
||||
// Check if we have package.json that looks like a Sim Studio project
|
||||
try {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
|
||||
// Check if it looks like our project
|
||||
if (
|
||||
packageJson.name === 'sim' ||
|
||||
packageJson.name === 'sim-studio' ||
|
||||
(packageJson.dependencies &&
|
||||
(packageJson.dependencies['next'] || packageJson.dependencies['@sim/cli']))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for Next.js app files
|
||||
const nextConfigPath = path.join(process.cwd(), 'next.config.js')
|
||||
const nextTsConfigPath = path.join(process.cwd(), 'next.config.ts')
|
||||
|
||||
if (fs.existsSync(nextConfigPath) || fs.existsSync(nextTsConfigPath)) {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't read/parse package.json, assume we're not in a project directory
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and extracts the standalone app
|
||||
*/
|
||||
async function downloadStandaloneApp(spinner: Ora): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create temp directory
|
||||
const tmpDir = path.join(os.tmpdir(), `sim-download-${Date.now()}`)
|
||||
fs.mkdirSync(tmpDir, { recursive: true })
|
||||
|
||||
const tarballPath = path.join(tmpDir, 'sim-standalone.tar.gz')
|
||||
const file = createWriteStream(tarballPath)
|
||||
|
||||
spinner.text = 'Downloading Sim Studio...'
|
||||
|
||||
// Download the tarball
|
||||
https
|
||||
.get(DOWNLOAD_URL, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
spinner.fail(`Failed to download: ${response.statusCode}`)
|
||||
return reject(new Error(`Download failed with status code: ${response.statusCode}`))
|
||||
}
|
||||
|
||||
response.pipe(file)
|
||||
|
||||
file.on('finish', () => {
|
||||
file.close()
|
||||
|
||||
// Clear the standalone directory if it exists
|
||||
if (fs.existsSync(SIM_STANDALONE_DIR)) {
|
||||
fs.rmSync(SIM_STANDALONE_DIR, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
// Create the directory
|
||||
fs.mkdirSync(SIM_STANDALONE_DIR, { recursive: true })
|
||||
|
||||
spinner.text = 'Extracting Sim Studio...'
|
||||
|
||||
// Extract the tarball
|
||||
extract({
|
||||
file: tarballPath,
|
||||
cwd: SIM_STANDALONE_DIR,
|
||||
})
|
||||
.then(() => {
|
||||
// Clean up
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true })
|
||||
|
||||
// Install dependencies if needed
|
||||
if (fs.existsSync(path.join(SIM_STANDALONE_DIR, 'package.json'))) {
|
||||
spinner.text = 'Installing dependencies...'
|
||||
|
||||
try {
|
||||
execSync('npm install --production', {
|
||||
cwd: SIM_STANDALONE_DIR,
|
||||
stdio: 'ignore',
|
||||
})
|
||||
} catch (error) {
|
||||
spinner.warn('Error installing dependencies, but trying to continue...')
|
||||
}
|
||||
}
|
||||
|
||||
// Save version info
|
||||
fs.writeFileSync(
|
||||
SIM_VERSION_FILE,
|
||||
JSON.stringify({
|
||||
version: STANDALONE_VERSION,
|
||||
installedAt: new Date().toISOString(),
|
||||
})
|
||||
)
|
||||
|
||||
resolve()
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
spinner.fail('Extraction failed')
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
.on('error', (err: Error) => {
|
||||
fs.unlink(tarballPath, () => {})
|
||||
spinner.fail('Download failed')
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
15
packages/@sim/cli/src/commands/version.ts
Normal file
15
packages/@sim/cli/src/commands/version.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
/**
|
||||
* Version command displays the current version of the CLI
|
||||
*/
|
||||
export function version() {
|
||||
const pkg = require('../../package.json')
|
||||
|
||||
console.log(`
|
||||
${chalk.bold('Sim Studio CLI')} ${chalk.green(`v${pkg.version}`)}
|
||||
${chalk.gray('Platform:')} ${process.platform}
|
||||
${chalk.gray('Node Version:')} ${process.version}
|
||||
${chalk.gray('CLI Path:')} ${__dirname}
|
||||
`)
|
||||
}
|
||||
73
packages/@sim/cli/src/index.ts
Normal file
73
packages/@sim/cli/src/index.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
import chalk from 'chalk'
|
||||
import { Command } from 'commander'
|
||||
import updateNotifier from 'update-notifier'
|
||||
import { help } from './commands/help'
|
||||
import { start } from './commands/start'
|
||||
import { version } from './commands/version'
|
||||
import { config } from './utils/config'
|
||||
import { logo } from './utils/logo'
|
||||
|
||||
// Package info for version checking
|
||||
const pkg = require('../package.json')
|
||||
|
||||
// Check for updates
|
||||
updateNotifier({ pkg }).notify()
|
||||
|
||||
// Create program
|
||||
const program = new Command()
|
||||
|
||||
// Initialize CLI
|
||||
async function main() {
|
||||
// Configure the CLI
|
||||
program
|
||||
.name('sim')
|
||||
.description('Sim Studio CLI')
|
||||
.version(pkg.version, '-v, --version', 'Output the current version')
|
||||
.helpOption('-h, --help', 'Display help for command')
|
||||
.on('--help', () => help())
|
||||
.action(() => {
|
||||
// Default command (no args) runs start with default options
|
||||
start({ port: config.get('port'), debug: config.get('debug') })
|
||||
})
|
||||
|
||||
// Start command
|
||||
program
|
||||
.command('start')
|
||||
.description('Start Sim Studio with local storage')
|
||||
.option('-p, --port <port>', 'Port to run on', config.get('port'))
|
||||
.option('-d, --debug', 'Enable debug mode', config.get('debug'))
|
||||
.action((options) => {
|
||||
start(options)
|
||||
})
|
||||
|
||||
// Version command
|
||||
program
|
||||
.command('version')
|
||||
.description('Show detailed version information')
|
||||
.action(() => {
|
||||
version()
|
||||
})
|
||||
|
||||
// Help command
|
||||
program
|
||||
.command('help')
|
||||
.description('Display help information')
|
||||
.action(() => {
|
||||
help()
|
||||
})
|
||||
|
||||
// Display logo if not in help mode
|
||||
if (!process.argv.includes('--help') && !process.argv.includes('-h')) {
|
||||
console.log(logo)
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
program.parse(process.argv)
|
||||
}
|
||||
|
||||
// Run the CLI
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red('Error:'), error)
|
||||
process.exit(1)
|
||||
})
|
||||
18
packages/@sim/cli/src/utils/config.ts
Normal file
18
packages/@sim/cli/src/utils/config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Conf from 'conf'
|
||||
|
||||
// Config schema definition
|
||||
interface ConfigSchema {
|
||||
port: string
|
||||
debug: boolean
|
||||
lastRun: string
|
||||
}
|
||||
|
||||
// Create a config instance with default values
|
||||
export const config = new Conf<ConfigSchema>({
|
||||
projectName: 'sim-studio',
|
||||
defaults: {
|
||||
port: '3000',
|
||||
debug: false,
|
||||
lastRun: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
19
packages/@sim/cli/src/utils/logo.ts
Normal file
19
packages/@sim/cli/src/utils/logo.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
/**
|
||||
* ASCII art logo for Sim Studio
|
||||
*/
|
||||
export const logo = `
|
||||
${chalk.bold(
|
||||
chalk.magenta(`
|
||||
███████╗██╗███╗ ███╗ ███████╗████████╗██╗ ██╗██████╗ ██╗ ██████╗
|
||||
██╔════╝██║████╗ ████║ ██╔════╝╚══██╔══╝██║ ██║██╔══██╗██║██╔═══██╗
|
||||
███████╗██║██╔████╔██║ ███████╗ ██║ ██║ ██║██║ ██║██║██║ ██║
|
||||
╚════██║██║██║╚██╔╝██║ ╚════██║ ██║ ██║ ██║██║ ██║██║██║ ██║
|
||||
███████║██║██║ ╚═╝ ██║ ███████║ ██║ ╚██████╔╝██████╔╝██║╚██████╔╝
|
||||
╚══════╝╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝
|
||||
|
||||
`)
|
||||
)}
|
||||
${chalk.cyan('Build, optimize, and test agent workflows with a powerful visual interface')}
|
||||
`
|
||||
16
packages/@sim/cli/standalone/package.json
Normal file
16
packages/@sim/cli/standalone/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "sim-studio-standalone",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Standalone server for Sim Studio",
|
||||
"main": "server.js",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
}
|
||||
}
|
||||
76
packages/@sim/cli/standalone/server.js
Normal file
76
packages/@sim/cli/standalone/server.js
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Sim Studio Standalone Server
|
||||
*
|
||||
* This is a simplified server that serves the pre-built Sim Studio app
|
||||
* and enables localStorage mode automatically.
|
||||
*/
|
||||
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { createServer } = require('http')
|
||||
const { parse } = require('url')
|
||||
|
||||
// Configuration
|
||||
const PORT = process.env.SIM_STUDIO_PORT || 3000
|
||||
const PUBLIC_DIR = path.join(__dirname, 'public')
|
||||
const HTML_FILE = path.join(PUBLIC_DIR, 'index.html')
|
||||
|
||||
// Create Express app
|
||||
const app = express()
|
||||
|
||||
// Set localStorage environment variable in HTML
|
||||
const injectLocalStorageScript = (html) => {
|
||||
const script = `
|
||||
<script>
|
||||
// Set localStorage flag for Sim Studio
|
||||
localStorage.setItem('USE_LOCAL_STORAGE', 'true');
|
||||
console.log('Sim Studio running in local storage mode');
|
||||
</script>
|
||||
`
|
||||
|
||||
// Insert script right before the closing </head> tag
|
||||
return html.replace('</head>', `${script}</head>`)
|
||||
}
|
||||
|
||||
// Middleware to inject localStorage flag
|
||||
app.use((req, res, next) => {
|
||||
if (req.path === '/' || req.path.endsWith('.html')) {
|
||||
const originalSend = res.send
|
||||
res.send = function (body) {
|
||||
if (typeof body === 'string' && body.includes('</head>')) {
|
||||
body = injectLocalStorageScript(body)
|
||||
}
|
||||
return originalSend.call(this, body)
|
||||
}
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
// Serve static files
|
||||
app.use(express.static(PUBLIC_DIR))
|
||||
|
||||
// SPA fallback - all routes not matched should serve index.html
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(HTML_FILE)
|
||||
})
|
||||
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`
|
||||
┌────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 🚀 Sim Studio is running in standalone mode! │
|
||||
│ │
|
||||
│ 🌐 Local: http://localhost:${PORT} ${PORT.toString().length < 4 ? ' ' : ''}│
|
||||
│ │
|
||||
│ 💾 Using localStorage for all data │
|
||||
│ 🔄 All changes will be saved in your browser │
|
||||
│ │
|
||||
│ Press Ctrl+C to stop the server │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────┘
|
||||
`)
|
||||
})
|
||||
16
packages/@sim/cli/tsconfig.json
Normal file
16
packages/@sim/cli/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
92
scripts/build-standalone.js
Executable file
92
scripts/build-standalone.js
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Build Standalone Distribution
|
||||
*
|
||||
* This script builds a standalone distribution of Sim Studio that can be downloaded
|
||||
* and run by the CLI with `npx sim`.
|
||||
*
|
||||
* The standalone package includes:
|
||||
* - Pre-built Next.js static export
|
||||
* - Simplified Express server
|
||||
* - Configured to use localStorage instead of a database
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
const packageJson = require('../package.json')
|
||||
|
||||
// Configuration
|
||||
const STANDALONE_DIR = path.join(__dirname, '../standalone-dist')
|
||||
const SOURCE_DIR = path.join(__dirname, '..')
|
||||
const OUTPUT_TARBALL = path.join(__dirname, '../sim-standalone.tar.gz')
|
||||
|
||||
console.log('🔨 Building Sim Studio standalone distribution')
|
||||
|
||||
// Clean up if the directory exists
|
||||
if (fs.existsSync(STANDALONE_DIR)) {
|
||||
console.log('Cleaning up previous build...')
|
||||
fs.rmSync(STANDALONE_DIR, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
// Create standalone directory
|
||||
fs.mkdirSync(STANDALONE_DIR, { recursive: true })
|
||||
fs.mkdirSync(path.join(STANDALONE_DIR, 'public'), { recursive: true })
|
||||
|
||||
// Build Next.js static export
|
||||
console.log('Building Next.js static export...')
|
||||
try {
|
||||
// Set environment variable for static export with localStorage
|
||||
process.env.USE_LOCAL_STORAGE = 'true'
|
||||
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE = 'true'
|
||||
|
||||
// Build the app
|
||||
execSync('npm run build', {
|
||||
cwd: SOURCE_DIR,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
USE_LOCAL_STORAGE: 'true',
|
||||
NEXT_PUBLIC_USE_LOCAL_STORAGE: 'true',
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
})
|
||||
|
||||
// Copy the output to standalone directory
|
||||
console.log('Copying files to standalone directory...')
|
||||
fs.cpSync(path.join(SOURCE_DIR, 'out'), path.join(STANDALONE_DIR, 'public'), { recursive: true })
|
||||
} catch (error) {
|
||||
console.error('Error building Next.js static export:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Copy standalone server files
|
||||
console.log('Copying standalone server files...')
|
||||
fs.copyFileSync(
|
||||
path.join(SOURCE_DIR, 'packages/@sim/cli/standalone/server.js'),
|
||||
path.join(STANDALONE_DIR, 'server.js')
|
||||
)
|
||||
fs.copyFileSync(
|
||||
path.join(SOURCE_DIR, 'packages/@sim/cli/standalone/package.json'),
|
||||
path.join(STANDALONE_DIR, 'package.json')
|
||||
)
|
||||
|
||||
// Create tarball
|
||||
console.log('Creating tarball...')
|
||||
try {
|
||||
execSync(`tar -czf "${OUTPUT_TARBALL}" -C "${STANDALONE_DIR}" .`, {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
console.log(`✅ Standalone distribution created: ${OUTPUT_TARBALL}`)
|
||||
console.log(`📦 Size: ${(fs.statSync(OUTPUT_TARBALL).size / (1024 * 1024)).toFixed(2)} MB`)
|
||||
} catch (error) {
|
||||
console.error('Error creating tarball:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('\n🚀 Next steps:')
|
||||
console.log('1. Upload the tarball to your release assets')
|
||||
console.log('2. Update the DOWNLOAD_URL in the CLI code to point to your release')
|
||||
console.log('3. Publish the CLI package to npm with: npm run cli:publish')
|
||||
33
scripts/setup_cli.sh
Normal file
33
scripts/setup_cli.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Setting up Sim Studio CLI Package..."
|
||||
|
||||
# Create directory structure if it doesn't exist
|
||||
mkdir -p packages/@sim/cli/bin
|
||||
mkdir -p packages/@sim/cli/src/commands
|
||||
mkdir -p packages/@sim/cli/src/utils
|
||||
|
||||
# Navigate to CLI directory
|
||||
cd packages/@sim/cli
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing CLI dependencies..."
|
||||
npm install
|
||||
|
||||
# Build the CLI package
|
||||
echo "Building CLI package..."
|
||||
npm run build
|
||||
|
||||
# Make the CLI executable
|
||||
chmod +x bin/sim.js
|
||||
|
||||
echo "✅ CLI setup complete!"
|
||||
echo ""
|
||||
echo "You can now run:"
|
||||
echo " npm run cli:start - to test the CLI"
|
||||
echo " npm run cli:dev - to develop the CLI with live reload"
|
||||
echo " npm run cli:publish - to publish to npm"
|
||||
echo ""
|
||||
echo "Try it out with: ./packages/@sim/cli/bin/sim.js"
|
||||
Reference in New Issue
Block a user