@gotcos/glasses-server 6.2.1 → 6.5.0

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
@@ -19,18 +19,29 @@ BIND_HOST=0.0.0.0
19
19
  # here for a stable token across restarts.
20
20
  # COS_API_TOKEN=pick-any-long-random-string
21
21
 
22
+ # Optional durable image-store location. Defaults to
23
+ # ~/.cos-glasses/data/media alongside the standalone conversation/archive data.
24
+ # COS_MEDIA_ROOT=/path/on-a-local-volume/media
25
+
22
26
  # ── THE LLM (chat) ──────────────────────────────────────────────────────
23
27
  # Chat runs through your LOCAL agent CLI — NOT an API key:
24
- # Opus / Sonnet / Haiku -> Claude Code CLI (https://claude.ai/download, then `claude login`)
25
- # Codex High -> Codex CLI (https://developers.openai.com/codex/, then `codex login`)
28
+ # Opus / Fable / Sonnet -> Claude Code CLI (https://claude.ai/download, then `claude login`)
29
+ # GPT Frontier/Balanced -> Codex CLI (https://developers.openai.com/codex/, then `codex login`)
26
30
  # Install at least one. Pick the default model the glasses use:
27
- # COS_G2_DEFAULT_MODEL=sonnet # opus | sonnet | haiku | codex-high
31
+ # COS_G2_DEFAULT_MODEL=sonnet # opus | fable | sonnet | codex-frontier | codex-balanced
32
+ #
33
+ # GPT slots refresh from the Codex model catalog every 15 minutes and retain
34
+ # their last-known-good resolution if discovery is temporarily unavailable.
35
+ # COS_CODEX_MODEL_REFRESH_TTL_MS=900000
36
+ # COS_CODEX_MODEL_REFRESH_TIMEOUT_MS=7000
28
37
  #
29
- # Codex options (only used for the codex-high model):
30
- # COS_CODEX_MODEL pins a specific codex model id; blank = use your codex CLI default.
31
- # COS_CODEX_REASONING_EFFORT is low | medium | high (default high).
38
+ # Legacy explicit overrides remain supported for migrated codex-high/frontier
39
+ # installs. Leave blank for auto-latest; Balanced always stays auto-catalog.
32
40
  # COS_CODEX_MODEL=
33
- # COS_CODEX_REASONING_EFFORT=high
41
+ # COS_CODEX_REASONING_EFFORT=high # low | medium | high | xhigh | max | ultra
42
+ #
43
+ # Codex remains read-only by default. This is the only broader trust opt-in:
44
+ # COS_CODEX_SANDBOX=workspace-write
34
45
 
35
46
  # ── VOICE (optional) ────────────────────────────────────────────────────
36
47
  # Local transcription is FREE via whisper.cpp (brew install whisper-cpp; the
