@foldspace_npm/harness 0.1.27 → 0.1.29

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.
Files changed (119) hide show
  1. package/CLAUDE.md +301 -188
  2. package/README.md +54 -36
  3. package/bin/ask.mjs +9 -7
  4. package/bin/attach.mjs +80 -51
  5. package/bin/badge.mjs +28 -8
  6. package/bin/build.ts +19 -6
  7. package/bin/cli.mjs +23 -6
  8. package/bin/inject.mjs +3 -4
  9. package/bin/observe.mjs +22 -6
  10. package/bin/published.mjs +1 -1
  11. package/package.json +6 -2
  12. package/recipes/INDEX.md +13 -8
  13. package/recipes/README.md +30 -7
  14. package/recipes/account-overview/README.md +6 -6
  15. package/recipes/account-overview/agent/actions/show_account_overview.ts +11 -16
  16. package/recipes/account-overview/agent/views/brand.ts +23 -14
  17. package/recipes/account-overview/agent/views/overview.ts +13 -17
  18. package/recipes/bottom-bar/README.md +12 -9
  19. package/recipes/bottom-bar/agent/bottomBar.ts +14 -12
  20. package/recipes/create-a-record/README.md +97 -0
  21. package/recipes/create-a-record/agent/actions/create_project.ts +26 -0
  22. package/recipes/create-a-record/agent/api/projects.ts +62 -0
  23. package/recipes/create-a-record/agent/projects.ts +145 -0
  24. package/recipes/create-a-record/agent/views/approval.ts +181 -0
  25. package/recipes/create-a-record/agent/views/brand.ts +35 -0
  26. package/recipes/create-a-record/agent/views/fields.ts +252 -0
  27. package/recipes/create-a-record/agent/views/project-card.ts +253 -0
  28. package/recipes/create-a-record/agent/views/steps.ts +92 -0
  29. package/recipes/create-a-record/fixtures/owners.ok.json +9 -0
  30. package/recipes/create-a-record/fixtures/project.created.json +1 -0
  31. package/recipes/create-a-record/fixtures/project.rejected.json +1 -0
  32. package/recipes/create-a-record/recipe.json +13 -0
  33. package/recipes/create-a-record/variants/actions/create_project.steps.ts +160 -0
  34. package/recipes/find-by-name/README.md +3 -1
  35. package/recipes/find-by-name/agent/actions/find_project_id.ts +1 -1
  36. package/recipes/lookup-then-view/README.md +73 -0
  37. package/recipes/lookup-then-view/agent/accounts.ts +174 -0
  38. package/recipes/lookup-then-view/agent/actions/find_account.ts +108 -0
  39. package/recipes/lookup-then-view/agent/actions/show_account_overview.ts +42 -0
  40. package/recipes/lookup-then-view/agent/api/accounts.ts +68 -0
  41. package/recipes/lookup-then-view/agent/views/account.ts +183 -0
  42. package/recipes/lookup-then-view/agent/views/brand.ts +35 -0
  43. package/recipes/lookup-then-view/agent/views/format.ts +73 -0
  44. package/recipes/lookup-then-view/agent/views/pick-one.ts +240 -0
  45. package/recipes/lookup-then-view/fixtures/accounts.json +319 -0
  46. package/recipes/lookup-then-view/fixtures/overview.ok.json +122 -0
  47. package/recipes/lookup-then-view/fixtures/overview.unsigned.json +14 -0
  48. package/recipes/lookup-then-view/recipe.json +11 -0
  49. package/recipes/lookup-then-view/studio/find_account.json +10 -0
  50. package/recipes/lookup-then-view/studio/show_account_overview.json +15 -0
  51. package/recipes/opportunities-at-risk/README.md +5 -5
  52. package/recipes/opportunities-at-risk/agent/actions/show_opportunities_at_risk.ts +9 -7
  53. package/recipes/opportunities-at-risk/agent/opportunities.ts +11 -10
  54. package/recipes/opportunities-at-risk/agent/views/at-risk.ts +1 -1
  55. package/recipes/opportunities-at-risk/agent/views/brand.ts +26 -15
  56. package/recipes/pick-from-a-list/README.md +7 -6
  57. package/recipes/pick-from-a-list/agent/actions/choose_project.ts +27 -12
  58. package/recipes/pick-from-a-list/agent/views/brand.ts +30 -9
  59. package/recipes/pick-from-a-list/agent/views/picker.ts +3 -3
  60. package/recipes/prepare-for-a-meeting/README.md +4 -4
  61. package/recipes/prepare-for-a-meeting/agent/actions/prepare_for_meeting.ts +3 -3
  62. package/recipes/records-list/README.md +6 -6
  63. package/recipes/records-list/agent/accounts.ts +3 -1
  64. package/recipes/records-list/agent/actions/list_accounts.ts +7 -7
  65. package/recipes/records-list/agent/views/brand.ts +26 -14
  66. package/recipes/server-rendered-forms/README.md +35 -0
  67. package/recipes/server-rendered-forms/agent/actions/create_record_via_form.ts +62 -0
  68. package/recipes/server-rendered-forms/agent/api/pages.ts +57 -0
  69. package/recipes/server-rendered-forms/agent/forms.ts +192 -0
  70. package/recipes/server-rendered-forms/fixtures/form.errors.html +9 -0
  71. package/recipes/server-rendered-forms/fixtures/form.html +17 -0
  72. package/recipes/server-rendered-forms/fixtures/list.html +9 -0
  73. package/recipes/server-rendered-forms/fixtures/record.html +4 -0
  74. package/recipes/server-rendered-forms/recipe.json +10 -0
  75. package/recipes/several-hosts/README.md +10 -13
  76. package/recipes/several-hosts/agent/hosts.ts +19 -136
  77. package/recipes/several-hosts/fixtures/wrong-page.json +6 -0
  78. package/recipes/several-hosts/recipe.json +3 -3
  79. package/recipes/swap-the-login-method/README.md +16 -5
  80. package/recipes/swap-the-login-method/agent/utils.ts +52 -3
  81. package/recipes/swap-the-login-method/variants/utils.cookies.ts +59 -6
  82. package/recipes/update-meeting-notes/README.md +10 -4
  83. package/recipes/update-meeting-notes/agent/actions/update_meeting_notes.ts +16 -5
  84. package/recipes/upload-contacts/README.md +5 -5
  85. package/recipes/upload-contacts/agent/actions/upload_contacts.ts +7 -7
  86. package/recipes/upload-contacts/agent/views/brand.ts +24 -14
  87. package/recipes/upload-contacts/agent/views/uploader.ts +28 -17
  88. package/recipes/who-is-the-user/README.md +7 -3
  89. package/src/ask-core.mjs +3 -3
  90. package/src/attach-daemon.mjs +9 -2
  91. package/src/attach-helpers.mjs +105 -0
  92. package/src/attach-preflight.mjs +34 -11
  93. package/src/badge-core.mjs +1 -1
  94. package/src/cdp-client.mjs +14 -19
  95. package/src/cdp-ownership.mjs +41 -20
  96. package/src/cli-help.mjs +7 -5
  97. package/src/cli-registry.mjs +65 -46
  98. package/src/diagnostics.mjs +1 -1
  99. package/src/init.mjs +5 -3
  100. package/src/lint/index.mjs +10 -1
  101. package/src/lint/rules.mjs +371 -35
  102. package/src/observe-core.mjs +163 -34
  103. package/src/region.mjs +1 -1
  104. package/src/runtime/agent.ts +16 -12
  105. package/src/runtime/http.ts +25 -15
  106. package/src/runtime/match.ts +1 -1
  107. package/src/runtime/render.ts +4 -4
  108. package/src/transports/README.md +2 -2
  109. package/src/upgrade.mjs +33 -10
  110. package/templates/agent-starter/CLAUDE.md +8 -5
  111. package/templates/agent-starter/README.md +32 -33
  112. package/templates/agent-starter/agent/actions/_example.ts +4 -4
  113. package/templates/agent-starter/agent/actions/index.ts +2 -2
  114. package/templates/agent-starter/agent/constants.ts +4 -3
  115. package/templates/agent-starter/agent/utils.ts +15 -18
  116. package/templates/agent-starter/claude-settings.json +1 -1
  117. package/templates/agent-starter/docs/app-profile.md +19 -14
  118. package/templates/agent-starter/foldspace.dev.json +1 -1
  119. package/recipes/several-hosts/fixtures/nav-record.json +0 -5
