@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 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 Codex** CLI, and pushes answers, voice
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 `ANTHROPIC_API_KEY` is needed chat runs through your installed CLI, billed
55
- > to your existing Claude or Codex subscription. Pick either per query, or set a
56
- > default with `COS_G2_DEFAULT_MODEL` (`opus`|`fable`|`sonnet`|`codex-frontier`|`codex-balanced`).
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, or GPT Balanced plus High, Extra
90
- High, Max, or Ultracode effort; optional redacted tool activity streams only
91
- to the authenticated query that requested it
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` / `codex login status`, then
194
- `claude auth login` / `codex login` when the provider reports signed out.
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 (not Claude Desktop) or Codex 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 of Claude Code / Codex is required
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
- const hasUsableAgent = (claudeVersion && claudeAuth !== 'signed-out') || (codexVersion && codexAuth !== 'signed-out')
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.0",
4
- "description": "COS Glasses — self-hosted AI heads-up-display server for Even G2 smart glasses, powered by your local Claude Code or Codex CLI",
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
- : /^(?:gpt-|codex-)[a-z0-9._\[\]-]*$/i
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
  }