@learnrudi/cli 1.9.11 → 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 +826 -37
- package/dist/packages-manifest.json +1 -16
- package/package.json +22 -21
package/dist/index.cjs
CHANGED
|
@@ -662,37 +662,64 @@ async function downloadDirectoryFromGitHub(dirUrl, destDir, onProgress) {
|
|
|
662
662
|
async function downloadRuntime(runtime, version, destPath, options = {}) {
|
|
663
663
|
const { onProgress } = options;
|
|
664
664
|
const platformArch = getPlatformArch2();
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
onProgress?.({ phase: "downloading", runtime, version, url });
|
|
665
|
+
const { execSync: execSync10 } = await import("child_process");
|
|
666
|
+
const runtimeManifest = await loadRuntimeManifest(runtime);
|
|
667
|
+
const customDownload = runtimeManifest?.download?.[platformArch];
|
|
669
668
|
const tempDir = import_path3.default.join(PATHS2.cache, "downloads");
|
|
670
669
|
if (!import_fs2.default.existsSync(tempDir)) {
|
|
671
670
|
import_fs2.default.mkdirSync(tempDir, { recursive: true });
|
|
672
671
|
}
|
|
673
|
-
|
|
672
|
+
if (import_fs2.default.existsSync(destPath)) {
|
|
673
|
+
import_fs2.default.rmSync(destPath, { recursive: true });
|
|
674
|
+
}
|
|
675
|
+
import_fs2.default.mkdirSync(destPath, { recursive: true });
|
|
676
|
+
let url;
|
|
677
|
+
let downloadType;
|
|
678
|
+
if (customDownload) {
|
|
679
|
+
url = typeof customDownload === "string" ? customDownload : customDownload.url;
|
|
680
|
+
downloadType = customDownload.type || "tar.gz";
|
|
681
|
+
} else {
|
|
682
|
+
const shortVersion = version.replace(/\.x$/, "").replace(/\.0$/, "");
|
|
683
|
+
const filename = `${runtime}-${shortVersion}-${platformArch}.tar.gz`;
|
|
684
|
+
url = `${RUNTIMES_DOWNLOAD_BASE}/${RUNTIMES_RELEASE_VERSION}/${filename}`;
|
|
685
|
+
downloadType = "tar.gz";
|
|
686
|
+
}
|
|
687
|
+
onProgress?.({ phase: "downloading", runtime, version, url });
|
|
688
|
+
const tempFile = import_path3.default.join(tempDir, `${runtime}-${version}-${platformArch}.download`);
|
|
674
689
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
690
|
+
if (url.includes("github.com")) {
|
|
691
|
+
execSync10(`curl -sL "${url}" -o "${tempFile}"`, { stdio: "pipe" });
|
|
692
|
+
} else {
|
|
693
|
+
const response = await fetch(url, {
|
|
694
|
+
headers: {
|
|
695
|
+
"User-Agent": "rudi-cli/2.0",
|
|
696
|
+
"Accept": "application/octet-stream"
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
if (!response.ok) {
|
|
700
|
+
throw new Error(`Failed to download ${runtime}: HTTP ${response.status}`);
|
|
679
701
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
throw new Error(`Failed to download ${runtime}: HTTP ${response.status}`);
|
|
702
|
+
const buffer = await response.arrayBuffer();
|
|
703
|
+
import_fs2.default.writeFileSync(tempFile, Buffer.from(buffer));
|
|
683
704
|
}
|
|
684
|
-
const buffer = await response.arrayBuffer();
|
|
685
|
-
import_fs2.default.writeFileSync(tempFile, Buffer.from(buffer));
|
|
686
705
|
onProgress?.({ phase: "extracting", runtime, version });
|
|
687
|
-
if (
|
|
688
|
-
|
|
706
|
+
if (downloadType === "binary") {
|
|
707
|
+
const binaryName = runtimeManifest?.binary || runtime;
|
|
708
|
+
const binaryPath = import_path3.default.join(destPath, binaryName);
|
|
709
|
+
import_fs2.default.renameSync(tempFile, binaryPath);
|
|
710
|
+
import_fs2.default.chmodSync(binaryPath, 493);
|
|
711
|
+
} else if (downloadType === "tar.gz" || downloadType === "tgz") {
|
|
712
|
+
execSync10(`tar -xzf "${tempFile}" -C "${destPath}" --strip-components=1`, { stdio: "pipe" });
|
|
713
|
+
import_fs2.default.unlinkSync(tempFile);
|
|
714
|
+
} else if (downloadType === "tar.xz") {
|
|
715
|
+
execSync10(`tar -xJf "${tempFile}" -C "${destPath}" --strip-components=1`, { stdio: "pipe" });
|
|
716
|
+
import_fs2.default.unlinkSync(tempFile);
|
|
717
|
+
} else if (downloadType === "zip") {
|
|
718
|
+
execSync10(`unzip -o "${tempFile}" -d "${destPath}"`, { stdio: "pipe" });
|
|
719
|
+
import_fs2.default.unlinkSync(tempFile);
|
|
720
|
+
} else {
|
|
721
|
+
throw new Error(`Unsupported download type: ${downloadType}`);
|
|
689
722
|
}
|
|
690
|
-
import_fs2.default.mkdirSync(destPath, { recursive: true });
|
|
691
|
-
const { execSync: execSync10 } = await import("child_process");
|
|
692
|
-
execSync10(`tar -xzf "${tempFile}" -C "${destPath}" --strip-components=1`, {
|
|
693
|
-
stdio: "pipe"
|
|
694
|
-
});
|
|
695
|
-
import_fs2.default.unlinkSync(tempFile);
|
|
696
723
|
import_fs2.default.writeFileSync(
|
|
697
724
|
import_path3.default.join(destPath, "runtime.json"),
|
|
698
725
|
JSON.stringify({
|
|
@@ -700,7 +727,9 @@ async function downloadRuntime(runtime, version, destPath, options = {}) {
|
|
|
700
727
|
version,
|
|
701
728
|
platformArch,
|
|
702
729
|
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
703
|
-
source: url
|
|
730
|
+
source: url,
|
|
731
|
+
...runtimeManifest?.commands && { commands: runtimeManifest.commands },
|
|
732
|
+
...runtimeManifest?.postInstall && { postInstall: runtimeManifest.postInstall }
|
|
704
733
|
}, null, 2)
|
|
705
734
|
);
|
|
706
735
|
onProgress?.({ phase: "complete", runtime, version, path: destPath });
|
|
@@ -882,6 +911,33 @@ async function extractBinaryFromPath(extractedPath, binaryPattern, destPath) {
|
|
|
882
911
|
}
|
|
883
912
|
}
|
|
884
913
|
}
|
|
914
|
+
async function loadRuntimeManifest(runtimeName) {
|
|
915
|
+
for (const basePath of getLocalRegistryPaths()) {
|
|
916
|
+
const registryDir = import_path3.default.dirname(basePath);
|
|
917
|
+
const manifestPath = import_path3.default.join(registryDir, "catalog", "runtimes", `${runtimeName}.json`);
|
|
918
|
+
if (import_fs2.default.existsSync(manifestPath)) {
|
|
919
|
+
try {
|
|
920
|
+
return JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
|
|
921
|
+
} catch {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const url = `https://raw.githubusercontent.com/learn-rudi/registry/main/catalog/runtimes/${runtimeName}.json`;
|
|
928
|
+
const response = await fetch(url, {
|
|
929
|
+
headers: {
|
|
930
|
+
"User-Agent": "rudi-cli/2.0",
|
|
931
|
+
"Accept": "application/json"
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
if (response.ok) {
|
|
935
|
+
return await response.json();
|
|
936
|
+
}
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
885
941
|
async function loadToolManifest(toolName) {
|
|
886
942
|
for (const basePath of getLocalRegistryPaths()) {
|
|
887
943
|
const registryDir = import_path3.default.dirname(basePath);
|
|
@@ -960,8 +1016,7 @@ async function resolvePackage(id) {
|
|
|
960
1016
|
if (id.startsWith("npm:")) {
|
|
961
1017
|
return resolveDynamicNpm(id);
|
|
962
1018
|
}
|
|
963
|
-
const
|
|
964
|
-
const pkg = await getPackage(normalizedId);
|
|
1019
|
+
const pkg = await getPackage(id);
|
|
965
1020
|
if (!pkg) {
|
|
966
1021
|
throw new Error(`Package not found: ${id}`);
|
|
967
1022
|
}
|
|
@@ -17247,6 +17302,7 @@ __export(sqlite_exports, {
|
|
|
17247
17302
|
clearEmbeddings: () => clearEmbeddings,
|
|
17248
17303
|
deleteEmbedding: () => deleteEmbedding,
|
|
17249
17304
|
ensureEmbeddingsSchema: () => ensureEmbeddingsSchema,
|
|
17305
|
+
getAllEmbeddingStats: () => getAllEmbeddingStats,
|
|
17250
17306
|
getEmbeddingStats: () => getEmbeddingStats,
|
|
17251
17307
|
getErrorTurns: () => getErrorTurns,
|
|
17252
17308
|
getMissingTurns: () => getMissingTurns,
|
|
@@ -17416,6 +17472,38 @@ function getEmbeddingStats(model) {
|
|
|
17416
17472
|
}
|
|
17417
17473
|
return stats;
|
|
17418
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
|
+
}
|
|
17419
17507
|
function deleteEmbedding(turnId) {
|
|
17420
17508
|
const db3 = getDb2();
|
|
17421
17509
|
db3.prepare("DELETE FROM turn_embeddings WHERE turn_id = ?").run(turnId);
|
|
@@ -35461,6 +35549,7 @@ function dbTables(flags) {
|
|
|
35461
35549
|
}
|
|
35462
35550
|
|
|
35463
35551
|
// src/commands/session.js
|
|
35552
|
+
var import_readline2 = require("readline");
|
|
35464
35553
|
var embeddingsModule = null;
|
|
35465
35554
|
async function getEmbeddings() {
|
|
35466
35555
|
if (!embeddingsModule) {
|
|
@@ -35468,6 +35557,92 @@ async function getEmbeddings() {
|
|
|
35468
35557
|
}
|
|
35469
35558
|
return embeddingsModule;
|
|
35470
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
|
+
}
|
|
35471
35646
|
async function cmdSession(args, flags) {
|
|
35472
35647
|
const subcommand = args[0];
|
|
35473
35648
|
switch (subcommand) {
|
|
@@ -35504,6 +35679,9 @@ async function cmdSession(args, flags) {
|
|
|
35504
35679
|
case "setup":
|
|
35505
35680
|
await sessionSetup(flags);
|
|
35506
35681
|
break;
|
|
35682
|
+
case "organize":
|
|
35683
|
+
await sessionOrganize(flags);
|
|
35684
|
+
break;
|
|
35507
35685
|
default:
|
|
35508
35686
|
console.log(`
|
|
35509
35687
|
rudi session - Manage RUDI sessions
|
|
@@ -35523,6 +35701,9 @@ SEMANTIC SEARCH
|
|
|
35523
35701
|
index [--embeddings] [--provider X] Index sessions for semantic search
|
|
35524
35702
|
similar <id> [--limit] Find similar sessions
|
|
35525
35703
|
|
|
35704
|
+
ORGANIZATION (batch operations)
|
|
35705
|
+
organize [--dry-run] [--out plan.json] Auto-organize sessions into projects
|
|
35706
|
+
|
|
35526
35707
|
OPTIONS
|
|
35527
35708
|
--provider <name> Filter by provider (claude, codex, gemini)
|
|
35528
35709
|
--project <name> Filter by project name
|
|
@@ -35870,8 +36051,12 @@ Found ${results.length} result(s) for "${query}":
|
|
|
35870
36051
|
async function semanticSearch(query, options) {
|
|
35871
36052
|
const { limit: limit2, format } = options;
|
|
35872
36053
|
try {
|
|
35873
|
-
const { createClient: createClient2
|
|
35874
|
-
const
|
|
36054
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36055
|
+
const result = await ensureEmbeddingProvider("auto");
|
|
36056
|
+
if (!result) {
|
|
36057
|
+
return;
|
|
36058
|
+
}
|
|
36059
|
+
const { provider, model } = result;
|
|
35875
36060
|
console.log(`Using ${provider.id} with ${model.name}`);
|
|
35876
36061
|
const client = createClient2({ provider, model });
|
|
35877
36062
|
const stats = client.getStats();
|
|
@@ -35920,18 +36105,25 @@ async function sessionIndex(flags) {
|
|
|
35920
36105
|
const providerName = flags.provider || "auto";
|
|
35921
36106
|
if (!flags.embeddings) {
|
|
35922
36107
|
try {
|
|
35923
|
-
const {
|
|
35924
|
-
const
|
|
35925
|
-
const stats = store.getEmbeddingStats(model);
|
|
36108
|
+
const { store } = await getEmbeddings();
|
|
36109
|
+
const stats = store.getAllEmbeddingStats();
|
|
35926
36110
|
const pct = stats.total > 0 ? (stats.done / stats.total * 100).toFixed(1) : 0;
|
|
35927
36111
|
console.log("\nEmbedding Index Status:");
|
|
35928
36112
|
console.log(` Total turns: ${stats.total}`);
|
|
35929
36113
|
console.log(` Indexed: ${stats.done} (${pct}%)`);
|
|
35930
36114
|
console.log(` Queued: ${stats.queued}`);
|
|
35931
36115
|
console.log(` Errors: ${stats.error}`);
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
|
|
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
|
+
}
|
|
35935
36127
|
} catch (err) {
|
|
35936
36128
|
console.log("Embedding status unavailable:", err.message);
|
|
35937
36129
|
}
|
|
@@ -35939,8 +36131,12 @@ async function sessionIndex(flags) {
|
|
|
35939
36131
|
}
|
|
35940
36132
|
console.log("Indexing sessions for semantic search...\n");
|
|
35941
36133
|
try {
|
|
35942
|
-
const { createClient: createClient2
|
|
35943
|
-
const
|
|
36134
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36135
|
+
const providerResult = await ensureEmbeddingProvider(providerName);
|
|
36136
|
+
if (!providerResult) {
|
|
36137
|
+
return;
|
|
36138
|
+
}
|
|
36139
|
+
const { provider, model } = providerResult;
|
|
35944
36140
|
console.log(`Provider: ${provider.id}`);
|
|
35945
36141
|
console.log(`Model: ${model.name} (${model.dimensions}d)
|
|
35946
36142
|
`);
|
|
@@ -36001,8 +36197,12 @@ async function sessionSimilar(args, flags) {
|
|
|
36001
36197
|
const format = flags.format || "table";
|
|
36002
36198
|
const providerName = flags.provider || "auto";
|
|
36003
36199
|
try {
|
|
36004
|
-
const { createClient: createClient2
|
|
36005
|
-
const
|
|
36200
|
+
const { createClient: createClient2 } = await getEmbeddings();
|
|
36201
|
+
const result = await ensureEmbeddingProvider(providerName);
|
|
36202
|
+
if (!result) {
|
|
36203
|
+
return;
|
|
36204
|
+
}
|
|
36205
|
+
const { provider, model } = result;
|
|
36006
36206
|
const client = createClient2({ provider, model });
|
|
36007
36207
|
const results = await client.findSimilar(turnId, { limit: limit2 });
|
|
36008
36208
|
if (format === "json") {
|
|
@@ -36051,6 +36251,223 @@ async function sessionSetup(flags) {
|
|
|
36051
36251
|
console.error("Setup error:", err.message);
|
|
36052
36252
|
}
|
|
36053
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
|
+
}
|
|
36054
36471
|
|
|
36055
36472
|
// src/commands/import.js
|
|
36056
36473
|
var import_fs17 = require("fs");
|
|
@@ -39200,6 +39617,371 @@ Lockfile: ${lockPath}`);
|
|
|
39200
39617
|
}
|
|
39201
39618
|
}
|
|
39202
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
|
+
|
|
39203
39985
|
// src/index.js
|
|
39204
39986
|
var VERSION2 = "2.0.0";
|
|
39205
39987
|
async function main() {
|
|
@@ -39250,6 +40032,13 @@ async function main() {
|
|
|
39250
40032
|
case "import":
|
|
39251
40033
|
await cmdImport(args, flags);
|
|
39252
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;
|
|
39253
40042
|
case "doctor":
|
|
39254
40043
|
await cmdDoctor(args, flags);
|
|
39255
40044
|
break;
|