@gotcos/glasses-server 6.16.0 → 6.16.2
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 +7 -0
- package/CHANGELOG.md +19 -0
- package/README.md +29 -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/cos-operations-meetings.ts +365 -0
- 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/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/models/silero_vad.onnx +0 -0
- package/server/routes/cli-debug.ts +8 -0
- package/server/routes/health.ts +56 -4
- package/server/routes/meetings.ts +43 -3
- package/server/routes/openai-compat.ts +28 -4
- package/shared/model-preference.ts +50 -2
package/.env.example
CHANGED
|
@@ -95,6 +95,13 @@ BIND_HOST=0.0.0.0
|
|
|
95
95
|
# Power users running the COS Starter Kit can point the glasses at their
|
|
96
96
|
# pipeline to inherit live tasks/calendar/people context. Omit for standalone.
|
|
97
97
|
# COS_SCRIPTS_DIR=/path/to/your/cos/operations/scripts
|
|
98
|
+
#
|
|
99
|
+
# G2 "Review Meetings" reads COS meeting markdown from an operations tree:
|
|
100
|
+
# {COS_OPERATIONS_DIR}/{quilt|personal|…}/meetings/YYYY-MM/*.md
|
|
101
|
+
# Prefer an explicit ops root (each COS layout can differ). If unset, the
|
|
102
|
+
# server falls back to COS_SCRIPTS_DIR/.. then to local G2 recordings only.
|
|
103
|
+
# COS_OPERATIONS_DIR=/path/to/your/cos/operations
|
|
104
|
+
# COS_MEETINGS_ROOT=/path/to/your/cos/operations # alias for COS_OPERATIONS_DIR
|
|
98
105
|
|
|
99
106
|
# Telegram session/activity notifications remain OFF even if the COS scripts
|
|
100
107
|
# directory contains .telegram_config.json. Enable export explicitly:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
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
|
+
|
|
1
20
|
## 6.16.0
|
|
2
21
|
|
|
3
22
|
- **Truthful HQ results.** An HQ request is reported as HQ only when the full
|
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,
|
|
@@ -133,6 +145,8 @@ optional except an installed CLI. Highlights: `BIND_HOST`, `PORT`,
|
|
|
133
145
|
`mcp__server__*` selectors shared by full and lightweight Claude paths),
|
|
134
146
|
`COS_CLAUDE_MCP_CONFIG` (optional absolute config path when `.mcp.json` is not
|
|
135
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),
|
|
136
150
|
`COS_SCRIPTS_DIR` (full pipeline), `COS_DURABLE_QUERY_JOBS=1` (build 204+
|
|
137
151
|
server-owned query recovery), and `COS_MEDIA_ROOT` (optional image-store
|
|
138
152
|
location; default `~/.cos-glasses/data/media`). Your name + transcription vocabulary live in
|
|
@@ -190,8 +204,14 @@ BIND_HOST=0.0.0.0 npm run start:server
|
|
|
190
204
|
Version 6.12.2+ never runs a second install from inside npm's temporary cache.
|
|
191
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.
|
|
192
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.
|
|
193
|
-
- *AI queries fail* — run `claude auth status
|
|
194
|
-
`
|
|
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.
|
|
195
215
|
- *Voice getting billed?* — voice is local-only by default in 6.12.0+. Confirm
|
|
196
216
|
`/api/health` reports `capabilities.transcription.mode: "local-only"`. Remove
|
|
197
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.16.
|
|
4
|
-
"description": "COS Glasses — self-hosted AI heads-up-display server for Even G2 smart glasses, powered by
|
|
3
|
+
"version": "6.16.2",
|
|
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
|
}
|