@vincentt-xr/cli 0.4.1 → 0.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/dist/cli/index.js +15 -3
- package/dist/lib/lifecycle.js +2 -2
- package/dist/lib/projects.js +1 -1
- package/dist/lib/whoami.js +2 -2
- package/dist/login/loginCommand.js +1 -1
- package/dist/preview/runner.d.ts +8 -0
- package/dist/preview/runner.js +1 -0
- package/dist/relay/server.d.ts +10 -0
- package/dist/relay/server.js +36 -14
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import { mintRateLimited, notSignedIn, previewConflict, startBlock, } from "../p
|
|
|
35
35
|
import { renderTerminalQr, shouldRenderQr } from "../preview/qr.js";
|
|
36
36
|
import { PreviewConflictError, PreviewRateLimitedError } from "../preview/api.js";
|
|
37
37
|
import { fetchNext, readCursor, writeCursor } from "../lib/feedback.js";
|
|
38
|
-
import { isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag } from "./args.js";
|
|
38
|
+
import { isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag, } from "./args.js";
|
|
39
39
|
// The console origin serves /cli-auth (the browser target for `vincentt login`). VINCENTT_
|
|
40
40
|
// CONSOLE_URL overrides for dev/dogfood; VINCENTT_EDITOR_URL is honored as a legacy alias.
|
|
41
41
|
const DEFAULT_CONSOLE_URL = "https://vincentt.app";
|
|
@@ -319,6 +319,14 @@ async function preview() {
|
|
|
319
319
|
onViewer: (label, uaClass) => ui.viewerSeen(label, uaClass),
|
|
320
320
|
onViewerGone: (label) => ui.viewerGone(label),
|
|
321
321
|
onEvents: (events, tags) => ui.events(events, tags),
|
|
322
|
+
onAttribute: (tags) => {
|
|
323
|
+
// The hello resolved a tester on connect. Same guard the events path
|
|
324
|
+
// uses: a connection with no viewer label has nothing to attribute in
|
|
325
|
+
// the roster (the roster is keyed by label).
|
|
326
|
+
if (tags.viewer !== undefined && tags.tester !== undefined) {
|
|
327
|
+
ui.testerSeen(tags.viewer, tags.tester);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
322
330
|
onCapReached: () => ui.capReached(),
|
|
323
331
|
onTunnelState: (state) => ui.tunnel(state),
|
|
324
332
|
onOriginError: () => ui.originError(),
|
|
@@ -377,7 +385,7 @@ async function preview() {
|
|
|
377
385
|
await done;
|
|
378
386
|
clearInterval(ceiling);
|
|
379
387
|
if (ending !== null) {
|
|
380
|
-
ui.endedBy(ending, (await activeOrgName()) || "your
|
|
388
|
+
ui.endedBy(ending, (await activeOrgName()) || "your workspace");
|
|
381
389
|
await previewStop();
|
|
382
390
|
process.exitCode = 1;
|
|
383
391
|
}
|
|
@@ -409,7 +417,11 @@ async function followPreview(info, ui, filterTester, filterViewer) {
|
|
|
409
417
|
});
|
|
410
418
|
while (!stop) {
|
|
411
419
|
try {
|
|
412
|
-
const result = await relay.query({
|
|
420
|
+
const result = await relay.query({
|
|
421
|
+
since,
|
|
422
|
+
tester: filterTester,
|
|
423
|
+
viewer: filterViewer,
|
|
424
|
+
});
|
|
413
425
|
// An unknown id is a FACT, not an error — and the remedy goes on the same
|
|
414
426
|
// line. Said once rather than on every poll, or a two-second loop becomes
|
|
415
427
|
// a wall of identical advisories.
|
package/dist/lib/lifecycle.js
CHANGED
|
@@ -64,8 +64,8 @@ export async function resolveOrgForCommand(cfg, input) {
|
|
|
64
64
|
return {
|
|
65
65
|
ok: false,
|
|
66
66
|
message: asked
|
|
67
|
-
? `✗ You are not an active member of "${asked.slug}".\n\n Run \`vincentt whoami\` to see which
|
|
68
|
-
: "✗ No active
|
|
67
|
+
? `✗ You are not an active member of "${asked.slug}".\n\n Run \`vincentt whoami\` to see which workspaces you belong to.`
|
|
68
|
+
: "✗ No active workspace.\n\n Run `vincentt whoami` to see which workspaces you belong to,\n then re-run with `--org <slug>`.",
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
/**
|
package/dist/lib/projects.js
CHANGED
|
@@ -161,7 +161,7 @@ export function printUnreachable(print, message) {
|
|
|
161
161
|
print(`✗ ${asSentence(message)}`);
|
|
162
162
|
print("");
|
|
163
163
|
print(" This folder is linked to a project that can't be reached with your current");
|
|
164
|
-
print(" account. It may have been deleted, or it may belong to
|
|
164
|
+
print(" account. It may have been deleted, or it may belong to a workspace you're");
|
|
165
165
|
print(" not a member of.");
|
|
166
166
|
print("");
|
|
167
167
|
print(" Check who you are: vincentt whoami");
|
package/dist/lib/whoami.js
CHANGED
|
@@ -31,8 +31,8 @@ export async function whoamiCommand(deps) {
|
|
|
31
31
|
// Printed even when there is no active org: an absent line is indistinguishable
|
|
32
32
|
// from a bug on exactly the path where the creator is already confused.
|
|
33
33
|
deps.print(org
|
|
34
|
-
? ` Active
|
|
35
|
-
: " Active
|
|
34
|
+
? ` Active workspace: ${org.name} (${org.slug})`
|
|
35
|
+
: " Active workspace: none — pass --org <slug>");
|
|
36
36
|
deps.print(` API: ${cfg.apiUrl}`);
|
|
37
37
|
return 0;
|
|
38
38
|
}
|
|
@@ -27,7 +27,7 @@ export function orgSummaryLine(identity) {
|
|
|
27
27
|
.filter((m) => m !== active)
|
|
28
28
|
.map((m) => m.orgName)
|
|
29
29
|
.filter(Boolean);
|
|
30
|
-
const base = ` Active
|
|
30
|
+
const base = ` Active workspace: ${activeName}`;
|
|
31
31
|
return others.length ? `${base} · also a member of: ${others.join(", ")}` : base;
|
|
32
32
|
}
|
|
33
33
|
/**
|
package/dist/preview/runner.d.ts
CHANGED
|
@@ -70,6 +70,14 @@ export interface StartPreviewOptions {
|
|
|
70
70
|
* itself, so the first events of a load are routinely un-attributed.
|
|
71
71
|
*/
|
|
72
72
|
onEvents?: (events: DiagEvent[], tags: EventTags) => void;
|
|
73
|
+
/**
|
|
74
|
+
* A connection introduced its tester on the `hello`, at connect, before any
|
|
75
|
+
* event. This is what makes `tester-N connected` print for a SILENT app and
|
|
76
|
+
* for one that crashes during mount — the key introduced itself before the
|
|
77
|
+
* crash. Fires once per connection; a client with no hello (an old harness)
|
|
78
|
+
* still resolves its tester on the first batch via `onEvents`.
|
|
79
|
+
*/
|
|
80
|
+
onAttribute?: (tags: EventTags) => void;
|
|
73
81
|
/**
|
|
74
82
|
* The tester cap was reached, once per refused session. The CLI prints one
|
|
75
83
|
* line; the connection's events are still stored, just un-attributed.
|
package/dist/preview/runner.js
CHANGED
package/dist/relay/server.d.ts
CHANGED
|
@@ -24,6 +24,16 @@ export interface RelayOptions {
|
|
|
24
24
|
* replacement, and a missing handler changes nothing.
|
|
25
25
|
*/
|
|
26
26
|
onEvents?: (events: DiagEvent[], tags: EventTags) => void;
|
|
27
|
+
/**
|
|
28
|
+
* A connection resolved its tester — on the `hello` at WS-open, before any
|
|
29
|
+
* batch. This is what lets the CLI print `tester-N connected` on connect, so a
|
|
30
|
+
* SILENT app (no events at all) and an app that crashes during mount are both
|
|
31
|
+
* attributed. Fires ONCE per connection, the moment the tester is known; a
|
|
32
|
+
* connection whose key misses the grammar resolves to no tester and never
|
|
33
|
+
* fires this. The events path still carries `tester` for un-helloed clients
|
|
34
|
+
* (an old harness), so attribution is not lost if the hello never arrives.
|
|
35
|
+
*/
|
|
36
|
+
onAttribute?: (tags: EventTags) => void;
|
|
27
37
|
/**
|
|
28
38
|
* Fires ONCE per refused session when the tester cap is reached, so the CLI
|
|
29
39
|
* can print the one warning line. Once per session rather than once per batch:
|
package/dist/relay/server.js
CHANGED
|
@@ -27,8 +27,7 @@ export function startRelay(opts = {}) {
|
|
|
27
27
|
let annotations;
|
|
28
28
|
let dispatcher;
|
|
29
29
|
if (opts.projectDir || opts.annotationStore) {
|
|
30
|
-
annotations =
|
|
31
|
-
opts.annotationStore ?? new AnnotationStore(opts.projectDir);
|
|
30
|
+
annotations = opts.annotationStore ?? new AnnotationStore(opts.projectDir);
|
|
32
31
|
const queuePath = path.join(opts.projectDir ? annotationsDir(opts.projectDir) : ".", "queue.ndjson");
|
|
33
32
|
dispatcher =
|
|
34
33
|
opts.dispatcher ??
|
|
@@ -57,22 +56,41 @@ export function startRelay(opts = {}) {
|
|
|
57
56
|
// skips.
|
|
58
57
|
const viewer = readViewerLabel(req.headers[VIEWER_STAMP_HEADER]);
|
|
59
58
|
log(viewer ? `client connected · ${viewer}` : "client connected");
|
|
60
|
-
// The tester is resolved ONCE per connection, from the
|
|
61
|
-
//
|
|
62
|
-
//
|
|
59
|
+
// The tester is resolved ONCE per connection, from the FIRST key the
|
|
60
|
+
// connection presents — the `hello` at WS-open, or (for an old harness with
|
|
61
|
+
// no hello) the first batch. Re-resolving per message would let one socket's
|
|
62
|
+
// events land under two ids if a client changed its key mid-stream.
|
|
63
|
+
//
|
|
64
|
+
// `attribute` grammar-guards the key against CLIENT_SESSION_ID_RE with no
|
|
65
|
+
// sanitize-and-keep branch: a malformed key resolves to `undefined` (the
|
|
66
|
+
// connection stays live and un-keyed), and the raw key is only ever a map
|
|
67
|
+
// lookup inside the registry — it reaches no sink, exactly as the batch key
|
|
68
|
+
// and the readViewerLabel discipline require.
|
|
63
69
|
let tester;
|
|
64
70
|
let resolved = false;
|
|
71
|
+
const resolveTester = (sessionId) => {
|
|
72
|
+
if (resolved)
|
|
73
|
+
return;
|
|
74
|
+
resolved = true;
|
|
75
|
+
const attribution = testers.attribute(sessionId);
|
|
76
|
+
tester = attribution.tester;
|
|
77
|
+
if (attribution.warnCapReached)
|
|
78
|
+
opts.onCapReached?.();
|
|
79
|
+
};
|
|
65
80
|
ws.on("message", (data) => {
|
|
66
81
|
try {
|
|
67
82
|
const msg = JSON.parse(String(data));
|
|
83
|
+
if (msg.type === "hello") {
|
|
84
|
+
// Resolve on connect, before any batch — the whole point of the hello.
|
|
85
|
+
// A well-formed key mints/looks-up the tester and announces it; a
|
|
86
|
+
// malformed one resolves to undefined and announces nothing.
|
|
87
|
+
resolveTester(msg.sessionId);
|
|
88
|
+
if (tester !== undefined)
|
|
89
|
+
opts.onAttribute?.({ viewer, tester });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
68
92
|
if (msg.type === "events" && Array.isArray(msg.events)) {
|
|
69
|
-
|
|
70
|
-
resolved = true;
|
|
71
|
-
const attribution = testers.attribute(msg.sessionId);
|
|
72
|
-
tester = attribution.tester;
|
|
73
|
-
if (attribution.warnCapReached)
|
|
74
|
-
opts.onCapReached?.();
|
|
75
|
-
}
|
|
93
|
+
resolveTester(msg.sessionId);
|
|
76
94
|
store.ingest(msg.events, { viewer, tester });
|
|
77
95
|
opts.onEvents?.(msg.events, { viewer, tester });
|
|
78
96
|
}
|
|
@@ -129,7 +147,9 @@ function handleQuery(req, res, url, ctx) {
|
|
|
129
147
|
readQuery(req, url)
|
|
130
148
|
.then((q) => {
|
|
131
149
|
const result = ctx.store.query(q);
|
|
132
|
-
res
|
|
150
|
+
res
|
|
151
|
+
.writeHead(200, { "content-type": "application/json" })
|
|
152
|
+
.end(JSON.stringify(result));
|
|
133
153
|
})
|
|
134
154
|
.catch((err) => {
|
|
135
155
|
ctx.log(`query error: ${err}`);
|
|
@@ -145,7 +165,9 @@ function handleAnnotationPost(req, res, ctx) {
|
|
|
145
165
|
readBody(req, MAX_ANNOTATION_BYTES)
|
|
146
166
|
.then(async (body) => {
|
|
147
167
|
const input = JSON.parse(body);
|
|
148
|
-
if (!input ||
|
|
168
|
+
if (!input ||
|
|
169
|
+
typeof input.message !== "string" ||
|
|
170
|
+
typeof input.screenshot !== "string") {
|
|
149
171
|
res.writeHead(400).end("annotation requires message + screenshot");
|
|
150
172
|
return;
|
|
151
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincentt-xr/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Vincentt CLI — the vincentt command-line dev loop (login, create, publish, preview, diagnostics, feedback, relay)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"qrcode": "^1.5.4",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
26
|
"zod": "^3.23.8",
|
|
27
|
-
"@vincentt-xr/harness": "1.
|
|
27
|
+
"@vincentt-xr/harness": "1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/qrcode": "^1.5.5",
|