@@ -24,63 +24,62 @@ npm run build
24
24
  agent/
25
25
  actions/ one file per action; register actions in index.ts
26
26
  api/ one file per HTTP helper
27
+ views/ cards; brand.ts comes from `npx foldspace observe styles --write`
27
28
  constants.ts product, agent, and domain identifiers
28
- utils.ts Foldspace agent lookup
29
- docs/ optional notes for coding-agent learnings (create when needed)
29
+ utils.ts configures the harness runtime; the only import surface for actions
30
+ docs/
31
+ app-profile.md what is known about this app, and how each fact was established
30
32
  foldspace.dev.json
31
33
  CLAUDE.md imports platform instructions from the installed harness
32
34
  ```
33
35
 
34
- ## Agent learnings
36
+ ## What is known about this app
35
37
 
36
- Coding agents may keep durable, repository-specific lessons under `docs/` so
37
- later sessions do not repeat the same mistakes. Create the folder when the first
38
- note is useful; do not commit an empty `docs/` directory.
39
-
40
- Good notes cover verified gotchas, failed approaches, design rationale, and
41
- useful verification commands. Keep them concise and evidence-based. Before
42
- similar work, read any relevant notes in `docs/`. Do not store secrets, cookies,
43
- HAR files, browser storage, or other transient session data there.
38
+ `docs/app-profile.md` holds every fact established about this app, each with
39
+ how it was established, and every workaround. Never store secrets, cookies, HAR
40
+ files or browser storage there.
44
41
 
45
42
  ## Commands
46
43
 
47
44
  ```bash
