@learnrudi/cli 1.9.12 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -137
- package/dist/index.cjs +747 -14
- package/dist/packages-manifest.json +1 -1
- package/package.json +22 -21
package/README.md
CHANGED
|
@@ -1,206 +1,263 @@
|
|
|
1
1
|
# RUDI CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A universal tool manager for MCP stacks, CLI tools, runtimes, and AI agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
RUDI provides a unified installation and management system for:
|
|
6
|
+
- **MCP Stacks** - Model Context Protocol servers for Claude, Codex, and Gemini
|
|
7
|
+
- **CLI Tools** - Any npm package or upstream binary (ffmpeg, ripgrep, etc.)
|
|
8
|
+
- **Runtimes** - Node.js, Python, Deno, Bun
|
|
9
|
+
- **AI Agents** - Claude Code, Codex CLI, Gemini CLI
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
|
-
npm
|
|
14
|
+
npm install -g @learnrudi/cli
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
Requires Node.js 18
|
|
17
|
+
Requires Node.js 18 or later. The installer creates `~/.rudi/` and adds shims to `~/.rudi/bins/`.
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
Add to your shell profile (`.bashrc`, `.zshrc`, or `.profile`):
|
|
14
20
|
|
|
15
21
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
rudi install npm:@stripe/cli
|
|
19
|
-
rudi install npm:vercel
|
|
22
|
+
export PATH="$HOME/.rudi/bins:$PATH"
|
|
23
|
+
```
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
rudi install slack
|
|
23
|
-
rudi install binary:ffmpeg
|
|
24
|
-
rudi install binary:supabase
|
|
25
|
+
## Core Concepts
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
tsc --version
|
|
28
|
-
ffmpeg -version
|
|
29
|
-
supabase --version
|
|
27
|
+
### Shim-Based Architecture
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
rudi secrets set SLACK_BOT_TOKEN "xoxb-your-token"
|
|
29
|
+
Every tool installed through RUDI gets a wrapper script (shim) in `~/.rudi/bins/`. This provides:
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
- Clean PATH integration without modifying system directories
|
|
32
|
+
- Version isolation per package
|
|
33
|
+
- Ownership tracking for clean uninstalls
|
|
34
|
+
- Consistent invocation across different package sources
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
When you run `tsc`, the shell finds `~/.rudi/bins/tsc`, which delegates to the actual TypeScript installation at `~/.rudi/binaries/npm/typescript/node_modules/.bin/tsc`.
|
|
39
37
|
|
|
40
|
-
###
|
|
38
|
+
### Package Sources
|
|
41
39
|
|
|
42
|
-
RUDI supports
|
|
40
|
+
RUDI supports three installation sources:
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
rudi install npm:typescript
|
|
48
|
-
rudi install npm:@railway/cli
|
|
42
|
+
1. **Dynamic npm** (`npm:<package>`) - Any npm package with a `bin` field
|
|
43
|
+
2. **Curated Registry** - Pre-configured stacks and binaries with documentation
|
|
44
|
+
3. **Upstream Binaries** - Direct downloads from official sources
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
rudi install slack # MCP stack
|
|
52
|
-
rudi install binary:ffmpeg # Upstream binary
|
|
53
|
-
rudi install binary:supabase # npm-based CLI
|
|
46
|
+
### Secret Management
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
```
|
|
48
|
+
MCP stacks often require API keys and tokens. RUDI stores secrets in `~/.rudi/secrets.json` (mode 0600) and injects them as environment variables when running stacks. Secrets are never exposed in process listings or logs.
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
## Usage
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
### Installing Packages
|
|
61
53
|
|
|
62
54
|
```bash
|
|
63
|
-
#
|
|
64
|
-
|
|
55
|
+
# Install any npm CLI tool
|
|
56
|
+
rudi install npm:typescript # Installs tsc, tsserver
|
|
57
|
+
rudi install npm:@stripe/cli # Installs stripe
|
|
58
|
+
rudi install npm:vercel # Installs vercel
|
|
65
59
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
ffmpeg
|
|
69
|
-
supabase
|
|
60
|
+
# Install from curated registry
|
|
61
|
+
rudi install slack # MCP stack for Slack
|
|
62
|
+
rudi install binary:ffmpeg # Upstream ffmpeg binary
|
|
63
|
+
rudi install binary:supabase # Supabase CLI
|
|
64
|
+
|
|
65
|
+
# Install with scripts enabled (when needed)
|
|
66
|
+
rudi install npm:puppeteer --allow-scripts
|
|
70
67
|
```
|
|
71
68
|
|
|
72
|
-
###
|
|
69
|
+
### Listing Installed Packages
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
```bash
|
|
72
|
+
rudi list # All installed packages
|
|
73
|
+
rudi list stacks # MCP stacks only
|
|
74
|
+
rudi list binaries # CLI tools only
|
|
75
|
+
rudi list runtimes # Language runtimes
|
|
76
|
+
rudi list agents # AI agent CLIs
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Searching the Registry
|
|
75
80
|
|
|
76
81
|
```bash
|
|
77
|
-
#
|
|
78
|
-
rudi
|
|
82
|
+
rudi search pdf # Search for packages
|
|
83
|
+
rudi search --all # List all available packages
|
|
84
|
+
rudi search --stacks # Filter to MCP stacks
|
|
85
|
+
rudi search --binaries # Filter to CLI tools
|
|
86
|
+
```
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
### Managing Secrets
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
rudi secrets list # Show configured secrets (masked)
|
|
92
|
+
rudi secrets set SLACK_BOT_TOKEN # Set a secret (prompts for value)
|
|
93
|
+
rudi secrets set OPENAI_API_KEY "sk-..." # Set with value
|
|
94
|
+
rudi secrets remove SLACK_BOT_TOKEN # Remove a secret
|
|
82
95
|
```
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
### Integrating with AI Agents
|
|
85
98
|
|
|
86
99
|
```bash
|
|
87
|
-
#
|
|
88
|
-
rudi
|
|
89
|
-
rudi
|
|
90
|
-
rudi
|
|
91
|
-
rudi install npm:<pkg> # Install any npm CLI
|
|
92
|
-
rudi remove <pkg> # Remove a package
|
|
93
|
-
|
|
94
|
-
# List and inspect
|
|
95
|
-
rudi list [kind] # List installed (stacks, binaries, agents)
|
|
96
|
-
rudi pkg <id> # Show package details and shim status
|
|
97
|
-
rudi shims list # List all shims
|
|
98
|
-
rudi shims check # Validate shim targets
|
|
99
|
-
|
|
100
|
-
# Secrets and integration
|
|
101
|
-
rudi secrets list # Show configured secrets
|
|
102
|
-
rudi secrets set <key> # Set a secret
|
|
103
|
-
rudi integrate <agent> # Wire stack to agent config
|
|
104
|
-
|
|
105
|
-
# Maintenance
|
|
106
|
-
rudi update [pkg] # Update packages
|
|
107
|
-
rudi doctor # Check system health
|
|
100
|
+
rudi integrate claude # Add stacks to Claude Desktop config
|
|
101
|
+
rudi integrate codex # Add stacks to Codex config
|
|
102
|
+
rudi integrate gemini # Add stacks to Gemini config
|
|
103
|
+
rudi integrate all # Add to all detected agents
|
|
108
104
|
```
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
This modifies the agent's MCP configuration file (e.g., `~/Library/Application Support/Claude/claude_desktop_config.json`) to include your installed stacks with proper secret injection.
|
|
111
107
|
|
|
112
|
-
###
|
|
108
|
+
### Inspecting Packages
|
|
113
109
|
|
|
114
110
|
```bash
|
|
115
|
-
rudi
|
|
116
|
-
|
|
111
|
+
rudi pkg slack # Show package details
|
|
112
|
+
rudi pkg npm:typescript # Show shims and paths
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
4. Discovers binaries from package.json (`tsc`, `tsserver`)
|
|
122
|
-
5. Creates wrapper shims in `~/.rudi/bins/`
|
|
123
|
-
6. Records ownership in shim registry
|
|
114
|
+
rudi shims list # List all shims
|
|
115
|
+
rudi shims check # Validate shim targets exist
|
|
116
|
+
```
|
|
124
117
|
|
|
125
|
-
###
|
|
118
|
+
### Maintenance
|
|
126
119
|
|
|
127
120
|
```bash
|
|
128
|
-
rudi
|
|
121
|
+
rudi update # Update all packages
|
|
122
|
+
rudi update slack # Update specific package
|
|
123
|
+
rudi remove slack # Uninstall a package
|
|
124
|
+
rudi doctor # Check system health
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Directory Structure
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
~/.rudi/
|
|
131
|
+
├── bins/ # Shims (add to PATH)
|
|
132
|
+
│ ├── tsc # → binaries/npm/typescript/...
|
|
133
|
+
│ ├── ffmpeg # → binaries/ffmpeg/...
|
|
134
|
+
│ └── rudi-mcp # MCP router for agents
|
|
135
|
+
│
|
|
136
|
+
├── stacks/ # MCP server installations
|
|
137
|
+
│ ├── slack/
|
|
138
|
+
│ │ ├── manifest.json
|
|
139
|
+
│ │ ├── index.js
|
|
140
|
+
│ │ └── node_modules/
|
|
141
|
+
│ └── google-workspace/
|
|
142
|
+
│
|
|
143
|
+
├── binaries/ # CLI tool installations
|
|
144
|
+
│ ├── ffmpeg/ # Upstream binary
|
|
145
|
+
│ ├── supabase/ # npm-based CLI
|
|
146
|
+
│ └── npm/ # Dynamic npm packages
|
|
147
|
+
│ ├── typescript/
|
|
148
|
+
│ └── vercel/
|
|
149
|
+
│
|
|
150
|
+
├── runtimes/ # Language runtimes
|
|
151
|
+
│ ├── node/
|
|
152
|
+
│ └── python/
|
|
153
|
+
│
|
|
154
|
+
├── agents/ # AI agent CLI installations
|
|
155
|
+
│
|
|
156
|
+
├── secrets.json # API keys (mode 0600)
|
|
157
|
+
├── shim-registry.json # Shim ownership tracking
|
|
158
|
+
└── rudi.db # Local metadata database
|
|
129
159
|
```
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
4. Shows which secrets need configuration
|
|
135
|
-
5. Ready for `rudi integrate` to wire to agents
|
|
161
|
+
## How MCP Integration Works
|
|
162
|
+
|
|
163
|
+
When you run `rudi integrate claude`, RUDI:
|
|
136
164
|
|
|
137
|
-
|
|
165
|
+
1. Reads the Claude Desktop config at `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
166
|
+
2. Adds entries for each installed stack pointing to `~/.rudi/bins/rudi-mcp`
|
|
167
|
+
3. Passes the stack ID as an argument
|
|
138
168
|
|
|
139
|
-
When
|
|
169
|
+
When Claude invokes the MCP server:
|
|
140
170
|
|
|
141
|
-
1.
|
|
142
|
-
2.
|
|
171
|
+
1. `rudi-mcp` receives the stack ID
|
|
172
|
+
2. Loads secrets from `~/.rudi/secrets.json`
|
|
143
173
|
3. Injects secrets as environment variables
|
|
144
|
-
4.
|
|
174
|
+
4. Spawns the actual MCP server process
|
|
175
|
+
5. Proxies stdio between Claude and the server
|
|
145
176
|
|
|
146
|
-
|
|
177
|
+
This architecture means secrets stay local and are never written to agent config files.
|
|
178
|
+
|
|
179
|
+
## Security Model
|
|
147
180
|
|
|
181
|
+
### npm Package Installation
|
|
182
|
+
|
|
183
|
+
By default, npm packages install with `--ignore-scripts` to prevent arbitrary code execution during install. If a package requires lifecycle scripts (e.g., native compilation), use:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
rudi install npm:puppeteer --allow-scripts
|
|
148
187
|
```
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
|
|
189
|
+
### Secret Storage
|
|
190
|
+
|
|
191
|
+
Secrets are stored in `~/.rudi/secrets.json` with file permissions `0600` (owner read/write only). This matches the security model used by SSH, AWS CLI, and other credential stores.
|
|
192
|
+
|
|
193
|
+
### Shim Isolation
|
|
194
|
+
|
|
195
|
+
Each package installs to its own directory. Shims are thin wrappers that set up the environment and delegate to the real binary. This prevents packages from interfering with each other.
|
|
196
|
+
|
|
197
|
+
## Available Stacks
|
|
198
|
+
|
|
199
|
+
| Stack | Description | Required Secrets |
|
|
200
|
+
|-------|-------------|------------------|
|
|
201
|
+
| slack | Channels, messages, reactions | `SLACK_BOT_TOKEN` |
|
|
202
|
+
| google-workspace | Gmail, Sheets, Docs, Drive | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` |
|
|
203
|
+
| notion-workspace | Pages, databases, search | `NOTION_API_KEY` |
|
|
204
|
+
| github | Issues, PRs, repos, actions | `GITHUB_TOKEN` |
|
|
205
|
+
| postgres | SQL queries | `DATABASE_URL` |
|
|
206
|
+
| stripe | Payments, subscriptions | `STRIPE_SECRET_KEY` |
|
|
207
|
+
| openai | DALL-E, Whisper, TTS | `OPENAI_API_KEY` |
|
|
208
|
+
| google-ai | Gemini, Imagen | `GOOGLE_AI_API_KEY` |
|
|
209
|
+
|
|
210
|
+
## Available Binaries
|
|
211
|
+
|
|
212
|
+
| Binary | Description | Source |
|
|
213
|
+
|--------|-------------|--------|
|
|
214
|
+
| ffmpeg | Video/audio processing | Upstream |
|
|
215
|
+
| ripgrep | Fast text search | Upstream |
|
|
216
|
+
| supabase | Supabase CLI | npm |
|
|
217
|
+
| vercel | Vercel CLI | npm |
|
|
218
|
+
| uv | Python package manager | Upstream |
|
|
219
|
+
|
|
220
|
+
## Troubleshooting
|
|
221
|
+
|
|
222
|
+
### Command not found after install
|
|
223
|
+
|
|
224
|
+
Ensure `~/.rudi/bins` is in your PATH:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
echo $PATH | grep -q '.rudi/bins' && echo "OK" || echo "Add ~/.rudi/bins to PATH"
|
|
163
228
|
```
|
|
164
229
|
|
|
165
|
-
|
|
230
|
+
### Shim points to missing target
|
|
166
231
|
|
|
167
|
-
|
|
232
|
+
Run `rudi shims check` to validate all shims. If a target is missing, reinstall the package:
|
|
168
233
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
| notion-workspace | Pages, databases, search |
|
|
174
|
-
| google-ai | Gemini, Imagen, Veo |
|
|
175
|
-
| openai | DALL-E, Whisper, TTS, Sora |
|
|
176
|
-
| postgres | PostgreSQL database queries |
|
|
177
|
-
| video-editor | ffmpeg-based video editing |
|
|
178
|
-
| github | Issues, PRs, repos, actions |
|
|
179
|
-
| stripe | Payments, subscriptions, invoices |
|
|
234
|
+
```bash
|
|
235
|
+
rudi remove npm:typescript
|
|
236
|
+
rudi install npm:typescript
|
|
237
|
+
```
|
|
180
238
|
|
|
181
|
-
###
|
|
239
|
+
### MCP stack not appearing in agent
|
|
182
240
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| ripgrep | Fast search |
|
|
187
|
-
| supabase | Supabase CLI |
|
|
188
|
-
| vercel | Vercel CLI |
|
|
189
|
-
| uv | Fast Python package manager |
|
|
241
|
+
1. Check the stack is installed: `rudi list stacks`
|
|
242
|
+
2. Run integration: `rudi integrate claude`
|
|
243
|
+
3. Restart the AI agent application
|
|
190
244
|
|
|
191
|
-
###
|
|
245
|
+
### Permission denied on secrets
|
|
192
246
|
|
|
193
|
-
|
|
247
|
+
Ensure correct permissions:
|
|
194
248
|
|
|
195
249
|
```bash
|
|
196
|
-
|
|
197
|
-
rudi install npm:cowsay # cowsay, cowthink
|
|
198
|
-
rudi install npm:@stripe/cli # stripe
|
|
199
|
-
rudi install npm:netlify-cli # netlify
|
|
250
|
+
chmod 600 ~/.rudi/secrets.json
|
|
200
251
|
```
|
|
201
252
|
|
|
202
253
|
## Links
|
|
203
254
|
|
|
204
|
-
-
|
|
255
|
+
- Documentation: https://learn-rudi.github.io/cli/
|
|
256
|
+
- Repository: https://github.com/learn-rudi/cli
|
|
205
257
|
- Registry: https://github.com/learn-rudi/registry
|
|
258
|
+
- npm: https://www.npmjs.com/package/@learnrudi/cli
|
|
206
259
|
- Issues: https://github.com/learn-rudi/cli/issues
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1016,8 +1016,7 @@ async function resolvePackage(id) {
|
|
|
1016
1016
|
if (id.startsWith("npm:")) {
|
|
1017
1017
|
return resolveDynamicNpm(id);
|
|
1018
1018
|
}
|
|
1019
|
-
const
|
|
1020
|
-
const pkg = await getPackage(normalizedId);
|
|
1019
|
+
const pkg = await getPackage(id);
|
|
1021
1020
|
if (!pkg) {
|
|
1022
1021
|
throw new Error(`Package not found: ${id}`);
|
|
1023
1022
|
}
|
|
@@ -17303,6 +17302,7 @@ __export(sqlite_exports, {
|
|
|
17303
17302
|
clearEmbeddings: () => clearEmbeddings,
|
|
17304
17303
|
deleteEmbedding: () => deleteEmbedding,
|
|
17305
17304
|
ensureEmbeddingsSchema: () => ensureEmbeddingsSchema,
|
|
17305
|
+
getAllEmbeddingStats: () => getAllEmbeddingStats,
|
|
17306
17306
|
getEmbeddingStats: () => getEmbeddingStats,
|
|
17307
17307
|
getErrorTurns: () => getErrorTurns,
|
|
17308
17308
|
getMissingTurns: () => getMissingTurns,
|
|
@@ -17472,6 +17472,38 @@ function getEmbeddingStats(model) {
|
|
|
17472
17472
|
}
|
|
17473
17473
|
return stats;
|
|
17474
17474
|
}
|
|
17475
|
+
function getAllEmbeddingStats() {
|
|
17476
|
+
const db3 = getDb2();
|
|
17477
|
+
const totalStmt = db3.prepare(`
|
|
17478
|
+
SELECT COUNT(*) as count
|
|
17479
|
+
FROM turns
|
|
17480
|
+
WHERE (user_message IS NOT NULL AND length(trim(user_message)) > 0)
|
|
17481
|
+
OR (assistant_response IS NOT NULL AND length(trim(assistant_response)) > 0)
|
|
17482
|
+
`);
|
|
17483
|
+
const total = totalStmt.get().count;
|
|
17484
|
+
const statsStmt = db3.prepare(`
|
|
17485
|
+
SELECT
|
|
17486
|
+
status,
|
|
17487
|
+
COUNT(*) as count
|
|
17488
|
+
FROM turn_embeddings
|
|
17489
|
+
GROUP BY status
|
|
17490
|
+
`);
|
|
17491
|
+
const stats = { total, done: 0, queued: 0, error: 0 };
|
|
17492
|
+
for (const row of statsStmt.all()) {
|
|
17493
|
+
stats[row.status] = row.count;
|
|
17494
|
+
}
|
|
17495
|
+
const modelsStmt = db3.prepare(`
|
|
17496
|
+
SELECT model, dimensions, COUNT(*) as count
|
|
17497
|
+
FROM turn_embeddings
|
|
17498
|
+
WHERE status = 'done'
|
|
17499
|
+
GROUP BY model, dimensions
|
|
17500
|
+
`);
|
|
17501
|
+
stats.models = {};
|
|
17502
|
+
for (const row of modelsStmt.all()) {
|
|
17503
|
+
stats.models[row.model] = { dimensions: row.dimensions, count: row.count };
|
|
17504
|
+
}
|
|
17505
|
+
return stats;
|
|
17506
|
+
}
|
|
17475
17507
|
function deleteEmbedding(turnId) {
|
|
17476
17508
|
const db3 = getDb2();
|
|
17477
17509
|
db3.prepare("DELETE FROM turn_embeddings WHERE turn_id = ?").run(turnId);
|
|
@@ -35517,6 +35549,7 @@ function dbTables(flags) {
|
|
|
35517
35549
|
}
|
|
35518
35550
|
|
|
35519
35551
|
// src/commands/session.js
|
|
35552
|
+
var import_readline2 = require("readline");
|
|
35520
35553
|
var embeddingsModule = null;
|
|
35521
35554
|
async function getEmbeddings() {
|
|
35522
35555
|
if (!embeddingsModule) {
|
|
@@ -35524,6 +35557,92 @@ async function getEmbeddings() {
|
|
|
35524
35557
|
}
|
|
35525
35558
|
return embeddingsModule;
|
|
35526
35559
|
}
|
|
35560
|
+
async function confirm(message) {
|
|
35561
|
+
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
35562
|
+
return new Promise((resolve) => {
|
|
35563
|
+
rl.question(`${message} [Y/n] `, (answer) => {
|
|
35564
|
+
rl.close();
|
|
35565
|
+
resolve(answer.toLowerCase() !== "n");
|
|
35566
|
+
});
|
|
35567
|
+
});
|
|
35568
|
+
}
|
|
35569
|
+
async function ensureEmbeddingProvider(preferredProvider = "auto", options = {}) {
|
|
35570
|
+
const { checkProviderStatus: checkProviderStatus2, getProvider: getProvider2 } = await getEmbeddings();
|
|
35571
|
+
const status = await checkProviderStatus2();
|
|
35572
|
+
if (preferredProvider === "openai") {
|
|
35573
|
+
if (status.openai.configured) {
|
|
35574
|
+
return await getProvider2("openai");
|
|
35575
|
+
}
|
|
35576
|
+
console.log("OpenAI not configured. Set OPENAI_API_KEY environment variable.");
|
|
35577
|
+
return null;
|
|
35578
|
+
}
|
|
35579
|
+
try {
|
|
35580
|
+
return await getProvider2("auto");
|
|
35581
|
+
} catch {
|
|
35582
|
+
}
|
|
35583
|
+
if (status.openai.configured) {
|
|
35584
|
+
console.log("\nOllama not available. OpenAI is configured.");
|
|
35585
|
+
const useOpenAI = await confirm("Use OpenAI for embeddings? (costs ~$0.02/1M tokens)");
|
|
35586
|
+
if (useOpenAI) {
|
|
35587
|
+
return await getProvider2("openai");
|
|
35588
|
+
}
|
|
35589
|
+
}
|
|
35590
|
+
console.log("\nNo embedding provider available.\n");
|
|
35591
|
+
console.log("Options:");
|
|
35592
|
+
console.log(" [1] Install Ollama (recommended - free, local, works offline)");
|
|
35593
|
+
console.log(" [2] Use OpenAI (requires OPENAI_API_KEY)");
|
|
35594
|
+
console.log(" [3] Cancel\n");
|
|
35595
|
+
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
35596
|
+
const choice = await new Promise((resolve) => {
|
|
35597
|
+
rl.question("Choice [1]: ", (answer) => {
|
|
35598
|
+
rl.close();
|
|
35599
|
+
resolve(answer || "1");
|
|
35600
|
+
});
|
|
35601
|
+
});
|
|
35602
|
+
if (choice === "3" || choice.toLowerCase() === "cancel") {
|
|
35603
|
+
return null;
|
|
35604
|
+
}
|
|
35605
|
+
if (choice === "2") {
|
|
35606
|
+
if (!status.openai.configured) {
|
|
35607
|
+
console.log("\nOpenAI not configured.");
|
|
35608
|
+
console.log("Set: export OPENAI_API_KEY=your-key");
|
|
35609
|
+
return null;
|
|
35610
|
+
}
|
|
35611
|
+
return await getProvider2("openai");
|
|
35612
|
+
}
|
|
35613
|
+
console.log("\nInstalling Ollama...");
|
|
35614
|
+
try {
|
|
35615
|
+
const { installPackage: installPackage2 } = await Promise.resolve().then(() => (init_src4(), src_exports2));
|
|
35616
|
+
await installPackage2("runtime:ollama", {
|
|
35617
|
+
onProgress: (p2) => {
|
|
35618
|
+
if (p2.phase === "downloading") process.stdout.write("\r Downloading...");
|
|
35619
|
+
if (p2.phase === "extracting") process.stdout.write("\r Installing... ");
|
|
35620
|
+
}
|
|
35621
|
+
});
|
|
35622
|
+
console.log("\r \u2713 Ollama installed ");
|
|
35623
|
+
console.log(" Starting ollama serve...");
|
|
35624
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
35625
|
+
const server = spawn4("ollama", ["serve"], {
|
|
35626
|
+
detached: true,
|
|
35627
|
+
stdio: "ignore",
|
|
35628
|
+
env: { ...process.env, HOME: process.env.HOME }
|
|
35629
|
+
});
|
|
35630
|
+
server.unref();
|
|
35631
|
+
await new Promise((r2) => setTimeout(r2, 2e3));
|
|
35632
|
+
console.log(" Pulling nomic-embed-text model (274MB)...");
|
|
35633
|
+
const { execSync: execSync10 } = await import("child_process");
|
|
35634
|
+
execSync10("ollama pull nomic-embed-text", { stdio: "inherit" });
|
|
35635
|
+
console.log(" \u2713 Model ready\n");
|
|
35636
|
+
return await getProvider2("ollama");
|
|
35637
|
+
} catch (err) {
|
|
35638
|
+
console.error("\nSetup failed:", err.message);
|
|
35639
|
+
console.log("\nManual setup:");
|
|
35640
|
+
console.log(" rudi install ollama");
|
|
35641
|
+
console.log(" ollama serve");
|
|
35642
|
+
console.log(" ollama pull nomic-embed-text");
|
|
35643
|
+
return null;
|
|
35644
|
+
}
|
|
35645
|
+
}
|
|
35527
35646
|
async function cmdSession(args, flags) {
|
|
35528
35647
|
const subcommand = args[0];
|
|
35529
35648
|
switch (subcommand) {
|
|
@@ -35560,6 +35679,9 @@ async function cmdSession(args, flags) {
|
|
|
35560
35679
|
case "setup":
|
|
35561
35680
|
await sessionSetup(flags);
|
|
35562
35681
|
break;
|
|
35682
|
+
case "organize":
|
|
35683
|
+
await sessionOrganize(flags);
|
|
35684
|
+
break;
|
|
35563
35685
|
default:
|
|
35564
35686
|
console.log(`
|
|
35565
35687
|
rudi session - Manage RUDI sessions
|
|
@@ -35579,6 +35701,9 @@ SEMANTIC SEARCH
|
|
|
35579
35701
|
index [--embeddings] [--provider X] Index sessions for semantic search
|
|
35580
35702
|
similar <id> [--limit] Find similar sessions
|
|
35581
35703
|
|
|
35704
|
+
ORGANIZATION (batch operations)
|
|
35705
|
+
organize [--dry-run] [--out plan.json] Auto-organize sessions into projects
|
|
35706
|
+
|
|
35582
35707
|
OPTIONS
|
|
35583
35708
|
--provider <name> Filter by provider (claude, codex, gemini)
|
|
35584
35709
|
--project <name> Filter by project name
|
|
@@ -35926,8 +36051,12 @@ Found ${results.length} result(s) for "${query}":
|
|
|
35926
36051
|
async function semanticSearch(query, options) {
|
|
35927
36052
|
const { limit: limit2, format } = options;
|
|
35928
36053
|
try {
|
|
35929
|
-
const { createClient: createClient2
|
|
35930
|
-
const
|
|
36054
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36055
|
+
const result = await ensureEmbeddingProvider("auto");
|
|
36056
|
+
if (!result) {
|
|
36057
|
+
return;
|
|
36058
|
+
}
|
|
36059
|
+
const { provider, model } = result;
|
|
35931
36060
|
console.log(`Using ${provider.id} with ${model.name}`);
|
|
35932
36061
|
const client = createClient2({ provider, model });
|
|
35933
36062
|
const stats = client.getStats();
|
|
@@ -35976,18 +36105,25 @@ async function sessionIndex(flags) {
|
|
|
35976
36105
|
const providerName = flags.provider || "auto";
|
|
35977
36106
|
if (!flags.embeddings) {
|
|
35978
36107
|
try {
|
|
35979
|
-
const {
|
|
35980
|
-
const
|
|
35981
|
-
const stats = store.getEmbeddingStats(model);
|
|
36108
|
+
const { store } = await getEmbeddings();
|
|
36109
|
+
const stats = store.getAllEmbeddingStats();
|
|
35982
36110
|
const pct = stats.total > 0 ? (stats.done / stats.total * 100).toFixed(1) : 0;
|
|
35983
36111
|
console.log("\nEmbedding Index Status:");
|
|
35984
36112
|
console.log(` Total turns: ${stats.total}`);
|
|
35985
36113
|
console.log(` Indexed: ${stats.done} (${pct}%)`);
|
|
35986
36114
|
console.log(` Queued: ${stats.queued}`);
|
|
35987
36115
|
console.log(` Errors: ${stats.error}`);
|
|
35988
|
-
|
|
35989
|
-
|
|
35990
|
-
|
|
36116
|
+
if (Object.keys(stats.models).length > 0) {
|
|
36117
|
+
console.log("\nIndexed by model:");
|
|
36118
|
+
for (const [model, info] of Object.entries(stats.models)) {
|
|
36119
|
+
console.log(` ${model} (${info.dimensions}d): ${info.count} turns`);
|
|
36120
|
+
}
|
|
36121
|
+
}
|
|
36122
|
+
if (stats.done < stats.total) {
|
|
36123
|
+
console.log("\nTo index missing turns:");
|
|
36124
|
+
console.log(" rudi session index --embeddings");
|
|
36125
|
+
console.log(" rudi session index --embeddings --provider ollama");
|
|
36126
|
+
}
|
|
35991
36127
|
} catch (err) {
|
|
35992
36128
|
console.log("Embedding status unavailable:", err.message);
|
|
35993
36129
|
}
|
|
@@ -35995,8 +36131,12 @@ async function sessionIndex(flags) {
|
|
|
35995
36131
|
}
|
|
35996
36132
|
console.log("Indexing sessions for semantic search...\n");
|
|
35997
36133
|
try {
|
|
35998
|
-
const { createClient: createClient2
|
|
35999
|
-
const
|
|
36134
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36135
|
+
const providerResult = await ensureEmbeddingProvider(providerName);
|
|
36136
|
+
if (!providerResult) {
|
|
36137
|
+
return;
|
|
36138
|
+
}
|
|
36139
|
+
const { provider, model } = providerResult;
|
|
36000
36140
|
console.log(`Provider: ${provider.id}`);
|
|
36001
36141
|
console.log(`Model: ${model.name} (${model.dimensions}d)
|
|
36002
36142
|
`);
|
|
@@ -36057,8 +36197,12 @@ async function sessionSimilar(args, flags) {
|
|
|
36057
36197
|
const format = flags.format || "table";
|
|
36058
36198
|
const providerName = flags.provider || "auto";
|
|
36059
36199
|
try {
|
|
36060
|
-
const { createClient: createClient2
|
|
36061
|
-
const
|
|
36200
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36201
|
+
const result = await ensureEmbeddingProvider(providerName);
|
|
36202
|
+
if (!result) {
|
|
36203
|
+
return;
|
|
36204
|
+
}
|
|
36205
|
+
const { provider, model } = result;
|
|
36062
36206
|
const client = createClient2({ provider, model });
|
|
36063
36207
|
const results = await client.findSimilar(turnId, { limit: limit2 });
|
|
36064
36208
|
if (format === "json") {
|
|
@@ -36107,6 +36251,223 @@ async function sessionSetup(flags) {
|
|
|
36107
36251
|
console.error("Setup error:", err.message);
|
|
36108
36252
|
}
|
|
36109
36253
|
}
|
|
36254
|
+
async function sessionOrganize(flags) {
|
|
36255
|
+
if (!isDatabaseInitialized()) {
|
|
36256
|
+
console.log("Database not initialized.");
|
|
36257
|
+
return;
|
|
36258
|
+
}
|
|
36259
|
+
const dryRun = flags["dry-run"] || flags.dryRun || true;
|
|
36260
|
+
const outputFile = flags.out || flags.output || "organize-plan.json";
|
|
36261
|
+
const threshold = parseFloat(flags.threshold) || 0.65;
|
|
36262
|
+
const db3 = getDb();
|
|
36263
|
+
console.log("\u2550".repeat(60));
|
|
36264
|
+
console.log("Session Organization");
|
|
36265
|
+
console.log("\u2550".repeat(60));
|
|
36266
|
+
console.log(`Mode: ${dryRun ? "Dry run (preview only)" : "LIVE - will apply changes"}`);
|
|
36267
|
+
console.log(`Output: ${outputFile}`);
|
|
36268
|
+
console.log(`Similarity threshold: ${(threshold * 100).toFixed(0)}%`);
|
|
36269
|
+
console.log("\u2550".repeat(60));
|
|
36270
|
+
const sessions = db3.prepare(`
|
|
36271
|
+
SELECT
|
|
36272
|
+
s.id, s.provider, s.title, s.title_override, s.project_id, s.cwd,
|
|
36273
|
+
s.turn_count, s.total_cost, s.created_at, s.last_active_at,
|
|
36274
|
+
p.name as project_name
|
|
36275
|
+
FROM sessions s
|
|
36276
|
+
LEFT JOIN projects p ON s.project_id = p.id
|
|
36277
|
+
WHERE s.status = 'active'
|
|
36278
|
+
ORDER BY s.total_cost DESC
|
|
36279
|
+
`).all();
|
|
36280
|
+
console.log(`
|
|
36281
|
+
Analyzing ${sessions.length} sessions...
|
|
36282
|
+
`);
|
|
36283
|
+
const projects = db3.prepare("SELECT id, name FROM projects").all();
|
|
36284
|
+
const projectMap = new Map(projects.map((p2) => [p2.name.toLowerCase(), p2]));
|
|
36285
|
+
console.log(`Existing projects: ${projects.map((p2) => p2.name).join(", ") || "(none)"}
|
|
36286
|
+
`);
|
|
36287
|
+
const cwdGroups = /* @__PURE__ */ new Map();
|
|
36288
|
+
for (const s2 of sessions) {
|
|
36289
|
+
if (!s2.cwd) continue;
|
|
36290
|
+
const match = s2.cwd.match(/\/([^/]+)$/);
|
|
36291
|
+
const projectKey = match ? match[1] : "other";
|
|
36292
|
+
if (!cwdGroups.has(projectKey)) {
|
|
36293
|
+
cwdGroups.set(projectKey, []);
|
|
36294
|
+
}
|
|
36295
|
+
cwdGroups.get(projectKey).push(s2);
|
|
36296
|
+
}
|
|
36297
|
+
const genericTitlePatterns = [
|
|
36298
|
+
/^(Imported|Agent|New|Untitled|Chat) Session$/i,
|
|
36299
|
+
/^Session \d+$/i,
|
|
36300
|
+
/^Untitled$/i,
|
|
36301
|
+
/^[A-Z][a-z]+ [A-Z][a-z]+ [A-Z][a-z]+$/
|
|
36302
|
+
// "Adjective Verb Noun" (Claude auto-generated)
|
|
36303
|
+
];
|
|
36304
|
+
const sessionsNeedingTitles = sessions.filter((s2) => {
|
|
36305
|
+
if (s2.title_override && s2.title_override !== s2.title) {
|
|
36306
|
+
return false;
|
|
36307
|
+
}
|
|
36308
|
+
const title = s2.title || "";
|
|
36309
|
+
return !title || genericTitlePatterns.some((p2) => p2.test(title));
|
|
36310
|
+
});
|
|
36311
|
+
console.log(`Sessions with generic titles: ${sessionsNeedingTitles.length}`);
|
|
36312
|
+
const titleSuggestions = [];
|
|
36313
|
+
for (const s2 of sessionsNeedingTitles.slice(0, 100)) {
|
|
36314
|
+
const firstTurn = db3.prepare(`
|
|
36315
|
+
SELECT user_message
|
|
36316
|
+
FROM turns
|
|
36317
|
+
WHERE session_id = ? AND user_message IS NOT NULL AND length(trim(user_message)) > 10
|
|
36318
|
+
ORDER BY turn_number
|
|
36319
|
+
LIMIT 1
|
|
36320
|
+
`).get(s2.id);
|
|
36321
|
+
if (firstTurn && firstTurn.user_message) {
|
|
36322
|
+
const msg = firstTurn.user_message.trim();
|
|
36323
|
+
let suggestedTitle = msg.split("\n")[0].slice(0, 60).trim();
|
|
36324
|
+
const skipPatterns = [
|
|
36325
|
+
/^\/[A-Za-z]/,
|
|
36326
|
+
// Unix paths
|
|
36327
|
+
/^<[a-z-]+>/,
|
|
36328
|
+
// XML tags
|
|
36329
|
+
/^[A-Z]:\\[A-Za-z]/,
|
|
36330
|
+
// Windows paths
|
|
36331
|
+
/^(cd|ls|cat|npm|node|git|rudi|pnpm|yarn)\s/i,
|
|
36332
|
+
// Commands
|
|
36333
|
+
/^[a-f0-9-]{8,}/i,
|
|
36334
|
+
// UUIDs or hashes
|
|
36335
|
+
/^https?:\/\//i,
|
|
36336
|
+
// URLs
|
|
36337
|
+
/^[>\*\-#\d\.]\s/,
|
|
36338
|
+
// Markdown list/quote starts
|
|
36339
|
+
/^(yes|no|ok|sure|y|n)$/i,
|
|
36340
|
+
// Single word responses
|
|
36341
|
+
/^[^a-zA-Z]*$/,
|
|
36342
|
+
// No letters at all
|
|
36343
|
+
/^\s*\[/,
|
|
36344
|
+
// JSON/array starts
|
|
36345
|
+
/^\s*\{/
|
|
36346
|
+
// Object starts
|
|
36347
|
+
];
|
|
36348
|
+
if (skipPatterns.some((p2) => p2.test(suggestedTitle))) {
|
|
36349
|
+
continue;
|
|
36350
|
+
}
|
|
36351
|
+
const wordCount = suggestedTitle.split(/\s+/).length;
|
|
36352
|
+
if (wordCount < 3) {
|
|
36353
|
+
continue;
|
|
36354
|
+
}
|
|
36355
|
+
if (suggestedTitle.length > 50) {
|
|
36356
|
+
suggestedTitle = suggestedTitle.slice(0, 47) + "...";
|
|
36357
|
+
}
|
|
36358
|
+
if (suggestedTitle && suggestedTitle.length > 10) {
|
|
36359
|
+
titleSuggestions.push({
|
|
36360
|
+
sessionId: s2.id,
|
|
36361
|
+
currentTitle: s2.title || "(none)",
|
|
36362
|
+
suggestedTitle,
|
|
36363
|
+
cost: s2.total_cost,
|
|
36364
|
+
confidence: "medium"
|
|
36365
|
+
// Could add scoring later
|
|
36366
|
+
});
|
|
36367
|
+
}
|
|
36368
|
+
}
|
|
36369
|
+
}
|
|
36370
|
+
const projectSuggestions = [];
|
|
36371
|
+
const moveSuggestions = [];
|
|
36372
|
+
const knownProjects = {
|
|
36373
|
+
"studio": "Prompt Stack Studio",
|
|
36374
|
+
"prompt-stack": "Prompt Stack Studio",
|
|
36375
|
+
"RUDI": "RUDI",
|
|
36376
|
+
"rudi": "RUDI",
|
|
36377
|
+
"cli": "RUDI",
|
|
36378
|
+
"registry": "RUDI",
|
|
36379
|
+
"resonance": "Resonance",
|
|
36380
|
+
"cloud": "Cloud"
|
|
36381
|
+
};
|
|
36382
|
+
for (const [cwdKey, cwdSessions] of cwdGroups) {
|
|
36383
|
+
const projectName = knownProjects[cwdKey];
|
|
36384
|
+
if (projectName && cwdSessions.length >= 2) {
|
|
36385
|
+
const existingProject = projectMap.get(projectName.toLowerCase());
|
|
36386
|
+
for (const s2 of cwdSessions) {
|
|
36387
|
+
if (!s2.project_id || existingProject && s2.project_id !== existingProject.id) {
|
|
36388
|
+
moveSuggestions.push({
|
|
36389
|
+
sessionId: s2.id,
|
|
36390
|
+
sessionTitle: s2.title_override || s2.title,
|
|
36391
|
+
currentProject: s2.project_name || null,
|
|
36392
|
+
suggestedProject: projectName,
|
|
36393
|
+
reason: `Working directory: ${cwdKey}`,
|
|
36394
|
+
cost: s2.total_cost
|
|
36395
|
+
});
|
|
36396
|
+
}
|
|
36397
|
+
}
|
|
36398
|
+
if (!existingProject && cwdSessions.length >= 3) {
|
|
36399
|
+
projectSuggestions.push({
|
|
36400
|
+
name: projectName,
|
|
36401
|
+
sessionCount: cwdSessions.length,
|
|
36402
|
+
totalCost: cwdSessions.reduce((sum, s2) => sum + (s2.total_cost || 0), 0)
|
|
36403
|
+
});
|
|
36404
|
+
}
|
|
36405
|
+
}
|
|
36406
|
+
}
|
|
36407
|
+
const plan = {
|
|
36408
|
+
version: "1.0",
|
|
36409
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36410
|
+
dryRun,
|
|
36411
|
+
threshold,
|
|
36412
|
+
summary: {
|
|
36413
|
+
totalSessions: sessions.length,
|
|
36414
|
+
sessionsWithProjects: sessions.filter((s2) => s2.project_id).length,
|
|
36415
|
+
sessionsNeedingTitles: sessionsNeedingTitles.length,
|
|
36416
|
+
projectsToCreate: projectSuggestions.length,
|
|
36417
|
+
movesToApply: moveSuggestions.length,
|
|
36418
|
+
titlesToUpdate: titleSuggestions.length
|
|
36419
|
+
},
|
|
36420
|
+
actions: {
|
|
36421
|
+
createProjects: projectSuggestions,
|
|
36422
|
+
moveSessions: moveSuggestions.slice(0, 200),
|
|
36423
|
+
// Limit batch size
|
|
36424
|
+
updateTitles: titleSuggestions.slice(0, 100)
|
|
36425
|
+
// Limit batch size
|
|
36426
|
+
}
|
|
36427
|
+
};
|
|
36428
|
+
console.log("\n" + "\u2500".repeat(60));
|
|
36429
|
+
console.log("PLAN SUMMARY");
|
|
36430
|
+
console.log("\u2500".repeat(60));
|
|
36431
|
+
console.log(`Sessions analyzed: ${plan.summary.totalSessions}`);
|
|
36432
|
+
console.log(`Already in projects: ${plan.summary.sessionsWithProjects}`);
|
|
36433
|
+
console.log(`
|
|
36434
|
+
Proposed actions:`);
|
|
36435
|
+
console.log(` Create projects: ${plan.summary.projectsToCreate}`);
|
|
36436
|
+
console.log(` Move sessions: ${plan.summary.movesToApply}`);
|
|
36437
|
+
console.log(` Update titles: ${plan.summary.titlesToUpdate}`);
|
|
36438
|
+
if (projectSuggestions.length > 0) {
|
|
36439
|
+
console.log("\nProjects to create:");
|
|
36440
|
+
for (const p2 of projectSuggestions) {
|
|
36441
|
+
console.log(` \u2022 ${p2.name} (${p2.sessionCount} sessions, $${p2.totalCost.toFixed(2)})`);
|
|
36442
|
+
}
|
|
36443
|
+
}
|
|
36444
|
+
if (moveSuggestions.length > 0) {
|
|
36445
|
+
console.log("\nTop session moves:");
|
|
36446
|
+
for (const m2 of moveSuggestions.slice(0, 10)) {
|
|
36447
|
+
console.log(` \u2022 "${m2.sessionTitle?.slice(0, 30) || m2.sessionId.slice(0, 8)}..." \u2192 ${m2.suggestedProject}`);
|
|
36448
|
+
}
|
|
36449
|
+
if (moveSuggestions.length > 10) {
|
|
36450
|
+
console.log(` ... and ${moveSuggestions.length - 10} more`);
|
|
36451
|
+
}
|
|
36452
|
+
}
|
|
36453
|
+
if (titleSuggestions.length > 0) {
|
|
36454
|
+
console.log("\nTop title updates:");
|
|
36455
|
+
for (const t2 of titleSuggestions.slice(0, 5)) {
|
|
36456
|
+
console.log(` \u2022 "${t2.currentTitle?.slice(0, 20) || "(none)"}..." \u2192 "${t2.suggestedTitle.slice(0, 30)}..."`);
|
|
36457
|
+
}
|
|
36458
|
+
if (titleSuggestions.length > 5) {
|
|
36459
|
+
console.log(` ... and ${titleSuggestions.length - 5} more`);
|
|
36460
|
+
}
|
|
36461
|
+
}
|
|
36462
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
36463
|
+
writeFileSync7(outputFile, JSON.stringify(plan, null, 2));
|
|
36464
|
+
console.log(`
|
|
36465
|
+
\u2713 Plan saved to: ${outputFile}`);
|
|
36466
|
+
console.log("\nTo apply this plan:");
|
|
36467
|
+
console.log(` rudi apply ${outputFile}`);
|
|
36468
|
+
console.log("\nTo review the full plan:");
|
|
36469
|
+
console.log(` cat ${outputFile} | jq .`);
|
|
36470
|
+
}
|
|
36110
36471
|
|
|
36111
36472
|
// src/commands/import.js
|
|
36112
36473
|
var import_fs17 = require("fs");
|
|
@@ -39256,6 +39617,371 @@ Lockfile: ${lockPath}`);
|
|
|
39256
39617
|
}
|
|
39257
39618
|
}
|
|
39258
39619
|
|
|
39620
|
+
// src/commands/apply.js
|
|
39621
|
+
var import_fs28 = require("fs");
|
|
39622
|
+
var import_path26 = require("path");
|
|
39623
|
+
var import_os10 = require("os");
|
|
39624
|
+
var import_crypto3 = require("crypto");
|
|
39625
|
+
async function cmdApply(args, flags) {
|
|
39626
|
+
const planFile = args[0];
|
|
39627
|
+
const force = flags.force;
|
|
39628
|
+
const undoPlanId = flags.undo;
|
|
39629
|
+
const only = flags.only;
|
|
39630
|
+
if (undoPlanId) {
|
|
39631
|
+
return undoPlan(undoPlanId);
|
|
39632
|
+
}
|
|
39633
|
+
if (!planFile) {
|
|
39634
|
+
console.log(`
|
|
39635
|
+
rudi apply - Execute organization plans
|
|
39636
|
+
|
|
39637
|
+
USAGE
|
|
39638
|
+
rudi apply <plan.json> Apply a plan file
|
|
39639
|
+
rudi apply --undo <id> Undo a previously applied plan
|
|
39640
|
+
|
|
39641
|
+
OPTIONS
|
|
39642
|
+
--force Skip confirmation prompts
|
|
39643
|
+
--only <type> Apply only specific operations:
|
|
39644
|
+
move - session moves only
|
|
39645
|
+
rename - title updates only
|
|
39646
|
+
project - project creation only
|
|
39647
|
+
|
|
39648
|
+
EXAMPLES
|
|
39649
|
+
rudi session organize --dry-run --out plan.json
|
|
39650
|
+
rudi apply plan.json
|
|
39651
|
+
rudi apply plan.json --only move # Moves first (low regret)
|
|
39652
|
+
rudi apply plan.json --only rename # Renames second
|
|
39653
|
+
rudi apply --undo plan-20260109-abc123
|
|
39654
|
+
`);
|
|
39655
|
+
return;
|
|
39656
|
+
}
|
|
39657
|
+
if (!(0, import_fs28.existsSync)(planFile)) {
|
|
39658
|
+
console.error(`Plan file not found: ${planFile}`);
|
|
39659
|
+
process.exit(1);
|
|
39660
|
+
}
|
|
39661
|
+
if (!isDatabaseInitialized()) {
|
|
39662
|
+
console.error("Database not initialized. Run: rudi db init");
|
|
39663
|
+
process.exit(1);
|
|
39664
|
+
}
|
|
39665
|
+
let plan;
|
|
39666
|
+
try {
|
|
39667
|
+
plan = JSON.parse((0, import_fs28.readFileSync)(planFile, "utf-8"));
|
|
39668
|
+
} catch (err) {
|
|
39669
|
+
console.error(`Invalid plan file: ${err.message}`);
|
|
39670
|
+
process.exit(1);
|
|
39671
|
+
}
|
|
39672
|
+
if (!plan.version || !plan.actions) {
|
|
39673
|
+
console.error("Invalid plan format: missing version or actions");
|
|
39674
|
+
process.exit(1);
|
|
39675
|
+
}
|
|
39676
|
+
console.log("\u2550".repeat(60));
|
|
39677
|
+
console.log("Apply Organization Plan");
|
|
39678
|
+
console.log("\u2550".repeat(60));
|
|
39679
|
+
console.log(`Plan file: ${planFile}`);
|
|
39680
|
+
console.log(`Created: ${plan.createdAt}`);
|
|
39681
|
+
console.log("\u2550".repeat(60));
|
|
39682
|
+
let { createProjects = [], moveSessions = [], updateTitles = [] } = plan.actions;
|
|
39683
|
+
if (only) {
|
|
39684
|
+
console.log(`
|
|
39685
|
+
Filter: --only ${only}`);
|
|
39686
|
+
if (only === "move") {
|
|
39687
|
+
createProjects = [];
|
|
39688
|
+
updateTitles = [];
|
|
39689
|
+
} else if (only === "rename") {
|
|
39690
|
+
createProjects = [];
|
|
39691
|
+
moveSessions = [];
|
|
39692
|
+
} else if (only === "project") {
|
|
39693
|
+
moveSessions = [];
|
|
39694
|
+
updateTitles = [];
|
|
39695
|
+
} else {
|
|
39696
|
+
console.error(`Unknown filter: ${only}. Use: move, rename, project`);
|
|
39697
|
+
process.exit(1);
|
|
39698
|
+
}
|
|
39699
|
+
}
|
|
39700
|
+
console.log("\nActions to apply:");
|
|
39701
|
+
console.log(` Create projects: ${createProjects.length}`);
|
|
39702
|
+
console.log(` Move sessions: ${moveSessions.length}`);
|
|
39703
|
+
console.log(` Update titles: ${updateTitles.length}`);
|
|
39704
|
+
const totalActions = createProjects.length + moveSessions.length + updateTitles.length;
|
|
39705
|
+
if (totalActions === 0) {
|
|
39706
|
+
console.log("\nNo actions to apply (filtered out or empty).");
|
|
39707
|
+
return;
|
|
39708
|
+
}
|
|
39709
|
+
if (!force) {
|
|
39710
|
+
console.log("\nThis will modify your database.");
|
|
39711
|
+
console.log("Add --force to skip this confirmation.\n");
|
|
39712
|
+
const readline3 = await import("readline");
|
|
39713
|
+
const rl = readline3.createInterface({
|
|
39714
|
+
input: process.stdin,
|
|
39715
|
+
output: process.stdout
|
|
39716
|
+
});
|
|
39717
|
+
const answer = await new Promise((resolve) => {
|
|
39718
|
+
rl.question("Apply this plan? (y/N): ", resolve);
|
|
39719
|
+
});
|
|
39720
|
+
rl.close();
|
|
39721
|
+
if (answer.toLowerCase() !== "y") {
|
|
39722
|
+
console.log("Cancelled.");
|
|
39723
|
+
return;
|
|
39724
|
+
}
|
|
39725
|
+
}
|
|
39726
|
+
const db3 = getDb();
|
|
39727
|
+
const planId = `plan-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}-${(0, import_crypto3.randomUUID)().slice(0, 6)}`;
|
|
39728
|
+
const undoActions = [];
|
|
39729
|
+
console.log(`
|
|
39730
|
+
Applying plan ${planId}...
|
|
39731
|
+
`);
|
|
39732
|
+
if (createProjects.length > 0) {
|
|
39733
|
+
console.log("Creating projects...");
|
|
39734
|
+
const insertProject = db3.prepare(`
|
|
39735
|
+
INSERT OR IGNORE INTO projects (id, provider, name, created_at)
|
|
39736
|
+
VALUES (?, 'claude', ?, datetime('now'))
|
|
39737
|
+
`);
|
|
39738
|
+
for (const p2 of createProjects) {
|
|
39739
|
+
const projectId = `proj-${p2.name.toLowerCase().replace(/\s+/g, "-")}`;
|
|
39740
|
+
try {
|
|
39741
|
+
insertProject.run(projectId, p2.name);
|
|
39742
|
+
console.log(` \u2713 Created: ${p2.name}`);
|
|
39743
|
+
undoActions.push({ type: "deleteProject", projectId, name: p2.name });
|
|
39744
|
+
} catch (err) {
|
|
39745
|
+
console.log(` \u26A0 Skipped (exists): ${p2.name}`);
|
|
39746
|
+
}
|
|
39747
|
+
}
|
|
39748
|
+
}
|
|
39749
|
+
if (moveSessions.length > 0) {
|
|
39750
|
+
console.log("\nMoving sessions...");
|
|
39751
|
+
const projectIds = /* @__PURE__ */ new Map();
|
|
39752
|
+
const projects = db3.prepare("SELECT id, name FROM projects").all();
|
|
39753
|
+
for (const p2 of projects) {
|
|
39754
|
+
projectIds.set(p2.name.toLowerCase(), p2.id);
|
|
39755
|
+
}
|
|
39756
|
+
const updateSession = db3.prepare(`
|
|
39757
|
+
UPDATE sessions SET project_id = ? WHERE id = ?
|
|
39758
|
+
`);
|
|
39759
|
+
let moved = 0;
|
|
39760
|
+
for (const m2 of moveSessions) {
|
|
39761
|
+
const projectId = projectIds.get(m2.suggestedProject.toLowerCase());
|
|
39762
|
+
if (!projectId) {
|
|
39763
|
+
console.log(` \u26A0 Project not found: ${m2.suggestedProject}`);
|
|
39764
|
+
continue;
|
|
39765
|
+
}
|
|
39766
|
+
const current = db3.prepare("SELECT project_id FROM sessions WHERE id = ?").get(m2.sessionId);
|
|
39767
|
+
try {
|
|
39768
|
+
updateSession.run(projectId, m2.sessionId);
|
|
39769
|
+
moved++;
|
|
39770
|
+
undoActions.push({
|
|
39771
|
+
type: "moveSession",
|
|
39772
|
+
sessionId: m2.sessionId,
|
|
39773
|
+
fromProject: current?.project_id,
|
|
39774
|
+
toProject: projectId
|
|
39775
|
+
});
|
|
39776
|
+
} catch (err) {
|
|
39777
|
+
console.log(` \u26A0 Failed: ${m2.sessionId} - ${err.message}`);
|
|
39778
|
+
}
|
|
39779
|
+
}
|
|
39780
|
+
console.log(` \u2713 Moved ${moved} sessions`);
|
|
39781
|
+
}
|
|
39782
|
+
if (updateTitles.length > 0) {
|
|
39783
|
+
console.log("\nUpdating titles...");
|
|
39784
|
+
const updateTitle = db3.prepare(`
|
|
39785
|
+
UPDATE sessions SET title = ?, title_override = ? WHERE id = ?
|
|
39786
|
+
`);
|
|
39787
|
+
let updated = 0;
|
|
39788
|
+
for (const t2 of updateTitles) {
|
|
39789
|
+
const current = db3.prepare("SELECT title, title_override FROM sessions WHERE id = ?").get(t2.sessionId);
|
|
39790
|
+
try {
|
|
39791
|
+
updateTitle.run(t2.suggestedTitle, t2.suggestedTitle, t2.sessionId);
|
|
39792
|
+
updated++;
|
|
39793
|
+
undoActions.push({
|
|
39794
|
+
type: "updateTitle",
|
|
39795
|
+
sessionId: t2.sessionId,
|
|
39796
|
+
fromTitle: current?.title,
|
|
39797
|
+
fromTitleOverride: current?.title_override,
|
|
39798
|
+
toTitle: t2.suggestedTitle
|
|
39799
|
+
});
|
|
39800
|
+
} catch (err) {
|
|
39801
|
+
console.log(` \u26A0 Failed: ${t2.sessionId} - ${err.message}`);
|
|
39802
|
+
}
|
|
39803
|
+
}
|
|
39804
|
+
console.log(` \u2713 Updated ${updated} titles`);
|
|
39805
|
+
}
|
|
39806
|
+
const undoDir = (0, import_path26.join)((0, import_os10.homedir)(), ".rudi", "plans");
|
|
39807
|
+
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
39808
|
+
try {
|
|
39809
|
+
mkdirSync5(undoDir, { recursive: true });
|
|
39810
|
+
} catch (e2) {
|
|
39811
|
+
}
|
|
39812
|
+
const undoFile = (0, import_path26.join)(undoDir, `${planId}.undo.json`);
|
|
39813
|
+
const undoPlan = {
|
|
39814
|
+
planId,
|
|
39815
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39816
|
+
sourceFile: planFile,
|
|
39817
|
+
actions: undoActions
|
|
39818
|
+
};
|
|
39819
|
+
(0, import_fs28.writeFileSync)(undoFile, JSON.stringify(undoPlan, null, 2));
|
|
39820
|
+
console.log("\n" + "\u2550".repeat(60));
|
|
39821
|
+
console.log("Plan applied successfully!");
|
|
39822
|
+
console.log("\u2550".repeat(60));
|
|
39823
|
+
console.log(`Plan ID: ${planId}`);
|
|
39824
|
+
console.log(`Undo file: ${undoFile}`);
|
|
39825
|
+
console.log(`
|
|
39826
|
+
To undo: rudi apply --undo ${planId}`);
|
|
39827
|
+
}
|
|
39828
|
+
|
|
39829
|
+
// src/commands/project.js
|
|
39830
|
+
async function cmdProject(args, flags) {
|
|
39831
|
+
const subcommand = args[0];
|
|
39832
|
+
switch (subcommand) {
|
|
39833
|
+
case "list":
|
|
39834
|
+
case "ls":
|
|
39835
|
+
projectList(flags);
|
|
39836
|
+
break;
|
|
39837
|
+
case "create":
|
|
39838
|
+
case "add":
|
|
39839
|
+
projectCreate(args.slice(1), flags);
|
|
39840
|
+
break;
|
|
39841
|
+
case "rename":
|
|
39842
|
+
projectRename(args.slice(1), flags);
|
|
39843
|
+
break;
|
|
39844
|
+
case "delete":
|
|
39845
|
+
case "rm":
|
|
39846
|
+
projectDelete(args.slice(1), flags);
|
|
39847
|
+
break;
|
|
39848
|
+
default:
|
|
39849
|
+
console.log(`
|
|
39850
|
+
rudi project - Manage session projects
|
|
39851
|
+
|
|
39852
|
+
COMMANDS
|
|
39853
|
+
list List all projects
|
|
39854
|
+
create <name> Create a new project
|
|
39855
|
+
rename <id> <new-name> Rename a project
|
|
39856
|
+
delete <id> Delete a project (sessions become unassigned)
|
|
39857
|
+
|
|
39858
|
+
OPTIONS
|
|
39859
|
+
--provider <name> Provider (claude, codex, gemini). Default: claude
|
|
39860
|
+
|
|
39861
|
+
EXAMPLES
|
|
39862
|
+
rudi project list
|
|
39863
|
+
rudi project create "RUDI CLI"
|
|
39864
|
+
rudi project rename proj-rudi "RUDI Tooling"
|
|
39865
|
+
rudi project delete proj-old
|
|
39866
|
+
`);
|
|
39867
|
+
}
|
|
39868
|
+
}
|
|
39869
|
+
function projectList(flags) {
|
|
39870
|
+
if (!isDatabaseInitialized()) {
|
|
39871
|
+
console.log("Database not initialized. Run: rudi db init");
|
|
39872
|
+
return;
|
|
39873
|
+
}
|
|
39874
|
+
const db3 = getDb();
|
|
39875
|
+
const provider = flags.provider;
|
|
39876
|
+
let query = `
|
|
39877
|
+
SELECT
|
|
39878
|
+
p.id, p.provider, p.name, p.color, p.created_at,
|
|
39879
|
+
COUNT(s.id) as session_count,
|
|
39880
|
+
ROUND(SUM(s.total_cost), 2) as total_cost
|
|
39881
|
+
FROM projects p
|
|
39882
|
+
LEFT JOIN sessions s ON s.project_id = p.id
|
|
39883
|
+
`;
|
|
39884
|
+
if (provider) {
|
|
39885
|
+
query += ` WHERE p.provider = '${provider}'`;
|
|
39886
|
+
}
|
|
39887
|
+
query += ` GROUP BY p.id ORDER BY total_cost DESC`;
|
|
39888
|
+
const projects = db3.prepare(query).all();
|
|
39889
|
+
if (projects.length === 0) {
|
|
39890
|
+
console.log("No projects found.");
|
|
39891
|
+
console.log('\nCreate one with: rudi project create "My Project"');
|
|
39892
|
+
return;
|
|
39893
|
+
}
|
|
39894
|
+
console.log(`
|
|
39895
|
+
Projects (${projects.length}):
|
|
39896
|
+
`);
|
|
39897
|
+
for (const p2 of projects) {
|
|
39898
|
+
console.log(`${p2.name}`);
|
|
39899
|
+
console.log(` ID: ${p2.id}`);
|
|
39900
|
+
console.log(` Provider: ${p2.provider}`);
|
|
39901
|
+
console.log(` Sessions: ${p2.session_count || 0}`);
|
|
39902
|
+
console.log(` Total cost: $${p2.total_cost || 0}`);
|
|
39903
|
+
console.log("");
|
|
39904
|
+
}
|
|
39905
|
+
}
|
|
39906
|
+
function projectCreate(args, flags) {
|
|
39907
|
+
if (!isDatabaseInitialized()) {
|
|
39908
|
+
console.log("Database not initialized. Run: rudi db init");
|
|
39909
|
+
return;
|
|
39910
|
+
}
|
|
39911
|
+
const name = args.join(" ");
|
|
39912
|
+
if (!name) {
|
|
39913
|
+
console.log("Error: Project name required");
|
|
39914
|
+
console.log('Usage: rudi project create "Project Name"');
|
|
39915
|
+
return;
|
|
39916
|
+
}
|
|
39917
|
+
const provider = flags.provider || "claude";
|
|
39918
|
+
const id = `proj-${name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")}`;
|
|
39919
|
+
const db3 = getDb();
|
|
39920
|
+
try {
|
|
39921
|
+
db3.prepare(`
|
|
39922
|
+
INSERT INTO projects (id, provider, name, created_at)
|
|
39923
|
+
VALUES (?, ?, ?, datetime('now'))
|
|
39924
|
+
`).run(id, provider, name);
|
|
39925
|
+
console.log(`
|
|
39926
|
+
Project created:`);
|
|
39927
|
+
console.log(` ID: ${id}`);
|
|
39928
|
+
console.log(` Name: ${name}`);
|
|
39929
|
+
console.log(` Provider: ${provider}`);
|
|
39930
|
+
} catch (err) {
|
|
39931
|
+
if (err.message.includes("UNIQUE")) {
|
|
39932
|
+
console.log(`Error: Project "${name}" already exists for ${provider}`);
|
|
39933
|
+
} else {
|
|
39934
|
+
console.log(`Error: ${err.message}`);
|
|
39935
|
+
}
|
|
39936
|
+
}
|
|
39937
|
+
}
|
|
39938
|
+
function projectRename(args, flags) {
|
|
39939
|
+
if (!isDatabaseInitialized()) {
|
|
39940
|
+
console.log("Database not initialized.");
|
|
39941
|
+
return;
|
|
39942
|
+
}
|
|
39943
|
+
const [id, ...nameParts] = args;
|
|
39944
|
+
const newName = nameParts.join(" ");
|
|
39945
|
+
if (!id || !newName) {
|
|
39946
|
+
console.log("Error: Project ID and new name required");
|
|
39947
|
+
console.log('Usage: rudi project rename <id> "New Name"');
|
|
39948
|
+
return;
|
|
39949
|
+
}
|
|
39950
|
+
const db3 = getDb();
|
|
39951
|
+
const result = db3.prepare("UPDATE projects SET name = ? WHERE id = ?").run(newName, id);
|
|
39952
|
+
if (result.changes === 0) {
|
|
39953
|
+
console.log(`Project not found: ${id}`);
|
|
39954
|
+
return;
|
|
39955
|
+
}
|
|
39956
|
+
console.log(`
|
|
39957
|
+
Project renamed to: ${newName}`);
|
|
39958
|
+
}
|
|
39959
|
+
function projectDelete(args, flags) {
|
|
39960
|
+
if (!isDatabaseInitialized()) {
|
|
39961
|
+
console.log("Database not initialized.");
|
|
39962
|
+
return;
|
|
39963
|
+
}
|
|
39964
|
+
const id = args[0];
|
|
39965
|
+
if (!id) {
|
|
39966
|
+
console.log("Error: Project ID required");
|
|
39967
|
+
console.log("Usage: rudi project delete <id>");
|
|
39968
|
+
return;
|
|
39969
|
+
}
|
|
39970
|
+
const db3 = getDb();
|
|
39971
|
+
const project = db3.prepare("SELECT name FROM projects WHERE id = ?").get(id);
|
|
39972
|
+
if (!project) {
|
|
39973
|
+
console.log(`Project not found: ${id}`);
|
|
39974
|
+
return;
|
|
39975
|
+
}
|
|
39976
|
+
const sessionsResult = db3.prepare("UPDATE sessions SET project_id = NULL WHERE project_id = ?").run(id);
|
|
39977
|
+
db3.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
39978
|
+
console.log(`
|
|
39979
|
+
Project deleted: ${project.name}`);
|
|
39980
|
+
if (sessionsResult.changes > 0) {
|
|
39981
|
+
console.log(`Unassigned ${sessionsResult.changes} sessions`);
|
|
39982
|
+
}
|
|
39983
|
+
}
|
|
39984
|
+
|
|
39259
39985
|
// src/index.js
|
|
39260
39986
|
var VERSION2 = "2.0.0";
|
|
39261
39987
|
async function main() {
|
|
@@ -39306,6 +40032,13 @@ async function main() {
|
|
|
39306
40032
|
case "import":
|
|
39307
40033
|
await cmdImport(args, flags);
|
|
39308
40034
|
break;
|
|
40035
|
+
case "apply":
|
|
40036
|
+
await cmdApply(args, flags);
|
|
40037
|
+
break;
|
|
40038
|
+
case "project":
|
|
40039
|
+
case "projects":
|
|
40040
|
+
await cmdProject(args, flags);
|
|
40041
|
+
break;
|
|
39309
40042
|
case "doctor":
|
|
39310
40043
|
await cmdDoctor(args, flags);
|
|
39311
40044
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learnrudi/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "RUDI CLI - Install and manage MCP stacks, runtimes, and AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -12,18 +12,26 @@
|
|
|
12
12
|
"scripts",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node src/index.js",
|
|
17
|
+
"prebuild": "node scripts/generate-manifest.js",
|
|
18
|
+
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:better-sqlite3 && cp src/router-mcp.js dist/router-mcp.js && cp src/packages-manifest.json dist/packages-manifest.json",
|
|
19
|
+
"postinstall": "node scripts/postinstall.js",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test": "node --test src/__tests__/"
|
|
22
|
+
},
|
|
15
23
|
"dependencies": {
|
|
16
|
-
"
|
|
17
|
-
"@learnrudi/
|
|
18
|
-
"@learnrudi/
|
|
19
|
-
"@learnrudi/env": "
|
|
20
|
-
"@learnrudi/
|
|
21
|
-
"@learnrudi/
|
|
22
|
-
"@learnrudi/
|
|
23
|
-
"@learnrudi/
|
|
24
|
-
"@learnrudi/
|
|
25
|
-
"@learnrudi/utils": "
|
|
26
|
-
"
|
|
24
|
+
"@learnrudi/core": "workspace:*",
|
|
25
|
+
"@learnrudi/db": "workspace:*",
|
|
26
|
+
"@learnrudi/embeddings": "workspace:*",
|
|
27
|
+
"@learnrudi/env": "workspace:*",
|
|
28
|
+
"@learnrudi/manifest": "workspace:*",
|
|
29
|
+
"@learnrudi/mcp": "workspace:*",
|
|
30
|
+
"@learnrudi/registry-client": "workspace:*",
|
|
31
|
+
"@learnrudi/runner": "workspace:*",
|
|
32
|
+
"@learnrudi/secrets": "workspace:*",
|
|
33
|
+
"@learnrudi/utils": "workspace:*",
|
|
34
|
+
"better-sqlite3": "^12.5.0"
|
|
27
35
|
},
|
|
28
36
|
"devDependencies": {
|
|
29
37
|
"esbuild": "^0.27.2"
|
|
@@ -49,12 +57,5 @@
|
|
|
49
57
|
"publishConfig": {
|
|
50
58
|
"access": "public"
|
|
51
59
|
},
|
|
52
|
-
"license": "MIT"
|
|
53
|
-
|
|
54
|
-
"start": "node src/index.js",
|
|
55
|
-
"prebuild": "node scripts/generate-manifest.js",
|
|
56
|
-
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:better-sqlite3 && cp src/router-mcp.js dist/router-mcp.js && cp src/packages-manifest.json dist/packages-manifest.json",
|
|
57
|
-
"postinstall": "node scripts/postinstall.js",
|
|
58
|
-
"test": "node --test src/__tests__/"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
60
|
+
"license": "MIT"
|
|
61
|
+
}
|