package/CHANGELOG.md CHANGED
@@ -1,5 +1,121 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.5.0
4
+
5
+ Durable phone photos and assistant-selected output images for COS Glasses
6
+ build 179, while preserving the public server's sandbox and privacy boundary.
7
+
8
+ - **One media contract.** Authenticated phone uploads become opaque attachment
9
+ refs, survive queues/restarts/archives/numbered-message recall, and resolve to
10
+ normalized server-owned files for Claude/Codex. Bytes and storage paths never
11
+ enter SSE, run ledgers, or archives.
12
+ - **Answer images.** Claude or Codex can publish an already-local generated,
13
+ researched, or explicitly used email image through a private run-scoped
14
+ capability. The server accepts JPEG/PNG/WebP/HEIC/HEIF/AVIF up to 16 MiB and
15
+ 16 megapixels, strips metadata, re-encodes through the existing media store,
16
+ and appends refs to the completed answer.
17
+ - **No mailbox or URL crawler.** The publisher rejects URLs, data URIs, base64,
18
+ unrelated discovery, symlinks, directory replacement, content-id tampering,
19
+ over-capacity output, and manifest fields that could carry private paths.
20
+ - **Codex remains read-only.** Output publishing adds only the random private
21
+ run directory via `codex exec --add-dir`; global sandbox flags precede
22
+ `resume`. Older CLIs without `--add-dir` keep chat working and simply disable
23
+ Codex output-image publishing. There is no full-access fallback.
24
+ - **Durable finalization.** Assistant text is persisted before image
25
+ normalization, request media associates even if SSE disconnects, partial
26
+ image failures do not discard successful refs, and completion emits one
27
+ canonical `attachments` list with safe aggregate stats.
28
+ - **Lens contract.** `/api/health` advertises `mediaProcessingReady` and
29
+ `g2LensVariant=png-288x144-v1`; the media endpoint serves the validated phone,
30
+ thumbnail, and exact 288×144 G2 variants expected by build 179.
31
+ - **Fresh-install diagnostics.** The npm launcher now reports whether ffmpeg is
32
+ ready for phone/output/lens images, gives a non-blocking install command when
33
+ absent, and sends setup questions directly to `gotcos.com/wizard/`.
34
+ - **One server owner.** The public runner now claims the same atomic
35
+ machine-wide lock as the installed LaunchAgent before mutable modules load.
36
+ A duplicate exits with code 75, and HTTP/HTTPS listeners bind as one required
37
+ set: if either port is occupied, any earlier listener closes and the process
38
+ exits instead of surviving half-bound with separate SSE and media state.
39
+
40
+ Release evidence: TypeScript, 130/130 tests across 23 files, package dry-run
41
+ including the executable publisher and startup hardening, and a live duplicate
42
+ start against the installed LaunchAgent rejected before server initialization.
43
+
44
+ ## 6.4.0
45
+
46
+ Fresh-install parity for COS Glasses builds 170–173, without weakening the
47
+ public server's sandbox defaults.
48
+
49
+ - **Auto-updating GPT Frontier + Balanced.** Stable client slots resolve to the
50
+ top two capable models in the newest visible GPT generation through Codex's
51
+ official `model/list` catalog. The server refreshes at boot and every 15
52
+ minutes, and each Codex run awaits the same TTL-cached/coalesced refresh
53
+ before resolving its slot. It preserves the last-known-good catalog on
54
+ failures and falls back to the CLI default only before any discovery succeeds.
55
+ - **Fable + effort controls.** Fable joins Opus and Sonnet as a first-class
56
+ Claude tier alias, and High / Extra High / Max / Ultracode now propagate from
57
+ `/api/query` to both Claude and Codex. Claude aliases remain versionless and
58
+ 1M-context capable; Codex effort is clamped to each live model's advertised
59
+ support. Per-run ledgers record the concrete resolved model and effort.
60
+ - **Safe live job activity.** `activityToolMode` supports off, status-only, or
61
+ bounded observable tool input/output previews. ANSI/control data, credential
62
+ assignments, auth headers, provider tokens, JWTs, URL credentials, and opaque
63
+ blobs are redacted, including 40–72-character PEM/private-key body chunks.
64
+ Hidden reasoning is never surfaced.
65
+ - **Same-session run safety.** Turns for one conversation now serialize until
66
+ the active bridge sends a terminal callback, while different sessions remain
67
+ concurrent. Failed or cancelled Claude/Codex runs remove the exact pending
68
+ user exchange by object identity, preventing phantom prompts, duplicate-text
69
+ deletion, and resume-history contamination.
70
+ - **Authenticated transport boundary.** Activity lines are returned only on the
71
+ authenticated `/api/query` SSE stream. They are deliberately excluded from
72
+ the unauthenticated global display bus and its replay buffer.
73
+ - **Public trust model retained.** Codex remains read-only by default with only
74
+ the existing `workspace-write` opt-in. Existing archive traversal, local-day,
75
+ malformed-file, starter-kit launch-directory, and conversation behavior are
76
+ unchanged. Legacy `codex-high` state migrates to the frontier slot without
77
+ changing saved thread trust mode.
78
+ - **Diagnostics and compatibility.** `/api/models`, health data, and `/v1/models`
79
+ expose stable slots plus concrete live models. `cos-codex-high` remains an
80
+ accepted alias for older clients. Existing `COS_CODEX_MODEL` and
81
+ `COS_CODEX_REASONING_EFFORT` overrides continue to apply to the migrated
82
+ legacy/frontier slot; leave them unset for auto-latest. A new regression suite covers catalog
83
+ selection/fallback/refresh, sandbox arguments, migrations, effort mappings,
84
+ activity redaction, and the display-bus security boundary.
85
+
86
+ ## 6.3.1
87
+
88
+ Security + robustness hardening on the 6.3.0 archive routes, from a 3-agent QA pass. (6.3.0 was never published; 6.3.1 is the first release of the expanded route set.)
89
+
90
+ - **SECURITY — path traversal blocked.** The new `:date` archive routes fed the param straight into `<dir>/${date}.json`, so an encoded traversal (`/api/archive/..%2F..%2Fetc%2Fhosts`) could read arbitrary `*.json` on the host (and rename-corrupt one via the quarantine path). Auth+IP gated, but a real exposure on a shared LAN/meshnet. Fixed: `archiveRouter.param('date', …)` enforces `^\d{4}-\d{2}-\d{2}$` on every `:date` route before any fs access; defense-in-depth guard in `readArchiveChatNumbered`. Verified: traversal/bad-format → 400, valid dates → 200.
91
+ - **Reference date label (US evenings).** Live-session `reference message N` stamped the date with UTC, labeling an evening reference with tomorrow's date. Now `localDay()`.
92
+ - **Malformed day file no longer wipes History.** A valid-JSON wrong-shape day file (no `chats[]`) 500'd the readers and dropped `listArchiveDates` into its catch, hiding all history. `loadArchive` coerces `chats` to `[]`; the bad day lists as 0 chats.
93
+ - **Thrift/cosmetic:** `/api/archive/now` passes `skipLLM:true` (no surprise LLM spend on a public manual snapshot); stale path comment + unused `__dirname` removed from `lib/archive.ts`.
94
+
95
+ ## 6.3.0
96
+
97
+ Message History, cross-day references, and history recovery for public installs.
98
+ These features previously required a full COS server; now `npx @gotcos/glasses-server`
99
+ exposes them too, so the G2 app's Message History and "reference message N" work
100
+ on a vanilla install.
101
+
102
+ - **Message History** — the archive routes (`/api/archive`, `/api/archive/:date/chats`,
103
+ `/api/archive/:date/chats/:i/messages`, `/api/archive/:date/messages`, `/api/archive/now`)
104
+ are now served. The daily archive-mirror (already in this package) writes prior-day
105
+ sessions to disk; these routes browse them. Each day row shows chat count + topic.
106
+ - **Cross-day "reference message N"** — new `/api/message/:num` resolves a permanent
107
+ message number across live sessions then day archives (newest-first), and
108
+ `/api/message-counter` publishes the numbering ceiling so a fresh/cleared client
109
+ never reuses a number. Message numbers were already stored (`globalMsgNum`); this
110
+ makes them resolvable.
111
+ - **History recovery** — session routes (`/api/sessions/today/all-messages`,
112
+ `/api/sessions/:id/messages`, recent-sessions index, context-break, end-session)
113
+ let the app restore recent history and open archived chats.
114
+
115
+ No change to the public-safe model curation (Sonnet default, no pinned/unreleased
116
+ model ids) or the core query/voice/display paths. Typecheck clean; new routes
117
+ smoke-tested (message-counter, archive list, message lookup).
118
+
3
119
  ## 6.2.1
