@foldspace_npm/harness 0.1.15 → 0.1.17

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/CLAUDE.md CHANGED
@@ -44,16 +44,23 @@ current CLI contract — risk, prerequisites, effects, next step.
44
44
  | **L2 · look up entities** | a search from the user's own words, showing rows |
45
45
 
46
46
  1. **Get the agent live first (L0) - with no actions.** Straight after
47
- orienting: `npm run build`, `npm run inject`, the human signs in to their
48
- app, then `npx foldspace attach --daemon`. An empty registry is valid. L0
49
- is `registration_ok` with the agent visible on their page - tell them to
50
- look at it and say hello to it. **Do not explore the app first**: no
47
+ orienting: `npm run build`, `npm run inject`, and one ask - *"A Chrome
48
+ window opened at <Product>'s login. Please log in there - I'll notice when
49
+ you're in."* Then `npx foldspace observe wait-login`: it returns the moment
50
+ they are in, so nobody has to type "I've logged in". Then
51
+ `npx foldspace attach --daemon`. An empty registry is valid. L0 is
52
+ `registration_ok` with the agent visible on their page: take
53
+ `npx foldspace observe screenshot`, send them the image, and tell them to
54
+ look at it and say hello to it. **From here on the agent stays on their
55
+ page** - you never detach to look at the app.
56
+ **Do not explore the app first**: no
51
57
  reading its bundle, no watching its traffic, no browsing its screens.
52
58
  Nothing about L0 needs any of it, and minutes of silent investigation
53
59
  before the human has seen anything is the wrong first impression.
54
60
  2. **Then offer experiences - from what you already have.** With the agent on
55
61
  their screen, offer three or four read-only experiences as a choice,
56
- recommended first. Sources: the conversation starters seeded at sign-up,
62
+ recommended first. Sources: the product's own menu (`npx foldspace observe
63
+ menu` takes a second and moves nothing), the conversation starters seeded at sign-up,
57
64
  `discover_actions`, and `docs/app-profile.md`. `discover_actions` gives
58
65
  candidates, not an inventory. Do not investigate the app to build this
59
66
  list. Order them cheapest first: the user's own data with no parameter
@@ -64,11 +71,15 @@ current CLI contract — risk, prerequisites, effects, next step.
64
71
  so and offer the next one. On an empty account the useful first action is
65
72
  one that creates data - say so instead.
66
73
 
67
- **Say when the test window is yours, and when it is theirs.** You and the
68
- human share one Chrome window. Before you start looking at the app or testing
69
- in it: *"I'm going to work in that Chrome window for a few minutes - please
70
- leave it alone until I tell you it's ready."* When it is their turn: *"It's
71
- ready. Try asking: <the question this experience answers>."*
74
+ **Say when the test window is yours, and when it is theirs - on the page and
75
+ in chat.** You and the human share one Chrome window. Before you start looking
76
+ at the app or testing in it, run `npx foldspace badge working` (a bar across
77
+ the top of the window says Foldspace is building here, leave it alone) and say
78
+ so in chat: *"I'm going to work in that Chrome window for a few minutes -
79
+ please leave it alone until I tell you it's ready."* When it is their turn,
80
+ `npx foldspace badge ready --text "Try asking: <the question this experience
81
+ answers>"` and the same line in chat. `foldspace badge label` puts the small
82
+ corner tag back; `off` removes it, for a screenshot they will share.
72
83
 
73
84
  Navigation, uploads, extraction and anything that writes are a later session.
74
85
 
@@ -92,15 +103,35 @@ and asking are not the only options — **observing is, and it comes first**:
92
103
 
93
104
  1. **Already known** — `docs/app-profile.md`. A verified endpoint needs no
94
105
  browsing.
