@loreai/gateway 0.14.0 → 0.14.1
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/dist/bin.cjs +27 -0
- package/dist/index.cjs +1042 -0
- package/dist/index.d.cts +21 -0
- package/package.json +10 -10
- package/dist/index.js +0 -50087
- package/src/auth.ts +0 -133
- package/src/batch-queue.ts +0 -575
- package/src/cache-analytics.ts +0 -344
- package/src/cli/agents.ts +0 -107
- package/src/cli/bin.ts +0 -11
- package/src/cli/help.ts +0 -55
- package/src/cli/lib/binary.ts +0 -353
- package/src/cli/lib/bspatch.ts +0 -306
- package/src/cli/lib/delta-upgrade.ts +0 -790
- package/src/cli/lib/errors.ts +0 -48
- package/src/cli/lib/ghcr.ts +0 -389
- package/src/cli/lib/patch-cache.ts +0 -342
- package/src/cli/lib/upgrade.ts +0 -454
- package/src/cli/lib/version-check.ts +0 -385
- package/src/cli/main.ts +0 -152
- package/src/cli/run.ts +0 -181
- package/src/cli/start.ts +0 -82
- package/src/cli/upgrade.ts +0 -311
- package/src/cli/version.ts +0 -22
- package/src/compaction.ts +0 -195
- package/src/config.ts +0 -199
- package/src/idle.ts +0 -240
- package/src/index.ts +0 -41
- package/src/llm-adapter.ts +0 -182
- package/src/pipeline.ts +0 -1681
- package/src/recall.ts +0 -433
- package/src/recorder.ts +0 -192
- package/src/server.ts +0 -250
- package/src/session.ts +0 -207
- package/src/stream/anthropic.ts +0 -708
- package/src/temporal-adapter.ts +0 -310
- package/src/translate/anthropic.ts +0 -469
- package/src/translate/openai.ts +0 -536
- package/src/translate/types.ts +0 -222
- package/src/worker-model.ts +0 -408
package/src/cli/run.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `lore run [command...]` — start gateway + launch an AI agent.
|
|
3
|
-
*
|
|
4
|
-
* If a command is given, launches it with gateway env vars injected.
|
|
5
|
-
* If no command is given, auto-detects installed agents and either
|
|
6
|
-
* uses the sole one found or prompts the user to pick.
|
|
7
|
-
*/
|
|
8
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
9
|
-
import { createInterface } from "node:readline";
|
|
10
|
-
import { startGateway, type StartOptions } from "./start";
|
|
11
|
-
import { detectAgents, AGENTS, type DetectedAgent } from "./agents";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Interactive agent picker (TTY only)
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
async function promptAgent(agents: DetectedAgent[]): Promise<DetectedAgent> {
|
|
18
|
-
console.error("\n[lore] Multiple AI agents detected:\n");
|
|
19
|
-
for (let i = 0; i < agents.length; i++) {
|
|
20
|
-
console.error(` ${i + 1}) ${agents[i].def.displayName} (${agents[i].path})`);
|
|
21
|
-
}
|
|
22
|
-
console.error();
|
|
23
|
-
|
|
24
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
25
|
-
|
|
26
|
-
return new Promise<DetectedAgent>((resolve) => {
|
|
27
|
-
const ask = () => {
|
|
28
|
-
rl.question("Choose an agent [1]: ", (answer) => {
|
|
29
|
-
const trimmed = answer.trim();
|
|
30
|
-
const idx = trimmed === "" ? 0 : Number.parseInt(trimmed, 10) - 1;
|
|
31
|
-
if (Number.isInteger(idx) && idx >= 0 && idx < agents.length) {
|
|
32
|
-
rl.close();
|
|
33
|
-
resolve(agents[idx]);
|
|
34
|
-
} else {
|
|
35
|
-
console.error(` Invalid choice. Enter 1–${agents.length}.`);
|
|
36
|
-
ask();
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
};
|
|
40
|
-
ask();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Resolve what to launch
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
interface LaunchTarget {
|
|
49
|
-
command: string;
|
|
50
|
-
args: string[];
|
|
51
|
-
env: Record<string, string>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function resolveLaunchTarget(
|
|
55
|
-
gatewayUrl: string,
|
|
56
|
-
cmdArgs: string[],
|
|
57
|
-
): Promise<LaunchTarget | null> {
|
|
58
|
-
// --- Explicit command given: inject all known env vars ---
|
|
59
|
-
if (cmdArgs.length > 0) {
|
|
60
|
-
const env: Record<string, string> = {};
|
|
61
|
-
for (const agent of AGENTS) {
|
|
62
|
-
Object.assign(env, agent.envVars(gatewayUrl));
|
|
63
|
-
}
|
|
64
|
-
return { command: cmdArgs[0], args: cmdArgs.slice(1), env };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// --- No command: auto-detect agents ---
|
|
68
|
-
const detected = detectAgents();
|
|
69
|
-
|
|
70
|
-
if (detected.length === 0) {
|
|
71
|
-
console.error("[lore] No known AI agents found on PATH.");
|
|
72
|
-
console.error("[lore] Install one of: Claude Code (claude), Codex (codex), Pi (pi), OpenCode (opencode)");
|
|
73
|
-
console.error(`[lore] Or run with an explicit command: lore run <command>`);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let agent: DetectedAgent;
|
|
78
|
-
|
|
79
|
-
if (detected.length === 1) {
|
|
80
|
-
agent = detected[0];
|
|
81
|
-
console.error(`[lore] Detected ${agent.def.displayName} at ${agent.path}`);
|
|
82
|
-
} else if (process.stdin.isTTY) {
|
|
83
|
-
agent = await promptAgent(detected);
|
|
84
|
-
} else {
|
|
85
|
-
// Non-TTY with multiple agents — can't prompt
|
|
86
|
-
console.error("[lore] Multiple agents detected but stdin is not a TTY.");
|
|
87
|
-
console.error("[lore] Specify which agent to run: lore run <command>");
|
|
88
|
-
for (const a of detected) {
|
|
89
|
-
console.error(` - ${a.def.displayName}: lore run ${a.def.binary}`);
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
command: agent.def.binary,
|
|
96
|
-
args: [],
|
|
97
|
-
env: agent.def.envVars(gatewayUrl),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Child process management
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
|
|
105
|
-
function launchChild(target: LaunchTarget): ChildProcess {
|
|
106
|
-
const env = { ...process.env, ...target.env };
|
|
107
|
-
|
|
108
|
-
const child = spawn(target.command, target.args, {
|
|
109
|
-
env,
|
|
110
|
-
stdio: "inherit",
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return child;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// Command entry point
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
export async function commandRun(
|
|
121
|
-
opts: StartOptions,
|
|
122
|
-
cmdArgs: string[],
|
|
123
|
-
): Promise<void> {
|
|
124
|
-
// 1. Start gateway
|
|
125
|
-
const { config, port, shutdown } = startGateway(opts);
|
|
126
|
-
const gatewayUrl = `http://${config.host}:${port}`;
|
|
127
|
-
|
|
128
|
-
console.error(`[lore] Gateway listening on ${gatewayUrl}`);
|
|
129
|
-
|
|
130
|
-
// 2. Resolve what to launch
|
|
131
|
-
const target = await resolveLaunchTarget(gatewayUrl, cmdArgs);
|
|
132
|
-
|
|
133
|
-
if (!target) {
|
|
134
|
-
// No agent found / non-interactive — fall back to server-only mode
|
|
135
|
-
console.error("[lore] Running in server-only mode. Point your agent at the gateway manually.");
|
|
136
|
-
console.error(`[lore] export ANTHROPIC_BASE_URL=${gatewayUrl}`);
|
|
137
|
-
|
|
138
|
-
const onSignal = async () => {
|
|
139
|
-
await shutdown();
|
|
140
|
-
process.exit(0);
|
|
141
|
-
};
|
|
142
|
-
process.on("SIGINT", () => onSignal());
|
|
143
|
-
process.on("SIGTERM", () => onSignal());
|
|
144
|
-
|
|
145
|
-
// Block forever
|
|
146
|
-
return new Promise(() => {});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 3. Launch agent child process
|
|
150
|
-
console.error(`[lore] Launching: ${target.command} ${target.args.join(" ")}`.trimEnd());
|
|
151
|
-
|
|
152
|
-
const child = launchChild(target);
|
|
153
|
-
|
|
154
|
-
// Forward signals to child
|
|
155
|
-
const forwardSignal = (signal: NodeJS.Signals) => {
|
|
156
|
-
child.kill(signal);
|
|
157
|
-
};
|
|
158
|
-
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
159
|
-
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
160
|
-
|
|
161
|
-
// Wait for child to exit, then tear down gateway
|
|
162
|
-
return new Promise<void>((resolve) => {
|
|
163
|
-
child.on("exit", async (code, signal) => {
|
|
164
|
-
await shutdown();
|
|
165
|
-
// Exit with the child's code (or 128 + signal number for signal deaths)
|
|
166
|
-
if (signal) {
|
|
167
|
-
const SIGNAL_CODES: Record<string, number> = {
|
|
168
|
-
SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGTERM: 15,
|
|
169
|
-
};
|
|
170
|
-
process.exit(128 + (SIGNAL_CODES[signal] ?? 1));
|
|
171
|
-
}
|
|
172
|
-
process.exit(code ?? 0);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
child.on("error", async (err) => {
|
|
176
|
-
console.error(`[lore] Failed to launch ${target.command}: ${err.message}`);
|
|
177
|
-
await shutdown();
|
|
178
|
-
process.exit(1);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
}
|
package/src/cli/start.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `lore start` — start the gateway server only.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from the old top-level index.ts boot logic.
|
|
5
|
-
*/
|
|
6
|
-
import { loadConfig, type GatewayConfig } from "../config";
|
|
7
|
-
import { startServer } from "../server";
|
|
8
|
-
import { resetPipelineState } from "../pipeline";
|
|
9
|
-
|
|
10
|
-
export interface StartOptions {
|
|
11
|
-
port?: number;
|
|
12
|
-
host?: string;
|
|
13
|
-
debug?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Start the gateway server, returning the actual port and a shutdown function.
|
|
18
|
-
*
|
|
19
|
-
* Merges CLI options on top of env-var config (CLI takes precedence).
|
|
20
|
-
*/
|
|
21
|
-
export function startGateway(opts: StartOptions = {}): {
|
|
22
|
-
config: GatewayConfig;
|
|
23
|
-
port: number;
|
|
24
|
-
shutdown: () => Promise<void>;
|
|
25
|
-
} {
|
|
26
|
-
const config = loadConfig();
|
|
27
|
-
|
|
28
|
-
// CLI overrides
|
|
29
|
-
if (opts.port !== undefined) config.port = opts.port;
|
|
30
|
-
if (opts.host !== undefined) config.host = opts.host;
|
|
31
|
-
if (opts.debug !== undefined) config.debug = opts.debug;
|
|
32
|
-
|
|
33
|
-
const server = startServer(config);
|
|
34
|
-
const actualPort = server.port;
|
|
35
|
-
|
|
36
|
-
const shutdown = async () => {
|
|
37
|
-
console.error("[lore] Shutting down…");
|
|
38
|
-
server.stop();
|
|
39
|
-
await resetPipelineState();
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return { config, port: actualPort, shutdown };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Run the `lore start` command — start gateway server and block until
|
|
47
|
-
* SIGINT/SIGTERM.
|
|
48
|
-
*/
|
|
49
|
-
export async function commandStart(opts: StartOptions): Promise<never> {
|
|
50
|
-
const { config, port, shutdown } = startGateway(opts);
|
|
51
|
-
|
|
52
|
-
const addr = `http://${config.host}:${port}`;
|
|
53
|
-
console.error(`[lore] Gateway listening on ${addr}`);
|
|
54
|
-
console.error(
|
|
55
|
-
`[lore] Model routing: claude-* → Anthropic, nvidia/* → Nvidia NIM, gpt-* → OpenAI, …`,
|
|
56
|
-
);
|
|
57
|
-
console.error("");
|
|
58
|
-
console.error("[lore] Point your AI agent at the gateway:");
|
|
59
|
-
console.error(` export ANTHROPIC_BASE_URL=${addr}`);
|
|
60
|
-
console.error(` export OPENAI_BASE_URL=${addr}/v1`);
|
|
61
|
-
console.error("");
|
|
62
|
-
console.error("[lore] Configuration (environment variables):");
|
|
63
|
-
console.error(` LORE_LISTEN_PORT Port to listen on (current: ${port})`);
|
|
64
|
-
console.error(` LORE_LISTEN_HOST Host to bind to (current: ${config.host})`);
|
|
65
|
-
console.error(` LORE_UPSTREAM_ANTHROPIC Anthropic API URL (current: ${config.upstreamAnthropic})`);
|
|
66
|
-
console.error(` LORE_UPSTREAM_OPENAI OpenAI API URL (current: ${config.upstreamOpenAI})`);
|
|
67
|
-
console.error(` LORE_IDLE_TIMEOUT Idle timeout in seconds (current: ${config.idleTimeoutSeconds})`);
|
|
68
|
-
console.error(` LORE_DEBUG Enable debug logging (current: ${config.debug})`);
|
|
69
|
-
console.error(` LORE_BATCH_DISABLED Disable batch background work (current: ${process.env.LORE_BATCH_DISABLED === "1"})`);
|
|
70
|
-
|
|
71
|
-
// Block until signal
|
|
72
|
-
const onSignal = async () => {
|
|
73
|
-
await shutdown();
|
|
74
|
-
process.exit(0);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
process.on("SIGINT", () => onSignal());
|
|
78
|
-
process.on("SIGTERM", () => onSignal());
|
|
79
|
-
|
|
80
|
-
// Keep the process alive (Bun.serve already does this, but be explicit)
|
|
81
|
-
return new Promise(() => {});
|
|
82
|
-
}
|
package/src/cli/upgrade.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `lore upgrade [version]` — self-update command.
|
|
3
|
-
*
|
|
4
|
-
* Self-update the Lore CLI to the latest or a specific version.
|
|
5
|
-
* After upgrading, replaces the running binary atomically.
|
|
6
|
-
*
|
|
7
|
-
* Supports two release channels:
|
|
8
|
-
* - stable (default): tracks the latest GitHub release
|
|
9
|
-
* - nightly: tracks the rolling nightly prerelease from GHCR
|
|
10
|
-
*
|
|
11
|
-
* The channel can be set via --channel or by passing "nightly"/"stable"
|
|
12
|
-
* as the version argument. The choice is persisted in ~/.lore/channel.
|
|
13
|
-
*
|
|
14
|
-
* Flags:
|
|
15
|
-
* --check Check for updates without installing
|
|
16
|
-
* --force Force re-download even if up to date
|
|
17
|
-
* --offline Upgrade from cached patches (no network)
|
|
18
|
-
* --channel Set release channel (stable or nightly)
|
|
19
|
-
*
|
|
20
|
-
* Adapted from Sentry CLI's upgrade command — stripped of brew/npm
|
|
21
|
-
* detection, setup spawning, release notes, and Sentry SDK telemetry.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { parseArgs } from "node:util";
|
|
25
|
-
import { dirname } from "node:path";
|
|
26
|
-
import { VERSION } from "./version";
|
|
27
|
-
import {
|
|
28
|
-
isDowngrade,
|
|
29
|
-
installBinary,
|
|
30
|
-
determineInstallDir,
|
|
31
|
-
releaseLock,
|
|
32
|
-
} from "./lib/binary";
|
|
33
|
-
import { UpgradeError } from "./lib/errors";
|
|
34
|
-
import {
|
|
35
|
-
executeUpgrade,
|
|
36
|
-
fetchLatestVersion,
|
|
37
|
-
getCurlInstallPaths,
|
|
38
|
-
getReleaseChannel,
|
|
39
|
-
NIGHTLY_TAG,
|
|
40
|
-
type OfflineMode,
|
|
41
|
-
type ReleaseChannel,
|
|
42
|
-
setReleaseChannel,
|
|
43
|
-
versionExists,
|
|
44
|
-
VERSION_PREFIX_REGEX,
|
|
45
|
-
} from "./lib/upgrade";
|
|
46
|
-
|
|
47
|
-
/** Special version strings that select a channel */
|
|
48
|
-
const CHANNEL_VERSIONS = new Set(["nightly", "stable"]);
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Flag parsing
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
interface UpgradeFlags {
|
|
55
|
-
check: boolean;
|
|
56
|
-
force: boolean;
|
|
57
|
-
offline: boolean;
|
|
58
|
-
channel?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function parseUpgradeFlags(args: string[]): {
|
|
62
|
-
flags: UpgradeFlags;
|
|
63
|
-
versionArg: string | undefined;
|
|
64
|
-
} {
|
|
65
|
-
const { values, positionals } = parseArgs({
|
|
66
|
-
args,
|
|
67
|
-
options: {
|
|
68
|
-
check: { type: "boolean", default: false },
|
|
69
|
-
force: { type: "boolean", default: false },
|
|
70
|
-
offline: { type: "boolean", default: false },
|
|
71
|
-
channel: { type: "string" },
|
|
72
|
-
},
|
|
73
|
-
allowPositionals: true,
|
|
74
|
-
strict: false,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
flags: {
|
|
79
|
-
check: !!values.check,
|
|
80
|
-
force: !!values.force,
|
|
81
|
-
offline: !!values.offline,
|
|
82
|
-
channel: values.channel as string | undefined,
|
|
83
|
-
},
|
|
84
|
-
versionArg: positionals[0],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Channel + version resolution
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
function resolveChannelAndVersion(
|
|
93
|
-
versionArg: string | undefined,
|
|
94
|
-
channelFlag: string | undefined,
|
|
95
|
-
): {
|
|
96
|
-
channel: ReleaseChannel;
|
|
97
|
-
cleanVersionArg: string | undefined;
|
|
98
|
-
} {
|
|
99
|
-
// "nightly" and "stable" as positional args select the channel
|
|
100
|
-
const lower = versionArg?.toLowerCase();
|
|
101
|
-
if (lower === "nightly" || lower === "stable") {
|
|
102
|
-
return { channel: lower, cleanVersionArg: undefined };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// --channel flag overrides persisted channel
|
|
106
|
-
if (channelFlag === "nightly" || channelFlag === "stable") {
|
|
107
|
-
return { channel: channelFlag, cleanVersionArg: versionArg };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
channel: getReleaseChannel(),
|
|
112
|
-
cleanVersionArg: versionArg,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// Cached version for offline mode
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
/** Simple file-based cache for last-known latest version */
|
|
121
|
-
function getCachedLatestVersion(): string | null {
|
|
122
|
-
try {
|
|
123
|
-
const fs = require("node:fs");
|
|
124
|
-
const path = require("node:path");
|
|
125
|
-
const { getConfigDir } = require("./lib/binary");
|
|
126
|
-
const content = fs
|
|
127
|
-
.readFileSync(path.join(getConfigDir(), "latest-version"), "utf-8")
|
|
128
|
-
.trim();
|
|
129
|
-
return content || null;
|
|
130
|
-
} catch {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function setCachedLatestVersion(version: string): void {
|
|
136
|
-
try {
|
|
137
|
-
const fs = require("node:fs");
|
|
138
|
-
const path = require("node:path");
|
|
139
|
-
const { getConfigDir } = require("./lib/binary");
|
|
140
|
-
const dir = getConfigDir();
|
|
141
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
-
fs.writeFileSync(path.join(dir, "latest-version"), version, "utf-8");
|
|
143
|
-
} catch {
|
|
144
|
-
// Best-effort
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function resolveOfflineTarget(versionArg: string | undefined): string {
|
|
149
|
-
if (versionArg) {
|
|
150
|
-
return versionArg.replace(VERSION_PREFIX_REGEX, "");
|
|
151
|
-
}
|
|
152
|
-
const cached = getCachedLatestVersion();
|
|
153
|
-
if (!cached) {
|
|
154
|
-
throw new UpgradeError(
|
|
155
|
-
"network_error",
|
|
156
|
-
"No cached version available. Run `lore upgrade` with network access first, then retry with --offline.",
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
return cached;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// Main command
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
export async function commandUpgrade(args: string[]): Promise<void> {
|
|
167
|
-
const { flags, versionArg: rawVersionArg } = parseUpgradeFlags(args);
|
|
168
|
-
|
|
169
|
-
const { channel, cleanVersionArg } = resolveChannelAndVersion(
|
|
170
|
-
rawVersionArg,
|
|
171
|
-
flags.channel,
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const currentChannel = getReleaseChannel();
|
|
175
|
-
const channelChanged = channel !== currentChannel;
|
|
176
|
-
|
|
177
|
-
console.error(`[lore] Current version: ${VERSION}`);
|
|
178
|
-
console.error(`[lore] Channel: ${channel}`);
|
|
179
|
-
|
|
180
|
-
// Resolve target version
|
|
181
|
-
let target: string;
|
|
182
|
-
let offline: OfflineMode = false;
|
|
183
|
-
|
|
184
|
-
if (flags.offline) {
|
|
185
|
-
// Read cached version BEFORE persisting channel
|
|
186
|
-
target = resolveOfflineTarget(cleanVersionArg);
|
|
187
|
-
offline = "explicit";
|
|
188
|
-
console.error(`[lore] Offline mode: using cached target ${target}`);
|
|
189
|
-
} else {
|
|
190
|
-
try {
|
|
191
|
-
const latest = await fetchLatestVersion(channel);
|
|
192
|
-
target = cleanVersionArg?.replace(VERSION_PREFIX_REGEX, "") ?? latest;
|
|
193
|
-
|
|
194
|
-
// Cache the latest version for future offline use
|
|
195
|
-
setCachedLatestVersion(latest);
|
|
196
|
-
} catch (error) {
|
|
197
|
-
// Try offline fallback
|
|
198
|
-
if (error instanceof UpgradeError && error.reason === "network_error") {
|
|
199
|
-
try {
|
|
200
|
-
target = resolveOfflineTarget(cleanVersionArg);
|
|
201
|
-
offline = "network-fallback";
|
|
202
|
-
console.error(
|
|
203
|
-
"[lore] Network unavailable, falling back to cached upgrade target",
|
|
204
|
-
);
|
|
205
|
-
} catch {
|
|
206
|
-
throw error; // Re-throw original network error
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Persist channel preference
|
|
215
|
-
if (channelChanged || CHANNEL_VERSIONS.has(rawVersionArg ?? "")) {
|
|
216
|
-
setReleaseChannel(channel);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// --check: just report status
|
|
220
|
-
if (flags.check) {
|
|
221
|
-
if (VERSION === target) {
|
|
222
|
-
console.error(`[lore] Already up to date (${VERSION})`);
|
|
223
|
-
} else {
|
|
224
|
-
const direction = isDowngrade(VERSION, target)
|
|
225
|
-
? "Downgrade"
|
|
226
|
-
: "Update";
|
|
227
|
-
console.error(`[lore] ${direction} available: ${VERSION} -> ${target}`);
|
|
228
|
-
console.error(`[lore] Run 'lore upgrade' to update.`);
|
|
229
|
-
}
|
|
230
|
-
if (offline) {
|
|
231
|
-
console.error("[lore] (resolved from cache — network unavailable)");
|
|
232
|
-
}
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Already on target — unless forced or switching channels
|
|
237
|
-
if (VERSION === target && !flags.force && !channelChanged) {
|
|
238
|
-
console.error(`[lore] Already up to date (${VERSION})`);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Validate pinned version exists (skip for channel keywords)
|
|
243
|
-
if (
|
|
244
|
-
cleanVersionArg &&
|
|
245
|
-
!CHANNEL_VERSIONS.has(cleanVersionArg) &&
|
|
246
|
-
!offline
|
|
247
|
-
) {
|
|
248
|
-
const exists = await versionExists(target, channel);
|
|
249
|
-
if (!exists) {
|
|
250
|
-
throw new UpgradeError(
|
|
251
|
-
"version_not_found",
|
|
252
|
-
`Version ${target} not found`,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const downgrade = isDowngrade(VERSION, target);
|
|
258
|
-
const verb = downgrade ? "Downgrading" : "Upgrading";
|
|
259
|
-
console.error(`[lore] ${verb} to ${target}...`);
|
|
260
|
-
|
|
261
|
-
// Use the rolling "nightly" tag only when upgrading to latest nightly
|
|
262
|
-
const downloadTag =
|
|
263
|
-
channel === "nightly" && !cleanVersionArg ? NIGHTLY_TAG : undefined;
|
|
264
|
-
|
|
265
|
-
// Download the new binary
|
|
266
|
-
const downloadResult = await executeUpgrade(target, downloadTag, offline);
|
|
267
|
-
|
|
268
|
-
if (downloadResult.patchBytes) {
|
|
269
|
-
console.error(
|
|
270
|
-
`[lore] Applied delta patch (${formatBytes(downloadResult.patchBytes)} downloaded)`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Install: replace the current binary atomically
|
|
275
|
-
try {
|
|
276
|
-
const currentInstallDir = dirname(getCurlInstallPaths().installPath);
|
|
277
|
-
const installDir = determineInstallDir(
|
|
278
|
-
require("node:os").homedir(),
|
|
279
|
-
process.env,
|
|
280
|
-
);
|
|
281
|
-
// Use current install dir if we're already in a known location
|
|
282
|
-
const targetDir = process.execPath.startsWith(currentInstallDir)
|
|
283
|
-
? currentInstallDir
|
|
284
|
-
: installDir;
|
|
285
|
-
|
|
286
|
-
const installedPath = await installBinary(
|
|
287
|
-
downloadResult.tempBinaryPath,
|
|
288
|
-
targetDir,
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
console.error(
|
|
292
|
-
`[lore] ${downgrade ? "Downgraded" : "Upgraded"} successfully: ${VERSION} -> ${target}`,
|
|
293
|
-
);
|
|
294
|
-
console.error(`[lore] Binary installed at: ${installedPath}`);
|
|
295
|
-
if (offline) {
|
|
296
|
-
console.error("[lore] (upgraded from cached patches)");
|
|
297
|
-
}
|
|
298
|
-
} finally {
|
|
299
|
-
releaseLock(downloadResult.lockPath);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// ---------------------------------------------------------------------------
|
|
304
|
-
// Helpers
|
|
305
|
-
// ---------------------------------------------------------------------------
|
|
306
|
-
|
|
307
|
-
function formatBytes(bytes: number): string {
|
|
308
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
309
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
310
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
311
|
-
}
|
package/src/cli/version.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI version — replaced at build time by esbuild `define`.
|
|
3
|
-
*
|
|
4
|
-
* During development (running via `bun run src/index.ts`), falls back
|
|
5
|
-
* to reading package.json at runtime.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// esbuild replaces this with a string literal at bundle time.
|
|
9
|
-
// In dev mode the identifier is left as-is and we fall back below.
|
|
10
|
-
declare const LORE_CLI_VERSION: string | undefined;
|
|
11
|
-
|
|
12
|
-
function readVersionFromPackageJson(): string {
|
|
13
|
-
try {
|
|
14
|
-
const pkg = require("../../package.json") as { version?: string };
|
|
15
|
-
return pkg.version ?? "dev";
|
|
16
|
-
} catch {
|
|
17
|
-
return "dev";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const VERSION: string =
|
|
22
|
-
typeof LORE_CLI_VERSION !== "undefined" ? LORE_CLI_VERSION : readVersionFromPackageJson();
|