4
120
 
5
121
  Foolproofing release — driven by an adversarial onboarding QA pass.
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** CLI, and pushes answers, voice
4
+ talks to your local **Claude Code or Codex** 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
 
@@ -11,22 +11,31 @@ API key is pasted into the phone for chat.
11
11
  npx @gotcos/glasses-server
12
12
  ```
13
13
 
14
- The launcher checks Node, finds your CLI, downloads the local voice model, writes
15
- `~/.cos-glasses/.env`, and starts the server on `0.0.0.0:3141`. On boot it prints
16
- an **API token** paste that into the COS Glasses app.
14
+ The launcher checks Node, finds your CLI, checks voice and image processing,
15
+ downloads the local voice model when needed, writes `~/.cos-glasses/.env`, and
16
+ starts the server on `0.0.0.0:3141`. On boot it prints
17
+ an **API token** — paste that into the COS Glasses app. Only one COS Glasses
18
+ server may run on a Mac at a time; a second `npx` or source runner exits before
19
+ opening ports or touching shared conversation/media state.
17
20
 
18
21
  ## Requirements
19
22
 
20
23
  - **Node.js 20.11+** — https://nodejs.org
21
- - **Claude Code CLI** (Opus/Sonnet/Haiku) — https://claude.ai/download, then `claude login`
22
- _or_ **Codex CLI** (Codex High) — https://developers.openai.com/codex/, then `codex login`
24
+ - **Claude Code CLI** (Opus/Fable/Sonnet) — https://claude.ai/download, then `claude login`
25
+ _or_ **Codex CLI** (GPT Frontier/Balanced) — https://developers.openai.com/codex/, then `codex login`
23
26
  - **Even G2 glasses** + the **COS Glasses** app from the Even Hub
24
27
  - _Optional:_ `brew install whisper-cpp` for free local voice (otherwise OpenAI API)
28
+ - _Optional:_ `brew install ffmpeg` for phone/output image attachments (text chat remains available without it)
25
29
  - _Optional:_ **Tailscale** so your phone reaches your Mac from anywhere
26
30
 
27
31
  > No `ANTHROPIC_API_KEY` is needed — chat runs through your installed CLI, billed
28
32
  > to your existing Claude or Codex subscription. Pick either per query, or set a
29
- > default with `COS_G2_DEFAULT_MODEL` (`opus`|`sonnet`|`haiku`|`codex-high`).
33
+ > default with `COS_G2_DEFAULT_MODEL` (`opus`|`fable`|`sonnet`|`codex-frontier`|`codex-balanced`).
34
+ > Claude tier aliases and the two GPT slots resolve dynamically, so new model
35
+ > releases do not require a new glasses package. GPT discovery refreshes every
36
+ > 15 minutes and retains its last-known-good catalog through transient failures.
37
+ > Existing `COS_CODEX_MODEL` / `COS_CODEX_REASONING_EFFORT` settings remain
38
+ > supported on the migrated Frontier slot; leave them blank for auto-latest.
30
39
  > Codex runs **sandboxed read-only** by default (`COS_CODEX_SANDBOX` to adjust).
31
40
 
32
41
  ## Connect your phone (the one gotcha)
@@ -44,6 +53,13 @@ The built-in IP allowlist blocks public-internet traffic regardless.
44
53
  ## What it does
45
54
 
46
55
  - Ask anything, get a streamed answer on the lens (`/api/query`, `/v1/chat/completions`)
56
+ - Choose Opus, Fable, Sonnet, GPT Frontier, or GPT Balanced plus High, Extra
57
+ High, Max, or Ultracode effort; optional redacted tool activity streams only
58
+ to the authenticated query that requested it
59
+ - Message History + cross-day "reference message N" — your chats are archived by day
60
+ and every message keeps a permanent number you can recall (`/api/archive`, `/api/message/:num`)
61
+ - Send phone photos with queued prompts, and review assistant-selected generated,
62
+ research, or explicitly used email images in Messages and on the G2 lens
47
63
  - Live voice capture + transcription during meetings
48
64
  - Local whisper.cpp transcription (free) with OpenAI fallback (optional)
49
65
  - Tasks / calendar / people context **if** you run the
@@ -55,7 +71,8 @@ The built-in IP allowlist blocks public-internet traffic regardless.
55
71
  Config lives at `~/.cos-glasses/.env` (created on first run). Every key is
56
72
  optional except an installed CLI. Highlights: `BIND_HOST`, `PORT`,
57
73
  `COS_API_TOKEN` (auto if unset), `OPENAI_API_KEY` (cloud voice fallback),
58
- `COS_SCRIPTS_DIR` (full pipeline). Your name + transcription vocabulary live in
74
+ `COS_SCRIPTS_DIR` (full pipeline), and `COS_MEDIA_ROOT` (optional image-store
75
+ location; default `~/.cos-glasses/data/media`). Your name + transcription vocabulary live in
59
76
  `~/.cos-glasses/.cos-profile.json` (see `.cos-profile.example.json`).
60
77
 
61
78
  ## Run from source
@@ -72,6 +89,7 @@ BIND_HOST=0.0.0.0 npm run start:server
72
89
  - *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.
73
90
  - *AI queries fail* — run `claude --version` / `codex --version`, then `claude login` / `codex login`.
74
91
  - *Voice getting billed?* — install `whisper-cpp` for free local transcription.
92
+ - *Photos unavailable?* — install `ffmpeg`, restart the server, and confirm `/api/health` reports `features.mediaProcessingReady: true`.
75
93
 
76
94
  ## License
77
95
 
package/bin/cli.cjs CHANGED
@@ -34,13 +34,13 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
34
34
  console.log('')
35
35
  console.log(' Requirements:')
36
36
  console.log(' - Node.js 20.11+')
37
- console.log(' - Claude Code CLI (Opus / Sonnet / Haiku) or Codex CLI (Codex High)')
37
+ console.log(' - Claude Code CLI (Opus / Fable / Sonnet) or Codex CLI (GPT Frontier / Balanced)')
38
38
  console.log(' - Even G2 smart glasses + the COS Glasses app (Even Hub)')
39
39
  console.log('')
40
40
  console.log(' No API key is needed for chat — it runs through your installed CLI.')
41
41
  console.log(' Config persists at ~/.cos-glasses/.env')
42
42
  console.log('')
43
- console.log(' Setup guide: https://www.gotcos.com')
43
+ console.log(' Setup guide: https://www.gotcos.com/wizard/')
44
44
  console.log('')
45
45
  process.exit(0)
46
46
  }
@@ -60,9 +60,9 @@ if (nodeMajor < 20 || (nodeMajor === 20 && nodeMinor < 11)) {
60
60
  console.log(green(' ✓') + ` Node.js ${process.versions.node}`)
61
61
 
62
62
  // Step 2: agent CLI detection — at least one of Claude Code / Codex is required
63
- function getCliVersion(command) {
63
+ function getCliVersion(command, versionArg = '--version') {
64
64
  try {
65
- return execSync(`${command} --version 2>&1`, { shell: '/bin/sh', stdio: 'pipe', timeout: 5000 }).toString().trim()
65
+ return execSync(`${command} ${versionArg} 2>&1`, { shell: '/bin/sh', stdio: 'pipe', timeout: 5000 }).toString().trim()
66
66
  } catch {
67
67
  return null
68
68
  }
@@ -75,15 +75,15 @@ function normalizeCodexVersion(raw) {
75
75
  const claudeVersion = getCliVersion('claude')
76
76
  const codexVersion = getCliVersion('codex')
77
77
  if (claudeVersion) {
78
- console.log(green(' ✓') + ` Claude Code ${claudeVersion} ` + dim('(Opus / Sonnet / Haiku)'))
78
+ console.log(green(' ✓') + ` Claude Code ${claudeVersion} ` + dim('(Opus / Fable / Sonnet)'))
79
79
  } else {
80
- console.log(yellow(' ⚠') + ' Claude Code CLI not found ' + dim('— Opus/Sonnet/Haiku unavailable'))
80
+ console.log(yellow(' ⚠') + ' Claude Code CLI not found ' + dim('— Opus/Fable/Sonnet unavailable'))
81
81
  console.log(' Install: ' + bold('https://claude.ai/download'))
82
82
  }
83
83
  if (codexVersion) {
84
- console.log(green(' ✓') + ` Codex CLI ${normalizeCodexVersion(codexVersion)} ` + dim('(Codex High)'))
84
+ console.log(green(' ✓') + ` Codex CLI ${normalizeCodexVersion(codexVersion)} ` + dim('(GPT Frontier / Balanced)'))
85
85
  } else {
86
- console.log(yellow(' ⚠') + ' Codex CLI not found ' + dim('— Codex High unavailable'))
86
+ console.log(yellow(' ⚠') + ' Codex CLI not found ' + dim('— GPT Frontier/Balanced unavailable'))
87
87
  }
88
88
  if (!claudeVersion && !codexVersion) {
89
89
  console.log('')
@@ -184,13 +184,25 @@ if (whisperCliPath && hasValidModel) {
184
184
  console.log(' Free local voice: ' + bold('brew install whisper-cpp') + dim(' (no Homebrew? https://brew.sh)'))
185
185
  }
186
186
 
187
- // Step 6: phone reachabilitythe glasses' phone app must reach this server.
187
+ // Step 6: image capabilityffmpeg validates, strips metadata, normalizes,
188
+ // and builds the exact 288x144 G2 variant. It is optional so text/voice remain
189
+ // useful on a minimal install, but the launcher should make the gap visible.
190
+ const ffmpegVersion = getCliVersion('ffmpeg', '-version')
191
+ if (ffmpegVersion) {
192
+ const firstLine = ffmpegVersion.split('\n')[0].trim()
193
+ console.log(green(' ✓') + ` ${firstLine} ` + dim('— phone + lens images ready'))
194
+ } else {
195
+ console.log(yellow(' ⚠') + ' ffmpeg not installed ' + dim('— phone and answer images disabled'))
196
+ console.log(' Enable photos: ' + bold('brew install ffmpeg') + dim(' (text + voice still work)'))
197
+ }
198
+
199
+ // Step 7: phone reachability — the glasses' phone app must reach this server.
188
200
  if (!process.env.BIND_HOST) {
189
201
  process.env.BIND_HOST = '0.0.0.0'
190
202
  console.log(yellow(' ⚠') + ' BIND_HOST not set — defaulting to 0.0.0.0 so your phone can reach the server')
191
203
  }
192
204
 
193
- // Step 7: start the bundled server
205
+ // Step 8: start the bundled server
194
206
  try {
195
207
  const ld = process.env.COS_LAUNCH_DIR
196
208
  if (ld && (existsSync(join(ld, '.cos', 'manifest.json')) || existsSync(join(ld, 'AGENTS.md')) || existsSync(join(ld, 'CLAUDE.md')))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.2.1",
3
+ "version": "6.5.0",
4
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",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,14 +9,25 @@
9
9
  "scripts": {
10
10
  "start": "node bin/cli.cjs",
11
11
  "start:server": "node --import tsx/esm server/index.ts",
12
+ "test": "vitest run",
12
13
  "typecheck": "tsc --noEmit"
13
14
  },
14
- "keywords": ["even-g2", "smart-glasses", "claude-code", "codex", "ai", "cos", "heads-up-display", "whisper"],
15
+ "keywords": [
16
+ "even-g2",
17
+ "smart-glasses",
18
+ "claude-code",
19
+ "codex",
20
+ "ai",
21
+ "cos",
22
+ "heads-up-display",
23
+ "whisper"
24
+ ],
15
25
  "files": [
16
26
  "bin/cli.cjs",
17
- "server/**/*.ts",
18
- "server/**/*.js",
19
- "shared/**/*.ts",
27
+ "server",
28
+ "shared",
29
+ "!server/**/*.test.ts",
30
+ "!shared/**/*.test.ts",
20
31
  ".env.example",
21
32
  ".cos-profile.example.json",
22
33
  "README.md",
@@ -48,6 +59,7 @@
48
59
  "@types/cors": "^2.8.19",
49
60
  "@types/express": "^5.0.6",
50
61
  "@types/node": "^22.10.0",
51
- "typescript": "^5.9.3"
62
+ "typescript": "^5.9.3",
63
+ "vitest": "^3.2.7"
52
64
  }
53
65
  }