95
- 2. **You drive - once they have chosen an experience.** The agent is already
96
- attached from L0, and `attach` owns the debug port: `npx foldspace attach
97
- --stop` first. Then *you* go to the one screen that experience needs: read
98
- the app's bundle for API paths and watch the requests it makes
99
- (chrome-devtools MCP against the inject Chrome, or a page-context
100
- `fetch`). Attach again when you have the call. Read the auth method off a request
101
- the page already sent — bearer from `localStorage`, cookie, custom header.
102
- Delegate the watching to a read-only subagent so the traffic does not flood
103
- your context.
106
+ 2. **You look - once they have chosen an experience - with `foldspace
107
+ observe`.** It reads the test window they signed in to, it is read-only by
108
+ construction, and it works **while the agent stays attached**: do not
109
+ `attach --stop` to look. Write no browser-driving script of your own, and do
110
+ not reach for a separate DevTools server - one launched on its own opens a
111
+ different, signed-out Chrome and can never see the window they logged in to.
112
+
113
+ ```bash
114
+ npx foldspace observe menu # the product's own screens, by label and path
115
+ npx foldspace observe screen --click "<a label from menu>" --match "<three to five words for the data>"
116
+ npx foldspace observe read "<the GET path screen returned>" # GET only: status, field names and types, row count
117
+ npx foldspace observe auth # only if a read came back 401 or 403
118
+ npx foldspace observe styles # their font and colours, for the card
119
+ ```
120
+
121
+ `screen` reaches a screen the way a person does and returns at most eight
122
+ requests, ranked by your words - field names, value types and row counts,
123
+ **never values**. `read` confirms the one you picked. `auth` reports *where*
124
+ each header value comes from, never the value: do not go hunting through
125
+ storage, cookies or bundles yourself. One experience needs one screen; two
126
+ `screen` calls is normal, five means the experience is wrong for this
127
+ product - offer the next one.
128
+
129
+ **`observe` refuses what is not provably looking**, and a refusal is an
130
+ answer (**Safety**): it follows links, tabs and menu openers only - a plain
131
+ button can do anything, so use `--goto <path>` from `menu` instead; it will
132
+ not open a path that does something (`/logout`, `/…/delete`); and `read`
133
+ only goes to the app's own origin, a host configured for this project, or
134
+ an origin `screen` saw the page call.
104
135
  3. **Ask the human to perform the workflow** — only for what you cannot reach:
105
136
  an MFA step, a paywall, a screen that needs a record the account does not
106
137
  have, or anything that would write.
@@ -180,11 +211,17 @@ with how you established each.
180
211
  | Navigation route | **yes** | `isActive: true` |
181
212
 
182
213
  Publishing a **new** object is inert; publishing an **edit to a live one** is
183
- the deploy above. Navigation has **no publish tool on the MCP** and
184
- `get_navigation_route` returns only `isActive` publish routes in Agent Studio
185
- and say so. Route descriptions are a retrieval surface: write what the page is
186
- *for*, in the user's words, and pair every parameterised route with the lookup
187
- action that produces its parameter or ship neither.
214
+ the deploy above. **Navigation has two switches, and both must be on.** Each
215
+ route has its own `isActive`, set when you create it (`create_navigation_route`
216
+ / `bulk_create_navigation_routes`, `isActive: true`). The agent has one
217
+ agent-level switch, **off by default on a new subscription**: `publish_navigation`
218
+ turns it on, `unpublish_navigation` off, and neither touches the routes' own
219
+ `isActive`. `list_navigation_routes` shows both. There is no MCP tool to flip a
220
+ single route's `isActive` after creation (it was disabled until Agent Studio
221
+ can reverse it) - create it right, or `update_navigation_route`, which replaces
222
+ the whole route. Route descriptions are a retrieval surface: write what the
223
+ page is *for*, in the user's words, and pair every parameterised route with the
224
+ lookup action that produces its parameter — or ship neither.
188
225
 
189
226
  **Ask before publishing.** It is required only so the copilot can call the
190
227
  action, and it is a live product change when the agent has real users.
@@ -198,9 +235,8 @@ npx foldspace attach --daemon
198
235
  ```
199
236
 
200
237
  `inject` launches an isolated Chrome profile and records its debug port; it
201
- loads nothing. In a first session `attach` comes straight after sign-in (L0);
202
- to observe afterwards, `attach --stop`, use chrome-devtools MCP against the
203
- same Chrome, then attach again. `attach` prepares the page and loads the local
238
+ loads nothing. In a first session `attach` comes straight after sign-in (L0),
239
+ and `foldspace observe` reads the same window while it runs. `attach` prepares the page and loads the local
204
240
  `dist/index.js` over CDP. Coding agents use `--daemon` so the tool returns after
205
241
  `[lifecycle] inspect_registration:…`. An empty local registry is valid; `npm run
