@phenx-inc/ctlsurf 0.17.3 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/ctlsurf-worker.js +243 -44
- package/out/headless/index.mjs +1 -1
- package/out/headless/index.mjs.map +1 -1
- package/out/main/index.js +5 -1
- package/package.json +1 -1
- package/scripts/postinstall.cjs +32 -3
- package/scripts/prepare-work-map.py +12 -0
- package/scripts/setup-ui.cjs +161 -0
- package/scripts/work-map-setup.cjs +127 -0
- package/src/main/index.ts +9 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Node.js 18+. A ctlsurf account (free tier works).
|
|
|
38
38
|
|
|
39
39
|
**Live work map.** Open **Work map** in the titlebar (Ctrl+Shift+M), choose **Clef-Flash · local** or **Rules · lightweight**, and turn the map on. One chronological thread combines stated choices, hypotheses, readable thinking, actions, and results. Choice cards show the selected excerpt and its stated reason; following actions remain underneath in the same thread. Collapsible groups keep the semantic topics while preserving event order when Clef updates their placement. Expanding a thinking card loads the complete readable source block on demand and highlights the selected choice and reason. Full source text stays out of live map snapshots. Scrolling up pauses following; **Follow latest** returns to the newest activity. Closing the pane hides it; its On/Off switch controls collection for all local tabs.
|
|
40
40
|
|
|
41
|
-
Work map is currently limited to the **desktop app on Apple Silicon Macs with an available Metal GPU**. npm installation performs a lightweight hardware check, without downloading or loading a model. Desktop startup checks the hardware again and verifies that Electron can access the Apple GPU. The option stays disabled during detection and on unsupported machines, including when saved preferences previously enabled it. GPU detection never blocks installation or other app features. The terminal/headless version does not start map collection or inference. HTTP and Cloudflare providers are not enabled in this release.
|
|
41
|
+
Work map is currently limited to the **desktop app on Apple Silicon Macs with an available Metal GPU**. npm installation performs a lightweight hardware check, without downloading or loading a model. The first desktop launch on a capable Mac offers to download the Clef-Flash model; it asks once, and the choice is remembered. Desktop startup checks the hardware again and verifies that Electron can access the Apple GPU. The option stays disabled during detection and on unsupported machines, including when saved preferences previously enabled it. GPU detection never blocks installation or other app features. The terminal/headless version does not start map collection or inference. HTTP and Cloudflare providers are not enabled in this release.
|
|
42
42
|
|
|
43
43
|
Claude Code's readable `thinking` blocks and Codex's public reasoning summaries (response items, completed reasoning items, and legacy `agent_reasoning` events) feed the same pipeline. Candidate segments come from the complete readable block before the source preview is shortened; Clef selects the important choice and a supporting reason, rather than only a type label. Public agent updates can also state decisions and are labelled as updates. The recent stated reasoning informs placement of following actions. Encrypted, redacted, or omitted reasoning is never treated as readable thinking. The panel reports when summaries are unavailable, rather than inferring private decisions from tool output. Some models/sessions omit readable summaries entirely. [Claude thinking format](https://platform.claude.com/docs/en/build-with-claude/thinking), [Codex reasoning summaries](https://learn.chatgpt.com/docs/app-server).
|
|
44
44
|
|
|
@@ -46,13 +46,13 @@ Clef-Flash suggests branch placement and activity types. Titles come from transc
|
|
|
46
46
|
|
|
47
47
|
The local engine uses one shared, offline Python/MLX worker on Apple Silicon. It processes at most two events per request, caps input at 2,048 tokens, limits the pending queue to 24 events per tab, applies MLX allocation/cache limits, and unloads after 60 seconds idle or when mapping is turned off. A watchdog unloads a process exceeding 16 GiB RSS. These are resource guards, not a guarantee against system-wide memory pressure. Requests that fail leave the transcript-based map available and surface an error with a Retry control.
|
|
48
48
|
|
|
49
|
-
This checkout automatically reuses the local pilot's downloaded Clef-Flash runtime. On another Mac, install `uv` and
|
|
49
|
+
This checkout automatically reuses the local pilot's downloaded Clef-Flash runtime. On another Mac, install `uv` (`brew install uv`) and accept the first-launch prompt, or download at any time (approximately 10.7 GB of model weights plus a ~0.6 GB Python/MLX runtime, in `~/.ctlsurf/clef-flash`):
|
|
50
50
|
|
|
51
51
|
```sh
|
|
52
|
-
|
|
52
|
+
ctlsurf workmap
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
The
|
|
55
|
+
It shows download progress, then saves the paths and turns the map on. The runtime lives outside the npm package, so updating ctlsurf does not download it again. From a checkout, `python3 scripts/prepare-work-map.py` does the same download and prints the Python executable and model folder for **Local model setup**. These paths can also be supplied with `CTLSURF_CLEF_PYTHON` and `CTLSURF_CLEF_MODEL` before launch. Selecting an engine or opening the pane does not download model files. Rules needs no model or Python installation.
|
|
56
56
|
|
|
57
57
|
Validation: `npm run test:workmap`, `npm run test:workmap:ui`. To also load the pilot's actual weights for one offline decision-extraction inference and unload check: `node scripts/test-work-map.cjs --real-clef`. Optional `--replay-claude /path/session.jsonl` and `--replay-codex /path/rollout.jsonl` replay local files and print only diagnostic counts, never transcript text.
|
|
58
58
|
|
package/bin/ctlsurf-worker.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Subcommands:
|
|
7
7
|
* ctlsurf setup — configure MCP for all detected coding agents
|
|
8
|
+
* ctlsurf workmap — download the local Work map model (Apple Silicon)
|
|
8
9
|
* ctlsurf update — update to latest version
|
|
9
10
|
* ctlsurf doctor — check all dependencies
|
|
10
11
|
* ctlsurf version — print version
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
* ctlsurf --desktop — Electron GUI mode
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
const { execFileSync, execSync } = require('child_process')
|
|
18
|
+
const { execFileSync, execSync, spawn } = require('child_process')
|
|
18
19
|
const path = require('path')
|
|
19
20
|
const fs = require('fs')
|
|
20
21
|
const os = require('os')
|
|
@@ -23,6 +24,11 @@ const ROOT = path.resolve(__dirname, '..')
|
|
|
23
24
|
const args = process.argv.slice(2)
|
|
24
25
|
const subcommand = args[0]
|
|
25
26
|
|
|
27
|
+
const ui = require(path.join(ROOT, 'scripts', 'setup-ui.cjs'))
|
|
28
|
+
const wm = require(path.join(ROOT, 'scripts', 'work-map-setup.cjs'))
|
|
29
|
+
const { detectCapability } = require(path.join(ROOT, 'scripts', 'check-work-map-gpu.cjs'))
|
|
30
|
+
const WORK_MAP_PLATFORM = process.platform === 'darwin' && process.arch === 'arm64'
|
|
31
|
+
|
|
26
32
|
// ─── Colors ───────────────────────────────────────
|
|
27
33
|
|
|
28
34
|
const G = '\x1b[32m'
|
|
@@ -123,6 +129,18 @@ if (subcommand === 'setup') {
|
|
|
123
129
|
process.exit(0)
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
if (subcommand === 'workmap') {
|
|
133
|
+
process.stdout.write(ui.banner(getPkgVersion()))
|
|
134
|
+
const box = new ui.Steps('work map setup', ['Apple GPU check', 'Work map model']).start()
|
|
135
|
+
workMapSetup(box, 0, { explicit: true })
|
|
136
|
+
.finally(() => box.finish())
|
|
137
|
+
.then(ok => { console.log(''); process.exit(ok ? 0 : 1) }, (err) => {
|
|
138
|
+
console.error(`\n${Y}!${R} Work map setup failed: ${err.message}\n`)
|
|
139
|
+
process.exit(1)
|
|
140
|
+
})
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
if (subcommand === 'update') {
|
|
127
145
|
console.log(`\n${B} ctlsurf update${R}\n`)
|
|
128
146
|
|
|
@@ -202,6 +220,16 @@ if (subcommand === 'doctor') {
|
|
|
202
220
|
console.log(` ${D}Install: cd ${ROOT} && npm install${R}`)
|
|
203
221
|
}
|
|
204
222
|
|
|
223
|
+
if (WORK_MAP_PLATFORM) {
|
|
224
|
+
const found = wm.findModel(normalizeSettings(readSettings(settingsPathForMode('desktop'))).workMap)
|
|
225
|
+
if (found) {
|
|
226
|
+
console.log(` ${G}✓${R} Work map model ${D}(Clef-Flash · ${tildify(found.modelPath)})${R}`)
|
|
227
|
+
} else {
|
|
228
|
+
console.log(` ${D}-${R} Work map model ${D}(optional, not installed)${R}`)
|
|
229
|
+
console.log(` ${D}Install: ctlsurf workmap${R}`)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
205
233
|
// Build status
|
|
206
234
|
const headlessBuilt = fs.existsSync(path.join(ROOT, 'out/headless/index.mjs'))
|
|
207
235
|
const desktopBuilt = fs.existsSync(path.join(ROOT, 'out/main/index.js'))
|
|
@@ -235,15 +263,28 @@ function detectMode(argv) {
|
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
const mode = detectMode(args)
|
|
266
|
+
// Read before checkApiKey records this version as seen.
|
|
267
|
+
const state = launchState(mode)
|
|
268
|
+
const setupWanted = mode === 'desktop' && desktopSetupWanted(state)
|
|
269
|
+
|
|
270
|
+
if (process.stdout.isTTY && !args.includes('--skip-setup') && (setupWanted || state.versionChanged)) {
|
|
271
|
+
process.stdout.write(ui.banner(getPkgVersion()))
|
|
272
|
+
}
|
|
238
273
|
|
|
239
274
|
// Validate the ctlsurf API key before launching — on install/update or when
|
|
240
275
|
// the stored key is missing/rejected. A check failure never blocks launch.
|
|
241
276
|
checkApiKey(mode).catch(() => {}).then(() => launch(mode))
|
|
242
277
|
|
|
243
|
-
function launch(launchMode) {
|
|
278
|
+
async function launch(launchMode) {
|
|
244
279
|
ensurePtyHelperExecutable()
|
|
245
|
-
if (launchMode === 'desktop')
|
|
246
|
-
|
|
280
|
+
if (launchMode === 'desktop') {
|
|
281
|
+
if (setupWanted) {
|
|
282
|
+
await desktopSetup(state).catch(err => console.error(`${Y}!${R} Setup step failed: ${err.message}`))
|
|
283
|
+
}
|
|
284
|
+
runDesktop()
|
|
285
|
+
} else {
|
|
286
|
+
runTerminal()
|
|
287
|
+
}
|
|
247
288
|
}
|
|
248
289
|
|
|
249
290
|
// node-pty runs spawn-helper from whichever native dir it loaded (build/Release,
|
|
@@ -272,24 +313,14 @@ function ensurePtyHelperExecutable() {
|
|
|
272
313
|
function runDesktop() {
|
|
273
314
|
let electronPath
|
|
274
315
|
try {
|
|
316
|
+
// Bust require cache (electron's index.js reads path.txt at require time)
|
|
317
|
+
delete require.cache[require.resolve('electron')]
|
|
275
318
|
electronPath = require('electron')
|
|
276
319
|
} catch {
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
console.error(`\n${Y}!${R} Falling back to terminal mode.\n`)
|
|
282
|
-
runTerminal()
|
|
283
|
-
process.exit(0)
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
// Bust require cache (electron's index.js reads path.txt at require time)
|
|
287
|
-
delete require.cache[require.resolve('electron')]
|
|
288
|
-
electronPath = require('electron')
|
|
289
|
-
} catch (err) {
|
|
290
|
-
console.error(`\n${Y}!${R} Electron resolution failed after install: ${err.message}\n`)
|
|
291
|
-
process.exit(1)
|
|
292
|
-
}
|
|
320
|
+
// desktopSetup already tried to install it and reported why it failed.
|
|
321
|
+
console.error(`\n${Y}!${R} Electron is unavailable — falling back to terminal mode.\n`)
|
|
322
|
+
runTerminal()
|
|
323
|
+
process.exit(0)
|
|
293
324
|
}
|
|
294
325
|
|
|
295
326
|
// Rebrand Electron.app → ctlsurf.app (idempotent; sentinel file inside
|
|
@@ -316,42 +347,210 @@ function runDesktop() {
|
|
|
316
347
|
}
|
|
317
348
|
}
|
|
318
349
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
350
|
+
// ─── First-run setup ──────────────────────────────
|
|
351
|
+
|
|
352
|
+
function launchState(m) {
|
|
353
|
+
const s = normalizeSettings(readSettings(settingsPathForMode(m)))
|
|
354
|
+
return { firstRun: !s.lastVersion, versionChanged: s.lastVersion !== getPkgVersion() }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Electron missing always needs setup. Otherwise show the box once per
|
|
358
|
+
// install/update, or once on Apple Silicon until the Work map choice is made.
|
|
359
|
+
function desktopSetupWanted(launch) {
|
|
360
|
+
if (!electronResolves()) return true
|
|
361
|
+
if (!process.stdout.isTTY || args.includes('--skip-setup')) return false
|
|
362
|
+
if (launch.versionChanged) return true
|
|
363
|
+
return WORK_MAP_PLATFORM && !normalizeSettings(readSettings(settingsPathForMode('desktop'))).workMapSetup
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function desktopSetup(launch) {
|
|
367
|
+
const title = launch.firstRun ? 'first-run setup' : launch.versionChanged ? `updated to v${getPkgVersion()}` : 'setup'
|
|
368
|
+
const box = new ui.Steps(title, ['node-pty native addon', 'Electron runtime', 'Apple GPU check', 'Work map model']).start()
|
|
322
369
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
370
|
+
box.set(0, { state: 'run' })
|
|
371
|
+
try {
|
|
372
|
+
require(path.join(ROOT, 'node_modules', 'node-pty'))
|
|
373
|
+
box.set(0, { state: 'done' })
|
|
374
|
+
} catch {
|
|
375
|
+
box.set(0, { state: 'fail', note: 'failed to load', detail: 'terminal tabs need it — run: ctlsurf doctor' })
|
|
376
|
+
}
|
|
377
|
+
await electronStep(box, 1)
|
|
378
|
+
await workMapSetup(box, 2, { explicit: false })
|
|
379
|
+
} finally {
|
|
380
|
+
box.finish()
|
|
331
381
|
}
|
|
382
|
+
console.log(`\n ${D}launching ctlsurf…${R}\n`)
|
|
383
|
+
}
|
|
332
384
|
|
|
333
|
-
|
|
334
|
-
// extract-zip stalls on Node >= 26), and npm won't re-run it for a package
|
|
335
|
-
// that's already present — so unzip it ourselves with the system tool.
|
|
385
|
+
async function electronStep(box, i) {
|
|
336
386
|
if (!electronResolves()) {
|
|
337
|
-
|
|
387
|
+
// Electron not installed (devDependency, or wiped by `npm update`).
|
|
388
|
+
const started = Date.now()
|
|
389
|
+
box.set(i, { state: 'run', progress: null, note: '0s', detail: 'one-time download, ~100 MB' })
|
|
390
|
+
const tick = setInterval(() => box.set(i, { note: ui.formatDuration(Date.now() - started) }), 1000)
|
|
338
391
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
392
|
+
await runQuiet('npm install electron --no-save --silent', [], { cwd: ROOT, shell: true })
|
|
393
|
+
// electron's postinstall can exit 0 with a half-extracted binary (its
|
|
394
|
+
// extract-zip stalls on Node >= 26), and npm won't re-run it for a package
|
|
395
|
+
// that's already present — so unzip it ourselves with the system tool.
|
|
396
|
+
if (!electronResolves()) {
|
|
397
|
+
box.set(i, { detail: 'binary incomplete — extracting with system unzip' })
|
|
398
|
+
await runQuiet(process.execPath, [path.join(ROOT, 'scripts', 'extract-electron.js')])
|
|
399
|
+
}
|
|
342
400
|
} catch (err) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
401
|
+
box.set(i, { state: 'fail', progress: undefined, note: 'install failed', detail: `${err.message} · try: cd ${ROOT} && npm install electron` })
|
|
402
|
+
return
|
|
403
|
+
} finally {
|
|
404
|
+
clearInterval(tick)
|
|
346
405
|
}
|
|
347
406
|
if (!electronResolves()) {
|
|
348
|
-
|
|
407
|
+
box.set(i, { state: 'fail', progress: undefined, note: 'binary missing', detail: `try: cd ${ROOT} && node scripts/extract-electron.js` })
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Rebrand Electron.app → ctlsurf.app now, quietly; runDesktop's call is then a no-op.
|
|
413
|
+
const quiet = process.env.CTLSURF_QUIET
|
|
414
|
+
process.env.CTLSURF_QUIET = '1'
|
|
415
|
+
try {
|
|
416
|
+
require(path.join(ROOT, 'scripts', 'rebrand-electron.js')).rebrand()
|
|
417
|
+
} catch { /* runDesktop retries and reports */ } finally {
|
|
418
|
+
if (quiet === undefined) delete process.env.CTLSURF_QUIET
|
|
419
|
+
else process.env.CTLSURF_QUIET = quiet
|
|
420
|
+
}
|
|
421
|
+
let version = ''
|
|
422
|
+
try { version = require(path.join(ROOT, 'node_modules', 'electron', 'package.json')).version } catch { /* unknown */ }
|
|
423
|
+
box.set(i, { state: 'done', progress: undefined, note: version, detail: '' })
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Fills the GPU row at i and the model row at i + 1. Resolves true when the model is ready.
|
|
427
|
+
async function workMapSetup(box, i, { explicit }) {
|
|
428
|
+
const settingsPath = settingsPathForMode('desktop')
|
|
429
|
+
const update = (patch) => {
|
|
430
|
+
const s = normalizeSettings(readSettings(settingsPath))
|
|
431
|
+
writeSettings(settingsPath, { ...s, ...(typeof patch === 'function' ? patch(s) : patch) })
|
|
432
|
+
}
|
|
433
|
+
const adopt = (paths, enable) => update(s => ({
|
|
434
|
+
workMapSetup: 'ready',
|
|
435
|
+
workMap: { ...s.workMap, ...paths, ...(enable ? { enabled: true, engine: 'clef-flash' } : {}) },
|
|
436
|
+
}))
|
|
437
|
+
const later = 'install any time: ctlsurf workmap'
|
|
438
|
+
const step = i + 1
|
|
439
|
+
|
|
440
|
+
if (!WORK_MAP_PLATFORM) {
|
|
441
|
+
box.set(i, { state: 'skip', note: 'Apple Silicon Macs only' })
|
|
442
|
+
box.set(step, { state: 'skip', note: 'not available on this machine' })
|
|
443
|
+
return false
|
|
444
|
+
}
|
|
445
|
+
box.set(i, { state: 'run' })
|
|
446
|
+
const capability = await detectCapability({ desktop: true })
|
|
447
|
+
if (capability.state !== 'available') {
|
|
448
|
+
box.set(i, { state: 'skip', note: 'no Metal GPU', detail: capability.reason })
|
|
449
|
+
box.set(step, { state: 'skip', note: 'not available on this Mac' })
|
|
450
|
+
update({ workMapSetup: 'unavailable' })
|
|
451
|
+
return false
|
|
452
|
+
}
|
|
453
|
+
box.set(i, { state: 'done', label: `${capability.gpuName} · Metal` })
|
|
454
|
+
|
|
455
|
+
const settings = normalizeSettings(readSettings(settingsPath))
|
|
456
|
+
const found = wm.findModel(settings.workMap)
|
|
457
|
+
if (found) {
|
|
458
|
+
// Already on disk (survives npm updates). Turn the map on only if it was never configured.
|
|
459
|
+
const enable = explicit || !settings.workMap
|
|
460
|
+
adopt(found, enable)
|
|
461
|
+
const on = enable || !!settings.workMap?.enabled
|
|
462
|
+
box.set(step, { state: 'done', note: `Clef-Flash · ${on ? 'on' : 'ready'}` })
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
if (!explicit && settings.workMapSetup === 'declined') {
|
|
466
|
+
box.set(step, { state: 'skip', note: 'skipped', detail: later })
|
|
467
|
+
return false
|
|
468
|
+
}
|
|
469
|
+
const uv = wm.findUv()
|
|
470
|
+
if (!uv) {
|
|
471
|
+
box.set(step, { state: 'warn', note: 'needs uv', detail: 'brew install uv, then: ctlsurf workmap' })
|
|
472
|
+
update({ workMapSetup: 'deferred' })
|
|
473
|
+
return false
|
|
474
|
+
}
|
|
475
|
+
const free = wm.freeBytes()
|
|
476
|
+
if (free !== null && free < wm.DISK_NEEDED) {
|
|
477
|
+
box.set(step, { state: 'warn', note: 'not enough disk', detail: `needs ~${ui.formatBytes(wm.DISK_NEEDED)} free, ${ui.formatBytes(free)} available` })
|
|
478
|
+
update({ workMapSetup: 'deferred' })
|
|
479
|
+
return false
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!explicit) {
|
|
483
|
+
if (!process.stdin.isTTY || args.includes('--skip-setup')) {
|
|
484
|
+
box.set(step, { state: 'skip', note: 'not installed', detail: later })
|
|
485
|
+
update({ workMapSetup: 'deferred' })
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
box.set(step, { state: 'ask', note: `Clef-Flash · ${ui.formatBytes(wm.MODEL_BYTES)}` })
|
|
489
|
+
const yes = await box.confirm([
|
|
490
|
+
`${B}Work map${R} draws a live map of your agent's choices and actions`,
|
|
491
|
+
`with Clef-Flash, a local model on your Mac's GPU. Nothing is uploaded.`,
|
|
492
|
+
`${D}~${ui.formatBytes(wm.MODEL_BYTES)} model + ~0.6 GB runtime → ${tildify(wm.DEFAULT_DIR)}`
|
|
493
|
+
+ `${free !== null ? ` · ${ui.formatBytes(free)} free` : ''}${R}`,
|
|
494
|
+
], 'Download it now?')
|
|
495
|
+
if (!yes) {
|
|
496
|
+
update({ workMapSetup: 'declined' })
|
|
497
|
+
box.set(step, { state: 'skip', note: 'skipped', detail: later })
|
|
349
498
|
return false
|
|
350
499
|
}
|
|
351
500
|
}
|
|
352
501
|
|
|
353
|
-
|
|
354
|
-
|
|
502
|
+
const phases = { env: 'Python 3.12 env', deps: 'MLX runtime', model: 'model weights' }
|
|
503
|
+
const gb = (n) => (n / 1e9).toFixed(1)
|
|
504
|
+
const samples = []
|
|
505
|
+
box.set(step, { state: 'run', progress: null, note: 'starting', detail: '' })
|
|
506
|
+
try {
|
|
507
|
+
const paths = await wm.prepareModel({
|
|
508
|
+
uv,
|
|
509
|
+
onPhase: (name) => box.set(step, { progress: name === 'model' ? 0 : null, note: phases[name] || name, detail: '' }),
|
|
510
|
+
onBytes: (bytes) => {
|
|
511
|
+
// Rate over the last ~5 s of samples
|
|
512
|
+
const now = Date.now()
|
|
513
|
+
samples.push([now, bytes])
|
|
514
|
+
while (samples.length > 2 && now - samples[0][0] > 5000) samples.shift()
|
|
515
|
+
const [t0, b0] = samples[0]
|
|
516
|
+
const rate = now > t0 ? (bytes - b0) / ((now - t0) / 1000) : 0
|
|
517
|
+
const left = rate > 0 ? ` · ~${ui.formatDuration((wm.MODEL_BYTES - bytes) / rate * 1000)} left` : ''
|
|
518
|
+
box.set(step, {
|
|
519
|
+
progress: bytes / wm.MODEL_BYTES,
|
|
520
|
+
note: `${gb(Math.min(bytes, wm.MODEL_BYTES))}/${gb(wm.MODEL_BYTES)} GB`,
|
|
521
|
+
detail: rate > 0 ? `${ui.formatBytes(rate)}/s${left}` : 'connecting…',
|
|
522
|
+
})
|
|
523
|
+
},
|
|
524
|
+
})
|
|
525
|
+
adopt(paths, true)
|
|
526
|
+
box.set(step, { state: 'done', progress: undefined, note: 'Clef-Flash · on', detail: '' })
|
|
527
|
+
return true
|
|
528
|
+
} catch (err) {
|
|
529
|
+
update({ workMapSetup: 'deferred' })
|
|
530
|
+
box.set(step, {
|
|
531
|
+
state: 'fail', progress: undefined, note: 'download failed',
|
|
532
|
+
detail: `${err.message.slice(0, 80)} · log: ${tildify(path.join(wm.DEFAULT_DIR, 'prepare.log'))}`,
|
|
533
|
+
})
|
|
534
|
+
return false
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function runQuiet(command, argv = [], options = {}) {
|
|
539
|
+
return new Promise((resolve, reject) => {
|
|
540
|
+
const child = spawn(command, argv, { ...options, stdio: ['ignore', 'ignore', 'pipe'] })
|
|
541
|
+
let stderr = ''
|
|
542
|
+
child.stderr.on('data', (chunk) => { stderr = (stderr + chunk).slice(-2000) })
|
|
543
|
+
child.on('error', reject)
|
|
544
|
+
child.on('close', (code) => {
|
|
545
|
+
if (code === 0) resolve()
|
|
546
|
+
else reject(new Error(stderr.trim().split('\n').pop() || `exit code ${code}`))
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function tildify(p) {
|
|
552
|
+
const home = os.homedir()
|
|
553
|
+
return p.startsWith(home + path.sep) ? '~' + p.slice(home.length) : p
|
|
355
554
|
}
|
|
356
555
|
|
|
357
556
|
function electronResolves() {
|
package/out/headless/index.mjs
CHANGED
|
@@ -17381,7 +17381,7 @@ var require_package = __commonJS({
|
|
|
17381
17381
|
"package.json"(exports, module) {
|
|
17382
17382
|
module.exports = {
|
|
17383
17383
|
name: "@phenx-inc/ctlsurf",
|
|
17384
|
-
version: "0.17.
|
|
17384
|
+
version: "0.17.4",
|
|
17385
17385
|
description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
17386
17386
|
main: "out/main/index.js",
|
|
17387
17387
|
bin: {
|