@widgex/wgx 0.0.0-stage → 0.4.2

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 (131) hide show
  1. package/LICENSE.txt +49 -0
  2. package/README.md +33 -2
  3. package/ThirdPartyNotices.txt +4685 -0
  4. package/package.json +30 -4
  5. package/provider-clis.json +1211 -0
  6. package/python/XACTOR.md +146 -0
  7. package/python/browser_actor_host.py +456 -0
  8. package/python/cdp_page.py +363 -0
  9. package/python/harness_check.py +77 -0
  10. package/python/requirements.txt +1 -0
  11. package/python/test_line_endings.py +185 -0
  12. package/python/xactor/__init__.py +18 -0
  13. package/python/xactor/__main__.py +226 -0
  14. package/python/xactor/api.py +882 -0
  15. package/python/xactor/bridge.py +784 -0
  16. package/python/xactor/dom.py +117 -0
  17. package/python/xactor/man/add_card.md +89 -0
  18. package/python/xactor/man/agent-vs-fork.md +108 -0
  19. package/python/xactor/man/agent.md +333 -0
  20. package/python/xactor/man/ask_model.md +110 -0
  21. package/python/xactor/man/ask_reference.md +56 -0
  22. package/python/xactor/man/ask_user.md +103 -0
  23. package/python/xactor/man/attach_app.md +595 -0
  24. package/python/xactor/man/attach_ide.md +65 -0
  25. package/python/xactor/man/attach_ide_surface.md +31 -0
  26. package/python/xactor/man/await_user.md +214 -0
  27. package/python/xactor/man/bash.md +142 -0
  28. package/python/xactor/man/browser-driver.md +137 -0
  29. package/python/xactor/man/browser_activity.md +53 -0
  30. package/python/xactor/man/browser_attach.md +84 -0
  31. package/python/xactor/man/browser_close_tab.md +50 -0
  32. package/python/xactor/man/browser_detach.md +50 -0
  33. package/python/xactor/man/browser_exec.md +66 -0
  34. package/python/xactor/man/browser_focus_tab.md +47 -0
  35. package/python/xactor/man/browser_hybrid.md +47 -0
  36. package/python/xactor/man/browser_open.md +50 -0
  37. package/python/xactor/man/browser_open_tab.md +58 -0
  38. package/python/xactor/man/browser_plumbing.md +107 -0
  39. package/python/xactor/man/browser_screenshot.md +56 -0
  40. package/python/xactor/man/browser_snapshot.md +52 -0
  41. package/python/xactor/man/browser_tabs.md +67 -0
  42. package/python/xactor/man/bulletin.md +88 -0
  43. package/python/xactor/man/capture_page.md +54 -0
  44. package/python/xactor/man/cd.md +64 -0
  45. package/python/xactor/man/chess_move.md +53 -0
  46. package/python/xactor/man/children.md +89 -0
  47. package/python/xactor/man/close_task.md +14 -0
  48. package/python/xactor/man/compact.md +75 -0
  49. package/python/xactor/man/drive.md +140 -0
  50. package/python/xactor/man/drive_space.md +72 -0
  51. package/python/xactor/man/driving.md +168 -0
  52. package/python/xactor/man/edit_file.md +112 -0
  53. package/python/xactor/man/finding.md +194 -0
  54. package/python/xactor/man/fork_agent.md +45 -0
  55. package/python/xactor/man/glob.md +83 -0
  56. package/python/xactor/man/grep.md +115 -0
  57. package/python/xactor/man/harness.md +165 -0
  58. package/python/xactor/man/harness_check.md +70 -0
  59. package/python/xactor/man/harness_format.md +257 -0
  60. package/python/xactor/man/highlight.md +66 -0
  61. package/python/xactor/man/instruct.md +111 -0
  62. package/python/xactor/man/ls.md +63 -0
  63. package/python/xactor/man/man.md +86 -0
  64. package/python/xactor/man/merge_wiz.md +40 -0
  65. package/python/xactor/man/notebook.md +98 -0
  66. package/python/xactor/man/overview.md +89 -0
  67. package/python/xactor/man/permissions.md +137 -0
  68. package/python/xactor/man/phase.md +150 -0
  69. package/python/xactor/man/powershell.md +74 -0
  70. package/python/xactor/man/profiles.md +60 -0
  71. package/python/xactor/man/project_drive.md +49 -0
  72. package/python/xactor/man/read_bulletin.md +89 -0
  73. package/python/xactor/man/read_file.md +90 -0
  74. package/python/xactor/man/relay.md +216 -0
  75. package/python/xactor/man/reply.md +72 -0
  76. package/python/xactor/man/reply_message.md +78 -0
  77. package/python/xactor/man/report_bug.md +118 -0
  78. package/python/xactor/man/request_answer.md +142 -0
  79. package/python/xactor/man/request_merge_decisions.md +121 -0
  80. package/python/xactor/man/request_operator_action.md +152 -0
  81. package/python/xactor/man/request_review.md +122 -0
  82. package/python/xactor/man/request_user_action.md +126 -0
  83. package/python/xactor/man/return_card.md +71 -0
  84. package/python/xactor/man/run.md +99 -0
  85. package/python/xactor/man/sandbox.md +144 -0
  86. package/python/xactor/man/session_fork.md +74 -0
  87. package/python/xactor/man/session_harness.md +109 -0
  88. package/python/xactor/man/session_join.md +102 -0
  89. package/python/xactor/man/session_land.md +73 -0
  90. package/python/xactor/man/session_nudge.md +73 -0
  91. package/python/xactor/man/session_rest.md +73 -0
  92. package/python/xactor/man/session_scope.md +84 -0
  93. package/python/xactor/man/session_terminate.md +58 -0
  94. package/python/xactor/man/session_token.md +78 -0
  95. package/python/xactor/man/sessions.md +93 -0
  96. package/python/xactor/man/set_harness.md +15 -0
  97. package/python/xactor/man/spawn_swarm.md +80 -0
  98. package/python/xactor/man/submit_evidence.md +54 -0
  99. package/python/xactor/man/swarm_brief.md +30 -0
  100. package/python/xactor/man/task_status.md +82 -0
  101. package/python/xactor/man/tasks.md +47 -0
  102. package/python/xactor/man/todo_write.md +104 -0
  103. package/python/xactor/man/update_card.md +70 -0
  104. package/python/xactor/man/use_profile.md +90 -0
  105. package/python/xactor/man/use_widget.md +82 -0
  106. package/python/xactor/man/wait_for_task.md +105 -0
  107. package/python/xactor/man/widget-model.md +284 -0
  108. package/python/xactor/man/widgets.md +59 -0
  109. package/python/xactor/man/write_file.md +76 -0
  110. package/python/xactor/man/writing-a-widget.md +142 -0
  111. package/python/xactor/man/xylon.md +120 -0
  112. package/python/xactor/man/xylon_blame.md +80 -0
  113. package/python/xactor/man/xylon_read.md +131 -0
  114. package/python/xactor/man/xylon_search.md +125 -0
  115. package/python/xactor/manual.py +195 -0
  116. package/python/xactor/parent_watch.py +115 -0
  117. package/python/xactor/plan_mode.py +350 -0
  118. package/python/xactor/sandbox.py +856 -0
  119. package/python/xactor/seals.py +438 -0
  120. package/python/xactor/shell_hints.py +210 -0
  121. package/python/xactor/states/__init__.py +13 -0
  122. package/python/xactor/states/base.py +523 -0
  123. package/python/xactor/states/registry.py +274 -0
  124. package/python/xactor/stdlib_api.py +6345 -0
  125. package/python/xactor/widgets/__init__.py +1 -0
  126. package/python/xactor/widgets/github_tokens.py +314 -0
  127. package/python/xactor/widgets/google_aistudio.py +365 -0
  128. package/settings-schema.json +403 -0
  129. package/viewer/index.css +160 -0
  130. package/viewer/index.js +15 -0
  131. package/wgx.mjs +290659 -0