206
242
  build` is still required so `dist/index.js` exists.
@@ -212,14 +248,32 @@ SDK without the configured agent.
212
248
 
213
249
  `inspect_registration:registration_ok` is the signal. On `registration_mismatch`
214
250
  read `npx foldspace help attach --json` and map `missingActionNames`,
215
- `unexpectedActionNames`, `diagnosticError`. While attach runs it owns the debug
216
- port `attach --stop` before using chrome-devtools MCP again.
251
+ `unexpectedActionNames`, `diagnosticError`. A running attach keeps the actions
252
+ it loaded: after a rebuild, `attach --stop` and attach again to load the new
253
+ ones. That is the only reason to stop it.
217
254
 
218
255
  Prove a named action through the visible agent: after the human talks to the
219
256
  copilot, read the daemon log for `[actions]` SDK callback and local
220
257
  execute/render lines. Those record names, statuses, durations and parameter keys
221
258
  only — never results or error bodies.
222
259
 
260
+ ## Done, and what you say at the end
261
+
262
+ A first session is done when the customer has seen one experience answer from
263
+ their own data, in the test window, and been invited to try it. Not when the
264
+ gates pass, and not when there is nothing left to improve.
265
+
266
+ If they ask *"how do I put this in front of my users?"*, the honest answer is:
267
+
268
+ > What you've seen runs in a test window on your machine. Putting it in front
269
+ > of your users is a separate step, and the Foldspace team does it with you.
270
+ > Reach them through the website - <https://foldspace.ai/demo> - or at
271
+ > <support@foldspace.ai>. Everything built today carries over: the actions are
272
+ > already in your Foldspace account, and the code is in this folder.
273
+
274
+ Never run `foldspace deploy`, never install cloud tooling, never ask them for
275
+ cloud credentials: `deploy` is Foldspace-internal and no customer can run it.
276
+
223
277
  ## Verification gates
224
278
 
225
279
  Do not report success without all six:
@@ -261,14 +315,17 @@ Do not report success without all six:
261
315
  MCP (`search_docs`, `fetch_page`) before asserting how the platform behaves;
262
316
  cite the page, or say plainly that the docs do not cover it and write down
263
317
  what you learned.
264
- 4. **Do not send chat messages to test.** Every message is a real conversation
265
- in the customer's list. `runTask` creates no conversation record use it.
266
- 5. **Test mode is off by default.** A new agent has no traffic to pollute, and
267
- watching real conversations appear is the point of the first run. Arm it
268
- only when the tenant already has real conversations and then on **every**
269
- instance via `armAllInstances`, because `foldspace.agent({apiName})` returns
270
- the overlay handle, which is not the one serving an embedded chat. Nothing
271
- can confirm it is on afterwards; never send probe messages to find out.
318
+ 4. **Test through the agent, then let them.** In a first session, talk to the
319
+ agent yourself to prove an experience - type the real question into its
320
+ panel in the test window and read the reply - and only then invite the
321
+ customer to try it. Those conversations appear in their Foldspace dashboard,
322
+ and that is wanted: seeing their own conversations recorded is part of what
323
+ they came to see. Never pass `--test-mode` in a first session.
324
+ 5. **Test mode is for an agent that already has real users.** Then `attach
325
+ --test-mode` keeps your conversations out of their data. It is not a safety
326
+ net: publishing still changes the live agent. Working on an agent in
327
+ production - test mode, pinned action versions, what may be published - is
328
+ a separate playbook, not this one.
272
329
  6. **The human is not necessarily a developer.** Never hand over a terminal
273
330
  line, a stack trace, or a choice between flags. Name experiences by outcome
274
331
  — *"show my plan and credits"*, not `get_account_status`. Their two jobs:
package/README.md CHANGED
@@ -190,8 +190,8 @@ Use `foldspace help attach` for mode requirements, effects, and safety options.
190
190
  Coding agents should run `foldspace attach --daemon` so the invoking tool
191
191
  returns after `[lifecycle] inspect_registration:…`. Foreground attach remains
192
192
  the default for humans watching the terminal. `attach --status` and
193
- `attach --stop` inspect and tear down a daemon. Observe customer-app network
194
- traffic after `inject` and before attach; attach owns the debug port.
193
+ `attach --stop` inspect and tear down a daemon. Look at the customer's app with
194
+ `foldspace observe`: it reads the same window, read-only, while attach runs.
195
195
 
196
196
  ### Verify actions through the agent
197
197
 
@@ -252,7 +252,7 @@ and **neither had all five**:
252
252
  | SDK load guard + retry | On an app with no `document.body` at document-start the append throws; the old guard tested for the stub, so nothing retried and the agent silently never loaded |
253
253
  | Worker release on auto-attach | Auto-attach pauses every worker until the attaching client releases it — the harness froze the app's own workers for the whole session |
254
254
  | `agentIds` enumeration | `agent({apiName})` returns the OVERLAY handle; arming it on an embedded copilot leaves real conversations untagged |
255
- | `--no-test-mode` | Test mode was armed unconditionally, hiding exactly what initial setup needs to see |
255
+ | `--test-mode` (off by default) | Test mode was armed unconditionally, hiding exactly what initial setup needs to see; now it is opt-in |
256
256
  | Honest badge | It asserted `TEST MODE` whether or not test mode was on |
257
257
 
258
258
  Two further changes came from packaging it: the bundle is **injected** rather
package/bin/attach.mjs CHANGED
@@ -43,6 +43,7 @@ import {
43
43
  shouldFulfillActionRequest,
44
44
  } from "../src/attach-preflight.mjs";
45
45
  import { serializeDiagnostic } from "../src/diagnostics.mjs";
46
+ import { recordEvent } from "../src/session-events.mjs";
46
47
  import {
47
48
  ERROR_CODES,
48
49
  createLifecycleResult,
@@ -134,11 +135,13 @@ try {
134
135
  }
135
136
  const bootstrap = attachMode === ATTACH_MODES.BOOTSTRAP;
136
137
  const replace = attachMode === ATTACH_MODES.REPLACE;
137
- // Test mode keeps conversations out of the customer's default list. Turn it off
138
- // only when you WANT the conversations and action calls to show up in the
139
- // Foldspace dashboard a brand-new agent during initial setup, where there is
140
- // no production traffic to pollute.
141
- const noTestMode = process.argv.includes("--no-test-mode");
138
+ // Test mode keeps conversations out of the customer's default list. It is OFF
139
+ // by default: in a first session the customer's own conversations appearing in
140
+ // their dashboard is the point - they see what Foldspace records - and an
141
+ // orange TEST MODE label would imply a safety (nothing published) that does
142
+ // not exist. --test-mode opts in, for an agent that already has real users.
143
+ // --no-test-mode is still accepted so older instructions keep working.
144
+ const noTestMode = !process.argv.includes("--test-mode");
142
145
  // The badge used to hardcode "TEST MODE" whether or not test mode was on — a
143
146
  // string that asserts you are safe while you are not. It now states what is
144
147
  // actually true. --no-badge drops it entirely, for recording a demo.
@@ -170,8 +173,8 @@ const agentKey = cfgTarget.overrideKey || `EU-${productId}-1-1`;
170
173
  const sdkUrl = cfgTarget.sdkUrl || cfg.sdkUrl;
171
174
  const actionTarget = { productId, agentApiName };
172
175
  const badgeText = noTestMode
173
- ? `FOLDSPACE DEV \u00b7 ${agentApiName} \u00b7 ${attachMode} \u00b7 LIVE`
174
- : `FOLDSPACE DEV \u00b7 ${agentApiName} \u00b7 ${attachMode} \u00b7 TEST MODE`;
176
+ ? `FOLDSPACE AGENT BUILDER \u00b7 ${agentApiName} \u00b7 ${attachMode} \u00b7 LIVE`
177
+ : `FOLDSPACE AGENT BUILDER \u00b7 ${agentApiName} \u00b7 ${attachMode} \u00b7 TEST MODE`;
175
178
 
176
179
  // Refuse to run against an uninitialised template. Without this, attach injects
177
180
  // the literal placeholder as an agent api name and the page fails with an opaque
@@ -217,19 +220,45 @@ const SDK_PATTERNS = [
217
220
  "*://*/*eucera.js*",
218
221
  ];
219
222
 
223
+ // The badge has three states, set from outside with \`foldspace badge\`:
224
+ // label - the small corner tag (default): who is attached, which mode
225
+ // working - a bar across the top: the agent is building in this window,
226
+ // please leave it alone
227
+ // ready - a bar: it is the human's turn, with the question to try
228
+ // The state lives in sessionStorage so it survives navigation inside the
229
+ // test window; \`foldspace badge\` writes it and the script redraws.
220
230
  const badgeSrc = `(() => {
221
231
  if (window.top !== window.self) return;
222
- const add = () => {
223
- if (document.getElementById("foldspace-dev-badge") || !document.body) return;
224
- const b = document.createElement("div");
225
- b.id = "foldspace-dev-badge";
226
- b.textContent = ${JSON.stringify(badgeText)};
227
- b.style.cssText = "position:fixed;top:0;left:0;z-index:2147483647;background:#3247F2;color:#fff;font:700 10px/1 ui-monospace,Menlo,monospace;letter-spacing:.12em;padding:5px 10px;border-bottom-right-radius:4px;pointer-events:none;box-shadow:0 1px 6px rgba(0,0,0,.35)";
228
- document.body.appendChild(b);
232
+ const LABEL = ${JSON.stringify(badgeText)};
233
+ const KEY = "__foldspace_badge__";
234
+ const read = () => {
235
+ try { return JSON.parse(sessionStorage.getItem(KEY) || "null") || { state: "label" }; }
236
+ catch (e) { return { state: "label" }; }
229
237
  };
230
- add();
231
- document.addEventListener("DOMContentLoaded", add);
232
- setInterval(add, 2000);
238
+ const draw = () => {
239
+ if (!document.body) return;
240
+ const s = read();
241
+ if (s.state === "off") { document.getElementById("foldspace-dev-badge")?.remove(); return; }
242
+ let b = document.getElementById("foldspace-dev-badge");
243
+ if (!b) { b = document.createElement("div"); b.id = "foldspace-dev-badge"; document.body.appendChild(b); }
244
+ const want = s.state + "|" + (s.text || "");
245
+ if (b.dataset.fsState === want) return;
246
+ b.dataset.fsState = want;
247
+ const base = "position:fixed;top:0;left:0;z-index:2147483647;color:#fff;font:700 10px/1 ui-monospace,Menlo,monospace;letter-spacing:.12em;pointer-events:none;box-shadow:0 1px 6px rgba(0,0,0,.35);";
248
+ if (s.state === "working") {
249
+ b.style.cssText = base + "right:0;background:#3247F2;padding:9px 14px;font-size:12px;letter-spacing:.04em;text-align:center;";
250
+ b.textContent = s.text || "Foldspace is building your agent on this page. Please leave this window alone until Claude says it is ready.";
251
+ } else if (s.state === "ready") {
252
+ b.style.cssText = base + "right:0;background:#0F9D58;padding:9px 14px;font-size:12px;letter-spacing:.04em;text-align:center;";
253
+ b.textContent = s.text || "Ready - your turn. Open your agent and try it.";
254
+ } else {
255
+ b.style.cssText = base + "background:#3247F2;padding:5px 10px;border-bottom-right-radius:4px;";
256
+ b.textContent = LABEL;
257
+ }
258
+ };
259
+ draw();
260
+ document.addEventListener("DOMContentLoaded", draw);
261
+ setInterval(draw, 1000);
233
262
  })();`;
234
263
 
235
264
  // Flag every local session as test traffic so it is excluded from analytics and
@@ -699,6 +728,8 @@ async function prepare(ws, sessionId, url) {
699
728
 
700
729
  {
701
730
  const verification = await verifyPreparedPage(ws, sessionId);
731
+ // For scoring a session: the moment the customer could first see their agent.
732
+ if (verification.ok) recordEvent(root, "agent_visible", { mode: attachMode });
702
733
  logLifecycle(
703
734
  createLifecycleResult({
704
735
  operation: "inspect_registration",
@@ -948,11 +979,11 @@ const ver = await connect(owned.version);
948
979
  console.log(`Attached to ${ver.Browser} on :${port}`);
949
980
  console.log(`Mode: ${attachMode}`);
950
981
  console.log(`Agent: ${agentApiName} (product ${productId}, ${agentMode})`);
951
- console.log(`Test: ${noTestMode ? "OFF — conversations WILL appear in the dashboard" : "on"}`);
982
+ console.log(`Test: ${noTestMode ? "off — conversations appear in the dashboard (pass --test-mode to hide them)" : "on"}`);
952
983
  console.log(`Hosts: ${hostPatterns.join(", ")}`);
953
984
  console.log(`Serving: ${path.relative(root, bundlePath)}`);
954
985
  console.log(
955
- `CDP: this process owns the debug port. Detach before using chrome-devtools MCP against the same Chrome.\n`,
986
+ `CDP: attached. \`foldspace observe\` reads this same window while attach runs; stop attach only to load rebuilt actions.\n`,
956
987
  );
