@scoutmesh/viewer 0.2.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/README.md +534 -0
- package/dist/cli.js +430 -0
- package/dist/external-browser.js +62 -0
- package/dist/input-error.js +8 -0
- package/dist/launcher.js +39 -0
- package/dist/public/app.js +1157 -0
- package/dist/public/auth.js +55 -0
- package/dist/public/data.js +105 -0
- package/dist/public/index.html +153 -0
- package/dist/public/styles.css +619 -0
- package/dist/server.js +339 -0
- package/dist/state-file.js +48 -0
- package/dist/store.js +437 -0
- package/dist/update-manager.js +448 -0
- package/dist/version.js +1 -0
- package/package.json +20 -0
- package/skills/local-candidate-viewer/SKILL.md +200 -0
package/README.md
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# Scout Mesh local viewer
|
|
2
|
+
|
|
3
|
+
A standalone candidate viewer for agents with a terminal and a browser. The
|
|
4
|
+
agent sends JSON over HTTP; the viewer handles cards, profile details,
|
|
5
|
+
shortlisting, reversible removal, and live updates. It does not use the Scout Mesh
|
|
6
|
+
portal, Convex, provider credentials, or paid searches.
|
|
7
|
+
|
|
8
|
+
## Install the packaged preview
|
|
9
|
+
|
|
10
|
+
This is an unpublished preview for agents with a local terminal and browser.
|
|
11
|
+
The bootstrap handles Node.js and npm installation for the user. macOS has been
|
|
12
|
+
tested with Node/npm absent from PATH; Windows installer and browser-launch code
|
|
13
|
+
are included but still need a trial on a Windows machine.
|
|
14
|
+
|
|
15
|
+
### npm delivery (preferred)
|
|
16
|
+
|
|
17
|
+
The package is prepared for publication but is not live on npm yet. After a
|
|
18
|
+
release is confirmed, the agent uses the exact version supplied by ScoutMesh:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx --yes --ignore-scripts --registry=https://registry.npmjs.org @scoutmesh/viewer@0.2.0 launch
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`launch` copies the package out of npm's cache into the workspace's `.viewer`
|
|
25
|
+
directory, then starts it on a free port. It returns `node` and `launcher`
|
|
26
|
+
absolute paths alongside `url`, `stateFile`, `version`, and `update`. Invoke the
|
|
27
|
+
returned paths as `"<node>" "<launcher>" request GET /api/views` (in PowerShell,
|
|
28
|
+
`& '<node>' '<launcher>' request GET /api/views`). Use that launcher for token,
|
|
29
|
+
status, stop, and requests throughout the session. Clearing npm's cache does not
|
|
30
|
+
remove the running installation or its rollback copies.
|
|
31
|
+
|
|
32
|
+
At the next session, ScoutMesh supplies the approved version again and the agent
|
|
33
|
+
runs the versioned npm command. A newer version uses the same backup, health
|
|
34
|
+
check, and rollback implementation as the bootstrap launcher. Equal versions
|
|
35
|
+
reuse the process; older versions do not downgrade. `current` compares against
|
|
36
|
+
the requested npm version, not a fresh registry check. A cached/offline launch
|
|
37
|
+
does not prove that the version is the newest published release. The saved local
|
|
38
|
+
launcher's `start` works offline but does not check npm for updates.
|
|
39
|
+
|
|
40
|
+
State defaults to `~/.scoutmesh/workspaces/state.json` on both platforms for the
|
|
41
|
+
npm route. Set `SCOUTMESH_VIEWER_HOME` consistently for a different workspace;
|
|
42
|
+
`launch` rejects `VIEWER_STATE_FILE` to prevent ambiguous ownership. Existing
|
|
43
|
+
bootstrap users should keep their managed launcher; switching delivery methods
|
|
44
|
+
requires stopping the old viewer and explicitly retaining its state directory.
|
|
45
|
+
Node.js 24+ and npm must be available. If absent, use the verified bootstrap below.
|
|
46
|
+
Neither route changes execution policy or promises to bypass host permissions.
|
|
47
|
+
|
|
48
|
+
### Publish the npm pilot
|
|
49
|
+
|
|
50
|
+
From the repository root, run the checks and pack the artifact:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run check
|
|
54
|
+
npm run check-types
|
|
55
|
+
npm run viewer:test
|
|
56
|
+
npm run viewer:package-test
|
|
57
|
+
npm run viewer:update-test
|
|
58
|
+
npm publish /tmp/scoutmesh-viewer-0.2.0.tgz --dry-run --access public --ignore-scripts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Inspect the tarball file list before publication: it must contain only the
|
|
62
|
+
package manifest, README, compiled code, public assets, and bundled skill. Local
|
|
63
|
+
candidate state, runtime credentials, fixtures, and repository files are excluded
|
|
64
|
+
by the package's `files` allowlist. Authenticate using `npm login` in a user
|
|
65
|
+
terminal with permission to publish under `@scoutmesh`; never put a token in chat.
|
|
66
|
+
Publish the reviewed artifact, not a rebuild:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm publish /tmp/scoutmesh-viewer-0.2.0.tgz --access public --tag next --ignore-scripts --registry=https://registry.npmjs.org
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use the `next` tag for this pilot while Windows validation is pending. Exact
|
|
73
|
+
version commands work independently of that tag. Read back the registry version
|
|
74
|
+
and integrity and test a clean registry install before marking publication done.
|
|
75
|
+
Promoting a tested release to `latest` and deploying the gateway instructions are
|
|
76
|
+
separate rollout steps. Each later release needs a new version. No npm login,
|
|
77
|
+
publication, or production gateway deployment is implied by building locally.
|
|
78
|
+
|
|
79
|
+
### Bootstrap without Node or npm
|
|
80
|
+
|
|
81
|
+
From the repository root on macOS, run `npm run viewer:bootstrap-pack`. It builds
|
|
82
|
+
`/tmp/scoutmesh-viewer-bootstrap-0.2.0.zip` and a `.zip.sha256` checksum file.
|
|
83
|
+
The ZIP contains both installers, a pinned Node runtime manifest, the viewer
|
|
84
|
+
package, and its checksum. The pack command also prints the extracted bundle path.
|
|
85
|
+
Build tooling needs Node and `zip`; the recruiter's machine does not.
|
|
86
|
+
|
|
87
|
+
After verifying and extracting the release ZIP, the agent runs:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
/bin/bash /absolute/path/to/bootstrap.sh
|
|
91
|
+
~/.scoutmesh/viewer/viewer status
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
On Windows, using the bundled PowerShell:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
powershell.exe -NoProfile -File C:\absolute\path\bootstrap.ps1
|
|
98
|
+
& "$env:LOCALAPPDATA\ScoutMesh\viewer\viewer.ps1" status
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The installer checks the included package checksum, reuses compatible Node.js 24+
|
|
102
|
+
with npm when present, or downloads the exact official Node archive pinned in
|
|
103
|
+
`bootstrap/runtime.json`. It verifies that archive before extracting or executing
|
|
104
|
+
it. npm installs the included zero-dependency package offline with install scripts
|
|
105
|
+
disabled. The runtime and npm cache stay inside the installation directory; nothing
|
|
106
|
+
changes the global runtime, PATH, or npm cache. The launcher remembers the paths.
|
|
107
|
+
|
|
108
|
+
Defaults are `~/.scoutmesh/viewer` and `~/.scoutmesh/workspaces` on macOS, or
|
|
109
|
+
`%LOCALAPPDATA%\ScoutMesh\viewer` and `...\workspaces` on Windows. Candidate JSON
|
|
110
|
+
stays in the workspaces directory, separate from replaceable package files.
|
|
111
|
+
The bootstrap chooses a free port; use the returned URL after every start.
|
|
112
|
+
|
|
113
|
+
For an isolated trial, use `--install-dir` and `--state-dir` (absolute paths),
|
|
114
|
+
`--force-runtime`, and optionally `--port` or `--no-start`. PowerShell equivalents
|
|
115
|
+
are `-InstallDir`, `-StateDir`, `-ForceRuntime`, `-Port`, and `-NoStart`.
|
|
116
|
+
Repeat installation reuses verified downloads and the same running workspace.
|
|
117
|
+
Managed startup handles viewer upgrades. An interrupted install may leave `.bootstrap.lock`; verify
|
|
118
|
+
no installer is running before removing that lock and retrying. Keep saved state.
|
|
119
|
+
|
|
120
|
+
Downloads and localhost access still depend on the agent host's permissions.
|
|
121
|
+
Use normal approval flows; do not change PowerShell execution policy or bypass a
|
|
122
|
+
rejected token read. An existing but unreachable viewer produces an explicit
|
|
123
|
+
connectivity/permission error instead of silently launching a duplicate.
|
|
124
|
+
|
|
125
|
+
### Automatic updates (0.2.0 onward)
|
|
126
|
+
|
|
127
|
+
The stable launcher reads `launcher.json` on every invocation, so its active package
|
|
128
|
+
can change without changing the command the agent remembers. Every managed `start`
|
|
129
|
+
fetches the configured release manifest and compares semantic versions. `status`
|
|
130
|
+
and the authenticated health endpoint report the running package version.
|
|
131
|
+
|
|
132
|
+
The release manifest has four required fields: `version` (for example `0.2.0`),
|
|
133
|
+
`nodeMajor` (currently `24`), `archiveUrl`, and `sha256` (the npm tarball checksum).
|
|
134
|
+
The manifest and tarball must share one HTTPS origin; redirects are rejected.
|
|
135
|
+
HTTP `127.0.0.1` is accepted only to support isolated local release tests.
|
|
136
|
+
The updater sends no candidate data to that origin. This relies on the configured
|
|
137
|
+
HTTPS publisher plus the manifest checksum; it is not a signed release system.
|
|
138
|
+
|
|
139
|
+
To package an official channel, set `SCOUTMESH_VIEWER_RELEASE_URL` to its stable
|
|
140
|
+
manifest URL and `SCOUTMESH_VIEWER_ARCHIVE_URL` to the immutable tarball URL for this
|
|
141
|
+
version, then run `npm run viewer:bootstrap-pack`. Both URLs are operator-supplied.
|
|
142
|
+
The ZIP embeds the channel URL and the packer writes a sibling `.zip.release.json`
|
|
143
|
+
for publishing at the manifest URL. Upload the tarball and ZIP first, then publish
|
|
144
|
+
the manifest last. Use a new package version for every release; never replace an
|
|
145
|
+
existing version's tarball. Serve the channel manifest with revalidation/no-cache.
|
|
146
|
+
The ZIP's checksum should be distributed through ScoutMesh's release instructions.
|
|
147
|
+
|
|
148
|
+
For a supplied channel, the installer also accepts `--release-url` / `-ReleaseUrl`.
|
|
149
|
+
Rerunning with that option updates the saved channel while retaining the active
|
|
150
|
+
package and state directory. Ordinary local builds have no channel and report
|
|
151
|
+
`update: "not-configured"`; there is no assumed live ScoutMesh download endpoint.
|
|
152
|
+
|
|
153
|
+
On a newer release, the launcher verifies and installs it in a separate directory
|
|
154
|
+
with npm lifecycle scripts disabled. It then stops the current writer, backs up
|
|
155
|
+
JSON and installation settings under `<stateDir>/backups`, starts the new package,
|
|
156
|
+
checks its authenticated health/version, and atomically changes the active pointer.
|
|
157
|
+
If startup fails, it first confirms the attempted server has stopped, preserves
|
|
158
|
+
its state as `failed.json`, restores `before.json`, and restarts the previous
|
|
159
|
+
package. A failed/ambiguous stop leaves recovery files and reports an error rather
|
|
160
|
+
than restoring over a live writer. Backups and previous packages are retained.
|
|
161
|
+
|
|
162
|
+
Startup returns `update` as `current`, `updated`, `newer-installed` (no downgrade),
|
|
163
|
+
`unavailable` (download, manifest, checksum or installation failed), `rolled-back`,
|
|
164
|
+
or `not-configured`. `unavailable` keeps the installed version usable and prints
|
|
165
|
+
the reason; it never claims the release is current. `updated`/`rolled-back` include
|
|
166
|
+
a backup directory. Always reopen the returned URL and unlock again after a restart.
|
|
167
|
+
Node runtime upgrades are separate from package updates; unsupported runtime
|
|
168
|
+
requirements are reported rather than installed automatically.
|
|
169
|
+
|
|
170
|
+
Managed commands serialize through `.update.lock`. An interrupted updater may
|
|
171
|
+
leave that lock or a partially completed transition. Confirm all relevant processes
|
|
172
|
+
have stopped before manual recovery, retain both state snapshots, restore the
|
|
173
|
+
appropriate installation pointer and matching JSON, and remove only stale locks.
|
|
174
|
+
Recovery after a process/laptop crash during the transition is not automatic yet.
|
|
175
|
+
|
|
176
|
+
The older unpublished 0.1.0 bootstrap needs to be run once with the new bundle;
|
|
177
|
+
subsequent package updates use the managed launcher. The npm `launch` command uses the requested package version and managed rollback;
|
|
178
|
+
plain `start` does not configure a channel or upgrade another running version. A direct CLI refuses to
|
|
179
|
+
reuse a running server whose version differs from its own.
|
|
180
|
+
|
|
181
|
+
### Release verification
|
|
182
|
+
|
|
183
|
+
`npm run viewer:update-test` serves local test releases, upgrades a running older
|
|
184
|
+
package, preserves Yes/Maybe/No decisions, rejects a bad checksum, tests unavailable
|
|
185
|
+
releases and downgrade avoidance, and rolls back a failed migration.
|
|
186
|
+
`npm run viewer:bootstrap-test` exercises the Mac installer with Node/npm absent
|
|
187
|
+
from PATH. Neither test alters the real candidate workspace.
|
|
188
|
+
|
|
189
|
+
The manual [Local viewer release checks](../../.github/workflows/viewer-smoke.yml)
|
|
190
|
+
workflow runs upgrade tests on macOS and Windows, then each platform's bootstrap
|
|
191
|
+
checks. The Windows smoke test uses stock Windows PowerShell 5.1, a private Node
|
|
192
|
+
download, paths containing spaces, repeated startup, and saved-state recovery.
|
|
193
|
+
It can also run directly with `bootstrap/windows-smoke.ps1 -BundleDir <extracted-bundle>`.
|
|
194
|
+
Windows execution, Intel Mac installation, and external-browser opening still need
|
|
195
|
+
platform validation before release. The workflow has not been dispatched by this
|
|
196
|
+
local implementation.
|
|
197
|
+
|
|
198
|
+
### Direct npm installation for development
|
|
199
|
+
|
|
200
|
+
Build and pack from the ScoutMesh repository root:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm run viewer:pack
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This produces `/tmp/scoutmesh-viewer-0.2.0.tgz`. Install that artifact into a
|
|
207
|
+
user-writable tools directory (the example uses a directory in the current folder):
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npm install --ignore-scripts --no-audit --no-fund --cache ./scoutmesh-tools/npm-cache --prefix ./scoutmesh-tools /tmp/scoutmesh-viewer-0.2.0.tgz
|
|
211
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer start
|
|
212
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer status
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Check `node --version` before installation. If your default Node is older than 24,
|
|
216
|
+
use an already installed Node 24+ and keep its bin directory on PATH for npm and
|
|
217
|
+
all viewer commands. The dedicated npm cache in the installation command avoids
|
|
218
|
+
permission problems with an existing global cache; no global runtime or cache
|
|
219
|
+
permissions need changing.
|
|
220
|
+
|
|
221
|
+
Use the returned URL. `start` runs a background process and reuses an authenticated
|
|
222
|
+
server already owning the same state file. `stop` stops that process without
|
|
223
|
+
clearing data. Run `start` after reboot; no login service is installed. Set
|
|
224
|
+
`VIEWER_PORT=4318` if the prototype is already using 4317. Keep the same
|
|
225
|
+
`SCOUTMESH_VIEWER_HOME` or `VIEWER_STATE_FILE` on subsequent commands.
|
|
226
|
+
|
|
227
|
+
The packaged CLI saves to `~/.scoutmesh/workspaces/state.json`, outside npm's
|
|
228
|
+
installation/cache directories. `SCOUTMESH_VIEWER_HOME` changes its directory;
|
|
229
|
+
`VIEWER_STATE_FILE` specifies an exact path. It contains all local workspaces.
|
|
230
|
+
The sibling runtime file contains the local token and endpoint; both files are
|
|
231
|
+
created with owner-only permissions on macOS; Windows uses the user directory's
|
|
232
|
+
inherited ACLs. The token is not an account login and is
|
|
233
|
+
never sent to ScoutMesh. `request` reads it automatically:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer request GET /api/views
|
|
237
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer request POST /api/views ./candidates.json
|
|
238
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer request PATCH /api/views/search-1 ./batch.json
|
|
239
|
+
./scoutmesh-tools/node_modules/.bin/scoutmesh-viewer skill
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The browser has an Unlock form. The agent reads `scoutmesh-viewer token` locally
|
|
243
|
+
and fills it using browser tools; users need not copy the token. Never put it in
|
|
244
|
+
a URL or conversation. Successful unlock creates an HttpOnly, SameSite=Strict
|
|
245
|
+
cookie. The token rotates on restart; reload the page and unlock again. API calls
|
|
246
|
+
require `Authorization: Bearer <local-token>` or the browser cookie. Origin/Host
|
|
247
|
+
checks still apply. Shutdown requires the CLI's bearer token. Local software
|
|
248
|
+
running as the same OS user can read the token file; this is not isolation from
|
|
249
|
+
that user's own processes.
|
|
250
|
+
|
|
251
|
+
For a direct npm installation, stop before upgrading the package, then start again. To migrate the existing
|
|
252
|
+
prototype, stop it first, then copy `.data/state.json` to the chosen permanent
|
|
253
|
+
state path before starting the package. Do not overwrite an existing destination
|
|
254
|
+
or run the prototype and packaged server against the same state file. The
|
|
255
|
+
prototype's data is deliberately not migrated or cleared automatically.
|
|
256
|
+
|
|
257
|
+
The CLI prevents concurrent writers with a state-file lock and recovers locks
|
|
258
|
+
whose owner process no longer exists. It refuses an ambiguous/incomplete lock or
|
|
259
|
+
a live PID. For manual recovery, first verify no viewer owns this file, then
|
|
260
|
+
remove only stale `.lock`, `.lock.recovery`, and `.runtime.json` metadata. Keep
|
|
261
|
+
`state.json`. A damaged state file fails startup without being overwritten. The
|
|
262
|
+
CLI reports the local diagnostic log path on startup failure.
|
|
263
|
+
|
|
264
|
+
### Agent skill and gateway discovery
|
|
265
|
+
|
|
266
|
+
The package includes [local-candidate-viewer](skills/local-candidate-viewer/SKILL.md).
|
|
267
|
+
Its canonical source is this folder. The gateway has a release copy in
|
|
268
|
+
`skills/local-candidate-viewer.md`; `load_skill` discovers that file automatically,
|
|
269
|
+
and the overview/presentation guidance points agents to it. After changing the
|
|
270
|
+
skill, run `npm run viewer:sync-skill -- /absolute/path/to/sourcing-gateway` and
|
|
271
|
+
review both repository diffs. Deploying that gateway change is a separate step.
|
|
272
|
+
The remote MCP service supplies instructions only; the agent performs local
|
|
273
|
+
installation and HTTP requests. Cloud-only chat clients still use chat results.
|
|
274
|
+
|
|
275
|
+
Publish the npm pilot using the procedure above, then put the confirmed package
|
|
276
|
+
version in the gateway release instructions. The Node-missing fallback still needs
|
|
277
|
+
a tested bootstrap ZIP at an official immutable URL with its SHA-256. Test Windows
|
|
278
|
+
and Intel macOS before general release. Sync the skill and deploy the gateway as a
|
|
279
|
+
separate rollout step. No artifact has been published and no production gateway
|
|
280
|
+
has been changed by this local work.
|
|
281
|
+
|
|
282
|
+
## Run the repository prototype
|
|
283
|
+
|
|
284
|
+
Use Node.js 24 or newer. From the repository root:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm run viewer
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
In a second terminal:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
npm run viewer:demo
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Open the returned URL. Shortlist or remove a candidate, then send another
|
|
297
|
+
batch from the terminal:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npm run viewer:demo -- --next
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This adds two fictional candidates and enriches one existing record. The page
|
|
304
|
+
updates automatically. All demo names, employers, and evidence are fictional;
|
|
305
|
+
the demo email is unverified. The initial demo command creates its view once;
|
|
306
|
+
use `--next` afterward or delete the view to start again.
|
|
307
|
+
|
|
308
|
+
Set `VIEWER_PORT` on both commands to use a port other than 4317.
|
|
309
|
+
|
|
310
|
+
## Large-list demo
|
|
311
|
+
|
|
312
|
+
Run `node prototypes/local-viewer/scale-demo.ts` to create a separate view with
|
|
313
|
+
100 fictional candidates. Open its URL, move to another page, then run
|
|
314
|
+
`node prototypes/local-viewer/scale-demo.ts --next` to append 20 more. Repeat
|
|
315
|
+
`--next` for another unique batch; rerunning the initial command keeps the view.
|
|
316
|
+
No real photos, contact information, or paid sourcing are used for this demo.
|
|
317
|
+
|
|
318
|
+
Cards are paginated in groups of 20, with Previous/Next above and below the list.
|
|
319
|
+
Each review list retains its page while the tab is open. New IDs arriving over
|
|
320
|
+
SSE produce a View new notice; enrichment of existing IDs does not. View new
|
|
321
|
+
opens that group, with Back to candidates returning to the prior candidate page
|
|
322
|
+
and scroll position. Additional arrivals remain separate until opened. The notice
|
|
323
|
+
and page position are browser-session state; a reload shows all records from page
|
|
324
|
+
one. Stable candidate IDs retain shortlist/removal decisions across pages and batches.
|
|
325
|
+
The full dataset still transfers to the browser; pagination limits rendered cards,
|
|
326
|
+
not API payload size. `batchId` is optional source metadata on each record; current
|
|
327
|
+
notifications detect newly added IDs, so agents need no special batch endpoint.
|
|
328
|
+
|
|
329
|
+
## Agent contract
|
|
330
|
+
|
|
331
|
+
1. Start `npm run viewer` and keep the process running. If it is already running,
|
|
332
|
+
inspect `GET /api/views` and reuse the intended view.
|
|
333
|
+
2. Create a view with `POST /api/views`. Open the `url` returned by the server.
|
|
334
|
+
3. Send the next batch to the existing view with `PATCH`. Never make a new page
|
|
335
|
+
just to append a batch. Use the same stable candidate ID for enrichment.
|
|
336
|
+
4. Read `GET /api/views/:id/state` before acting on the shortlist. `selectedIds` contains the shortlist;
|
|
337
|
+
`removedIds` contains candidates set aside by the user.
|
|
338
|
+
5. Reuse the same `updateId` and identical body for an HTTP retry. Generate a new
|
|
339
|
+
ID for genuinely new work. Optionally send `expectedRevision` to reject stale
|
|
340
|
+
updates; on HTTP 409, read the current view before deciding what to change.
|
|
341
|
+
|
|
342
|
+
Example creation:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
curl http://127.0.0.1:4317/api/views \
|
|
346
|
+
-H 'Content-Type: application/json' \
|
|
347
|
+
--data '{"id":"search-1","title":"Engineering leads","records":[{"id":"source:42","name":"Anna","role":"Engineering lead","company":"Example"}],"layout":{"type":"table","columns":["name","role","company"]}}'
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Example enrichment and next batch:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
curl -X PATCH http://127.0.0.1:4317/api/views/search-1 \
|
|
354
|
+
-H 'Content-Type: application/json' \
|
|
355
|
+
--data '{"updateId":"batch-2","expectedRevision":1,"operation":"upsert","records":[{"id":"source:42","location":"Amsterdam"},{"id":"source:43","name":"Noah","company":"Example"}]}'
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Endpoint | Purpose |
|
|
359
|
+
| --------------------------- | --------------------------------------------------------------------------------------- |
|
|
360
|
+
| `GET /api/views` | List local views and record counts. |
|
|
361
|
+
| `POST /api/views` | Create with `title`, `records`, optional `id` and `layout`. |
|
|
362
|
+
| `GET /api/views/:id` | Read current records, layout, revision, and UI state. |
|
|
363
|
+
| `PATCH /api/views/:id` | Apply `upsert`, `replace`, `remove`, or `set_layout`. Requires `updateId`. |
|
|
364
|
+
| `GET /api/views/:id/state` | Read selected IDs, filter, sorting, revision, record count, and connected browsers. |
|
|
365
|
+
| `PUT /api/views/:id/state` | Save `selectedIds` (shortlist), `maybeIds`, `removedIds`, and legacy filter/sort state. |
|
|
366
|
+
| `DELETE /api/views/:id` | Remove this view and its records from the saved session. |
|
|
367
|
+
| `GET /api/views/:id/events` | Browser SSE subscription; refetch on notifications and reconnect. |
|
|
368
|
+
|
|
369
|
+
`upsert` merges fields by ID. New records require `id` and `name`; enrichment
|
|
370
|
+
can omit the name. `replace` replaces the record set; `remove` takes `ids`.
|
|
371
|
+
`set_layout` takes `layout: {type: "table" | "cards", columns: [...]}`. A PATCH
|
|
372
|
+
can also include `title` and `layout` alongside a data update.
|
|
373
|
+
|
|
374
|
+
Candidate fields accept strings, finite numbers, booleans, null, or string
|
|
375
|
+
arrays. Preserve source links and evidence as explicit fields. The viewer does
|
|
376
|
+
not verify claims, compute fit scores, or merge identities across providers.
|
|
377
|
+
Profile details expose all supplied fields. Candidate text renders as text,
|
|
378
|
+
never HTML; only HTTP(S) values become external links.
|
|
379
|
+
|
|
380
|
+
On macOS and Windows, clicking a profile or source link (including a middle-click or
|
|
381
|
+
modified click) opens it in the system's default browser. The viewer sends a
|
|
382
|
+
JSON POST to `/api/open-external`; the local server validates an HTTP(S) URL
|
|
383
|
+
without embedded credentials and invokes `/usr/bin/open` with an argument
|
|
384
|
+
array, without a shell. The endpoint uses the same Host/Origin checks as the
|
|
385
|
+
rest of the API. Failure displays an error instead of opening an in-app tab.
|
|
386
|
+
Windows uses a constant PowerShell `Start-Process` command with the validated URL
|
|
387
|
+
supplied in an environment variable, never interpolated into code. Other operating
|
|
388
|
+
systems return an unsupported-platform error. Windows execution remains unverified.
|
|
389
|
+
Email addresses continue to use the default mail application through `mailto:`.
|
|
390
|
+
|
|
391
|
+
## Candidate cards
|
|
392
|
+
|
|
393
|
+
The UI labels positive and negative review decisions **Yes** and **No**, with
|
|
394
|
+
Maybe unchanged. Yes uses the existing `selectedIds` field; No uses `removedIds`.
|
|
395
|
+
Saved decisions and the API schema are unchanged. Undo No returns a candidate to
|
|
396
|
+
Unreviewed. References to shortlisting or removal below describe those same states.
|
|
397
|
+
|
|
398
|
+
Review lists show All, Unreviewed (the default), Yes, Maybe, and No
|
|
399
|
+
with counts. All includes removed candidates, who have a Restore action. Shortlist
|
|
400
|
+
and Maybe are mutually exclusive toggles; pressing an active choice returns the
|
|
401
|
+
candidate to Unreviewed. Removing a candidate clears the other decisions. Undo
|
|
402
|
+
restores the prior decision; Restore returns them to Unreviewed.
|
|
403
|
+
|
|
404
|
+
Profiles keep identity, section navigation, and review actions visible while the
|
|
405
|
+
content scrolls. Overview prioritizes supplied `fitSummary` and `unconfirmed`,
|
|
406
|
+
with the full assessment under a disclosure. Career appears only with career,
|
|
407
|
+
education, skills, or experience evidence. Contact appears only with supplied
|
|
408
|
+
contact/profile links or drafts. Empty tabs are omitted; Previous/Next opens each
|
|
409
|
+
person on Overview. Profile tabs support arrow keys, Home, and End.
|
|
410
|
+
|
|
411
|
+
The open profile has Previous and Next buttons with a position count. Navigation
|
|
412
|
+
follows the current All, Unreviewed, Yes, Maybe, No, or new-batch list across page
|
|
413
|
+
boundaries. The buttons stop at the first and last person. Moving resets profile
|
|
414
|
+
scroll; Close or Escape returns focus to the original card without changing the
|
|
415
|
+
list page. Live updates refresh both the profile and navigation.
|
|
416
|
+
|
|
417
|
+
Cards show the candidate's photo, name, role, company, location, and two assessment rows: Why they may fit and Needs confirmation.
|
|
418
|
+
Send an optional `photoUrl` containing an HTTPS image URL returned by the
|
|
419
|
+
candidate's source (for example, GitHub's `avatar_url`). Keep attribution in
|
|
420
|
+
`photoSource` and `photoSourceUrl`; these appear under More information. The same photo appears in the open profile. Missing, invalid, or
|
|
421
|
+
failed images fall back to initials. Images load directly from the provider
|
|
422
|
+
without sending a referrer; the viewer does not download or store image files.
|
|
423
|
+
Source URLs can expire; refresh `photoUrl` through the normal upsert API.
|
|
424
|
+
Send `fitSummary` with a concise, role-specific reason to consider the person,
|
|
425
|
+
and `unconfirmed` with the main missing requirements or uncertainties. Keep
|
|
426
|
+
source qualifiers such as "reported" or "public repository"; a title or code
|
|
427
|
+
sample does not prove proficiency. These rows are fully visible, without text
|
|
428
|
+
clamping. The viewer renders the agent's assessment; it does not calculate fit.
|
|
429
|
+
Without these fields it uses existing `justification`/`summary` and `gaps`.
|
|
430
|
+
Missing uncertainty is shown as "Not assessed yet", never as all requirements met.
|
|
431
|
+
|
|
432
|
+
View details opens a separate profile card on Overview. Sources shows named
|
|
433
|
+
profile and repository links, deduplicating a repeated `evidenceLink`. Other
|
|
434
|
+
supplied fields remain under More information. A valid `linkedin` URL adds a
|
|
435
|
+
direct card link. A valid `email` address adds a card Email action with supplied
|
|
436
|
+
verification status and Copy email. Missing contacts do not produce inactive buttons.
|
|
437
|
+
|
|
438
|
+
The viewer preserves the agent's record order. Search, sorting, table controls,
|
|
439
|
+
and selection checkboxes are not shown. Decisions can be changed from either a
|
|
440
|
+
card or the fixed profile footer; deciding does not close the profile.
|
|
441
|
+
|
|
442
|
+
Review actions use `PUT /api/views/:id/state`. `selectedIds` remains the API name
|
|
443
|
+
for shortlisted IDs; `maybeIds` tracks candidates needing further review and
|
|
444
|
+
`removedIds` tracks removals. All three survive upsert batches
|
|
445
|
+
and page reloads in the local state file. Decisions do not change candidate records.
|
|
446
|
+
An agent should exclude `removedIds` when proposing more actions on the batch.
|
|
447
|
+
A permanent API remove prunes all three lists.
|
|
448
|
+
Legacy filter/sort/layout fields remain accepted by the API but do not control
|
|
449
|
+
the card interface. Older state writes may omit `removedIds` or `maybeIds` to preserve them.
|
|
450
|
+
The three lists are mutually exclusive. Existing JSON files without `maybeIds`
|
|
451
|
+
load with an empty Maybe list and retain their previous decisions.
|
|
452
|
+
|
|
453
|
+
The profile card has an always-visible Close button and supports Escape.
|
|
454
|
+
Closing it returns focus to the original result. Shortlist, Maybe, and Remove stay
|
|
455
|
+
available in the profile footer. Revealed emails and source disclosures are
|
|
456
|
+
browser-only and reset on reload; shortlist/removal state is saved to disk.
|
|
457
|
+
|
|
458
|
+
## State and delivery
|
|
459
|
+
|
|
460
|
+
The repository prototype automatically saves candidate records, shortlist/removal decisions,
|
|
461
|
+
record batch IDs, revisions, and retry history to
|
|
462
|
+
`prototypes/local-viewer/.data/state.json`. This is plain, readable JSON, not
|
|
463
|
+
SQL. It stays on this laptop, is excluded from Git, and is not served as a web
|
|
464
|
+
asset or sent to the portal. Arrays and nested state make JSON a better session
|
|
465
|
+
format than CSV. Images remain provider URLs, not downloaded image files.
|
|
466
|
+
|
|
467
|
+
Every successful mutation writes a temporary file, flushes it, then atomically
|
|
468
|
+
replaces the state file. A failed save rejects the change without updating memory.
|
|
469
|
+
An unreadable, invalid, or unsupported state file stops startup rather than
|
|
470
|
+
silently clearing saved work. The file is created with owner-only permissions.
|
|
471
|
+
Run only one viewer process per state file. To use a different path:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
VIEWER_STATE_FILE=/absolute/path/viewer-state.json npm run viewer
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
After a laptop restart, run `npm run viewer` and reopen the same URL; this does
|
|
478
|
+
not install an automatic startup service. Saved views and decisions are restored.
|
|
479
|
+
Active tabs, pages, open profiles, Undo, and new-batch notices remain temporary
|
|
480
|
+
browser state and reset on reload. To back up the session, copy the JSON file.
|
|
481
|
+
Stop the server before manually editing or restoring it. DELETE a view to remove
|
|
482
|
+
it from the saved session; stopping the process no longer clears data.
|
|
483
|
+
The `createViewerServer` factory uses an in-memory store by default for tests;
|
|
484
|
+
the `npm run viewer` entry point explicitly uses the file-backed store.
|
|
485
|
+
|
|
486
|
+
HTTP success confirms the mutation was saved by the running store, not browser rendering.
|
|
487
|
+
SSE tells connected pages to refetch. `connectedBrowsers` describes active
|
|
488
|
+
subscriptions, not proof that a person saw the update. Reconnecting pages fetch
|
|
489
|
+
the latest complete snapshot, so missed notifications do not lose batches.
|
|
490
|
+
|
|
491
|
+
Review decisions and legacy filter/sort state survive data updates, page
|
|
492
|
+
refreshes, and server restarts. They are shared by tabs viewing the same view; the last UI state
|
|
493
|
+
write wins. The server prunes selections when records are removed. This is a
|
|
494
|
+
single-user prototype, not a collaborative editing protocol.
|
|
495
|
+
|
|
496
|
+
The process binds only to `127.0.0.1`. Host and browser-origin checks reject
|
|
497
|
+
cross-site access, mutations require JSON, and responses disable caching. No
|
|
498
|
+
request bodies are logged. Other local programs are trusted. Do not expose the
|
|
499
|
+
port publicly or forward it without adding authentication. A remote agent needs
|
|
500
|
+
its browser on the same host or an authenticated preview mechanism.
|
|
501
|
+
|
|
502
|
+
Limits: 20 views, 2,000 candidates per view, 2 MB per request, 12 visible columns,
|
|
503
|
+
and retry deduplication for the last 500 update IDs per view. Components are
|
|
504
|
+
shared by the repository prototype and the packaged CLI. The package is not
|
|
505
|
+
yet published; gateway integration distributes instructions, not a remote UI. It uses browser DOM components instead of the
|
|
506
|
+
portal's React components to keep the prototype free of bundling and auth setup.
|
|
507
|
+
|
|
508
|
+
## Verify
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
npm run viewer:check
|
|
512
|
+
npm run viewer:test
|
|
513
|
+
npm run viewer:package-test
|
|
514
|
+
npm run viewer:update-test
|
|
515
|
+
npm run viewer:bootstrap-test # macOS; downloads the pinned runtime
|
|
516
|
+
npm run check
|
|
517
|
+
npm run check-types
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
The automated tests cover batch merging, enrichment, retries, stale revisions,
|
|
521
|
+
atomic validation, selection preservation, removal/replacement, layout changes,
|
|
522
|
+
HTTP boundaries, SSE delivery, and deletion. Browser validation must additionally
|
|
523
|
+
check that an external PATCH updates the open page and preserves UI state.
|
|
524
|
+
|
|
525
|
+
The package test installs the real tarball into a temporary directory, starts two
|
|
526
|
+
clients concurrently, adds 100 + 20 records, saves decisions and an email draft,
|
|
527
|
+
reinstalls the package, restarts, retries the update, and checks that corrupt state
|
|
528
|
+
is left intact. It also verifies token rotation and private file permissions.
|
|
529
|
+
|
|
530
|
+
The bootstrap test removes Node and npm from PATH, downloads and verifies the
|
|
531
|
+
pinned runtime, installs in paths containing spaces, checks repeat installation,
|
|
532
|
+
restarts with saved decisions, and rejects a corrupt package. It uses temporary
|
|
533
|
+
fictional data and stops its server on completion. Windows needs a separate real
|
|
534
|
+
machine trial before release.
|