@oh-my-pi/pi-coding-agent 2.0.1337 → 2.2.1337
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/CHANGELOG.md +15 -0
- package/package.json +7 -9
- package/src/bun-imports.d.ts +16 -0
- package/src/cli/update-cli.ts +273 -0
- package/src/config.ts +21 -73
- package/src/core/agent-session.ts +1 -1
- package/src/core/auth-storage.ts +2 -3
- package/src/core/export-html/index.ts +36 -121
- package/src/core/export-html/template.css +0 -7
- package/src/core/export-html/template.html +3 -4
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/tools/task/agents.ts +20 -36
- package/src/core/tools/task/commands.ts +31 -10
- package/src/main.ts +13 -1
- package/src/modes/interactive/interactive-mode.ts +2 -2
- package/src/modes/interactive/theme/theme.ts +8 -11
- package/src/modes/rpc/rpc-mode.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.2.1337] - 2026-01-03
|
|
6
|
+
|
|
7
|
+
## [2.1.1337] - 2026-01-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added `pi update` command to check for and install updates from GitHub releases or via bun
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Changed HTML export to use compile-time bundled templates via Bun macros for improved performance
|
|
16
|
+
- Changed `exportToHtml` and `exportFromFile` functions to be async
|
|
17
|
+
- Simplified build process by embedding assets (themes, templates, agents, commands) directly into the binary at compile time
|
|
18
|
+
- Removed separate asset copying steps from build scripts
|
|
19
|
+
|
|
5
20
|
## [2.0.1337] - 2026-01-03
|
|
6
21
|
### Added
|
|
7
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1337",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -31,17 +31,15 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"check": "tsgo --noEmit",
|
|
33
33
|
"clean": "rm -rf dist",
|
|
34
|
-
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js
|
|
35
|
-
"build:binary": "
|
|
36
|
-
"
|
|
37
|
-
"copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/ && mkdir -p dist/theme && cp src/modes/interactive/theme/*.json dist/theme/ && mkdir -p dist/export-html && cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/export-html/ && cp -r docs dist/ && cp -r examples dist/",
|
|
38
|
-
"test": "vitest --run",
|
|
34
|
+
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
35
|
+
"build:binary": "bun build --compile ./src/cli.ts --outfile dist/pi",
|
|
36
|
+
"test": "bun test",
|
|
39
37
|
"prepublishOnly": "npm run clean && npm run build"
|
|
40
38
|
},
|
|
41
39
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-agent-core": "
|
|
43
|
-
"@oh-my-pi/pi-ai": "
|
|
44
|
-
"@oh-my-pi/pi-tui": "
|
|
40
|
+
"@oh-my-pi/pi-agent-core": "2.2.1337",
|
|
41
|
+
"@oh-my-pi/pi-ai": "2.2.1337",
|
|
42
|
+
"@oh-my-pi/pi-tui": "2.2.1337",
|
|
45
43
|
"@sinclair/typebox": "^0.34.46",
|
|
46
44
|
"ajv": "^8.17.1",
|
|
47
45
|
"chalk": "^5.5.0",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for Bun's import attributes.
|
|
3
|
+
* These allow importing non-JS files as text at build time.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Markdown files imported as text
|
|
7
|
+
declare module "*.md" {
|
|
8
|
+
const content: string;
|
|
9
|
+
export default content;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Text files imported as text
|
|
13
|
+
declare module "*.txt" {
|
|
14
|
+
const content: string;
|
|
15
|
+
export default content;
|
|
16
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update CLI command handler.
|
|
3
|
+
*
|
|
4
|
+
* Handles `pi update` to check for and install updates.
|
|
5
|
+
* Uses bun if available, otherwise downloads binary from GitHub releases.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
9
|
+
import { createWriteStream, existsSync, renameSync, unlinkSync } from "node:fs";
|
|
10
|
+
import { dirname } from "node:path";
|
|
11
|
+
import { Readable } from "node:stream";
|
|
12
|
+
import { pipeline } from "node:stream/promises";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { VERSION } from "../config";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect if we're running as a Bun compiled binary.
|
|
18
|
+
*/
|
|
19
|
+
const isBunBinary =
|
|
20
|
+
import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
|
|
21
|
+
|
|
22
|
+
const REPO = "can1357/oh-my-pi";
|
|
23
|
+
const PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
24
|
+
|
|
25
|
+
interface ReleaseInfo {
|
|
26
|
+
tag: string;
|
|
27
|
+
version: string;
|
|
28
|
+
assets: Array<{ name: string; url: string }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse update subcommand arguments.
|
|
33
|
+
* Returns undefined if not an update command.
|
|
34
|
+
*/
|
|
35
|
+
export function parseUpdateArgs(args: string[]): { force: boolean; check: boolean } | undefined {
|
|
36
|
+
if (args.length === 0 || args[0] !== "update") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
force: args.includes("--force") || args.includes("-f"),
|
|
42
|
+
check: args.includes("--check") || args.includes("-c"),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if bun is available in PATH.
|
|
48
|
+
*/
|
|
49
|
+
function hasBun(): boolean {
|
|
50
|
+
try {
|
|
51
|
+
const result = spawnSync("bun", ["--version"], { encoding: "utf-8", stdio: "pipe" });
|
|
52
|
+
return result.status === 0;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the latest release info from GitHub.
|
|
60
|
+
*/
|
|
61
|
+
async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
62
|
+
const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`Failed to fetch release info: ${response.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data = (await response.json()) as {
|
|
68
|
+
tag_name: string;
|
|
69
|
+
assets: Array<{ name: string; browser_download_url: string }>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
tag: data.tag_name,
|
|
74
|
+
version: data.tag_name.replace(/^v/, ""),
|
|
75
|
+
assets: data.assets.map((a) => ({ name: a.name, url: a.browser_download_url })),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compare semver versions. Returns:
|
|
81
|
+
* - negative if a < b
|
|
82
|
+
* - 0 if a == b
|
|
83
|
+
* - positive if a > b
|
|
84
|
+
*/
|
|
85
|
+
function compareVersions(a: string, b: string): number {
|
|
86
|
+
const pa = a.split(".").map(Number);
|
|
87
|
+
const pb = b.split(".").map(Number);
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
90
|
+
const na = pa[i] || 0;
|
|
91
|
+
const nb = pb[i] || 0;
|
|
92
|
+
if (na !== nb) return na - nb;
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the appropriate binary name for this platform.
|
|
99
|
+
*/
|
|
100
|
+
function getBinaryName(): string {
|
|
101
|
+
const platform = process.platform;
|
|
102
|
+
const arch = process.arch;
|
|
103
|
+
|
|
104
|
+
let os: string;
|
|
105
|
+
switch (platform) {
|
|
106
|
+
case "linux":
|
|
107
|
+
os = "linux";
|
|
108
|
+
break;
|
|
109
|
+
case "darwin":
|
|
110
|
+
os = "darwin";
|
|
111
|
+
break;
|
|
112
|
+
case "win32":
|
|
113
|
+
os = "windows";
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let archName: string;
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case "x64":
|
|
122
|
+
archName = "x64";
|
|
123
|
+
break;
|
|
124
|
+
case "arm64":
|
|
125
|
+
archName = "arm64";
|
|
126
|
+
break;
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (os === "windows") {
|
|
132
|
+
return `pi-${os}-${archName}.exe`;
|
|
133
|
+
}
|
|
134
|
+
return `pi-${os}-${archName}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Update via bun package manager.
|
|
139
|
+
*/
|
|
140
|
+
async function updateViaBun(): Promise<void> {
|
|
141
|
+
console.log(chalk.dim("Updating via bun..."));
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
execSync(`bun update -g ${PACKAGE}`, { stdio: "inherit" });
|
|
145
|
+
console.log(chalk.green("\n✓ Update complete"));
|
|
146
|
+
} catch {
|
|
147
|
+
throw new Error("bun update failed");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Update by downloading binary from GitHub releases.
|
|
153
|
+
*/
|
|
154
|
+
async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
155
|
+
const binaryName = getBinaryName();
|
|
156
|
+
const asset = release.assets.find((a) => a.name === binaryName);
|
|
157
|
+
|
|
158
|
+
if (!asset) {
|
|
159
|
+
throw new Error(`No binary found for ${binaryName}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const execPath = process.execPath;
|
|
163
|
+
const _execDir = dirname(execPath);
|
|
164
|
+
const tempPath = `${execPath}.new`;
|
|
165
|
+
const backupPath = `${execPath}.bak`;
|
|
166
|
+
|
|
167
|
+
console.log(chalk.dim(`Downloading ${binaryName}...`));
|
|
168
|
+
|
|
169
|
+
// Download to temp file
|
|
170
|
+
const response = await fetch(asset.url, { redirect: "follow" });
|
|
171
|
+
if (!response.ok || !response.body) {
|
|
172
|
+
throw new Error(`Download failed: ${response.statusText}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fileStream = createWriteStream(tempPath, { mode: 0o755 });
|
|
176
|
+
const nodeStream = Readable.fromWeb(response.body as import("stream/web").ReadableStream);
|
|
177
|
+
await pipeline(nodeStream, fileStream);
|
|
178
|
+
|
|
179
|
+
// Replace current binary
|
|
180
|
+
console.log(chalk.dim("Installing update..."));
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Backup current binary
|
|
184
|
+
if (existsSync(backupPath)) {
|
|
185
|
+
unlinkSync(backupPath);
|
|
186
|
+
}
|
|
187
|
+
renameSync(execPath, backupPath);
|
|
188
|
+
|
|
189
|
+
// Move new binary into place
|
|
190
|
+
renameSync(tempPath, execPath);
|
|
191
|
+
|
|
192
|
+
// Clean up backup
|
|
193
|
+
unlinkSync(backupPath);
|
|
194
|
+
|
|
195
|
+
console.log(chalk.green(`\n✓ Updated to ${release.version}`));
|
|
196
|
+
console.log(chalk.dim("Restart pi to use the new version"));
|
|
197
|
+
} catch (err) {
|
|
198
|
+
// Restore from backup if possible
|
|
199
|
+
if (existsSync(backupPath) && !existsSync(execPath)) {
|
|
200
|
+
renameSync(backupPath, execPath);
|
|
201
|
+
}
|
|
202
|
+
if (existsSync(tempPath)) {
|
|
203
|
+
unlinkSync(tempPath);
|
|
204
|
+
}
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Run the update command.
|
|
211
|
+
*/
|
|
212
|
+
export async function runUpdateCommand(opts: { force: boolean; check: boolean }): Promise<void> {
|
|
213
|
+
console.log(chalk.dim(`Current version: ${VERSION}`));
|
|
214
|
+
|
|
215
|
+
// Check for updates
|
|
216
|
+
let release: ReleaseInfo;
|
|
217
|
+
try {
|
|
218
|
+
release = await getLatestRelease();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error(chalk.red(`Failed to check for updates: ${err}`));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const comparison = compareVersions(release.version, VERSION);
|
|
225
|
+
|
|
226
|
+
if (comparison <= 0 && !opts.force) {
|
|
227
|
+
console.log(chalk.green("✓ Already up to date"));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (comparison > 0) {
|
|
232
|
+
console.log(chalk.cyan(`New version available: ${release.version}`));
|
|
233
|
+
} else {
|
|
234
|
+
console.log(chalk.yellow(`Forcing reinstall of ${release.version}`));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (opts.check) {
|
|
238
|
+
// Just check, don't install
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Choose update method
|
|
243
|
+
try {
|
|
244
|
+
if (!isBunBinary && hasBun()) {
|
|
245
|
+
await updateViaBun();
|
|
246
|
+
} else {
|
|
247
|
+
await updateViaBinary(release);
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error(chalk.red(`Update failed: ${err}`));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Print update command help.
|
|
257
|
+
*/
|
|
258
|
+
export function printUpdateHelp(): void {
|
|
259
|
+
console.log(`${chalk.bold("pi update")} - Check for and install updates
|
|
260
|
+
|
|
261
|
+
${chalk.bold("Usage:")}
|
|
262
|
+
pi update [options]
|
|
263
|
+
|
|
264
|
+
${chalk.bold("Options:")}
|
|
265
|
+
-c, --check Check for updates without installing
|
|
266
|
+
-f, --force Force reinstall even if up to date
|
|
267
|
+
|
|
268
|
+
${chalk.bold("Examples:")}
|
|
269
|
+
pi update Update to latest version
|
|
270
|
+
pi update --check Check if updates are available
|
|
271
|
+
pi update --force Force reinstall
|
|
272
|
+
`);
|
|
273
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
|
+
// Embed package.json at build time for config
|
|
6
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
7
|
+
|
|
5
8
|
// =============================================================================
|
|
6
|
-
//
|
|
9
|
+
// App Config (from embedded package.json)
|
|
7
10
|
// =============================================================================
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export const APP_NAME: string = (packageJson as { piConfig?: { name?: string } }).piConfig?.name || "pi";
|
|
13
|
+
export const CONFIG_DIR_NAME: string =
|
|
14
|
+
(packageJson as { piConfig?: { configDir?: string } }).piConfig?.configDir || ".pi";
|
|
15
|
+
export const VERSION: string = (packageJson as { version: string }).version;
|
|
16
|
+
|
|
17
|
+
// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR
|
|
18
|
+
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
15
19
|
|
|
16
20
|
// =============================================================================
|
|
17
|
-
// Package
|
|
21
|
+
// Package Directory (for optional external docs/examples)
|
|
18
22
|
// =============================================================================
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
|
-
* Get the base directory for resolving package assets (
|
|
22
|
-
*
|
|
23
|
-
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
|
24
|
-
* - For tsx (src/): returns parent directory (the package root)
|
|
25
|
+
* Get the base directory for resolving optional package assets (docs, examples).
|
|
26
|
+
* Walk up from import.meta.dir until we find package.json, or fall back to cwd.
|
|
25
27
|
*/
|
|
26
28
|
export function getPackageDir(): string {
|
|
27
|
-
if (isBunBinary) {
|
|
28
|
-
// Bun binary: process.execPath points to the compiled executable
|
|
29
|
-
return dirname(process.execPath);
|
|
30
|
-
}
|
|
31
|
-
// Node.js: walk up from import.meta.dir until we find package.json
|
|
32
29
|
let dir = import.meta.dir;
|
|
33
30
|
while (dir !== dirname(dir)) {
|
|
34
31
|
if (existsSync(join(dir, "package.json"))) {
|
|
@@ -36,79 +33,30 @@ export function getPackageDir(): string {
|
|
|
36
33
|
}
|
|
37
34
|
dir = dirname(dir);
|
|
38
35
|
}
|
|
39
|
-
// Fallback (
|
|
40
|
-
return
|
|
36
|
+
// Fallback to cwd (docs/examples won't be found, but that's fine)
|
|
37
|
+
return process.cwd();
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
/**
|
|
44
|
-
* Get path to built-in themes directory (shipped with package)
|
|
45
|
-
* - For Bun binary: theme/ next to executable
|
|
46
|
-
* - For Node.js (dist/): dist/modes/interactive/theme/
|
|
47
|
-
* - For tsx (src/): src/modes/interactive/theme/
|
|
48
|
-
*/
|
|
49
|
-
export function getThemesDir(): string {
|
|
50
|
-
if (isBunBinary) {
|
|
51
|
-
return join(dirname(process.execPath), "theme");
|
|
52
|
-
}
|
|
53
|
-
// Theme is in modes/interactive/theme/ relative to src/ or dist/
|
|
54
|
-
const packageDir = getPackageDir();
|
|
55
|
-
const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
|
|
56
|
-
return join(packageDir, srcOrDist, "modes", "interactive", "theme");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get path to HTML export template directory (shipped with package)
|
|
61
|
-
* - For Bun binary: export-html/ next to executable
|
|
62
|
-
* - For Node.js (dist/): dist/core/export-html/
|
|
63
|
-
* - For tsx (src/): src/core/export-html/
|
|
64
|
-
*/
|
|
65
|
-
export function getExportTemplateDir(): string {
|
|
66
|
-
if (isBunBinary) {
|
|
67
|
-
return join(dirname(process.execPath), "export-html");
|
|
68
|
-
}
|
|
69
|
-
const packageDir = getPackageDir();
|
|
70
|
-
const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
|
|
71
|
-
return join(packageDir, srcOrDist, "core", "export-html");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Get path to package.json */
|
|
75
|
-
export function getPackageJsonPath(): string {
|
|
76
|
-
return join(getPackageDir(), "package.json");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Get path to README.md */
|
|
40
|
+
/** Get path to README.md (optional, may not exist in binary) */
|
|
80
41
|
export function getReadmePath(): string {
|
|
81
42
|
return resolve(join(getPackageDir(), "README.md"));
|
|
82
43
|
}
|
|
83
44
|
|
|
84
|
-
/** Get path to docs directory */
|
|
45
|
+
/** Get path to docs directory (optional, may not exist in binary) */
|
|
85
46
|
export function getDocsPath(): string {
|
|
86
47
|
return resolve(join(getPackageDir(), "docs"));
|
|
87
48
|
}
|
|
88
49
|
|
|
89
|
-
/** Get path to examples directory */
|
|
50
|
+
/** Get path to examples directory (optional, may not exist in binary) */
|
|
90
51
|
export function getExamplesPath(): string {
|
|
91
52
|
return resolve(join(getPackageDir(), "examples"));
|
|
92
53
|
}
|
|
93
54
|
|
|
94
|
-
/** Get path to CHANGELOG.md */
|
|
55
|
+
/** Get path to CHANGELOG.md (optional, may not exist in binary) */
|
|
95
56
|
export function getChangelogPath(): string {
|
|
96
57
|
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
|
97
58
|
}
|
|
98
59
|
|
|
99
|
-
// =============================================================================
|
|
100
|
-
// App Config (from package.json piConfig)
|
|
101
|
-
// =============================================================================
|
|
102
|
-
|
|
103
|
-
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
|
104
|
-
|
|
105
|
-
export const APP_NAME: string = pkg.piConfig?.name || "pi";
|
|
106
|
-
export const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || ".pi";
|
|
107
|
-
export const VERSION: string = pkg.version;
|
|
108
|
-
|
|
109
|
-
// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR
|
|
110
|
-
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
111
|
-
|
|
112
60
|
// =============================================================================
|
|
113
61
|
// User Config Paths (~/.pi/agent/*)
|
|
114
62
|
// =============================================================================
|
|
@@ -1900,7 +1900,7 @@ export class AgentSession {
|
|
|
1900
1900
|
* @param outputPath Optional output path (defaults to session directory)
|
|
1901
1901
|
* @returns Path to exported file
|
|
1902
1902
|
*/
|
|
1903
|
-
exportToHtml(outputPath?: string): string {
|
|
1903
|
+
async exportToHtml(outputPath?: string): Promise<string> {
|
|
1904
1904
|
const themeName = this.settingsManager.getTheme();
|
|
1905
1905
|
return exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName });
|
|
1906
1906
|
}
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles loading, saving, and refreshing credentials from auth.json.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
7
7
|
import { dirname } from "node:path";
|
|
8
8
|
import {
|
|
9
9
|
getEnvApiKey,
|
|
@@ -73,8 +73,7 @@ export class AuthStorage {
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
try {
|
|
76
|
-
|
|
77
|
-
this.data = JSON.parse(file.text() as unknown as string);
|
|
76
|
+
this.data = JSON.parse(readFileSync(this.authPath, "utf-8"));
|
|
78
77
|
} catch {
|
|
79
78
|
this.data = {};
|
|
80
79
|
}
|
|
@@ -1,44 +1,19 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { basename
|
|
1
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
3
|
import type { AgentState } from "@oh-my-pi/pi-agent-core";
|
|
4
|
-
import { APP_NAME
|
|
4
|
+
import { APP_NAME } from "../../config";
|
|
5
5
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme";
|
|
6
6
|
import { SessionManager } from "../session-manager";
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
let cachedJs: string | null = null;
|
|
11
|
-
|
|
12
|
-
/** Minify CSS by removing comments, unnecessary whitespace, and newlines. */
|
|
13
|
-
function minifyCss(css: string): string {
|
|
14
|
-
return css
|
|
15
|
-
.replace(/\/\*[\s\S]*?\*\//g, "") // Remove comments
|
|
16
|
-
.replace(/\s+/g, " ") // Collapse whitespace
|
|
17
|
-
.replace(/\s*([{}:;,>+~])\s*/g, "$1") // Remove space around punctuation
|
|
18
|
-
.replace(/;}/g, "}") // Remove trailing semicolons
|
|
19
|
-
.trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Minify JS using Bun's transpiler. */
|
|
23
|
-
function minifyJs(js: string): string {
|
|
24
|
-
const transpiler = new Bun.Transpiler({ loader: "js", minifyWhitespace: true });
|
|
25
|
-
return transpiler.transformSync(js);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Minify HTML by collapsing whitespace outside of tags. */
|
|
29
|
-
function minifyHtml(html: string): string {
|
|
30
|
-
return html
|
|
31
|
-
.replace(/>\s+</g, "><") // Remove whitespace between tags
|
|
32
|
-
.replace(/\s{2,}/g, " ") // Collapse multiple spaces
|
|
33
|
-
.trim();
|
|
34
|
-
}
|
|
8
|
+
// Bun macro: bundles HTML+CSS+JS at compile time, evaluated at bundle time
|
|
9
|
+
import { getTemplate } from "./template.macro" with { type: "macro" };
|
|
35
10
|
|
|
36
11
|
export interface ExportOptions {
|
|
37
12
|
outputPath?: string;
|
|
38
13
|
themeName?: string;
|
|
39
14
|
}
|
|
40
15
|
|
|
41
|
-
/** Parse a color string to RGB values.
|
|
16
|
+
/** Parse a color string to RGB values. */
|
|
42
17
|
function parseColor(color: string): { r: number; g: number; b: number } | undefined {
|
|
43
18
|
const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
|
|
44
19
|
if (hexMatch) {
|
|
@@ -68,7 +43,7 @@ function getLuminance(r: number, g: number, b: number): number {
|
|
|
68
43
|
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
69
44
|
}
|
|
70
45
|
|
|
71
|
-
/** Adjust color brightness.
|
|
46
|
+
/** Adjust color brightness. */
|
|
72
47
|
function adjustBrightness(color: string, factor: number): string {
|
|
73
48
|
const parsed = parseColor(color);
|
|
74
49
|
if (!parsed) return color;
|
|
@@ -76,21 +51,15 @@ function adjustBrightness(color: string, factor: number): string {
|
|
|
76
51
|
return `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;
|
|
77
52
|
}
|
|
78
53
|
|
|
79
|
-
/** Derive export background colors from a base color
|
|
54
|
+
/** Derive export background colors from a base color. */
|
|
80
55
|
function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {
|
|
81
56
|
const parsed = parseColor(baseColor);
|
|
82
57
|
if (!parsed) {
|
|
83
|
-
return {
|
|
84
|
-
pageBg: "rgb(24, 24, 30)",
|
|
85
|
-
cardBg: "rgb(30, 30, 36)",
|
|
86
|
-
infoBg: "rgb(60, 55, 40)",
|
|
87
|
-
};
|
|
58
|
+
return { pageBg: "rgb(24, 24, 30)", cardBg: "rgb(30, 30, 36)", infoBg: "rgb(60, 55, 40)" };
|
|
88
59
|
}
|
|
89
60
|
|
|
90
61
|
const luminance = getLuminance(parsed.r, parsed.g, parsed.b);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (isLight) {
|
|
62
|
+
if (luminance > 0.5) {
|
|
94
63
|
return {
|
|
95
64
|
pageBg: adjustBrightness(baseColor, 0.96),
|
|
96
65
|
cardBg: baseColor,
|
|
@@ -104,9 +73,7 @@ function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string
|
|
|
104
73
|
};
|
|
105
74
|
}
|
|
106
75
|
|
|
107
|
-
/**
|
|
108
|
-
* Generate CSS custom property declarations from theme colors.
|
|
109
|
-
*/
|
|
76
|
+
/** Generate CSS custom properties for theme. */
|
|
110
77
|
function generateThemeVars(themeName?: string): string {
|
|
111
78
|
const colors = getResolvedThemeColors(themeName);
|
|
112
79
|
const lines: string[] = [];
|
|
@@ -114,16 +81,15 @@ function generateThemeVars(themeName?: string): string {
|
|
|
114
81
|
lines.push(`--${key}: ${value};`);
|
|
115
82
|
}
|
|
116
83
|
|
|
117
|
-
// Use explicit theme export colors if available, otherwise derive from userMessageBg
|
|
118
84
|
const themeExport = getThemeExportColors(themeName);
|
|
119
85
|
const userMessageBg = colors.userMessageBg || "#343541";
|
|
120
|
-
const
|
|
86
|
+
const derived = deriveExportColors(userMessageBg);
|
|
121
87
|
|
|
122
|
-
lines.push(`--
|
|
123
|
-
lines.push(`--
|
|
124
|
-
lines.push(`--
|
|
88
|
+
lines.push(`--body-bg: ${themeExport.pageBg ?? derived.pageBg};`);
|
|
89
|
+
lines.push(`--container-bg: ${themeExport.cardBg ?? derived.cardBg};`);
|
|
90
|
+
lines.push(`--info-bg: ${themeExport.infoBg ?? derived.infoBg};`);
|
|
125
91
|
|
|
126
|
-
return lines.join("
|
|
92
|
+
return lines.join(" ");
|
|
127
93
|
}
|
|
128
94
|
|
|
129
95
|
interface SessionData {
|
|
@@ -134,61 +100,28 @@ interface SessionData {
|
|
|
134
100
|
tools?: { name: string; description: string }[];
|
|
135
101
|
}
|
|
136
102
|
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
*/
|
|
140
|
-
function generateHtml(sessionData: SessionData, themeName?: string): string {
|
|
141
|
-
const templateDir = getExportTemplateDir();
|
|
142
|
-
|
|
143
|
-
// Load and minify assets on first use
|
|
144
|
-
if (!cachedTemplate) {
|
|
145
|
-
cachedTemplate = minifyHtml(readFileSync(join(templateDir, "template.html"), "utf-8"));
|
|
146
|
-
}
|
|
147
|
-
if (!cachedJs) {
|
|
148
|
-
cachedJs = minifyJs(readFileSync(join(templateDir, "template.js"), "utf-8"));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const templateCss = readFileSync(join(templateDir, "template.css"), "utf-8");
|
|
152
|
-
|
|
103
|
+
/** Generate HTML from bundled template with runtime substitutions. */
|
|
104
|
+
async function generateHtml(sessionData: SessionData, themeName?: string): Promise<string> {
|
|
153
105
|
const themeVars = generateThemeVars(themeName);
|
|
154
|
-
const colors = getResolvedThemeColors(themeName);
|
|
155
|
-
const exportColors = deriveExportColors(colors.userMessageBg || "#343541");
|
|
156
|
-
const bodyBg = exportColors.pageBg;
|
|
157
|
-
const containerBg = exportColors.cardBg;
|
|
158
|
-
const infoBg = exportColors.infoBg;
|
|
159
|
-
|
|
160
|
-
// Base64 encode session data to avoid escaping issues
|
|
161
106
|
const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString("base64");
|
|
107
|
+
const template = await getTemplate();
|
|
162
108
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
templateCss
|
|
166
|
-
.replace("{{THEME_VARS}}", themeVars)
|
|
167
|
-
.replace("{{BODY_BG}}", bodyBg)
|
|
168
|
-
.replace("{{CONTAINER_BG}}", containerBg)
|
|
169
|
-
.replace("{{INFO_BG}}", infoBg),
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
return cachedTemplate
|
|
173
|
-
.replace("{{CSS}}", css)
|
|
174
|
-
.replace("{{JS}}", cachedJs)
|
|
109
|
+
return template
|
|
110
|
+
.replace("<theme-vars/>", `<style>:root { ${themeVars} }</style>`)
|
|
175
111
|
.replace("{{SESSION_DATA}}", sessionDataBase64);
|
|
176
112
|
}
|
|
177
113
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
114
|
+
/** Export session to HTML using SessionManager and AgentState. */
|
|
115
|
+
export async function exportSessionToHtml(
|
|
116
|
+
sm: SessionManager,
|
|
117
|
+
state?: AgentState,
|
|
118
|
+
options?: ExportOptions | string,
|
|
119
|
+
): Promise<string> {
|
|
183
120
|
const opts: ExportOptions = typeof options === "string" ? { outputPath: options } : options || {};
|
|
184
121
|
|
|
185
122
|
const sessionFile = sm.getSessionFile();
|
|
186
|
-
if (!sessionFile)
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
if (!existsSync(sessionFile)) {
|
|
190
|
-
throw new Error("Nothing to export yet - start a conversation first");
|
|
191
|
-
}
|
|
123
|
+
if (!sessionFile) throw new Error("Cannot export in-memory session to HTML");
|
|
124
|
+
if (!existsSync(sessionFile)) throw new Error("Nothing to export yet - start a conversation first");
|
|
192
125
|
|
|
193
126
|
const sessionData: SessionData = {
|
|
194
127
|
header: sm.getHeader(),
|
|
@@ -198,46 +131,28 @@ export function exportSessionToHtml(sm: SessionManager, state?: AgentState, opti
|
|
|
198
131
|
tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),
|
|
199
132
|
};
|
|
200
133
|
|
|
201
|
-
const html = generateHtml(sessionData, opts.themeName);
|
|
202
|
-
|
|
203
|
-
let outputPath = opts.outputPath;
|
|
204
|
-
if (!outputPath) {
|
|
205
|
-
const sessionBasename = basename(sessionFile, ".jsonl");
|
|
206
|
-
outputPath = `${APP_NAME}-session-${sessionBasename}.html`;
|
|
207
|
-
}
|
|
134
|
+
const html = await generateHtml(sessionData, opts.themeName);
|
|
135
|
+
const outputPath = opts.outputPath || `${APP_NAME}-session-${basename(sessionFile, ".jsonl")}.html`;
|
|
208
136
|
|
|
209
137
|
writeFileSync(outputPath, html, "utf8");
|
|
210
138
|
return outputPath;
|
|
211
139
|
}
|
|
212
140
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
* Used by CLI for exporting arbitrary session files.
|
|
216
|
-
*/
|
|
217
|
-
export function exportFromFile(inputPath: string, options?: ExportOptions | string): string {
|
|
141
|
+
/** Export session file to HTML (standalone). */
|
|
142
|
+
export async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {
|
|
218
143
|
const opts: ExportOptions = typeof options === "string" ? { outputPath: options } : options || {};
|
|
219
144
|
|
|
220
|
-
if (!existsSync(inputPath)) {
|
|
221
|
-
throw new Error(`File not found: ${inputPath}`);
|
|
222
|
-
}
|
|
145
|
+
if (!existsSync(inputPath)) throw new Error(`File not found: ${inputPath}`);
|
|
223
146
|
|
|
224
147
|
const sm = SessionManager.open(inputPath);
|
|
225
|
-
|
|
226
148
|
const sessionData: SessionData = {
|
|
227
149
|
header: sm.getHeader(),
|
|
228
150
|
entries: sm.getEntries(),
|
|
229
151
|
leafId: sm.getLeafId(),
|
|
230
|
-
systemPrompt: undefined,
|
|
231
|
-
tools: undefined,
|
|
232
152
|
};
|
|
233
153
|
|
|
234
|
-
const html = generateHtml(sessionData, opts.themeName);
|
|
235
|
-
|
|
236
|
-
let outputPath = opts.outputPath;
|
|
237
|
-
if (!outputPath) {
|
|
238
|
-
const inputBasename = basename(inputPath, ".jsonl");
|
|
239
|
-
outputPath = `${APP_NAME}-session-${inputBasename}.html`;
|
|
240
|
-
}
|
|
154
|
+
const html = await generateHtml(sessionData, opts.themeName);
|
|
155
|
+
const outputPath = opts.outputPath || `${APP_NAME}-session-${basename(inputPath, ".jsonl")}.html`;
|
|
241
156
|
|
|
242
157
|
writeFileSync(outputPath, html, "utf8");
|
|
243
158
|
return outputPath;
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Session Export</title>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
</style>
|
|
7
|
+
<template-css/>
|
|
8
|
+
<theme-vars/>
|
|
10
9
|
</head>
|
|
11
10
|
<body>
|
|
12
11
|
<button id="hamburger" title="Open sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><rect x="5" y="6" width="2" height="12"/><path d="M6 12h10c1 0 2 0 2-2V8"/></svg></button>
|
|
@@ -41,6 +40,6 @@
|
|
|
41
40
|
<script id="session-data" type="application/json">{{SESSION_DATA}}</script>
|
|
42
41
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.4/marked.min.js" integrity="sha512-VmLxPVdDGeR+F0DzUHVqzHwaR4ZSSh1g/7aYXwKT1PAGVxunOEcysta+4H5Utvmpr2xExEPybZ8q+iM9F1tGdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
43
42
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
44
|
-
<
|
|
43
|
+
<template-js/>
|
|
45
44
|
</body>
|
|
46
45
|
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun macro that inlines HTML template with CSS/JS at compile time.
|
|
3
|
+
* This runs during `bun build` and embeds the result as a string.
|
|
4
|
+
*/
|
|
5
|
+
export async function getTemplate(): Promise<string> {
|
|
6
|
+
const dir = new URL(".", import.meta.url).pathname;
|
|
7
|
+
|
|
8
|
+
// Read all files
|
|
9
|
+
const html = await Bun.file(`${dir}template.html`).text();
|
|
10
|
+
const css = await Bun.file(`${dir}template.css`).text();
|
|
11
|
+
const js = await Bun.file(`${dir}template.js`).text();
|
|
12
|
+
|
|
13
|
+
// Minify CSS
|
|
14
|
+
const minifiedCss = css
|
|
15
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
16
|
+
.replace(/\s+/g, " ")
|
|
17
|
+
.replace(/\s*([{}:;,])\s*/g, "$1")
|
|
18
|
+
.trim();
|
|
19
|
+
|
|
20
|
+
// Inline everything
|
|
21
|
+
return html
|
|
22
|
+
.replace("<template-css/>", `<style>${minifiedCss}</style>`)
|
|
23
|
+
.replace("<template-js/>", `<script>${js}</script>`);
|
|
24
|
+
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled agent definitions.
|
|
3
3
|
*
|
|
4
|
-
* Agents are
|
|
5
|
-
* These serve as defaults when no user/project agents are discovered.
|
|
4
|
+
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
7
|
+
// Embed agent markdown files at build time
|
|
8
|
+
import browserMd from "./bundled-agents/browser.md" with { type: "text" };
|
|
9
|
+
import exploreMd from "./bundled-agents/explore.md" with { type: "text" };
|
|
10
|
+
import planMd from "./bundled-agents/plan.md" with { type: "text" };
|
|
11
|
+
import reviewerMd from "./bundled-agents/reviewer.md" with { type: "text" };
|
|
12
|
+
import taskMd from "./bundled-agents/task.md" with { type: "text" };
|
|
11
13
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
12
14
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
+
const EMBEDDED_AGENTS: { name: string; content: string }[] = [
|
|
16
|
+
{ name: "browser.md", content: browserMd },
|
|
17
|
+
{ name: "explore.md", content: exploreMd },
|
|
18
|
+
{ name: "plan.md", content: planMd },
|
|
19
|
+
{ name: "reviewer.md", content: reviewerMd },
|
|
20
|
+
{ name: "task.md", content: taskMd },
|
|
21
|
+
];
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Parse YAML frontmatter from markdown content.
|
|
@@ -47,16 +54,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
|
-
*
|
|
57
|
+
* Parse an agent from embedded content.
|
|
51
58
|
*/
|
|
52
|
-
function
|
|
53
|
-
let content: string;
|
|
54
|
-
try {
|
|
55
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
59
|
+
function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
|
|
60
60
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
61
61
|
|
|
62
62
|
if (!frontmatter.name || !frontmatter.description) {
|
|
@@ -79,7 +79,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
|
|
|
79
79
|
recursive,
|
|
80
80
|
systemPrompt: body,
|
|
81
81
|
source,
|
|
82
|
-
filePath
|
|
82
|
+
filePath: `embedded:${fileName}`,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
|
|
|
87
87
|
let bundledAgentsCache: AgentDefinition[] | null = null;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Load all bundled agents from
|
|
90
|
+
* Load all bundled agents from embedded content.
|
|
91
91
|
* Results are cached after first load.
|
|
92
92
|
*/
|
|
93
93
|
export function loadBundledAgents(): AgentDefinition[] {
|
|
@@ -97,24 +97,8 @@ export function loadBundledAgents(): AgentDefinition[] {
|
|
|
97
97
|
|
|
98
98
|
const agents: AgentDefinition[] = [];
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return agents;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let entries: fs.Dirent[];
|
|
106
|
-
try {
|
|
107
|
-
entries = fs.readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true });
|
|
108
|
-
} catch {
|
|
109
|
-
bundledAgentsCache = agents;
|
|
110
|
-
return agents;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
115
|
-
|
|
116
|
-
const filePath = path.join(BUNDLED_AGENTS_DIR, entry.name);
|
|
117
|
-
const agent = loadAgentFromFile(filePath, "bundled");
|
|
100
|
+
for (const { name, content } of EMBEDDED_AGENTS) {
|
|
101
|
+
const agent = parseAgent(name, content, "bundled");
|
|
118
102
|
if (agent) {
|
|
119
103
|
agents.push(agent);
|
|
120
104
|
}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow commands for orchestrating multi-agent workflows.
|
|
3
3
|
*
|
|
4
|
-
* Commands are
|
|
5
|
-
* They define multi-step workflows that chain agent outputs.
|
|
4
|
+
* Commands are embedded at build time via Bun's import with { type: "text" }.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import * as fs from "node:fs";
|
|
9
8
|
import * as os from "node:os";
|
|
10
9
|
import * as path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Embed command markdown files at build time
|
|
12
|
+
import architectPlanMd from "./bundled-commands/architect-plan.md" with { type: "text" };
|
|
13
|
+
import implementMd from "./bundled-commands/implement.md" with { type: "text" };
|
|
14
|
+
import implementWithCriticMd from "./bundled-commands/implement-with-critic.md" with { type: "text" };
|
|
15
|
+
|
|
16
|
+
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
17
|
+
{ name: "architect-plan.md", content: architectPlanMd },
|
|
18
|
+
{ name: "implement-with-critic.md", content: implementWithCriticMd },
|
|
19
|
+
{ name: "implement.md", content: implementMd },
|
|
20
|
+
];
|
|
15
21
|
|
|
16
22
|
/** Workflow command definition */
|
|
17
23
|
export interface WorkflowCommand {
|
|
@@ -56,9 +62,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/**
|
|
59
|
-
* Load commands from a directory.
|
|
65
|
+
* Load commands from a directory (for user/project commands).
|
|
60
66
|
*/
|
|
61
|
-
function loadCommandsFromDir(dir: string, source: "
|
|
67
|
+
function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowCommand[] {
|
|
62
68
|
const commands: WorkflowCommand[] = [];
|
|
63
69
|
|
|
64
70
|
if (!fs.existsSync(dir)) {
|
|
@@ -137,15 +143,30 @@ function findNearestDir(cwd: string, relPath: string): string | null {
|
|
|
137
143
|
let bundledCommandsCache: WorkflowCommand[] | null = null;
|
|
138
144
|
|
|
139
145
|
/**
|
|
140
|
-
* Load all bundled commands.
|
|
146
|
+
* Load all bundled commands from embedded content.
|
|
141
147
|
*/
|
|
142
148
|
export function loadBundledCommands(): WorkflowCommand[] {
|
|
143
149
|
if (bundledCommandsCache !== null) {
|
|
144
150
|
return bundledCommandsCache;
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
const commands: WorkflowCommand[] = [];
|
|
154
|
+
|
|
155
|
+
for (const { name, content } of EMBEDDED_COMMANDS) {
|
|
156
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
157
|
+
const cmdName = name.replace(/\.md$/, "");
|
|
158
|
+
|
|
159
|
+
commands.push({
|
|
160
|
+
name: cmdName,
|
|
161
|
+
description: frontmatter.description || "",
|
|
162
|
+
instructions: body,
|
|
163
|
+
source: "bundled",
|
|
164
|
+
filePath: `embedded:${name}`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
bundledCommandsCache = commands;
|
|
169
|
+
return commands;
|
|
149
170
|
}
|
|
150
171
|
|
|
151
172
|
/**
|
package/src/main.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { processFileArguments } from "./cli/file-processor";
|
|
|
14
14
|
import { listModels } from "./cli/list-models";
|
|
15
15
|
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
|
|
16
16
|
import { selectSession } from "./cli/session-picker";
|
|
17
|
+
import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
|
|
17
18
|
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config";
|
|
18
19
|
import type { AgentSession } from "./core/agent-session";
|
|
19
20
|
import type { LoadedCustomTool } from "./core/custom-tools/index";
|
|
@@ -308,6 +309,17 @@ export async function main(args: string[]) {
|
|
|
308
309
|
return;
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
// Handle update subcommand
|
|
313
|
+
const updateCmd = parseUpdateArgs(args);
|
|
314
|
+
if (updateCmd) {
|
|
315
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
316
|
+
printUpdateHelp();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await runUpdateCommand(updateCmd);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
311
323
|
// Run migrations
|
|
312
324
|
const { migratedAuthProviders: migratedProviders } = runMigrations();
|
|
313
325
|
|
|
@@ -338,7 +350,7 @@ export async function main(args: string[]) {
|
|
|
338
350
|
if (parsed.export) {
|
|
339
351
|
try {
|
|
340
352
|
const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
|
|
341
|
-
const result = exportFromFile(parsed.export, outputPath);
|
|
353
|
+
const result = await exportFromFile(parsed.export, outputPath);
|
|
342
354
|
console.log(`Exported to: ${result}`);
|
|
343
355
|
return;
|
|
344
356
|
} catch (error: unknown) {
|
|
@@ -2130,7 +2130,7 @@ export class InteractiveMode {
|
|
|
2130
2130
|
|
|
2131
2131
|
// HTML file export
|
|
2132
2132
|
try {
|
|
2133
|
-
const filePath = this.session.exportToHtml(arg);
|
|
2133
|
+
const filePath = await this.session.exportToHtml(arg);
|
|
2134
2134
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
2135
2135
|
} catch (error: unknown) {
|
|
2136
2136
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -2153,7 +2153,7 @@ export class InteractiveMode {
|
|
|
2153
2153
|
// Export to a temp file
|
|
2154
2154
|
const tmpFile = path.join(os.tmpdir(), "session.html");
|
|
2155
2155
|
try {
|
|
2156
|
-
this.session.exportToHtml(tmpFile);
|
|
2156
|
+
await this.session.exportToHtml(tmpFile);
|
|
2157
2157
|
} catch (error: unknown) {
|
|
2158
2158
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2159
2159
|
return;
|
|
@@ -5,8 +5,11 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
5
5
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
8
|
-
import { getCustomThemesDir
|
|
8
|
+
import { getCustomThemesDir } from "../../../config";
|
|
9
9
|
import { logger } from "../../../core/logger";
|
|
10
|
+
// Embed theme JSON files at build time
|
|
11
|
+
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
12
|
+
import lightThemeJson from "./light.json" with { type: "json" };
|
|
10
13
|
|
|
11
14
|
// ============================================================================
|
|
12
15
|
// Types & Schema
|
|
@@ -450,18 +453,12 @@ export class Theme {
|
|
|
450
453
|
// Theme Loading
|
|
451
454
|
// ============================================================================
|
|
452
455
|
|
|
453
|
-
|
|
456
|
+
const BUILTIN_THEMES: Record<string, ThemeJson> = {
|
|
457
|
+
dark: darkThemeJson as ThemeJson,
|
|
458
|
+
light: lightThemeJson as ThemeJson,
|
|
459
|
+
};
|
|
454
460
|
|
|
455
461
|
function getBuiltinThemes(): Record<string, ThemeJson> {
|
|
456
|
-
if (!BUILTIN_THEMES) {
|
|
457
|
-
const themesDir = getThemesDir();
|
|
458
|
-
const darkPath = path.join(themesDir, "dark.json");
|
|
459
|
-
const lightPath = path.join(themesDir, "light.json");
|
|
460
|
-
BUILTIN_THEMES = {
|
|
461
|
-
dark: JSON.parse(fs.readFileSync(darkPath, "utf-8")) as ThemeJson,
|
|
462
|
-
light: JSON.parse(fs.readFileSync(lightPath, "utf-8")) as ThemeJson,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
462
|
return BUILTIN_THEMES;
|
|
466
463
|
}
|
|
467
464
|
|
|
@@ -401,7 +401,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
case "export_html": {
|
|
404
|
-
const path = session.exportToHtml(command.outputPath);
|
|
404
|
+
const path = await session.exportToHtml(command.outputPath);
|
|
405
405
|
return success(id, "export_html", { path });
|
|
406
406
|
}
|
|
407
407
|
|