48
- npx foldspace help # discover commands, risks, and next steps
49
- npm run build # create dist/index.js
50
- npm run dev # rebuild dist/index.js on changes
51
- npm run inject # launch the dedicated Chrome profile
52
- npm run attach # load local actions and observe the agent over CDP
53
- npm run attach:daemon # same, but return after inspect_registration
45
+ npx foldspace help # commands, risks, and next steps
46
+ npm run build # create dist/index.js
47
+ npm run dev # rebuild dist/index.js on changes
48
+ npm run inject # open the test window; the human signs in there
49
+ npx foldspace observe wait-login # returns when they are signed in
50
+ npx foldspace attach --daemon # load this build into the test window
54
51
  ```
55
52
 
56
- Run `inject` before `attach`. Sign in to the product in the Chrome window that
57
- `inject` opens. To see how the product loads its data, use `npx foldspace
58
- observe` - it reads that same window, read-only, and works while `attach` runs.
59
- `inject` does not generate an application extension; `attach` loads
60
- `dist/index.js` directly through CDP. An empty local registry is valid,
61
- so you can attach before implementing handlers. Coding agents should use
62
- `attach --daemon` (or `npm run attach:daemon`) so the command returns after
53
+ Run `inject` before `attach`. `npx foldspace observe` reads the test window,
54
+ read-only, and works while `attach` runs. `attach` loads `dist/index.js` into
55
+ the page over CDP; an empty registry is valid, so attach before any handler
56
+ exists. Run it as `npx foldspace attach --daemon`: it returns after
63
57
  registration instead of occupying the terminal.
64
58
 
65
- Choose the attach mode from the state of the target page:
59
+ The attach mode is read off the page:
66
60
 
67
61
  - Default swap: the page already has the configured product and agent.
68
- - `npm run attach -- --bootstrap`: the page has no Foldspace SDK.
69
- - `npm run attach -- --replace`: the page has a different product or agent, or
70
- an SDK without the configured agent.
62
+ - `npx foldspace attach --bootstrap --daemon`: the page has no Foldspace SDK.
63
+ - `npx foldspace attach --replace --daemon`: the page has a different product
64
+ or agent, or an SDK without the configured agent.
71
65
 
72
66
  Run `npx foldspace help attach` for full requirements and safety options. Coding
73
67
  agents can read the versioned contract with `npx foldspace help --json`.
74
68
 
75
69
  ## Add an action
76
70
 
77
- 1. Observe the real product workflow in the inject Chrome and capture a real
78
- 200 for the API the action will call.
79
- 2. Create the action as a draft in Agent Studio or through Product MCP.
71
+ 1. Once the human has chosen the experience, observe what it needs in the
72
+ test window (`npx foldspace observe screen`, then `read`) and capture a
73
+ real 200 for the API the action will call.
74
+ 2. Create the action as a draft with the Foldspace tools (`create_action`).
80
75
  3. Copy `agent/actions/_example.ts` to `agent/actions/<action_key>.ts`.
81
76
  4. Implement the handler from that observed request and response.
82
77
  5. Import and register the handler in `agent/actions/index.ts`.
83
- 6. Type-check, rebuild, `attach --daemon`, and verify it through the visible
84
- agent. Publish only when the copilot must call the action.
78
+ 6. Type-check and rebuild; if attach is running, `npx foldspace attach --stop`,
79
+ then `npx foldspace attach --daemon` with the flags it ran with. Ask the human
80
+ before publishing - the agent cannot call the action until it is published.
81
+ On a yes, publish it and prove it with
82
+ `npx foldspace ask "<question>" --retry 2`; on a not yet, it stays a draft:
83
+ say so in one line.
85
84
 
86
85
  Action keys and parameter names must exactly match the action schema.
@@ -17,9 +17,9 @@ export const example_action = {
17
17
 
18
18
  const result = await apiFetch<{ items?: Item[] }>("/__observe_me");
19
19
  if (!result.ok) {
20
- // Pass `reason` through so `render` can call renderFailure — an expired
21
- // session, a 404 and a throttled call are three different widget states.
22
- return { ok: false, error: result.error, reason: result.reason };
20
+ // Pass `reason` through so `render` can call renderFailure - an expired
21
+ // session, a 404 and a throttled call are three different card states.
22
+ return { success: false, error: result.error, reason: result.reason };
23
23
  }
24
24
 
25
25
  const items = result.data.items ?? [];
@@ -30,6 +30,6 @@ export const example_action = {
30
30
 
31
31
  // Zero matches is an answer, not an error. Return the query so the empty
32
32
  // state can name what was searched for.
33
- return { ok: true, query, matches };
33
+ return { success: true, query, matches };
34
34
  },
35
35
  };
@@ -2,9 +2,9 @@
2
2
  //
3
3
  // Registration here is the switch: the SDK transmits these to the server, and
4
4
  // the server excludes any active action it does not receive. Comment an entry
5
- // out and the copilot can no longer see it — no unpublishing required.
5
+ // out and the agent can no longer see it - no unpublishing required.
6
6
  //
7
- // LOAD_MODE "injected" publishes the registry for attach to swap. "embedded"
7
+ // LOAD_MODE "injected" puts the registry on window for attach to swap. "embedded"
8
8
  // skips that assignment because the host page already loads Foldspace.
9
9
 
10
10
  import { LOAD_MODE } from "../constants";
@@ -13,8 +13,9 @@ export const API_BASE = "";
13
13
  * `{ kind: "localStorage", name: "access_token" }` or
14
14
  * `{ kind: "cookie", name: "session" }`.
15
15
  *
16
- * Custom headers that are not Bearer belong in a local `apiFetch` override —
17
- * see CLAUDE.md. Do not guess this value.
16
+ * Auth that is not Bearer (a custom header, or cookies with no token) needs
17
+ * its own `apiFetch` - see "Custom auth" in `agent/utils.ts`. Do not guess
18
+ * this value.
18
19
  */
19
20
  export const AUTH_SOURCE:
20
21
  | { kind: "localStorage" | "cookie"; name: string }
@@ -22,7 +23,7 @@ export const AUTH_SOURCE:
22
23
 
23
24
  /**
24
25
  * "injected" assigns `window.__FOLDSPACE_REMOTE_ACTIONS__` so attach can swap
25
- * the bundle. "embedded" does not — the page already publishes handlers
26
+ * the bundle. "embedded" does not - the page already registers handlers
26
27
  * through its own SDK snippet.
27
28
  */
28
29
  export const LOAD_MODE: "injected" | "embedded" = "injected";
@@ -11,8 +11,8 @@
11
11
  * ## What `export *` gives you
12
12
  *
13
13
  * HTTP (customer API, from the signed-in page):
14
- * - `apiFetch` — JSON + `Authorization: Bearer`. Stop re-exporting this if
15
- * the live app uses cookies, custom headers, or no Bearer (see below).
14
+ * - `apiFetch` - JSON + `Authorization: Bearer`. For any other sign-in, see
15
+ * "Custom auth" below.
16
16
  * - `apiFetchBinary` — same auth, raw bytes (file download).
17
17
  * - `publicFetch` — no token; marketing / public origin only.
18
18
  * - `getAuthToken` / `parseJwt` / `redact` — session read and safe logging.
@@ -21,11 +21,11 @@
21
21
  * Foldspace SDK (thin wrappers; the SDK is the source of truth):
22
22
  * - `getAgent` — overlay handle for `searchInList` and similar.
23
23
  * - `rankBy` — substring first, then SDK fuzzy match. Not for domain ranking.
24
- * - `armAllInstances` — **attach/bootstrap only**, never from `execute`.
25
- * Local `foldspace attach` already arms test mode. Importing this from an
26
- * action can put those SDK calls in the production `dist` bundle.
24
+ * - `armAllInstances` - never in agent code: attach handles the page, and
25
+ * test mode is `npx foldspace attach --test-mode`, only for an agent that
26
+ * already has real users. Anything in `agent/` ships to production.
27
27
  *
28
- * Widget chrome (unstyled; replace in `agent/views/` when brand matters):
28
+ * Card states (unstyled; replace in `agent/views/` when brand matters):
29
29
  * - `renderLoading` / `renderEmpty` / `renderError` / `renderFatal`
30
30
  * - `renderFailure` — picks one of those from `ApiFailure.reason`, so an expired
31
31
  * session, a 404 and a throttled call each draw the right state.
@@ -36,20 +36,17 @@
36
36
  * Leave `API_BASE` and `AUTH_SOURCE` empty in `constants.ts` until you capture
37
37
  * a real XHR. Empty config makes `apiFetch` return `{ ok: false }` instead of
38
38
  * guessing a host. Errors are returned, never thrown; show `result.error` in
39
- * the widget and log `result.detail` only.
39
+ * the card and log `result.detail` only.
40
40
  *
41
- * ## Override example (custom auth — do not guess Bearer)
41
+ * ## Custom auth (not Bearer)
42
42
  *
43
- * ```ts
44
- * import { configure, apiFetch as defaultApiFetch, ... } from "@foldspace_npm/harness/runtime";
45
- * // After configure(...):
46
- * export async function apiFetch<T>(path: string, init: RequestInit = {}) {
47
- * // observe the live request first, then copy its headers here
48
- * return defaultApiFetch<T>(path, { ...init, headers: { ... } });
49
- * }
50
- * export { getAgent, rankBy, renderEmpty, renderError, renderFatal, renderLoading };
51
- * // Do not `export *` if that would re-export the default apiFetch.
52
- * ```
43
+ * Read `node_modules/@foldspace_npm/harness/recipes/swap-the-login-method/README.md`
44
+ * and copy over this file the variant it names for what the app's requests
45
+ * carry: `agent/utils.ts` for a custom header, `variants/utils.cookies.ts` for
46
+ * cookies with no token. Each defines its own `apiFetch`, keeps the harness's
47
+ * failure `reason`s through `httpFailure` / `networkFailure`, and re-exports
48
+ * the other helpers by name - keep it that way: an `export *` would also bring
49
+ * in helpers such as `apiFetchBinary` that still use the default sign-in.
53
50
  */
54
51
 
55
52
  import { configure } from "@foldspace_npm/harness/runtime";
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "Written by foldspace init. The commands a build runs, allowed up front so the coding agent is not stopped one denial at a time mid-build. Named one by one on purpose: init, upgrade and deploy are not here and still ask. Everything else still asks.",
2
+ "$comment": "Written by foldspace init: the commands a build runs, allowed up front so the coding agent is not stopped one denial at a time. init and upgrade are left out on purpose, so they ask, like anything not listed. foldspace deploy is Foldspace-internal and never runs in a build.",
3
3
  "permissions": {
4
4
  "allow": [
5
5
  "Bash(npx foldspace help:*)",
@@ -3,9 +3,10 @@
3
3
  Durable facts about `{{APP_DOMAIN}}` and its API, established by observing the
4
4
  running app on **<date>** from a logged-in session.
5
5
 
6
- **Everything here should be observed.** Each entry says how. Where something is
7
- only believed, say so in the same breath — do not promote an assumption by
8
- quoting this file. Re-run the probe before trusting an entry that matters.
6
+ **Every entry says how it was established.** Where something is only
7
+ believed, mark it **assumed** in the same breath - do not promote an
8
+ assumption by quoting this file. Re-run the probe before trusting an entry
9
+ that matters.
9
10
 
10
11
  Do not store secrets, cookies, HAR files, tokens, or session data here. Record
11
12
  *where* the token lives and *what a redacted value looks like*, never the value.
@@ -13,9 +14,9 @@ Do not store secrets, cookies, HAR files, tokens, or session data here. Record
13
14
  How to re-run any probe below:
14
15
 
15
16
  ```bash