957
988
 
958
989
  function printSessionSummary() {
package/bin/badge.mjs ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * foldspace badge <working|ready|label|off> [--text "<what to show>"]
4
+ *
5
+ * Sets the banner the attach badge draws across the top of the test window.
6
+ * The agent and the human share that one Chrome window, so the banner says
7
+ * whose turn it is:
8
+ * working - "Foldspace is building your agent on this page. Please leave
9
+ * this window alone until Claude says it is ready."
10
+ * ready - "Ready - your turn. Open your agent and try it." (pass --text
11
+ * with the question to try)
12
+ * label - back to the small corner tag
13
+ * off - remove it
14
+ * The state is kept in the page's sessionStorage, so it survives navigation;
15
+ * attach's badge script redraws within a second. Nothing else on the page is
16
+ * touched.
17
+ */
18
+ import { CdpPage, parseArgs, printJson } from "../src/cdp-client.mjs";
19
+ import { resolveProjectDir } from "../src/upgrade.mjs";
20
+ import { badgePayload } from "../src/badge-core.mjs";
21
+
22
+ const projectDir = resolveProjectDir();
23
+ const { flags, rest } = parseArgs(process.argv.slice(2), ["text"]);
24
+
25
+ let payload;
26
+ try {
27
+ const unknown = Object.keys(flags).filter((name) => name !== "text");
28
+ if (unknown.length) throw new Error(`unknown option '--${unknown[0]}'`);
29
+ if (rest.length > 1) throw new Error(`unexpected argument '${rest[1]}'`);
30
+ payload = badgePayload(rest[0], flags.text);
31
+ } catch (error) {
32
+ console.error(`badge: ${error instanceof Error ? error.message : String(error)}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ let page;
37
+ try {
38
+ page = await CdpPage.open({ projectDir });
39
+ const shown = await page.evaluate(
40
+ `sessionStorage.setItem("__foldspace_badge__", ${JSON.stringify(JSON.stringify(payload))});
41
+ const b = document.getElementById("foldspace-dev-badge");
42
+ return { url: location.href.split(/[?#]/)[0], hadBadge: Boolean(b) };`,
43
+ );
44
+ printJson({ ok: true, state: payload.state, ...(payload.text ? { text: payload.text } : {}), ...shown });
45
+ } catch (error) {
46
+ printJson({ ok: false, error: error instanceof Error ? error.message : String(error) });
47
+ process.exit(1);
48
+ } finally {
49
+ page?.close();
50
+ }
package/bin/inject.mjs CHANGED
@@ -11,6 +11,7 @@
11
11
  * npm run inject -- --url https://app.other.com --product ABC123 --agent other-agent
12
12
  * npm run inject -- --print # resolve target only, don't launch
13
13
  */
14
+ import { recordEvent } from "../src/session-events.mjs";
14
15
  import fs from "fs";
15
16
  import path from "path";
16
17
  import os from "os";
@@ -143,7 +144,7 @@ fs.writeFileSync(
143
144
  path.join(themeDir, "manifest.json"),
144
145
  JSON.stringify({
145
146
  manifest_version: 3,
146
- name: "Foldspace Dev Theme",
147
+ name: "Foldspace Agent Builder Theme",
147
148
  version: "1.0",
148
149
  description: "Paints the dev browser Foldspace blue.",
149
150
  theme: {
@@ -196,7 +197,7 @@ function nameProfile() {
196
197
  fs.mkdirSync(path.dirname(file), { recursive: true });
197
198
  fs.writeFileSync(file, JSON.stringify(data));
198
199
  };
199
- const label = `Foldspace Dev \u2014 ${target.name}`;
200
+ const label = `Foldspace Agent Builder \u2014 ${target.name}`;
200
201
  writeMerged(path.join(profileDir, "Local State"), (d) => {
201
202
  d.profile = d.profile || {};
202
203
  d.profile.info_cache = d.profile.info_cache || {};
@@ -279,6 +280,8 @@ fs.writeFileSync(
279
280
  ),
280
281
  );
281
282
 
283
+ recordEvent(root, "chrome_opened", { url: startUrl });
284
+
282
285
  // --- theme -------------------------------------------------------------
283
286
  //
284
287
  // Chrome 151 ignores --load-extension, so load the optional cosmetic theme