@gotcos/glasses-server 6.15.5 → 6.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -1
- package/CHANGELOG.md +37 -0
- package/README.md +59 -9
- package/bin/cli.cjs +71 -5
- package/package.json +8 -3
- package/server/lib/claude-bridge.ts +14 -1
- package/server/lib/cli-debug-view.ts +31 -4
- package/server/lib/cursor-bridge.ts +726 -0
- package/server/lib/cursor-engine-sessions.ts +162 -0
- package/server/lib/cursor-model-catalog.ts +288 -0
- package/server/lib/cursor-run-ledger.ts +300 -0
- package/server/lib/hallucination-filter.ts +1 -1
- package/server/lib/model-router.ts +26 -1
- package/server/lib/prompt-draft-store.ts +1 -0
- package/server/lib/provider-terminal-error.ts +1 -1
- package/server/lib/query-job-coordinator.ts +1 -0
- package/server/lib/query-job-runtime.ts +8 -2
- package/server/lib/query-job-store.ts +11 -3
- package/server/lib/query-job-types.ts +2 -1
- package/server/lib/speaker-trainer.ts +1 -1
- package/server/lib/token-audit.ts +11 -1
- package/server/lib/transcribe-audio.ts +11 -2
- package/server/lib/whisper-local.ts +78 -3
- package/server/models/silero_vad.onnx +0 -0
- package/server/routes/cli-debug.ts +8 -0
- package/server/routes/health.ts +65 -7
- package/server/routes/openai-compat.ts +28 -4
- package/server/routes/prompt-drafts.ts +44 -3
- package/server/routes/transcribe.ts +9 -1
- package/shared/model-preference.ts +50 -2
package/.env.example
CHANGED
|
@@ -70,11 +70,15 @@ BIND_HOST=0.0.0.0
|
|
|
70
70
|
|
|
71
71
|
# ── VOICE (optional) ────────────────────────────────────────────────────
|
|
72
72
|
# Local transcription is FREE via whisper.cpp (brew install whisper-cpp; the
|
|
73
|
-
# model auto-downloads on first run).
|
|
73
|
+
# real-time turbo model auto-downloads on first run). Full HQ dictation also
|
|
74
|
+
# needs ggml-large-v3.bin as documented in README. Voice is local-only by default. Merely
|
|
74
75
|
# configuring a key never uploads audio. To allow OpenAI Whisper only after a
|
|
75
76
|
# local failure, set BOTH the exact opt-in and a key:
|
|
76
77
|
# COS_OPENAI_WHISPER_FALLBACK=1
|
|
77
78
|
# OPENAI_API_KEY=sk-...
|
|
79
|
+
# COS_HQ_SPECULATIVE_WARM=0 # disable background HQ warm
|
|
80
|
+
# COS_BATCH_LARGE_V3=0 # explicitly use turbo instead of full HQ
|
|
81
|
+
# COS_HQ_BEAM_INTERACTIVE=2 # interactive only; meetings stay at beam 5
|
|
78
82
|
|
|
79
83
|
# Spoken reply playback defaults to local Kokoro on Apple silicon Macs. The
|
|
80
84
|
# first run creates a private venv and downloads the model. Local mode fails
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
## 6.16.1
|
|
2
|
+
|
|
3
|
+
- **Cursor Agent models (Composer 2.5 / Grok 4.5).** Managed installs now ship
|
|
4
|
+
the Cursor bridge, model catalog, engine sessions, and run ledger. `/api/health`
|
|
5
|
+
advertises `features.cursor` + `cursor_models`; authenticated `/api/models`
|
|
6
|
+
merges Cursor slots with Codex. Fail-closed: unresolved Cursor never falls
|
|
7
|
+
through to Claude/Codex.
|
|
8
|
+
- **Cursor-complete setup and diagnostics.** The public launcher now accepts a
|
|
9
|
+
Cursor-only installation after `agent models` proves both slots, including the
|
|
10
|
+
`~/.local/bin/agent` fallback used by launchd. Authenticated CLI diagnostics
|
|
11
|
+
include redacted Cursor run state alongside Claude and Codex.
|
|
12
|
+
- **Silero VAD in the npm tarball.** `.npmignore` previously excluded
|
|
13
|
+
`server/models/`, so managed 6.16.0 ran with `silero_vad: disabled` and
|
|
14
|
+
untrimmed audio. Package now ships `server/models/silero_vad.onnx`.
|
|
15
|
+
- **HQ path retained.** Speculative HQ warm + interactive beam/light enhance from
|
|
16
|
+
6.15.5/6.16.0 remain the default Render path.
|
|
17
|
+
- **Public-package boundary.** The tarball contract excludes runtime data,
|
|
18
|
+
certificates, tests, operator-specific paths, and operator-specific names.
|
|
19
|
+
|
|
20
|
+
## 6.16.0
|
|
21
|
+
|
|
22
|
+
- **Truthful HQ results.** An HQ request is reported as HQ only when the full
|
|
23
|
+
local large-v3 decoder actually ran. Turbo, real-time server, long-audio, and
|
|
24
|
+
decode-error fallbacks now retain the requested mode while returning their
|
|
25
|
+
actual quality, backend, degradation flag, and bounded reason code.
|
|
26
|
+
- **HQ capability health.** `/api/health` and `/api/models` add a path-free
|
|
27
|
+
`capabilities.transcription.hq` block with availability, model, backend, and
|
|
28
|
+
a user-safe missing-prerequisite reason. Generic Whisper liveness no longer
|
|
29
|
+
implies that large-v3 HQ is installed.
|
|
30
|
+
- **Phone-visible fallback telemetry.** One-shot transcription and prompt-draft
|
|
31
|
+
finalize responses expose the same additive quality fields. Draft finalize
|
|
32
|
+
aggregates the records it actually used and reuses a successful degraded
|
|
33
|
+
warm result instead of paying for an identical second turbo decode.
|
|
34
|
+
- **Default unchanged.** Absent an explicit Fast request, prompt dictation still
|
|
35
|
+
requests HQ. `COS_HQ_SPECULATIVE_WARM=0` remains the immediate warm-path
|
|
36
|
+
rollback, and meeting batch beam/isolation behavior is unchanged.
|
|
37
|
+
|
|
1
38
|
## 6.15.5
|
|
2
39
|
|
|
3
40
|
- **Speculative HQ warm (no EHPK).** While a prompt-draft chunk is acknowledged,
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# COS Glasses Server
|
|
2
2
|
|
|
3
3
|
Self-hosted AI heads-up display for **Even G2 smart glasses**. Runs on your Mac,
|
|
4
|
-
talks to your local **Claude Code or
|
|
4
|
+
talks to your local **Claude Code, Codex, or Cursor Agent** CLI, and pushes answers, voice
|
|
5
5
|
transcription, and notes to the lens. Your data never leaves your machine, and no
|
|
6
6
|
API key is pasted into the phone for chat.
|
|
7
7
|
|
|
@@ -43,6 +43,11 @@ without silently losing completed replies.
|
|
|
43
43
|
`npm install -g @anthropic-ai/claude-code` (**never with `sudo`**), then run
|
|
44
44
|
`claude` and finish the browser sign-in
|
|
45
45
|
_or_ **Codex CLI** (GPT Frontier/Balanced) — https://developers.openai.com/codex/, then `codex login`
|
|
46
|
+
- _Optional:_ **Cursor Agent CLI** for Composer 2.5 Fast and Grok 4.5 Fast.
|
|
47
|
+
Ensure `agent` is on `PATH`, run `agent login`, and verify `agent models`
|
|
48
|
+
lists `composer-2.5-fast` and `cursor-grok-4.5-high-fast`. COS exposes the
|
|
49
|
+
Cursor slots only after both models resolve; it never silently substitutes
|
|
50
|
+
Claude or Codex.
|
|
46
51
|
- **Even G2 glasses** + the **COS Glasses** app from the Even Hub
|
|
47
52
|
- `brew install whisper-cpp` for free local voice (the launcher can download the model)
|
|
48
53
|
- _Optional:_ `brew install python@3.12 ffmpeg espeak-ng` for local Kokoro
|
|
@@ -51,12 +56,18 @@ without silently losing completed replies.
|
|
|
51
56
|
without these optional dependencies.
|
|
52
57
|
- _Optional:_ **Tailscale** so your phone reaches your Mac from anywhere
|
|
53
58
|
|
|
54
|
-
> No
|
|
55
|
-
> to
|
|
56
|
-
> default with `COS_G2_DEFAULT_MODEL`
|
|
59
|
+
> No provider API key is needed for chat when using signed-in CLIs. Usage is
|
|
60
|
+
> billed to the corresponding Claude, Codex, or Cursor subscription. Pick a
|
|
61
|
+
> provider per query, or set a default with `COS_G2_DEFAULT_MODEL`
|
|
62
|
+
> (`opus`|`fable`|`sonnet`|`codex-frontier`|`codex-balanced`|`cursor-grok`|`cursor-composer`).
|
|
57
63
|
> Claude tier aliases and the two GPT slots resolve dynamically, so new model
|
|
58
64
|
> releases do not require a new glasses package. GPT discovery refreshes every
|
|
59
65
|
> 15 minutes and retains its last-known-good catalog through transient failures.
|
|
66
|
+
> Cursor discovery also refreshes every 15 minutes and retains its last-known-good
|
|
67
|
+
> catalog through transient failures.
|
|
68
|
+
> Cursor **Agent** mode can edit files and run shell commands in the selected
|
|
69
|
+
> workspace. Choose **Ask** mode when you want a non-editing answer; clients that
|
|
70
|
+
> omit the execution mode default to Ask.
|
|
60
71
|
> Existing `COS_CODEX_MODEL` / `COS_CODEX_REASONING_EFFORT` settings remain
|
|
61
72
|
> supported on the migrated Frontier slot; leave them blank for auto-latest.
|
|
62
73
|
> Codex runs **sandboxed read-only** by default (`COS_CODEX_SANDBOX` to adjust).
|
|
@@ -86,9 +97,10 @@ range is the exact Tailscale/CGNAT allocation (`100.64.0.0/10`), not all of
|
|
|
86
97
|
`COS_DURABLE_QUERY_JOBS=1`: accepted work survives phone backgrounding,
|
|
87
98
|
WebView reloads, and network handoffs, then reattaches without duplicate work
|
|
88
99
|
or duplicate replies
|
|
89
|
-
- Choose Opus, Fable, Sonnet, GPT Frontier,
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
- Choose Opus, Fable, Sonnet, GPT Frontier, GPT Balanced, Composer 2.5 Fast, or
|
|
101
|
+
Grok 4.5 Fast. Cursor slots fail closed when the local CLI or concrete model
|
|
102
|
+
is unavailable; optional redacted tool activity streams only to the
|
|
103
|
+
authenticated query that requested it
|
|
92
104
|
- Message History + cross-day "reference message N" — your chats are archived by day
|
|
93
105
|
and every message keeps a permanent number you can recall (`/api/archive`, `/api/message/:num`)
|
|
94
106
|
- Send phone photos with queued prompts, and review assistant-selected generated,
|
|
@@ -106,6 +118,10 @@ range is the exact Tailscale/CGNAT allocation (`100.64.0.0/10`), not all of
|
|
|
106
118
|
Whisper fallback is optional and requires both the exact
|
|
107
119
|
`COS_OPENAI_WHISPER_FALLBACK=1` opt-in and a configured key; a key alone never
|
|
108
120
|
uploads audio.
|
|
121
|
+
- HQ prompt dictation is requested by default; the phone's **Fast mode** switch
|
|
122
|
+
opts into turbo. Server 6.16.0 reports whether full local large-v3 actually
|
|
123
|
+
ran, and compatible companions alert once if an HQ request used Fast or
|
|
124
|
+
Cloud instead of silently claiming HQ.
|
|
109
125
|
- Local-first spoken reply playback through Kokoro on Apple silicon. The first
|
|
110
126
|
use creates a private Python environment and downloads its model without
|
|
111
127
|
blocking the API. Selecting Local fails closed; `local_first` can fall back
|
|
@@ -129,6 +145,8 @@ optional except an installed CLI. Highlights: `BIND_HOST`, `PORT`,
|
|
|
129
145
|
`mcp__server__*` selectors shared by full and lightweight Claude paths),
|
|
130
146
|
`COS_CLAUDE_MCP_CONFIG` (optional absolute config path when `.mcp.json` is not
|
|
131
147
|
in the managed CLI working directory),
|
|
148
|
+
`COS_CURSOR_AGENT_BIN` (optional absolute Cursor `agent` binary),
|
|
149
|
+
`COS_CURSOR_PERSIST_SESSIONS=0` (disable Cursor session resume),
|
|
132
150
|
`COS_SCRIPTS_DIR` (full pipeline), `COS_DURABLE_QUERY_JOBS=1` (build 204+
|
|
133
151
|
server-owned query recovery), and `COS_MEDIA_ROOT` (optional image-store
|
|
134
152
|
location; default `~/.cos-glasses/data/media`). Your name + transcription vocabulary live in
|
|
@@ -137,6 +155,32 @@ Telegram activity export is disabled by default even when a private COS
|
|
|
137
155
|
pipeline contains `.telegram_config.json`; enable it only with the explicit
|
|
138
156
|
`COS_TELEGRAM_NOTIFICATIONS=1` opt-in.
|
|
139
157
|
|
|
158
|
+
## HQ dictation
|
|
159
|
+
|
|
160
|
+
Prompt dictation defaults to HQ. The phone owns the preference: **Fast mode
|
|
161
|
+
OFF** requests HQ, and **Fast mode ON** requests turbo. The Mac performs all
|
|
162
|
+
decoding; the phone does not run Whisper.
|
|
163
|
+
|
|
164
|
+
The first server start downloads the real-time turbo model. True HQ additionally
|
|
165
|
+
requires the full `ggml-large-v3.bin` model (about 3.1 GB):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
mkdir -p "$HOME/.local/share/whisper-models"
|
|
169
|
+
curl -fL --progress-bar \
|
|
170
|
+
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin \
|
|
171
|
+
-o "$HOME/.local/share/whisper-models/ggml-large-v3.bin.partial"
|
|
172
|
+
mv "$HOME/.local/share/whisper-models/ggml-large-v3.bin.partial" \
|
|
173
|
+
"$HOME/.local/share/whisper-models/ggml-large-v3.bin"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Restart the server, then confirm
|
|
177
|
+
`capabilities.transcription.hq.hqAvailable: true` at `/api/health`. The response
|
|
178
|
+
does not expose local paths. If the CLI or model is unavailable, dictation stays
|
|
179
|
+
usable on Fast and reports the downgrade truthfully. Set
|
|
180
|
+
`COS_HQ_SPECULATIVE_WARM=0` to disable background HQ warm immediately; set
|
|
181
|
+
`COS_BATCH_LARGE_V3=0` to explicitly use turbo. Interactive HQ uses beam 2 by
|
|
182
|
+
default (`COS_HQ_BEAM_INTERACTIVE`); meeting batch remains beam 5.
|
|
183
|
+
|
|
140
184
|
## Run from source
|
|
141
185
|
|
|
142
186
|
```bash
|
|
@@ -160,8 +204,14 @@ BIND_HOST=0.0.0.0 npm run start:server
|
|
|
160
204
|
Version 6.12.2+ never runs a second install from inside npm's temporary cache.
|
|
161
205
|
- *Phone can't connect* — check `BIND_HOST=0.0.0.0`, the same Tailscale account on both devices, and the correct `100.x` IP + token.
|
|
162
206
|
- *Safari connects but the app does not* — confirm `npx --yes @gotcos/glasses-server@latest` is 6.6.0+, then use the app's server reconnect/edit control to verify the current URL and token. Do not run a second source or `npx` server alongside it.
|
|
163
|
-
- *AI queries fail* — run `claude auth status
|
|
164
|
-
`
|
|
207
|
+
- *AI queries fail* — run `claude auth status`, `codex login status`, or
|
|
208
|
+
`agent status` for the selected provider, then authenticate with
|
|
209
|
+
`claude auth login`, `codex login`, or `agent login` when signed out.
|
|
210
|
+
- *Composer or Grok is missing* — update to server 6.16.1+, confirm `agent` is
|
|
211
|
+
discoverable on the service `PATH`, and run `agent models`. `/api/health`
|
|
212
|
+
must report `features.cursor: true`; authenticated `/api/models` must include
|
|
213
|
+
both `cursor-composer` and `cursor-grok`. Missing models fail closed instead
|
|
214
|
+
of falling through to another provider.
|
|
165
215
|
- *Voice getting billed?* — voice is local-only by default in 6.12.0+. Confirm
|
|
166
216
|
`/api/health` reports `capabilities.transcription.mode: "local-only"`. Remove
|
|
167
217
|
`COS_OPENAI_WHISPER_FALLBACK` (or set it to `0`) to disable an earlier opt-in.
|
package/bin/cli.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
renameSync,
|
|
17
17
|
chmodSync,
|
|
18
18
|
} = require('fs')
|
|
19
|
-
const { join, resolve } = require('path')
|
|
19
|
+
const { delimiter, join, resolve } = require('path')
|
|
20
20
|
const { homedir } = require('os')
|
|
21
21
|
|
|
22
22
|
// bin/cli.cjs -> package root is one level up. The server lives at <root>/server.
|
|
@@ -45,7 +45,7 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
45
45
|
console.log('')
|
|
46
46
|
console.log(' Requirements:')
|
|
47
47
|
console.log(' - Node.js 20.11+')
|
|
48
|
-
console.log(' - Claude Code CLI
|
|
48
|
+
console.log(' - Claude Code CLI, Codex CLI, or Cursor Agent CLI')
|
|
49
49
|
console.log(' - Even G2 smart glasses + the COS Glasses app (Even Hub)')
|
|
50
50
|
console.log(' - Optional local Kokoro voice: Apple silicon + Python 3.11 or 3.12')
|
|
51
51
|
console.log('')
|
|
@@ -71,7 +71,7 @@ if (nodeMajor < 20 || (nodeMajor === 20 && nodeMinor < 11)) {
|
|
|
71
71
|
}
|
|
72
72
|
console.log(green(' ✓') + ` Node.js ${process.versions.node}`)
|
|
73
73
|
|
|
74
|
-
// Step 2: agent CLI detection — at least one
|
|
74
|
+
// Step 2: agent CLI detection — at least one supported, signed-in CLI is required.
|
|
75
75
|
function getCliVersion(command, versionArg = '--version') {
|
|
76
76
|
try {
|
|
77
77
|
return execSync(`${command} ${versionArg} 2>&1`, { shell: '/bin/sh', stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
@@ -128,10 +128,57 @@ function codexAuthState() {
|
|
|
128
128
|
if (/not logged in|logged out|sign[ -]?in required/i.test(result.output)) return 'signed-out'
|
|
129
129
|
return 'unknown'
|
|
130
130
|
}
|
|
131
|
+
function resolveCursorAgentBinary() {
|
|
132
|
+
const configured = process.env.COS_CURSOR_AGENT_BIN?.trim()
|
|
133
|
+
if (configured && existsSync(configured)) return configured
|
|
134
|
+
for (const entry of (process.env.PATH || '').split(delimiter).filter(Boolean)) {
|
|
135
|
+
const candidate = resolve(entry, 'agent')
|
|
136
|
+
if (existsSync(candidate)) return candidate
|
|
137
|
+
}
|
|
138
|
+
const homeLocal = resolve(homedir(), '.local', 'bin', 'agent')
|
|
139
|
+
return existsSync(homeLocal) ? homeLocal : null
|
|
140
|
+
}
|
|
141
|
+
function cursorCliState() {
|
|
142
|
+
const binary = resolveCursorAgentBinary()
|
|
143
|
+
if (!binary) return { binary: null, version: null, auth: null }
|
|
144
|
+
|
|
145
|
+
let version = 'available'
|
|
146
|
+
try {
|
|
147
|
+
const about = execFileSync(binary, ['about'], {
|
|
148
|
+
encoding: 'utf8',
|
|
149
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
150
|
+
timeout: 5000,
|
|
151
|
+
}).trim()
|
|
152
|
+
const versionLine = about.split('\n').map((line) => line.trim()).find((line) => /CLI Version|cursor|agent/i.test(line))
|
|
153
|
+
if (versionLine) version = versionLine
|
|
154
|
+
} catch { /* `agent models` below is the readiness proof */ }
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const models = execFileSync(binary, ['models'], {
|
|
158
|
+
encoding: 'utf8',
|
|
159
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
160
|
+
timeout: 7000,
|
|
161
|
+
})
|
|
162
|
+
const required = ['composer-2.5-fast', 'cursor-grok-4.5-high-fast']
|
|
163
|
+
return {
|
|
164
|
+
binary,
|
|
165
|
+
version,
|
|
166
|
+
auth: required.every((model) => models.includes(model)) ? 'ready' : 'models-unresolved',
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const output = `${err.stdout?.toString() || ''}\n${err.stderr?.toString() || ''}`
|
|
170
|
+
return {
|
|
171
|
+
binary,
|
|
172
|
+
version,
|
|
173
|
+
auth: /not logged in|logged out|sign[ -]?in required|authenticate/i.test(output) ? 'signed-out' : 'unknown',
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
131
177
|
const claudeVersion = getCliVersion('claude')
|
|
132
178
|
const codexVersion = getCliVersion('codex')
|
|
133
179
|
const claudeAuth = claudeVersion ? claudeAuthState() : null
|
|
134
180
|
const codexAuth = codexVersion ? codexAuthState() : null
|
|
181
|
+
const cursor = cursorCliState()
|
|
135
182
|
if (claudeVersion) {
|
|
136
183
|
if (claudeAuth === 'signed-out') {
|
|
137
184
|
console.log(yellow(' ⚠') + ` Claude Code ${claudeVersion} installed — sign-in required`)
|
|
@@ -161,14 +208,33 @@ if (codexVersion) {
|
|
|
161
208
|
} else {
|
|
162
209
|
console.log(yellow(' ⚠') + ' Codex CLI not found ' + dim('— GPT Frontier/Balanced unavailable'))
|
|
163
210
|
}
|
|
164
|
-
|
|
211
|
+
if (cursor.binary) {
|
|
212
|
+
if (cursor.auth === 'ready') {
|
|
213
|
+
console.log(green(' ✓') + ` Cursor Agent ${cursor.version} ` + dim('(Composer 2.5 / Grok 4.5)'))
|
|
214
|
+
} else if (cursor.auth === 'signed-out') {
|
|
215
|
+
console.log(yellow(' ⚠') + ` Cursor Agent ${cursor.version} installed — sign-in required`)
|
|
216
|
+
console.log(' Run: ' + bold('agent login'))
|
|
217
|
+
} else if (cursor.auth === 'models-unresolved') {
|
|
218
|
+
console.log(yellow(' ⚠') + ` Cursor Agent ${cursor.version} installed — required models unresolved`)
|
|
219
|
+
console.log(' Verify: ' + bold('agent models') + ' includes Composer 2.5 Fast and Grok 4.5 Fast')
|
|
220
|
+
} else {
|
|
221
|
+
console.log(yellow(' ⚠') + ` Cursor Agent ${cursor.version} installed — readiness unavailable`)
|
|
222
|
+
console.log(' Verify: ' + bold('agent models'))
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
console.log(yellow(' ⚠') + ' Cursor Agent CLI not found ' + dim('— Composer/Grok unavailable'))
|
|
226
|
+
}
|
|
227
|
+
const hasUsableAgent = (claudeVersion && claudeAuth !== 'signed-out')
|
|
228
|
+
|| (codexVersion && codexAuth !== 'signed-out')
|
|
229
|
+
|| cursor.auth === 'ready'
|
|
165
230
|
if (!hasUsableAgent) {
|
|
166
231
|
console.log('')
|
|
167
232
|
console.log(red(' ✗ No signed-in agent CLI is ready'))
|
|
168
|
-
console.log(' Claude Desktop alone is not enough; COS needs a terminal CLI.')
|
|
233
|
+
console.log(' Claude Desktop alone is not enough; COS needs a signed-in terminal CLI.')
|
|
169
234
|
console.log(' Install Claude Code (no sudo): ' + bold('npm install -g @anthropic-ai/claude-code'))
|
|
170
235
|
console.log(' Then run: ' + bold('claude auth login'))
|
|
171
236
|
console.log(' or Codex CLI: ' + bold('https://developers.openai.com/codex/') + ' then ' + bold('codex login'))
|
|
237
|
+
console.log(' or Cursor Agent CLI: run ' + bold('agent login') + ', then verify ' + bold('agent models'))
|
|
172
238
|
console.log(' Setup help: ' + bold('https://www.gotcos.com/wizard/'))
|
|
173
239
|
console.log('')
|
|
174
240
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "COS Glasses — self-hosted AI heads-up-display server for Even G2 smart glasses, powered by
|
|
3
|
+
"version": "6.16.1",
|
|
4
|
+
"description": "COS Glasses — self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"glasses-server": "bin/cli.cjs",
|
|
@@ -21,13 +21,18 @@
|
|
|
21
21
|
"ai",
|
|
22
22
|
"cos",
|
|
23
23
|
"heads-up-display",
|
|
24
|
-
"whisper"
|
|
24
|
+
"whisper",
|
|
25
|
+
"cursor"
|
|
25
26
|
],
|
|
26
27
|
"files": [
|
|
27
28
|
"bin/cli.cjs",
|
|
28
29
|
"bin/managed-server.cjs",
|
|
29
30
|
"managed-runtime-contract.json",
|
|
30
31
|
"server",
|
|
32
|
+
"!server/data/**",
|
|
33
|
+
"!server/certs/**",
|
|
34
|
+
"!server/models/**",
|
|
35
|
+
"server/models/silero_vad.onnx",
|
|
31
36
|
"shared",
|
|
32
37
|
"!server/**/*.test.ts",
|
|
33
38
|
"!shared/**/*.test.ts",
|
|
@@ -303,8 +303,11 @@ export interface ModelRunMetadata {
|
|
|
303
303
|
claudeRunId?: string
|
|
304
304
|
clientJobId?: string
|
|
305
305
|
generation?: number
|
|
306
|
+
turnId?: string
|
|
306
307
|
codexRunId?: string
|
|
307
308
|
codexThreadId?: string
|
|
309
|
+
cursorRunId?: string
|
|
310
|
+
cursorChatId?: string
|
|
308
311
|
outputAttachments?: MediaAttachmentRef[]
|
|
309
312
|
outputImageStats?: RunOutputImageCollectionStats
|
|
310
313
|
}
|
|
@@ -312,7 +315,7 @@ export interface ModelRunMetadata {
|
|
|
312
315
|
/** Public-safe provider launch metadata for durable job coordination. It
|
|
313
316
|
* deliberately exposes no ChildProcess object, kill handle, paths, or env. */
|
|
314
317
|
export interface ProviderProcessMetadata {
|
|
315
|
-
provider: 'claude' | 'codex'
|
|
318
|
+
provider: 'claude' | 'codex' | 'cursor'
|
|
316
319
|
runId: string
|
|
317
320
|
pid?: number
|
|
318
321
|
clientJobId?: string
|
|
@@ -365,6 +368,16 @@ export interface CallOptions {
|
|
|
365
368
|
effort?: EffortPreference
|
|
366
369
|
clientJobId?: string
|
|
367
370
|
generation?: number
|
|
371
|
+
/** Alias used by Cursor/durable-job callers; preferred over `generation`. */
|
|
372
|
+
jobGeneration?: number
|
|
373
|
+
turnId?: string
|
|
374
|
+
surface?: 'query' | 'openai_compat' | 'unknown'
|
|
375
|
+
messageEra?: string
|
|
376
|
+
globalMsgNum?: number
|
|
377
|
+
/** Optional prompt block prepended for handoff-style context. */
|
|
378
|
+
handoffContext?: { promptBlock?: string }
|
|
379
|
+
/** Cursor ask vs full agent. Omitted → ask. */
|
|
380
|
+
cursorExecutionMode?: import('../../shared/model-preference.js').CursorExecutionMode
|
|
368
381
|
/** Durable coordinator already owns the per-session provider lease. */
|
|
369
382
|
sessionLockHeld?: boolean
|
|
370
383
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ClaudeRunConfig, ClaudeRunRecord } from './claude-run-ledger.js'
|
|
2
2
|
import type { CodexRunConfig, CodexRunRecord } from './codex-run-ledger.js'
|
|
3
|
+
import type { CursorRunConfig, CursorRunRecord } from './cursor-run-ledger.js'
|
|
3
4
|
|
|
4
5
|
export const CLI_DEBUG_CAPABILITY = Object.freeze({
|
|
5
6
|
schemaVersion: 1,
|
|
6
|
-
providers: Object.freeze({ claude: true, codex: true }),
|
|
7
|
+
providers: Object.freeze({ claude: true, codex: true, cursor: true }),
|
|
7
8
|
metadataOnly: true,
|
|
8
9
|
})
|
|
9
10
|
|
|
@@ -37,6 +38,7 @@ export interface SafeCliDebugResponse {
|
|
|
37
38
|
providers: {
|
|
38
39
|
claude: SafeCliDebugProvider
|
|
39
40
|
codex: SafeCliDebugProvider
|
|
41
|
+
cursor: SafeCliDebugProvider
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -55,12 +57,14 @@ function safeStatus(value: unknown): SafeCliRunStatus {
|
|
|
55
57
|
: 'failed'
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
function safeModelLabel(value: unknown, provider: 'claude' | 'codex'): string | undefined {
|
|
60
|
+
function safeModelLabel(value: unknown, provider: 'claude' | 'codex' | 'cursor'): string | undefined {
|
|
59
61
|
const model = optionalString(value)
|
|
60
62
|
if (!model || model.length > 96) return undefined
|
|
61
63
|
const allowed = provider === 'claude'
|
|
62
64
|
? /^(?:claude-|opus(?:\[1m\])?$|sonnet(?:\[1m\])?$|fable(?:\[1m\])?$|haiku(?:\[1m\])?$)[a-z0-9._\[\]-]*$/i
|
|
63
|
-
:
|
|
65
|
+
: provider === 'codex'
|
|
66
|
+
? /^(?:gpt-|codex-)[a-z0-9._\[\]-]*$/i
|
|
67
|
+
: /^(?:cursor-|composer-)[a-z0-9._\[\]-]*$/i
|
|
64
68
|
return allowed.test(model) ? model : undefined
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -77,7 +81,7 @@ function safeTimestamp(value: unknown): string {
|
|
|
77
81
|
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : new Date(0).toISOString()
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
function safeErrorCode(provider: 'claude' | 'codex', value: unknown): string | undefined {
|
|
84
|
+
function safeErrorCode(provider: 'claude' | 'codex' | 'cursor', value: unknown): string | undefined {
|
|
81
85
|
const candidate = optionalString(value)
|
|
82
86
|
if (!candidate) return undefined
|
|
83
87
|
const allowed = new Set([
|
|
@@ -126,11 +130,28 @@ export function safeCodexLatestRun(run?: CodexRunRecord): SafeCliDebugLatestRun
|
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
export function safeCursorLatestRun(run?: CursorRunRecord): SafeCliDebugLatestRun | null {
|
|
134
|
+
if (!run) return null
|
|
135
|
+
const concreteModel = safeModelLabel(run.cliModel, 'cursor')
|
|
136
|
+
const errorCode = safeErrorCode('cursor', run.errorCode)
|
|
137
|
+
return {
|
|
138
|
+
status: safeStatus(run.status),
|
|
139
|
+
model: safeModelLabel(run.model, 'cursor') ?? 'unknown',
|
|
140
|
+
...(concreteModel ? { concreteModel } : {}),
|
|
141
|
+
resumed: run.resumed === true,
|
|
142
|
+
...(optionalFiniteNumber(run.durationMs) !== undefined ? { durationMs: run.durationMs } : {}),
|
|
143
|
+
updatedAt: safeTimestamp(run.updatedAt),
|
|
144
|
+
...(errorCode ? { errorCode } : {}),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
export function safeCliDebugResponse(
|
|
130
149
|
claudeConfig: ClaudeRunConfig,
|
|
131
150
|
claudeRun: ClaudeRunRecord | undefined,
|
|
132
151
|
codexConfig: CodexRunConfig,
|
|
133
152
|
codexRun: CodexRunRecord | undefined,
|
|
153
|
+
cursorConfig: CursorRunConfig,
|
|
154
|
+
cursorRun: CursorRunRecord | undefined,
|
|
134
155
|
): SafeCliDebugResponse {
|
|
135
156
|
return {
|
|
136
157
|
schemaVersion: 1,
|
|
@@ -147,6 +168,12 @@ export function safeCliDebugResponse(
|
|
|
147
168
|
workspaceConfigured: Boolean(codexConfig.cwd),
|
|
148
169
|
latestRun: safeCodexLatestRun(codexRun),
|
|
149
170
|
},
|
|
171
|
+
cursor: {
|
|
172
|
+
supported: true,
|
|
173
|
+
persistenceEnabled: cursorConfig.persistenceEnabled === true,
|
|
174
|
+
workspaceConfigured: Boolean(cursorConfig.cwd),
|
|
175
|
+
latestRun: safeCursorLatestRun(cursorRun),
|
|
176
|
+
},
|
|
150
177
|
},
|
|
151
178
|
}
|
|
152
179
|
}
|