16
- npm run inject
17
- # Sign in, then read the running app with foldspace observe. It reads the
18
- # window you signed in to, and works while foldspace attach runs.
17
+ npm run inject # only if the test window is closed; the human signs in there
18
+ npx foldspace observe wait-login
19
+ # observe reads the test window, and works while attach runs.
19
20
  npx foldspace observe screen --click "<a label from observe menu>" --match "<words for the data>"
20
21
  npx foldspace observe read "<the GET path screen returned>"
21
22
  ```
@@ -27,9 +28,9 @@ Start URL: `{{START_URL}}`
27
28
  ## Auth
28
29
 
29
30
  - Where the token lives (cookie name, `localStorage` key, custom header), and
30
- how you know — wrapping XHR/`fetch` on the live page beats guessing.
31
- - Its lifetime, measured. (One app's expired in ~52 minutes and killed three
32
- probing sessions.)
31
+ how you know: `npx foldspace observe auth` reports where each header value
32
+ comes from, never the value.
33
+ - Its lifetime, measured: a short one ends probing sessions halfway.
33
34
  - What an expired or refused response looks like, verbatim. A CORS-less refusal
34
35
  often surfaces as `TypeError: Failed to fetch` with no status — that is not
35
36
  "host down".
@@ -43,8 +44,11 @@ Start URL: `{{START_URL}}`
43
44
 
44
45
  ## Account contents
45
46
 
46
- Row counts from the signed-in account **before** choosing the experience. On an
47
- empty account the only useful first action is one that creates data.
47
+ Row counts for the experience being built, checked when you observe it. Zero
48
+ rows makes a demo of an empty state: say so and offer the next experience. On
49
+ an account with nothing to read at all, say so and offer first a card that
50
+ creates a record when the end user approves
51
+ (`node_modules/@foldspace_npm/harness/recipes/create-a-record`).
48
52
 
49
53
  | Resource | Count | How established |
50
54
  |---|---|---|
@@ -93,9 +97,10 @@ Mark which you verified and which came from reading the app bundle.
93
97
 
94
98
  ## Brand (sampled, not guessed)
95
99
 
96
- Colour, typeface, greys, base size and radii from the running app's computed
97
- styles — not from a screenshot. Re-sample per tenant; do not carry values
98
- across.
100
+ `npx foldspace observe styles --write` samples the app's accent, text, border,
101
+ font and radius and writes `agent/views/brand.ts`. Record here the page and
102
+ date it sampled, and anything it fell back on. Re-sample per product; never
103
+ copy values across.
99
104
 
100
105
  ## Endpoints seen in the bundle but NOT called
101
106
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "Generated by foldspace init. Use the bare product ID, not the EU-… SDK key.",
2
+ "$comment": "Generated by foldspace init. Use the bare product ID, not the SDK key (the one that starts with EU-, in every region).",
3
3
  "defaultTarget": "app",
4
4
  "region": {{REGION_JSON}},
5
5
  "sdkUrl": {{SDK_URL_JSON}},
@@ -1,5 +0,0 @@
1
- {
2
- "payload": { "conversationId": "conv_123", "actionName": "navigate" },
3
- "normalizedUrl": "https://communicate.example-campus.net/applications/acme/test",
4
- "expiry": 9999999999999
5
- }