@@ -0,0 +1,150 @@
1
+ # Phases — phase / open / add / abort / delegate / verify
2
+ <!-- also: phase_open, phase_add_step, phase_abort_step, delegate_steps, phase_complete, phases, steps -->
3
+ <!-- group: Sessions -->
4
+
5
+ A task's work is split into **phases**, worked in order: alpha, beta, gamma and
6
+ on. Only one is current. A phase is a contract:
7
+
8
+ - a **design document** — the feature spec the phase is verified against, kept
9
+ in the task's folder as `phases/<name>/plan.md`;
10
+ - the **files** that came with it (images, mockups), copied into
11
+ `phases/<name>/files/` — open them by path;
12
+ - a **lead**, the task's lead unless it says otherwise;
13
+ - a list of **steps**: one or two sentences each, numbered within the phase.
14
+ A step is never a spec. The document is.
15
+
16
+ The user watches all of it in the task's **Overview** tab, and the steps you
17
+ are on sit on a dial over your chat box.
18
+
19
+ ## Your todos are your steps
20
+
21
+ While a phase is open, the todo list you keep — TaskCreate/TaskUpdate,
22
+ TodoWrite, or [`todo_write`](todo_write.md) — **is** the list of steps assigned
23
+ to you:
24
+
25
+ | Todo | Step |
26
+ |---|---|
27
+ | a new item | a new step, assigned to you |
28
+ | `in_progress` / `completed` | started / marked off |
29
+ | deleted, or dropped from a `todo_write` | aborted |
30
+
31
+ Steps can be marked off in any order — finish step 4 before step 3 if that is
32
+ how the work lands. Moving a step that is not yours, or one that was aborted,
33
+ is refused, and nothing in that call is applied. A step a person
34
+ added from the Overview tab joins your list as soon as you write it there.
35
+
36
+ ## How a phase opens
37
+
38
+ If you lead the task, **an approved plan opens the phase**: the plan you wrote
39
+ in plan mode becomes the document. Write your steps as todos straight after.
40
+ Re-planning an open phase replaces its document and keeps its steps.
41
+
42
+ Outside plan mode:
43
+
44
+ ```python
45
+ await api.phase_open(
46
+ "Drafts autosave",
47
+ doc=open("docs/autosave.md").read(),
48
+ files=["design/autosave-states.png"], # copied into the task's folder
49
+ )
50
+ ```
51
+
52
+ ## Reading it
53
+
54
+ ```python
55
+ p = await api.phase()
56
+ p["phase"]["plan_path"] # read this — it is the spec
57
+ p["phase"]["files"] # absolute paths to the copies
58
+ p["phase"]["your_steps"] # [1, 2, 5]
59
+ p["phase"]["your_next_step"] # 2
60
+ ```
61
+
62
+ `phase` is `None` when the current phase has not been opened yet.
63
+
64
+ ## Steps without a todo list
65
+
66
+ ```python
67
+ await api.phase_add_step("Show a saved indicator under the title")
68
+ await api.phase_abort_step(6, reason="covered by step 4")
69
+ ```
70
+
71
+ Only a step's assignee or the phase lead can abort it.
72
+
73
+ ## Delegating (lead only)
74
+
75
+ ```python
76
+ await api.delegate_steps([3, 4]) # a new agent in this task
77
+ await api.delegate_steps([5], to="drafts/session2") # one already here
78
+ await api.delegate_steps([5], to="drafts/session1") # naming yourself takes it back
79
+ ```
80
+
81
+ The delegate is sent the phase document, the paths of its files, and its steps,
82
+ and works them as its own todos. On your side it is a **background task**: keep
83
+ working, and you are woken when its last step is done — or if it fails or is
84
+ closed with steps still open, in which case they stay assigned to it for you to
85
+ hand on.
86
+
87
+ ## Finishing a phase (lead only)
88
+
89
+ Two calls, for two different questions. Both need every step done or aborted.
90
+
91
+ ### "I have finished it" — `phase_complete`
92
+
93
+ When you are done implementing and want a person to review the phase:
94
+
95
+ ```python
96
+ await api.phase_complete(
97
+ "Drafts now save on their own and come back after a reload.",
98
+ checks=["Type in a new draft, wait a second, reload: the text is still there"],
99
+ )
100
+ return "Phase beta is up for review." # end the turn here
101
+ ```
102
+
103
+ First it puts a **review gate** in your chat: should an adversarial review run
104
+ before the phase is approved? **Allow** or **Skip**, and it is the person's
105
+ call, not yours — do not run a review of your own before calling this.
106
+
107
+ - **Skip** puts the approval card up at once and does not wake you.
108
+ - **Allow**, or words typed instead (what the review should look at), opens
109
+ the phase again and wakes you with a brief: spawn 2–3 read-only reviewers
110
+ told to find where the phase's claims do not hold, add what must be fixed
111
+ as steps, fix it, and call `phase_complete` again. That second call goes
112
+ straight to the approval card; say in its summary what the review found.
113
+
114
+ The approval card is a **question card in your own chat** — the phase, your
115
+ summary, the checks, and two answers: **Approve** or **Needs changes**. After
116
+ Needs changes, the next `phase_complete` asks the gate again. Nothing to attach
117
+ first: the person reading your conversation is who reviews it. They can also
118
+ type instead of choosing, which counts as changes, with what they typed as the
119
+ notes.
120
+
121
+ It **does not wait**, and the card does not expire. It is stored like an inbox
122
+ row: it stays in your chat until somebody answers, comes back after a reload,
123
+ and can be answered from the inbox too. So end your turn straight afterwards —
124
+ it returns `{"status": "queued", "id", ...}` — and the answer wakes you with
125
+ what was decided:
126
+
127
+ - **Approve** closes the phase and moves the task to the next letter.
128
+ - **Needs changes**, or words typed instead, opens it again with what they
129
+ wrote. Add steps for what needs doing and call it once more when they are
130
+ done.
131
+
132
+ More work while a card is up — a step added, or a todo taken up — opens the
133
+ phase again by itself and withdraws the gate or approval card: neither
134
+ describes the phase any more. Call `phase_complete` again when that work is
135
+ done, and the person is asked about all of it.
136
+
137
+ When the phase changed something visible, attach the app and drive it to where
138
+ the checks can be tried *before* calling this, so the reviewer has it in front
139
+ of them. The review card is the one way a phase is closed: there is no separate
140
+ in-app "does this work?" card.
141
+
142
+ ## Returns
143
+
144
+ ```python
145
+ api.phase() -> {"task", "you", "current", "phase": {...} | None, "done": [...]}
146
+ api.phase_open(...) -> the phase, as api.phase() describes it
147
+ api.phase_add_step -> {"n", "phase", "assignee", "reopened"?: True, "note"?}
148
+ api.delegate_steps -> {"delegation", "steps", "to", "spawned", "background": True}
149
+ api.phase_complete -> {"status": "queued", "id", "phase", "note", "gate"?: True}
150
+ ```
@@ -0,0 +1,74 @@
1
+ # api.powershell(script, timeout=120, background=False) -> dict
2
+ <!-- group: Coding -->
3
+ <!-- also: pwsh, ps1, Get-CimInstance, Start-Sleep, Stop-Process, windows -->
4
+
5
+ Run a PowerShell script exactly as written. **Gated** (it asks as `bash`).
6
+ **Never raises — read `exit_code`.**
7
+
8
+ ## Why this exists
9
+
10
+ Through `api.bash`, PowerShell sat inside bash double quotes, and bash spoke
11
+ first. `$_` is bash's own variable — the last argument of the previous command —
12
+ so `ForEach-Object { $_.Name }` reached PowerShell as
13
+ `ForEach-Object { /usr/bin/bash.Name }`. `$p`, `$s` and `$PSItem` became
14
+ nothing at all. And `powershell -Command` exits **0** after most errors, so a
15
+ `Stop-Process` that stopped nothing came back looking like success, and the
16
+ next step ran against the process it had meant to kill.
17
+
18
+ `powershell` never puts the script on a command line, so nothing interprets it
19
+ but PowerShell; and any error ends the script with exit code 1 and the error on
20
+ stderr.
21
+
22
+ ## Examples
23
+
24
+ ```python
25
+ r = await api.powershell(r'''
26
+ Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
27
+ Where-Object { $_.CommandLine -like '*vite*' } |
28
+ ForEach-Object { Stop-Process -Id $_.ProcessId -Force; $_.ProcessId }
29
+ ''')
30
+ if r["exit_code"] != 0:
31
+ return r["stderr"]
32
+ ```
33
+
34
+ Use a raw string, `r'''…'''`: a Windows path or a regex in a normal string has
35
+ its backslashes consumed by your own Python before PowerShell gets it.
36
+
37
+ ## What it sets up
38
+
39
+ Before your script, on one line:
40
+
41
+ - every cmdlet defaulting to `-ErrorAction Stop`, and a `trap` that writes the
42
+ error to stderr and exits 1. A failing cmdlet stops the script rather than
43
+ printing and carrying on. Native programs are not affected: `git fetch 2>&1`
44
+ printing progress to stderr does not stop anything.
45
+ - UTF-8 output, so non-ASCII text is not mangled by the console code page.
46
+
47
+ Because that is one line, a position PowerShell reports — `ps1:4 char:9` — is
48
+ your script's line plus one.
49
+
50
+ If the script's **last** statement is a program that fails (`git`, `cmd /c`),
51
+ `exit_code` is that program's code. A program failing earlier does not raise in
52
+ PowerShell and does not stop the script; check `$LASTEXITCODE` after it, or
53
+ call it with `api.run` instead.
54
+
55
+ The script is written to a temporary `.ps1` exactly as given, line endings
56
+ included, and deleted as soon as the process ends — background runs too.
57
+
58
+ ## Returns
59
+
60
+ ```python
61
+ {"stdout": str, "stderr": str, "exit_code": int, "program": str}
62
+ ```
63
+
64
+ Windows PowerShell 5.1 on Windows; `pwsh` elsewhere, if installed. A missing
65
+ PowerShell is `exit_code` 127.
66
+
67
+ ## Waiting
68
+
69
+ To wait, do not shell out to `Start-Sleep`: `await asyncio.sleep(seconds)` does
70
+ it in place.
71
+
72
+ ## See also
73
+
74
+ `run`, `bash`, `sandbox`
@@ -0,0 +1,60 @@
1
+ # api.profiles() -> dict
2
+ <!-- group: Working on a website -->
3
+
4
+ The browser identities on this machine, and which one you are using.
5
+
6
+ Ungated: listing opens nothing, starts nothing and acts as nobody.
7
+ `api.use_profile` is the one that has consequences.
8
+
9
+ ## Returns
10
+
11
+ ```python
12
+ {
13
+ "profiles": [{"id": str, "name": str, "active": bool, "running": bool}],
14
+ "current": str | None,
15
+ }
16
+ ```
17
+
18
+ - **`active`** — the profile the *picker* is set to. A per-window setting a
19
+ human chose.
20
+ - **`current`** — the profile *this session* is browsing as, and therefore what
21
+ your next `drive` will use.
22
+ - **`running`** — whether that profile's browser is up. `False` is ordinary;
23
+ `use_profile` starts it.
24
+
25
+ **`active` and `current` are allowed to differ, and often should.** Two sessions
26
+ can hold two identities at once, so there is no single answer to "which profile
27
+ is WidgeX using" — only "which one is *this* session using", which is `current`.
28
+ If you want to know who you are about to act as, read `current`, never `active`.
29
+
30
+ ## Examples
31
+
32
+ ```python
33
+ state = await api.profiles()
34
+ if state["current"] is None:
35
+ await api.use_profile("personal")
36
+ ```
37
+
38
+ Picking the one that is already warm, when either would do:
39
+
40
+ ```python
41
+ state = await api.profiles()
42
+ warm = next((p for p in state["profiles"] if p["running"]), None)
43
+ if warm:
44
+ await api.use_profile(warm["name"])
45
+ ```
46
+
47
+ ## Getting it wrong
48
+
49
+ **Reading `active` to decide what you are driving.** That is the human's picker
50
+ setting, not your session's identity. `current` is yours.
51
+
52
+ **Treating an empty list as broken.** A machine with no profiles yet is a normal
53
+ first run. One is created from the browser widget.
54
+
55
+ **Calling it in a loop to wait for `running`.** `use_profile` does not return
56
+ until the browser is up, so there is nothing to poll for.
57
+
58
+ ## See also
59
+
60
+ `use_profile`, `drive`, `request_operator_action`
@@ -0,0 +1,49 @@
1
+ # api.project_drive / project_drive_suggest — the project's WidgeX Drive
2
+ <!-- also: project_drive_suggest, widgex_drive, team_drive, documents, team_documents -->
3
+ <!-- group: Coding -->
4
+
5
+ Every project has a **Drive**: the team's documents, kept like code in git.
6
+ Every approved version of every document is in it, and teammates get it
7
+ through the project's own sync. A Google Drive or OneDrive folder may be linked
8
+ as a remote, but the Drive works without one.
9
+
10
+ Not the same thing as a drive space (`man('drive_space')`), which opens a
11
+ drive folder *as the workspace*. The project Drive sits beside any workspace.
12
+
13
+ ## Where the documents are
14
+
15
+ `api.project_drive()` returns `folder`: a real folder on this machine holding
16
+ a copy of every document. It is the same copy the person edits — they open
17
+ Word, PowerPoint and Excel files in Google's editor inside WidgeX, and what
18
+ they write there comes back into these files. Read and edit the files there
19
+ with your ordinary file tools; there is no special read or write call.
20
+
21
+ ```python
22
+ d = await api.project_drive()
23
+ d['folder'] # '/Users/…/.widgex/project-drives/widgex-1a2b3c4d/files'
24
+ d['files'] # what is in the Drive
25
+ d['changed'] # what differs in the copy: added, modified or deleted
26
+ d['changes'] # changes open or sent back, with their comments
27
+ ```
28
+
29
+ `drive_moved_on` on a changed file means a teammate's change to it landed
30
+ after the copy was taken. Suggesting yours would replace theirs; say so in the
31
+ title, or ask the person first.
32
+
33
+ ## Getting an edit into the Drive
34
+
35
+ Nothing you write reaches the Drive by itself. When the documents are ready:
36
+
37
+ ```python
38
+ await api.project_drive_suggest(['Pitch deck.pptx'], 'Tighten slides 3–5')
39
+ ```
40
+
41
+ That makes a **change**. It appears on the Drive page, where a person looks
42
+ at each document before and after, then approves it or sends it back with a
43
+ note. Approved, it lands for everyone and, if a remote is linked, is pushed
44
+ there in place. Sent back, the note is in the change's `comments`; edit the
45
+ files again, and the person presses Suggest again.
46
+
47
+ Only files listed under `changed` can be suggested. A `.pptx`, `.docx` or
48
+ `.xlsx` you edit should stay in that format — a Google document in a linked
49
+ folder is written back from it.
@@ -0,0 +1,89 @@
1
+ # api.read_bulletin() -> str
2
+ <!-- group: Talking to other agents -->
3
+
4
+ Read what every other agent in this codebase is working on. Not gated.
5
+
6
+ The bulletin is a board this machine keeps for this repository. Every agent
7
+ that has published a task list is on it, and each agent's list is exactly what
8
+ it last sent to `todo_write` — or to the native `TodoWrite` tool, which posts to
9
+ the same place.
10
+
11
+ It is a **hint, not a lock**. Nothing on it reserves a file or blocks a write.
12
+ It exists so you can notice a collision before it happens rather than after.
13
+
14
+ ## Returns
15
+
16
+ A string. One block per agent:
17
+
18
+ ```
19
+ LOCAL BULLETIN — 2 other agents have an active task list in this codebase.
20
+ Positions are 1-based and are what api.wait_for_task(node, n) refers to.
21
+
22
+ AGENT N1 ACTIVE WORK:
23
+ 1. [x] Read XylonMountConfig for the file-watch shape
24
+ 2. [~] Wire the bulletin store into setSessionTodos
25
+ 3. [ ] Add api.read_bulletin and api.wait_for_task
26
+
27
+ AGENT N2/E1 ACTIVE WORK: (fork, running in a clone)
28
+ 1. [~] Bisect the crash
29
+ ```
30
+
31
+ `[x]` is completed, `[~]` is in progress, `[ ]` is pending. The number is the
32
+ `n` for `wait_for_task`.
33
+
34
+ An empty board comes back as a **single sentence** saying so, never as an empty
35
+ string — a call that returns nothing reads as a broken call.
36
+
37
+ The header line for your own entry is annotated `(this is you)`. Other
38
+ annotations you may see: `(harness)`, `(fork, running in a clone)`, and
39
+ `(last seen 4m ago)` for an agent that has not touched its list in a while.
40
+
41
+ ## How agents get on and off it
42
+
43
+ You go **on** the board the first time you call `todo_write` with anything
44
+ unfinished. You come **off** it when every task on your list is `completed`, or
45
+ when your session ends.
46
+
47
+ That means an agent with nothing on the board is not necessarily idle — it may
48
+ simply never have published a list. Absence is not evidence.
49
+
50
+ ## Examples
51
+
52
+ Before starting something that sounds like it might already be underway:
53
+
54
+ ```python
55
+ board = await api.read_bulletin()
56
+ if "provider catalog" in board:
57
+ return f"Someone is already on this:\n\n{board}"
58
+ ```
59
+
60
+ After noticing a file changed under you:
61
+
62
+ ```python
63
+ return await api.read_bulletin()
64
+ ```
65
+
66
+ ## Getting it wrong
67
+
68
+ **Treating it as a lock.** Nothing stops two agents editing one file, and
69
+ nobody is waiting on you. Reading the board tells you a collision is likely; it
70
+ does not prevent one, and there is no way to claim anything.
71
+
72
+ **Polling it in a loop.** If you actually need to wait for someone, that is
73
+ `wait_for_task`, which blocks properly. A loop around `read_bulletin` burns
74
+ turns and finds out no sooner.
75
+
76
+ **Assuming your own list is on it.** You appear once you have called
77
+ `todo_write`, not before. If you have not published a list, other agents cannot
78
+ see what you are doing either — which is the more important half.
79
+
80
+ **Reading an absent agent as an idle one.** A session that never published a
81
+ list never appears, however busy it is.
82
+
83
+ **Leaving finished work marked `in_progress`.** You only leave the board when
84
+ every task is complete. Until then everyone else is reading a list that says
85
+ you are still inside work you finished an hour ago.
86
+
87
+ ## See also
88
+
89
+ `wait_for_task`, `todo_write`, `bulletin`, `relay`, `sessions`
@@ -0,0 +1,90 @@
1
+ # api.read_file(path, offset=0, limit=None, raw=False) -> str
2
+ <!-- group: Coding -->
3
+
4
+ Read a file. Returns text with line numbers prepended — or, with `raw=True`,
5
+ exactly what is on disk. Not gated.
6
+
7
+ ## Options
8
+
9
+ - **`path`** — absolute, or relative to the shell's cwd. `/c/Users/...`, `/tmp`
10
+ and `~` mean what they mean in `api.bash`.
11
+ - **`offset`** — first line to return, **0-based**. `offset=0` starts at the
12
+ top; `offset=100` skips the first 100 lines.
13
+ - **`limit`** — how many lines to return. `None` (default) means the rest of
14
+ the file.
15
+ - **`raw`** — return the text itself: no numbers, trailing whitespace kept,
16
+ `\r\n` kept. This is the form to parse (`json.loads`), to search with `re`,
17
+ or to change and hand back to `write_file`. It is what `cat` through
18
+ `api.bash` was being used for.
19
+
20
+ ```python
21
+ import json
22
+ pkg = json.loads(await api.read_file("package.json", raw=True))
23
+ ```
24
+
25
+ Decoded as UTF-8 with `errors="replace"`, so a binary or mis-encoded file comes
26
+ back full of `\ufffd` rather than raising.
27
+
28
+ ## Returns
29
+
30
+ One string. Each line is formatted as:
31
+
32
+ ```
33
+ {line_number:>6}\t{line_content}
34
+ ```
35
+
36
+ — the number right-aligned in six columns, then a **tab**, then the line with
37
+ trailing whitespace stripped. Numbering is 1-based and accounts for `offset`,
38
+ so `offset=100` starts the display at 101.
39
+
40
+ An empty file, or an `offset` past the end, returns `""`.
41
+
42
+ ## Examples
43
+
44
+ ```python
45
+ return await api.read_file("src/index.ts")
46
+ ```
47
+
48
+ A window in the middle of a large file:
49
+
50
+ ```python
51
+ return await api.read_file("src/index.ts", offset=400, limit=80)
52
+ ```
53
+
54
+ Getting the file's *actual* text, unnumbered, to build an `edit_file` anchor:
55
+
56
+ ```python
57
+ raw = (await api.bash("sed -n '40,60p' src/index.ts"))["stdout"]
58
+ return raw
59
+ ```
60
+
61
+ ## Getting it wrong
62
+
63
+ **Feeding the output back into `edit_file`.** This is the most common mistake
64
+ with this call.
65
+
66
+ ```python
67
+ chunk = await api.read_file("a.py", offset=10, limit=3)
68
+ # chunk is " 11\tdef go():\n 12\t return 1"
69
+ # Wrong — that string does not occur anywhere in a.py.
70
+ await api.edit_file("a.py", chunk, "...")
71
+ ```
72
+
73
+ The numbers and tabs are display formatting. So is the stripped trailing
74
+ whitespace. Use `read_file` to *locate* an edit and to understand the code;
75
+ write `old_string` as the file actually contains it, or pull raw text with
76
+ `api.bash("cat ...")` / `sed -n`.
77
+
78
+ **Off-by-one on `offset`.** `offset` is 0-based but the displayed numbers are
79
+ 1-based. To start at the line displayed as 250, pass `offset=249`.
80
+
81
+ **Reading a whole large file to find one thing.** `api.grep` gives you file,
82
+ line and text without spending the context. Grep first, then read the region
83
+ around the hit.
84
+
85
+ **Using it to test existence.** A missing file raises `FileNotFoundError`,
86
+ which kills the exec. `await api.glob(path)` returns `[]` instead.
87
+
88
+ ## See also
89
+
90
+ `edit_file`, `write_file`, `grep`, `glob`, `bash`