@oh-my-pi/pi-coding-agent 14.9.5 → 14.9.8
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 +69 -0
- package/package.json +7 -7
- package/scripts/generate-template.ts +4 -3
- package/src/cli/setup-cli.ts +14 -161
- package/src/cli/stats-cli.ts +56 -2
- package/src/cli.ts +0 -1
- package/src/config/settings-schema.ts +0 -10
- package/src/eval/eval.lark +30 -10
- package/src/eval/js/context-manager.ts +334 -564
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/rewrite-imports.ts +211 -0
- package/src/eval/js/shared/runtime.ts +168 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +2 -4
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/parse.ts +218 -49
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +74 -89
- package/src/eval/py/index.ts +1 -2
- package/src/eval/py/kernel.ts +472 -900
- package/src/eval/py/prelude.py +95 -7
- package/src/eval/py/runner.py +879 -0
- package/src/eval/py/runtime.ts +3 -16
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/export/html/index.ts +5 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +93 -5
- package/src/export/html/template.macro.ts +4 -3
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/components/read-tool-group.ts +9 -0
- package/src/modes/controllers/command-controller.ts +0 -23
- package/src/prompts/tools/eval.md +14 -27
- package/src/prompts/tools/read.md +1 -0
- package/src/session/agent-session.ts +0 -1
- package/src/session/history-storage.ts +77 -19
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +86 -5
- package/src/tools/browser/tab-worker.ts +104 -58
- package/src/tools/conflict-detect.ts +661 -0
- package/src/tools/eval.ts +1 -1
- package/src/tools/index.ts +6 -0
- package/src/tools/path-utils.ts +1 -1
- package/src/tools/read.ts +130 -0
- package/src/tools/write.ts +204 -0
- package/src/web/search/index.ts +6 -4
- package/src/cli/jupyter-cli.ts +0 -106
- package/src/commands/jupyter.ts +0 -32
- package/src/eval/py/cancellation.ts +0 -28
- package/src/eval/py/gateway-coordinator.ts +0 -424
- /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
- /package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.9.8] - 2026-05-12
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Changed the `eval` tool input format to a single-line `*** Cell <lang>:"<title>" [t:<duration>] [rst]` header per cell, replacing the `*** Begin <LANG>` / `*** End <LANG>` envelope and the standalone `*** Title:` / `*** Timeout:` / `*** Reset` directives. The lark grammar enforces a fixed attribute order; the runtime parser remains lenient (alias keys, bare positional tokens, single-quoted titles).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `:conflicts` read selector (`read <path>:conflicts`) to return a one-line index of all unresolved merge conflicts with stable `#N` IDs for quick inspection
|
|
14
|
+
- Added bulk conflict resolution with `write({ path: "conflict://*", content })` to resolve all currently registered conflicts across files in one call, expanding `@ours`/`@theirs`/`@base`/`@both` per conflict and returning per-file counts
|
|
15
|
+
- Added `read` support for `conflict://<N>` and `read conflict://<N>/<scope>` to inspect unresolved conflict regions captured by a prior read, including `ours`, `theirs`, and `base` side views with original file line alignment
|
|
16
|
+
- Added shorthand content tokens `@ours`, `@theirs`, `@both`, and `@base` to conflict-resolution writes using `path: "conflict://<N>"` so replacement content can be composed from recorded conflict sections
|
|
17
|
+
- Added conflict count metadata to read results so conflict files now show a warning badge (`⚠ N`) in the read tool UI
|
|
18
|
+
- Added support for explicit boolean `rst` values (`rst:true`, `rst:false`, `rst:1`, `rst:0`, `rst:yes`, `rst:no`, `rst:on`, `rst:off`) in `*** Cell` headers
|
|
19
|
+
- Added detection of unresolved git merge conflicts in `read` output: each marker block is registered with a session-stable id and surfaced in a footer with `ours`/`theirs` previews. Resolve a block by calling `write({ path: "conflict://<id>", content })` — the tool splices the recorded marker region (markers and all sides) with the supplied content and routes through the normal writethrough (LSP format/diagnostics, fs-cache invalidation).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Changed `read` conflict warning footers to show `X of Y` unresolved conflicts when a range only captures part of a file and provide a `read <path>:conflicts` hint for the full list
|
|
24
|
+
- Changed conflict scanning in conflict read paths to inspect the whole file (with a 10 MB cap) so totals better reflect hidden conflicts and truncated scans are called out
|
|
25
|
+
- Changed conflict marker scanning during `read` to only register fully formed, column-0 merge-marker blocks, so indented or malformed marker-like lines are no longer treated as conflicts
|
|
26
|
+
- Changed `write` conflict resolution to validate `conflict://` IDs and report clear errors for malformed or unknown conflict URIs
|
|
27
|
+
- Changed the HTML transcript renderer to parse the new `*** Cell` headers while keeping the older `*** Begin <LANG>` and `===== ... =====` formats renderable for historical sessions.
|
|
28
|
+
- Changed the `eval` tool parser so a stray non-marker line between cells no longer crashes with `null is not an object (evaluating 'BEGIN_RE.exec(lines[i])[1]')`; stray content is consumed without aborting parsing.
|
|
29
|
+
- Changed `*** End` to be an optional, undocumented per-cell terminator (kept in the lark to satisfy GPT-trained models' natural terminator habit during constrained sampling).
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fixed single-conflict `write` retries to re-locate the recorded conflict block by exact marker content so shifted line numbers from out-of-band edits no longer prevent resolution
|
|
34
|
+
- Fixed `read conflict://*` handling by rejecting wildcard reads with a clear write-only guidance error
|
|
35
|
+
- Fixed conflict resolution to verify the live file still contains recorded `<<<<<<<` and `>>>>>>>` markers before splicing, preventing stale conflict IDs from silently corrupting out-of-band-edited files
|
|
36
|
+
- Fixed `@base` token handling so two-way conflicts without a base section now return a clear error
|
|
37
|
+
- Improved `*** Cell` header parsing to reject invalid `rst` values with a clear `invalid rst value` error
|
|
38
|
+
|
|
39
|
+
## [14.9.7] - 2026-05-12
|
|
40
|
+
|
|
41
|
+
### Breaking Changes
|
|
42
|
+
|
|
43
|
+
- Changed the `timeoutMs` execution option to no longer be enforced during worker-based JS runs, so callers must rely on external cancellation signals for time limits
|
|
44
|
+
- Replaced the Jupyter kernel gateway + WebSocket protocol behind the Python `eval` backend with a subprocess-backed runner that speaks NDJSON over stdin/stdout; removed the `jupyter_kernel_gateway`/`ipykernel` pip dependencies, the `python.sharedGateway` setting, the `omp jupyter` CLI command, and the `PI_PYTHON_GATEWAY_URL` / `PI_PYTHON_GATEWAY_TOKEN` environment variables
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Added Python `tool.<name>(args)` support to `executePython` sessions so evaluated Python code can invoke session tools through the prelude `tool` proxy
|
|
49
|
+
- Added per-execution Python tool bridge session registration and loopback endpoint wiring so Python tool calls resolve to host tools and return tool results
|
|
50
|
+
- Added status-event forwarding for Python tool bridge calls so `tool` invocations can emit execution status updates
|
|
51
|
+
- Added browser-tab JavaScript execution through the shared runtime so tab runs now expose the standard helper globals (`read`, `write`, `sort`, `uniq`, `counter`, `diff`, `tree`, `env`, `output`, `display`, and `tool`)
|
|
52
|
+
- Added static ESM `import` support to browser-tab JavaScript by rewriting top-level imports and resolving them against the tab session context
|
|
53
|
+
- Added substring fallback matching to `HistoryStorage.search` so infix and short-token queries that FTS5 prefix matching misses are still returned
|
|
54
|
+
- Added a live single-line sync progress display to the stats command showing current/total sessions while syncing
|
|
55
|
+
- Added automatic inline JS evaluation fallback when worker creation failed so script execution still works in environments without worker support
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Changed `setup python` to only verify a reachable Python 3 interpreter instead of installing Jupyter dependencies
|
|
60
|
+
- Changed `info` output to remove the obsolete Python Gateway status block now that shared gateway management is no longer available
|
|
61
|
+
- Changed JavaScript execution in `executeJs` to expose the worker\u2019s real `process` object instead of a restricted, frozen subset
|
|
62
|
+
- Changed JavaScript evaluation to run per session in a worker-backed runner with explicit initialization and teardown handling
|
|
63
|
+
- Changed the Python backend to launch one `python -u runner.py` subprocess per kernel; cancellation now sends `SIGINT` which raises a real `KeyboardInterrupt` in user code, and the same subprocess is reused across cells in session mode
|
|
64
|
+
- Changed Python magic handling so `%pip`, `%cd`, `%env`, `%pwd`, `%ls`, `%time`, `%timeit`, `%who`, `%reset`, `%load`, `%run`, `%%bash`, `%%capture`, `%%timeit`, `%%writefile`, and `!shell` work without depending on IPython
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Fixed Python output rendering so `text/markdown` takes precedence over `text/plain` and status bundles are emitted as status updates rather than plain text
|
|
69
|
+
- Fixed query tokenization in `HistoryStorage.search` so punctuation-delimited terms like `git-commit` are aligned with indexing and matched correctly
|
|
70
|
+
- Fixed history search result merging to de-duplicate matches and return full-text matches before substring-only matches while still respecting the requested limit
|
|
71
|
+
- Fixed JS run cancellation so aborting a run now also cancels in-flight tool calls and terminates the active worker session
|
|
72
|
+
- Fixed top-level `const`, `let`, and `class` declarations in evaluated JavaScript to persist across subsequent runs by rewriting top-level declarations
|
|
73
|
+
|
|
5
74
|
## [14.9.5] - 2026-05-12
|
|
6
75
|
### Breaking Changes
|
|
7
76
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "14.9.
|
|
4
|
+
"version": "14.9.8",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "14.9.
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "14.9.
|
|
52
|
-
"@oh-my-pi/pi-ai": "14.9.
|
|
53
|
-
"@oh-my-pi/pi-natives": "14.9.
|
|
54
|
-
"@oh-my-pi/pi-tui": "14.9.
|
|
55
|
-
"@oh-my-pi/pi-utils": "14.9.
|
|
50
|
+
"@oh-my-pi/omp-stats": "14.9.8",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "14.9.8",
|
|
52
|
+
"@oh-my-pi/pi-ai": "14.9.8",
|
|
53
|
+
"@oh-my-pi/pi-natives": "14.9.8",
|
|
54
|
+
"@oh-my-pi/pi-tui": "14.9.8",
|
|
55
|
+
"@oh-my-pi/pi-utils": "14.9.8",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34.49",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
|
@@ -18,10 +18,11 @@ const minifiedCss = css
|
|
|
18
18
|
.replace(/\s*([{}:;,])\s*/g, "$1")
|
|
19
19
|
.trim();
|
|
20
20
|
|
|
21
|
-
// Inline everything
|
|
21
|
+
// Inline everything; use function replacements so `$'`, `$&`, `$$`, etc. inside
|
|
22
|
+
// the embedded CSS/JS are not interpreted as substitution patterns.
|
|
22
23
|
const template = html
|
|
23
|
-
.replace("<template-css/>", `<style>${minifiedCss}</style>`)
|
|
24
|
-
.replace("<template-js/>", `<script>${js}</script>`);
|
|
24
|
+
.replace("<template-css/>", () => `<style>${minifiedCss}</style>`)
|
|
25
|
+
.replace("<template-js/>", () => `<script>${js}</script>`);
|
|
25
26
|
|
|
26
27
|
// Write generated file
|
|
27
28
|
const output = `// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface SetupCommandArgs {
|
|
|
21
21
|
|
|
22
22
|
const VALID_COMPONENTS: SetupComponent[] = ["python", "stt"];
|
|
23
23
|
|
|
24
|
-
const PYTHON_PACKAGES = ["jupyter_kernel_gateway", "ipykernel"];
|
|
25
24
|
const MANAGED_PYTHON_ENV = getPythonEnvDir();
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -65,10 +64,6 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
|
65
64
|
interface PythonCheckResult {
|
|
66
65
|
available: boolean;
|
|
67
66
|
pythonPath?: string;
|
|
68
|
-
uvPath?: string;
|
|
69
|
-
pipPath?: string;
|
|
70
|
-
missingPackages: string[];
|
|
71
|
-
installedPackages: string[];
|
|
72
67
|
usingManagedEnv?: boolean;
|
|
73
68
|
managedEnvPath?: string;
|
|
74
69
|
}
|
|
@@ -85,8 +80,6 @@ function managedPythonPath(): string {
|
|
|
85
80
|
async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
86
81
|
const result: PythonCheckResult = {
|
|
87
82
|
available: false,
|
|
88
|
-
missingPackages: [],
|
|
89
|
-
installedPackages: [],
|
|
90
83
|
managedEnvPath: MANAGED_PYTHON_ENV,
|
|
91
84
|
};
|
|
92
85
|
|
|
@@ -94,109 +87,24 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
|
94
87
|
const managedPath = managedPythonPath();
|
|
95
88
|
const hasManagedEnv = await Bun.file(managedPath).exists();
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const candidates = [systemPythonPath, hasManagedEnv ? managedPath : undefined].filter(
|
|
101
|
-
(candidate): candidate is string => !!candidate,
|
|
102
|
-
);
|
|
103
|
-
if (candidates.length === 0) {
|
|
90
|
+
const pythonPath = systemPythonPath ?? (hasManagedEnv ? managedPath : undefined);
|
|
91
|
+
if (!pythonPath) {
|
|
104
92
|
return result;
|
|
105
93
|
}
|
|
106
|
-
|
|
107
|
-
result.pythonPath =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
missingPackages: [...PYTHON_PACKAGES],
|
|
111
|
-
installedPackages: [] as string[],
|
|
112
|
-
usingManagedEnv: candidates[0] === managedPath,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
for (const pythonPath of candidates) {
|
|
116
|
-
const installedPackages: string[] = [];
|
|
117
|
-
const missingPackages: string[] = [];
|
|
118
|
-
for (const pkg of PYTHON_PACKAGES) {
|
|
119
|
-
const moduleName = pkg === "jupyter_kernel_gateway" ? "kernel_gateway" : pkg;
|
|
120
|
-
const script = `import importlib.util; raise SystemExit(0 if importlib.util.find_spec('${moduleName}') else 1)`;
|
|
121
|
-
const check = await $`${pythonPath} -c ${script}`.quiet().nothrow();
|
|
122
|
-
if (check.exitCode === 0) {
|
|
123
|
-
installedPackages.push(pkg);
|
|
124
|
-
} else {
|
|
125
|
-
missingPackages.push(pkg);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (missingPackages.length < bestMatch.missingPackages.length) {
|
|
130
|
-
bestMatch = {
|
|
131
|
-
pythonPath,
|
|
132
|
-
missingPackages,
|
|
133
|
-
installedPackages,
|
|
134
|
-
usingManagedEnv: pythonPath === managedPath,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (missingPackages.length === 0) {
|
|
139
|
-
result.available = true;
|
|
140
|
-
result.pythonPath = pythonPath;
|
|
141
|
-
result.missingPackages = missingPackages;
|
|
142
|
-
result.installedPackages = installedPackages;
|
|
143
|
-
result.usingManagedEnv = pythonPath === managedPath;
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
result.pythonPath = bestMatch.pythonPath;
|
|
149
|
-
result.missingPackages = bestMatch.missingPackages;
|
|
150
|
-
result.installedPackages = bestMatch.installedPackages;
|
|
151
|
-
result.usingManagedEnv = bestMatch.usingManagedEnv;
|
|
94
|
+
const probe = await $`${pythonPath} -c "import sys;sys.exit(0)"`.quiet().nothrow();
|
|
95
|
+
result.pythonPath = pythonPath;
|
|
96
|
+
result.available = probe.exitCode === 0;
|
|
97
|
+
result.usingManagedEnv = pythonPath === managedPath;
|
|
152
98
|
return result;
|
|
153
99
|
}
|
|
154
100
|
|
|
155
101
|
/**
|
|
156
102
|
* Install Python packages using uv (preferred) or pip.
|
|
157
103
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pipPath?: string,
|
|
163
|
-
): Promise<{ success: boolean; usedManagedEnv: boolean }> {
|
|
164
|
-
if (uvPath) {
|
|
165
|
-
console.log(chalk.dim(`Installing via uv: ${packages.join(" ")}`));
|
|
166
|
-
const result = await $`${uvPath} pip install ${packages}`.nothrow();
|
|
167
|
-
if (result.exitCode === 0) {
|
|
168
|
-
return { success: true, usedManagedEnv: false };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (pipPath) {
|
|
173
|
-
console.log(chalk.dim(`Installing via pip: ${packages.join(" ")}`));
|
|
174
|
-
const result = await $`${pipPath} install ${packages}`.nothrow();
|
|
175
|
-
if (result.exitCode === 0) {
|
|
176
|
-
return { success: true, usedManagedEnv: false };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
console.log(chalk.dim(`Falling back to managed virtual environment: ${MANAGED_PYTHON_ENV}`));
|
|
181
|
-
|
|
182
|
-
if (uvPath) {
|
|
183
|
-
const createEnv = await $`${uvPath} venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
|
184
|
-
if (createEnv.exitCode !== 0) {
|
|
185
|
-
return { success: false, usedManagedEnv: true };
|
|
186
|
-
}
|
|
187
|
-
const installInManagedEnv = await $`${uvPath} pip install --python ${MANAGED_PYTHON_ENV} ${packages}`.nothrow();
|
|
188
|
-
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const createEnv = await $`${pythonPath} -m venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
|
192
|
-
if (createEnv.exitCode !== 0) {
|
|
193
|
-
return { success: false, usedManagedEnv: true };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const managedPython = managedPythonPath();
|
|
197
|
-
const installInManagedEnv = await $`${managedPython} -m pip install ${packages}`.nothrow();
|
|
198
|
-
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
|
199
|
-
}
|
|
104
|
+
// Python installation helper removed: the subprocess runner has no Python
|
|
105
|
+
// package dependencies beyond a working interpreter. `omp setup python --check`
|
|
106
|
+
// remains as a probe; users install optional libs (pandas, matplotlib, ...)
|
|
107
|
+
// directly via pip or the in-process `%pip` magic.
|
|
200
108
|
|
|
201
109
|
/**
|
|
202
110
|
* Run the setup command.
|
|
@@ -232,67 +140,13 @@ async function handlePythonSetup(flags: { json?: boolean; check?: boolean }): Pr
|
|
|
232
140
|
console.log(chalk.dim(`Using managed environment: ${check.managedEnvPath}`));
|
|
233
141
|
}
|
|
234
142
|
|
|
235
|
-
if (check.
|
|
236
|
-
console.log(chalk.dim(`uv: ${check.uvPath}`));
|
|
237
|
-
} else if (check.pipPath) {
|
|
238
|
-
console.log(chalk.dim(`pip: ${check.pipPath}`));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (check.installedPackages.length > 0) {
|
|
242
|
-
console.log(chalk.green(`${theme.status.success} Installed: ${check.installedPackages.join(", ")}`));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (check.missingPackages.length === 0) {
|
|
143
|
+
if (check.available) {
|
|
246
144
|
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
247
145
|
return;
|
|
248
146
|
}
|
|
249
147
|
|
|
250
|
-
console.
|
|
251
|
-
|
|
252
|
-
if (flags.check) {
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!check.uvPath && !check.pipPath) {
|
|
257
|
-
console.error(chalk.red(`\n${theme.status.error} No package manager found`));
|
|
258
|
-
console.error(chalk.dim("Install uv (recommended) or pip:"));
|
|
259
|
-
console.error(chalk.dim(" curl -LsSf https://astral.sh/uv/install.sh | sh"));
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
console.log("");
|
|
264
|
-
const install = await installPythonPackages(check.missingPackages, check.pythonPath, check.uvPath, check.pipPath);
|
|
265
|
-
|
|
266
|
-
if (!install.success) {
|
|
267
|
-
console.error(chalk.red(`\n${theme.status.error} Installation failed`));
|
|
268
|
-
console.error(chalk.dim("Try installing manually:"));
|
|
269
|
-
if (install.usedManagedEnv) {
|
|
270
|
-
if (check.uvPath) {
|
|
271
|
-
console.error(chalk.dim(` uv venv ${MANAGED_PYTHON_ENV}`));
|
|
272
|
-
console.error(
|
|
273
|
-
chalk.dim(` uv pip install --python ${MANAGED_PYTHON_ENV} ${check.missingPackages.join(" ")}`),
|
|
274
|
-
);
|
|
275
|
-
} else {
|
|
276
|
-
console.error(chalk.dim(` ${check.pythonPath} -m venv ${MANAGED_PYTHON_ENV}`));
|
|
277
|
-
console.error(chalk.dim(` ${managedPythonPath()} -m pip install ${check.missingPackages.join(" ")}`));
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
console.error(chalk.dim(` ${check.uvPath ? "uv pip" : "pip"} install ${check.missingPackages.join(" ")}`));
|
|
281
|
-
}
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const recheck = await checkPythonSetup();
|
|
286
|
-
if (recheck.available) {
|
|
287
|
-
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
288
|
-
if (recheck.usingManagedEnv) {
|
|
289
|
-
console.log(chalk.dim(`Managed Python environment: ${recheck.managedEnvPath}`));
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
console.error(chalk.red(`\n${theme.status.error} Setup incomplete`));
|
|
293
|
-
console.error(chalk.dim(`Still missing: ${recheck.missingPackages.join(", ")}`));
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
148
|
+
console.error(chalk.red(`\n${theme.status.error} Python interpreter reported failure`));
|
|
149
|
+
process.exit(1);
|
|
296
150
|
}
|
|
297
151
|
|
|
298
152
|
async function handleSttSetup(flags: { json?: boolean; check?: boolean }): Promise<void> {
|
|
@@ -359,9 +213,8 @@ ${chalk.bold("Usage:")}
|
|
|
359
213
|
${APP_NAME} setup <component> [options]
|
|
360
214
|
|
|
361
215
|
${chalk.bold("Components:")}
|
|
362
|
-
python
|
|
216
|
+
python Verify a Python 3 interpreter is reachable for code execution
|
|
363
217
|
stt Install speech-to-text dependencies (openai-whisper, recording tools)
|
|
364
|
-
Packages: ${PYTHON_PACKAGES.join(", ")}
|
|
365
218
|
|
|
366
219
|
${chalk.bold("Options:")}
|
|
367
220
|
-c, --check Check if dependencies are installed without installing
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -8,6 +8,58 @@ import { APP_NAME, formatDuration, formatNumber, formatPercent } from "@oh-my-pi
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { openPath } from "../utils/open";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Single-line TTY progress bar. On a non-TTY stream we just stay quiet -
|
|
13
|
+
* the final "Synced ..." summary still prints either way.
|
|
14
|
+
*/
|
|
15
|
+
function createSyncProgressReporter(): {
|
|
16
|
+
onProgress: (event: { current: number; total: number; sessionFile: string }) => void;
|
|
17
|
+
finish: () => void;
|
|
18
|
+
} {
|
|
19
|
+
const stream = process.stderr;
|
|
20
|
+
const isTty = stream.isTTY === true;
|
|
21
|
+
let lastWidth = 0;
|
|
22
|
+
let lastRender = 0;
|
|
23
|
+
return {
|
|
24
|
+
onProgress(event) {
|
|
25
|
+
if (!isTty) return;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
// Throttle to ~30 fps and always force a render for the last file.
|
|
28
|
+
if (event.current < event.total && now - lastRender < 33) return;
|
|
29
|
+
lastRender = now;
|
|
30
|
+
const label = chalk.dim(shortenSessionFile(event.sessionFile));
|
|
31
|
+
const pct = ((event.current / event.total) * 100).toFixed(0).padStart(3, " ");
|
|
32
|
+
const counter = chalk.cyan(`[${event.current}/${event.total}]`);
|
|
33
|
+
const line = `${counter} ${pct}% ${label}`;
|
|
34
|
+
const columns = stream.columns ?? 120;
|
|
35
|
+
const trimmed = truncateToColumns(line, columns - 1);
|
|
36
|
+
stream.write(`\r${trimmed.padEnd(lastWidth)}`);
|
|
37
|
+
lastWidth = trimmed.length;
|
|
38
|
+
},
|
|
39
|
+
finish() {
|
|
40
|
+
if (!isTty || lastWidth === 0) return;
|
|
41
|
+
stream.write(`\r${" ".repeat(lastWidth)}\r`);
|
|
42
|
+
lastWidth = 0;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function shortenSessionFile(p: string): string {
|
|
48
|
+
const marker = "/sessions/";
|
|
49
|
+
const idx = p.indexOf(marker);
|
|
50
|
+
return idx >= 0 ? p.slice(idx + marker.length) : p;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function truncateToColumns(s: string, max: number): string {
|
|
54
|
+
if (max <= 0) return "";
|
|
55
|
+
const width = Bun.stringWidth(s, { countAnsiEscapeCodes: false });
|
|
56
|
+
if (width <= max) return s;
|
|
57
|
+
// Cheap right-trim with an ellipsis - we don't need ANSI-aware slicing
|
|
58
|
+
// because the colored prefix is short and the truncated tail is the
|
|
59
|
+
// dim filename, where dropping bytes is fine.
|
|
60
|
+
return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
61
|
+
}
|
|
62
|
+
|
|
11
63
|
// =============================================================================
|
|
12
64
|
// Types
|
|
13
65
|
// =============================================================================
|
|
@@ -74,8 +126,10 @@ export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
|
|
|
74
126
|
);
|
|
75
127
|
|
|
76
128
|
// Sync session files first
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
const progress = createSyncProgressReporter();
|
|
130
|
+
process.stderr.write("Syncing session files...\n");
|
|
131
|
+
const { processed, files } = await syncAllSessions({ onProgress: progress.onProgress });
|
|
132
|
+
progress.finish();
|
|
79
133
|
const total = await getTotalMessageCount();
|
|
80
134
|
console.log(`Synced ${processed} new entries from ${files} files (${total} total)\n`);
|
|
81
135
|
|
package/src/cli.ts
CHANGED
|
@@ -55,7 +55,6 @@ const commands: CommandEntry[] = [
|
|
|
55
55
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
56
56
|
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
|
|
57
57
|
{ name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
|
|
58
|
-
{ name: "jupyter", load: () => import("./commands/jupyter").then(m => m.default) },
|
|
59
58
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
60
59
|
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
61
60
|
{ name: "shell", load: () => import("./commands/shell").then(m => m.default) },
|
|
@@ -1662,16 +1662,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
1662
1662
|
},
|
|
1663
1663
|
},
|
|
1664
1664
|
|
|
1665
|
-
"python.sharedGateway": {
|
|
1666
|
-
type: "boolean",
|
|
1667
|
-
default: true,
|
|
1668
|
-
ui: {
|
|
1669
|
-
tab: "editing",
|
|
1670
|
-
label: "Shared Python Gateway",
|
|
1671
|
-
description: "Share IPython kernel gateway across pi instances",
|
|
1672
|
-
},
|
|
1673
|
-
},
|
|
1674
|
-
|
|
1675
1665
|
// ────────────────────────────────────────────────────────────────────────
|
|
1676
1666
|
// Tools
|
|
1677
1667
|
// ────────────────────────────────────────────────────────────────────────
|
package/src/eval/eval.lark
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
// Canonical Eval input. Each cell is introduced by a single header line:
|
|
2
|
+
//
|
|
3
|
+
// *** Cell <LANG>:"<title>" [t:<duration>] [rst]
|
|
4
|
+
//
|
|
5
|
+
// Attribute order is fixed: language+title, then optional timeout, then
|
|
6
|
+
// optional reset flag. Title may be empty (`py:""`).
|
|
7
|
+
//
|
|
8
|
+
// Tokens:
|
|
9
|
+
//
|
|
10
|
+
// py:"..." | js:"..." language plus title (required)
|
|
11
|
+
// t:<digits>(ms|s|m)? per-cell timeout (default 30s)
|
|
12
|
+
// rst reset this language's kernel before running
|
|
13
|
+
//
|
|
14
|
+
// Everything between one header line and the next (or the optional trailing
|
|
15
|
+
// `*** End`, or end of input) is the cell's code, verbatim. The runtime
|
|
16
|
+
// parser additionally accepts content before the first header as an implicit
|
|
17
|
+
// default-language cell, but that is lenient fallback and MUST NOT be relied
|
|
18
|
+
// on.
|
|
2
19
|
|
|
3
|
-
|
|
4
|
-
begin_cell: "*** Begin " LANG LF
|
|
5
|
-
end_cell: "*** End Cell" LF?
|
|
20
|
+
start: cell+ end_marker
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
title: "*** Title: " /(.+)/ LF
|
|
9
|
-
timeout: "*** Timeout: " /\d+(ms|s|m)?/ LF
|
|
10
|
-
reset: "*** Reset" LF
|
|
22
|
+
cell: cell_header code_line*
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
cell_header: "*** Cell" WS_INLINE LANG_TITLE (WS_INLINE T_ATTR)? (WS_INLINE RST_FLAG)? LF
|
|
13
25
|
|
|
14
|
-
|
|
26
|
+
end_marker: "*** End" LF?
|
|
27
|
+
|
|
28
|
+
code_line: CODE_TEXT LF | LF
|
|
29
|
+
CODE_TEXT: /([^*\r\n]|\*\*?[^*\r\n])+\*{0,2}|\*{1,2}/
|
|
30
|
+
|
|
31
|
+
LANG_TITLE: ("py" | "js") ":\"" /[^"\r\n]*/ "\""
|
|
32
|
+
T_ATTR: "t:" /\d+(ms|s|m)?/
|
|
33
|
+
RST_FLAG: "rst"
|
|
15
34
|
|
|
16
35
|
%import common.LF
|
|
36
|
+
%import common.WS_INLINE
|