team 0.0.1 → 0.1.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.
Files changed (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +310 -0
  3. package/dist/approve/approval.d.ts +38 -0
  4. package/dist/approve/approval.js +78 -0
  5. package/dist/approve/diff.d.ts +13 -0
  6. package/dist/approve/diff.js +47 -0
  7. package/dist/approve/fingerprint.d.ts +44 -0
  8. package/dist/approve/fingerprint.js +109 -0
  9. package/dist/args.d.ts +7 -0
  10. package/dist/args.js +22 -0
  11. package/dist/budgets/checks.d.ts +35 -0
  12. package/dist/budgets/checks.js +77 -0
  13. package/dist/budgets/gate.d.ts +20 -0
  14. package/dist/budgets/gate.js +96 -0
  15. package/dist/budgets/readings.d.ts +47 -0
  16. package/dist/budgets/readings.js +119 -0
  17. package/dist/budgets/run.d.ts +66 -0
  18. package/dist/budgets/run.js +119 -0
  19. package/dist/budgets/table.d.ts +24 -0
  20. package/dist/budgets/table.js +90 -0
  21. package/dist/caller.d.ts +48 -0
  22. package/dist/caller.js +126 -0
  23. package/dist/check/config.d.ts +51 -0
  24. package/dist/check/config.js +50 -0
  25. package/dist/check/git.d.ts +27 -0
  26. package/dist/check/git.js +97 -0
  27. package/dist/check/message.d.ts +27 -0
  28. package/dist/check/message.js +101 -0
  29. package/dist/check/run.d.ts +31 -0
  30. package/dist/check/run.js +77 -0
  31. package/dist/check/signature.d.ts +10 -0
  32. package/dist/check/signature.js +22 -0
  33. package/dist/cli.d.ts +9 -0
  34. package/dist/cli.js +76 -0
  35. package/dist/clis.d.ts +7 -0
  36. package/dist/clis.js +9 -0
  37. package/dist/commands/add.d.ts +23 -0
  38. package/dist/commands/add.js +445 -0
  39. package/dist/commands/approve.d.ts +11 -0
  40. package/dist/commands/approve.js +146 -0
  41. package/dist/commands/check.d.ts +17 -0
  42. package/dist/commands/check.js +103 -0
  43. package/dist/commands/doctor.d.ts +38 -0
  44. package/dist/commands/doctor.js +232 -0
  45. package/dist/commands/down.d.ts +37 -0
  46. package/dist/commands/down.js +258 -0
  47. package/dist/commands/init.d.ts +9 -0
  48. package/dist/commands/init.js +138 -0
  49. package/dist/commands/remove.d.ts +23 -0
  50. package/dist/commands/remove.js +235 -0
  51. package/dist/commands/status.d.ts +41 -0
  52. package/dist/commands/status.js +147 -0
  53. package/dist/commands/up.d.ts +49 -0
  54. package/dist/commands/up.js +361 -0
  55. package/dist/commands/watch.d.ts +35 -0
  56. package/dist/commands/watch.js +302 -0
  57. package/dist/commands/worktree.d.ts +12 -0
  58. package/dist/commands/worktree.js +388 -0
  59. package/dist/end/condition.d.ts +28 -0
  60. package/dist/end/condition.js +77 -0
  61. package/dist/file/current.d.ts +13 -0
  62. package/dist/file/current.js +53 -0
  63. package/dist/file/lines.d.ts +25 -0
  64. package/dist/file/lines.js +277 -0
  65. package/dist/file/load.d.ts +7 -0
  66. package/dist/file/load.js +90 -0
  67. package/dist/file/paths.d.ts +6 -0
  68. package/dist/file/paths.js +77 -0
  69. package/dist/file/secrets.d.ts +6 -0
  70. package/dist/file/secrets.js +54 -0
  71. package/dist/file/signature.d.ts +13 -0
  72. package/dist/file/signature.js +36 -0
  73. package/dist/file/types.d.ts +130 -0
  74. package/dist/file/types.js +1 -0
  75. package/dist/file/validate.d.ts +13 -0
  76. package/dist/file/validate.js +702 -0
  77. package/dist/file/write.d.ts +11 -0
  78. package/dist/file/write.js +13 -0
  79. package/dist/herdr.d.ts +38 -0
  80. package/dist/herdr.js +227 -0
  81. package/dist/index.d.ts +10 -0
  82. package/dist/index.js +6 -0
  83. package/dist/io.d.ts +10 -0
  84. package/dist/io.js +1 -0
  85. package/dist/launch/deliver.d.ts +10 -0
  86. package/dist/launch/deliver.js +39 -0
  87. package/dist/launch/execute.d.ts +46 -0
  88. package/dist/launch/execute.js +284 -0
  89. package/dist/launch/plan.d.ts +185 -0
  90. package/dist/launch/plan.js +280 -0
  91. package/dist/launch/rules.d.ts +25 -0
  92. package/dist/launch/rules.js +36 -0
  93. package/dist/log.d.ts +1 -0
  94. package/dist/log.js +24 -0
  95. package/dist/profiles/antigravity.yaml +66 -0
  96. package/dist/profiles/claude-code.yaml +69 -0
  97. package/dist/profiles/codex.yaml +77 -0
  98. package/dist/profiles/cursor.yaml +68 -0
  99. package/dist/profiles/index.d.ts +1 -0
  100. package/dist/profiles/index.js +1 -0
  101. package/dist/profiles/profile.d.ts +60 -0
  102. package/dist/profiles/profile.js +313 -0
  103. package/dist/profiles/quota.d.ts +22 -0
  104. package/dist/profiles/quota.js +56 -0
  105. package/dist/state.d.ts +59 -0
  106. package/dist/state.js +127 -0
  107. package/dist/status/compare.d.ts +26 -0
  108. package/dist/status/compare.js +110 -0
  109. package/dist/status/statusline.d.ts +9 -0
  110. package/dist/status/statusline.js +26 -0
  111. package/dist/store/store.d.ts +62 -0
  112. package/dist/store/store.js +116 -0
  113. package/dist/watch/check.d.ts +82 -0
  114. package/dist/watch/check.js +29 -0
  115. package/dist/watch/checks/approval.d.ts +2 -0
  116. package/dist/watch/checks/approval.js +16 -0
  117. package/dist/watch/checks/attention.d.ts +2 -0
  118. package/dist/watch/checks/attention.js +23 -0
  119. package/dist/watch/checks/budget.d.ts +2 -0
  120. package/dist/watch/checks/budget.js +141 -0
  121. package/dist/watch/checks/disk.d.ts +2 -0
  122. package/dist/watch/checks/disk.js +13 -0
  123. package/dist/watch/checks/extra.d.ts +2 -0
  124. package/dist/watch/checks/extra.js +18 -0
  125. package/dist/watch/checks/idle.d.ts +2 -0
  126. package/dist/watch/checks/idle.js +32 -0
  127. package/dist/watch/checks/load.d.ts +2 -0
  128. package/dist/watch/checks/load.js +11 -0
  129. package/dist/watch/checks/memory.d.ts +2 -0
  130. package/dist/watch/checks/memory.js +11 -0
  131. package/dist/watch/checks/missing.d.ts +2 -0
  132. package/dist/watch/checks/missing.js +12 -0
  133. package/dist/watch/checks/model-drift.d.ts +2 -0
  134. package/dist/watch/checks/model-drift.js +14 -0
  135. package/dist/watch/checks/swap-free.d.ts +2 -0
  136. package/dist/watch/checks/swap-free.js +12 -0
  137. package/dist/watch/checks/swap-growth.d.ts +2 -0
  138. package/dist/watch/checks/swap-growth.js +18 -0
  139. package/dist/watch/checks/team-idle.d.ts +2 -0
  140. package/dist/watch/checks/team-idle.js +18 -0
  141. package/dist/watch/checks/unsent.d.ts +2 -0
  142. package/dist/watch/checks/unsent.js +18 -0
  143. package/dist/watch/close.d.ts +19 -0
  144. package/dist/watch/close.js +40 -0
  145. package/dist/watch/dialect.d.ts +5 -0
  146. package/dist/watch/dialect.js +430 -0
  147. package/dist/watch/end.d.ts +11 -0
  148. package/dist/watch/end.js +17 -0
  149. package/dist/watch/machine.d.ts +38 -0
  150. package/dist/watch/machine.js +137 -0
  151. package/dist/watch/notify.d.ts +1 -0
  152. package/dist/watch/notify.js +15 -0
  153. package/dist/watch/pass.d.ts +30 -0
  154. package/dist/watch/pass.js +237 -0
  155. package/dist/watch/screen-core.d.ts +14 -0
  156. package/dist/watch/screen-core.js +320 -0
  157. package/dist/watch/screen-data.d.ts +65 -0
  158. package/dist/watch/screen-data.js +1 -0
  159. package/dist/watch/screen-file.d.ts +2 -0
  160. package/dist/watch/screen-file.js +269 -0
  161. package/dist/watch/screen.d.ts +20 -0
  162. package/dist/watch/screen.js +38 -0
  163. package/dist/worktree/place.d.ts +18 -0
  164. package/dist/worktree/place.js +126 -0
  165. package/dist/yaml.d.ts +30 -0
  166. package/dist/yaml.js +380 -0
  167. package/examples/checks/codex-quota +217 -0
  168. package/examples/team.yaml +76 -0
  169. package/package.json +29 -10
@@ -0,0 +1,280 @@
1
+ import { join } from 'node:path';
2
+ import { profileFor } from "../profiles/index.js";
3
+ import { launchCommand, shellQuote } from "../profiles/profile.js";
4
+ /** The variables a herdr server starts with: nothing else reaches a seat's pane. */
5
+ export const SERVER_ENVIRONMENT = ['HOME', 'USER', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'LANG'];
6
+ /** `herdr` aimed at a session; herdr's default session takes no option. */
7
+ export function herdr(session, ...args) {
8
+ return session === 'default' ? ['herdr', ...args] : ['herdr', '--session', session, ...args];
9
+ }
10
+ /** What stands for a pane id that only exists once its workspace is created. */
11
+ export function paneOf(label) {
12
+ return `<pane of ${label}>`;
13
+ }
14
+ /** Every step of `team up`, in order. Nothing here runs anything. */
15
+ export function upPlan(input) {
16
+ const { session } = input;
17
+ const steps = [];
18
+ if (!input.sessionRunning) {
19
+ steps.push({
20
+ kind: 'run',
21
+ argv: ['env', '-i', ...SERVER_ENVIRONMENT.map((name) => `${name}=$${name}`), ...herdr(session, 'server')],
22
+ note: 'detached, in a clean environment; SSH_AUTH_SOCK is passed too when it is set',
23
+ do: { do: 'server', session },
24
+ });
25
+ steps.push({
26
+ kind: 'wait',
27
+ text: `until session ${session} is running (30 s at most)`,
28
+ do: { do: 'wait-session', session, seconds: 30 },
29
+ });
30
+ }
31
+ const lobbies = new Set();
32
+ for (const seat of input.seats) {
33
+ if (seat.stopped) {
34
+ steps.push({ kind: 'skip', text: `${seat.name}: stopped in the file; start it with \`team add ${seat.name}\`` });
35
+ continue;
36
+ }
37
+ const profile = profileFor(seat.cli);
38
+ if (!profile) {
39
+ steps.push({
40
+ kind: 'skip',
41
+ text: `${seat.name}: no launch profile for \`${seat.cli}\` in this version; left out`,
42
+ });
43
+ continue;
44
+ }
45
+ if (seat.stage === 'ready') {
46
+ steps.push({ kind: 'skip', text: `${seat.name}: already ready; left as it is` });
47
+ continue;
48
+ }
49
+ const pane = seat.pane ?? paneOf(seat.label);
50
+ const cwd = join(input.root, seat.cwd);
51
+ const fresh = seat.stage === undefined || !seat.pane;
52
+ // The same condition as the launch step below. A seat already running keeps
53
+ // its idle wait, rename and rules, and hears the reading as a notice.
54
+ const wouldLaunch = fresh || (seat.stage === 'launched' && !seat.agentLive);
55
+ if (seat.budget?.kind === 'refuse' && wouldLaunch) {
56
+ steps.push({
57
+ kind: 'skip',
58
+ text: `${seat.name}: would refuse: ${seat.budget.why}`,
59
+ do: { do: 'refuse', seat: seat.name, why: seat.budget.why },
60
+ });
61
+ continue;
62
+ }
63
+ let notice;
64
+ if (seat.budget?.kind === 'unknown')
65
+ notice = seat.budget.text;
66
+ else if (seat.budget?.kind === 'refuse')
67
+ notice = seat.budget.why;
68
+ const takeNotice = () => {
69
+ const text = notice;
70
+ notice = undefined;
71
+ return text;
72
+ };
73
+ if (fresh) {
74
+ // The lobby is one folder for the team, made before the first seat waits in it.
75
+ if (seat.lobby && !lobbies.has(cwd)) {
76
+ lobbies.add(cwd);
77
+ steps.push({
78
+ kind: 'run',
79
+ argv: ['mkdir', '-p', cwd],
80
+ note: 'the lobby: where a seat that works in worktrees waits, outside every protected checkout',
81
+ do: { do: 'lobby', path: cwd },
82
+ });
83
+ }
84
+ const said = takeNotice();
85
+ steps.push({
86
+ kind: 'run',
87
+ argv: herdr(session, 'workspace', 'create', '--cwd', cwd, '--label', seat.label, '--no-focus'),
88
+ ...(said ? { note: `${said}; would launch` } : {}),
89
+ do: { do: 'create', seat: seat.name, label: seat.label, cwd, ...(said ? { notice: said } : {}) },
90
+ });
91
+ }
92
+ const command = launchCommand(profile, seat.launch, seat.rules);
93
+ if (fresh || (seat.stage === 'launched' && !seat.agentLive)) {
94
+ const said = takeNotice();
95
+ steps.push({
96
+ kind: 'run',
97
+ argv: herdr(session, 'pane', 'run', pane, command),
98
+ ...(said ? { note: `${said}; would launch` } : {}),
99
+ do: { do: 'launch', seat: seat.name, label: seat.label, command, pane: seat.pane, ...(said ? { notice: said } : {}) },
100
+ });
101
+ }
102
+ const rules = profile.rulesOption === null ? 'message' : 'option';
103
+ if (seat.stage !== 'named') {
104
+ const said = takeNotice();
105
+ steps.push({
106
+ kind: 'wait',
107
+ text: `until ${seat.name} shows its idle prompt (${profile.idleTimeout} s at most); ` +
108
+ 'anything else is reported, its workspace closed without input, and the seat left out',
109
+ ...(said ? { note: said } : {}),
110
+ do: {
111
+ do: 'idle',
112
+ seat: seat.name,
113
+ label: seat.label,
114
+ cli: seat.cli,
115
+ seconds: profile.idleTimeout,
116
+ pane: seat.pane,
117
+ workspace: seat.workspace,
118
+ ...(said ? { notice: said } : {}),
119
+ },
120
+ });
121
+ steps.push({
122
+ kind: 'run',
123
+ argv: herdr(session, 'agent', 'rename', pane, seat.name),
124
+ do: { do: 'rename', seat: seat.name, label: seat.label, seconds: profile.idleTimeout, rules, pane: seat.pane },
125
+ });
126
+ }
127
+ if (profile.rulesOption === null) {
128
+ const said = takeNotice();
129
+ steps.push({
130
+ kind: 'run',
131
+ argv: herdr(session, 'pane', 'send-text', pane, seat.rules),
132
+ note: said
133
+ ? `${said}; the rules, only at an empty idle prompt; re-read before Enter, then wait for working with empty input`
134
+ : 'the rules, only at an empty idle prompt; re-read before Enter, then wait for working with empty input',
135
+ do: {
136
+ do: 'deliver',
137
+ seat: seat.name,
138
+ label: seat.label,
139
+ cli: seat.cli,
140
+ rules: seat.rules,
141
+ seconds: profile.idleTimeout,
142
+ pane: seat.pane,
143
+ ...(said ? { notice: said } : {}),
144
+ },
145
+ });
146
+ }
147
+ else if (seat.stage === 'named') {
148
+ const said = takeNotice();
149
+ steps.push({
150
+ kind: 'skip',
151
+ text: `${seat.name}: named, and its rules went with the launch; marked ready`,
152
+ ...(said ? { note: said } : {}),
153
+ do: { do: 'ready', seat: seat.name, rules: 'option', ...(said ? { notice: said } : {}) },
154
+ });
155
+ }
156
+ }
157
+ if (input.watchAlive)
158
+ return steps;
159
+ const watchArguments = session === 'default' ? '' : ` --session ${shellQuote(session)}`;
160
+ const watchLine = input.watchLine ?? `team watch${watchArguments}`;
161
+ steps.push({
162
+ kind: 'run',
163
+ argv: herdr(session, 'workspace', 'create', '--cwd', input.root, '--label', 'watchdog', '--no-focus'),
164
+ do: { do: 'create', label: 'watchdog', cwd: input.root },
165
+ });
166
+ steps.push({
167
+ kind: 'run',
168
+ argv: herdr(session, 'pane', 'run', paneOf('watchdog'), watchLine),
169
+ note: 'a desktop notification follows when the watch exits',
170
+ do: { do: 'watch', label: 'watchdog', command: watchLine },
171
+ });
172
+ return steps;
173
+ }
174
+ const LEFT = {
175
+ working: 'is working (`--wait` waits for it)',
176
+ blocked: 'is blocked at a prompt, which `team` never answers',
177
+ unknown: 'shows a screen the profile does not recognise',
178
+ unsent: 'holds unsent text in its input box',
179
+ };
180
+ /** Every step of `team down`, in order. Nothing here runs anything. */
181
+ export function downPlan(input) {
182
+ const { session } = input;
183
+ const steps = [];
184
+ let left = input.extra;
185
+ for (const seat of input.seats) {
186
+ if (input.keep.includes(seat.name)) {
187
+ steps.push({
188
+ kind: 'skip',
189
+ text: `${seat.name}: left running; only the owner stops the coordinator's or the operator's seat`,
190
+ });
191
+ left++;
192
+ continue;
193
+ }
194
+ const profile = profileFor(seat.cli);
195
+ if (!profile) {
196
+ if (input.abandon) {
197
+ steps.push({
198
+ kind: 'run',
199
+ argv: herdr(session, 'workspace', 'close', seat.workspace),
200
+ note: 'abandoned: nothing was typed',
201
+ do: { do: 'close', seat: seat.name, workspace: seat.workspace },
202
+ });
203
+ continue;
204
+ }
205
+ steps.push({
206
+ kind: 'skip',
207
+ text: `${seat.name}: no launch profile for \`${seat.cli}\` in this version; left running`,
208
+ });
209
+ left++;
210
+ continue;
211
+ }
212
+ if (seat.state !== 'free') {
213
+ if (input.abandon) {
214
+ steps.push({
215
+ kind: 'run',
216
+ argv: herdr(session, 'workspace', 'close', seat.workspace),
217
+ note: 'abandoned: nothing was typed',
218
+ do: { do: 'close', seat: seat.name, workspace: seat.workspace },
219
+ });
220
+ continue;
221
+ }
222
+ steps.push({ kind: 'skip', text: `${seat.name}: ${LEFT[seat.state]}; left running` });
223
+ left++;
224
+ continue;
225
+ }
226
+ // The printed command is `pane run`. The live step types with `typeText` and `pressEnter`:
227
+ // `/exit` has to go into the idle prompt, and the screen is read again before the Enter.
228
+ steps.push({
229
+ kind: 'run',
230
+ argv: herdr(session, 'pane', 'run', seat.pane, profile.exit),
231
+ do: { do: 'type', seat: seat.name, pane: seat.pane, text: profile.exit },
232
+ });
233
+ steps.push({
234
+ kind: 'wait',
235
+ text: `until ${seat.name}'s pane is back at its shell (${profile.exitTimeout} s at most); on a time-out it is left as it is`,
236
+ do: { do: 'gone', seat: seat.name, pane: seat.pane, seconds: profile.exitTimeout },
237
+ });
238
+ steps.push({
239
+ kind: 'run',
240
+ argv: herdr(session, 'workspace', 'close', seat.workspace),
241
+ do: { do: 'close', seat: seat.name, workspace: seat.workspace },
242
+ });
243
+ }
244
+ if (input.watchPid !== null) {
245
+ steps.push({
246
+ kind: 'run',
247
+ argv: ['kill', String(input.watchPid)],
248
+ note: 'the watch',
249
+ do: { do: 'kill', pid: input.watchPid },
250
+ });
251
+ }
252
+ if (session === 'default') {
253
+ steps.push({ kind: 'skip', text: "session default: herdr's default session is never stopped" });
254
+ }
255
+ else if (left > 0) {
256
+ steps.push({
257
+ kind: 'skip',
258
+ text: `session ${session}: not stopped, ${left} agent${left === 1 ? '' : 's'} left in it`,
259
+ });
260
+ }
261
+ else {
262
+ steps.push({ kind: 'run', argv: ['herdr', 'session', 'stop', session], do: { do: 'stop', session } });
263
+ }
264
+ return steps;
265
+ }
266
+ /** The plan as `--dry-run` prints it. */
267
+ export function formatPlan(steps) {
268
+ const lines = steps.map((step) => {
269
+ if (step.kind === 'run') {
270
+ const command = step.argv
271
+ .map((word) => (/^<pane of .+>$|^[A-Z_]+=\$[A-Z_]+$/.test(word) ? word : shellQuote(word)))
272
+ .join(' ');
273
+ return `+ ${command}${step.note ? `\n (${step.note})` : ''}`;
274
+ }
275
+ if (step.kind === 'wait')
276
+ return ` wait ${step.text}${step.note ? `\n (${step.note})` : ''}`;
277
+ return ` skip ${step.text}${step.note ? `\n (${step.note})` : ''}`;
278
+ });
279
+ return `${lines.join('\n')}\ndry run: nothing was run\n`;
280
+ }
@@ -0,0 +1,25 @@
1
+ /** What the rules of a seat are written from. */
2
+ export interface RulesInput {
3
+ coordinator: string;
4
+ /** The file's own `rules`. */
5
+ rules: readonly string[];
6
+ /** The seat's signature lines, already rendered. */
7
+ signature: {
8
+ commit: string;
9
+ pullRequest: string;
10
+ commitPosition: 'last-line' | 'trailer' | 'anywhere';
11
+ };
12
+ workspace: {
13
+ mode: 'worktree' | 'shared';
14
+ protected: readonly string[];
15
+ branch?: string;
16
+ };
17
+ }
18
+ /**
19
+ * The rules a seat gets at launch, one per line, in this order: the profile's
20
+ * own (what `team` itself depends on), the file's, the seat's signature, the
21
+ * workspace's.
22
+ */
23
+ export declare function seatRules(input: RulesInput): string[];
24
+ /** The rules as the one text a launch option or a first message carries. */
25
+ export declare function rulesText(input: RulesInput): string;
@@ -0,0 +1,36 @@
1
+ const COMMIT_PLACE = {
2
+ trailer: 'as a trailer, in a last paragraph of its own that holds trailers only',
3
+ 'last-line': 'as its last line',
4
+ anywhere: 'on a line of its own',
5
+ };
6
+ /**
7
+ * The rules a seat gets at launch, one per line, in this order: the profile's
8
+ * own (what `team` itself depends on), the file's, the seat's signature, the
9
+ * workspace's.
10
+ */
11
+ export function seatRules(input) {
12
+ const { signature, workspace } = input;
13
+ const own = [
14
+ 'End every commit message and every pull request body with your signature, given below.',
15
+ `Never stop at a question: tell the coordinator (${input.coordinator}) in one line and keep working.`,
16
+ 'Never run `team trust` or `team approve`, and never edit `trust:` in the team file.',
17
+ 'In a protected checkout, never switch the branch, reset or commit.',
18
+ ];
19
+ const signing = [
20
+ `Your signature in a commit message, ${COMMIT_PLACE[signature.commitPosition]}: ${signature.commit}`,
21
+ `Your signature in a pull request body, as its last line: ${signature.pullRequest}`,
22
+ ];
23
+ const working = [];
24
+ if (workspace.protected.length > 0) {
25
+ working.push(`Protected checkouts, relative to the project root: ${workspace.protected.join(', ')}`);
26
+ }
27
+ if (workspace.mode === 'worktree') {
28
+ working.push('Work on the code in the worktree your brief names, never in the checkout you started in.' +
29
+ (workspace.branch ? ` Branches are named ${workspace.branch}.` : ''));
30
+ }
31
+ return [...own, ...input.rules, ...signing, ...working];
32
+ }
33
+ /** The rules as the one text a launch option or a first message carries. */
34
+ export function rulesText(input) {
35
+ return ['Rules for this session, from the team file:', ...seatRules(input).map((rule) => `- ${rule}`)].join('\n');
36
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function logLine(dir: string, command: string, caller: string, what: string, now?: Date): void;
package/dist/log.js ADDED
@@ -0,0 +1,24 @@
1
+ import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { LOG_FILE } from "./state.js";
4
+ const MAX_BYTES = 1_000_000;
5
+ const KEPT = 3;
6
+ // One line per change: when, which command, called by whom, what changed. Classifications only,
7
+ // never the text of a screen. The log rotates at 1 MB and keeps three files.
8
+ export function logLine(dir, command, caller, what, now = new Date()) {
9
+ mkdirSync(dir, { recursive: true });
10
+ const path = join(dir, LOG_FILE);
11
+ if (existsSync(path) && statSync(path).size >= MAX_BYTES)
12
+ rotate(path);
13
+ appendFileSync(path, `${now.toISOString()} ${command} [${caller}] ${what.replace(/\s+/g, ' ')}\n`);
14
+ }
15
+ function rotate(path) {
16
+ const oldest = `${path}.${KEPT - 1}`;
17
+ if (existsSync(oldest))
18
+ unlinkSync(oldest);
19
+ for (let n = KEPT - 2; n >= 1; n--) {
20
+ if (existsSync(`${path}.${n}`))
21
+ renameSync(`${path}.${n}`, `${path}.${n + 1}`);
22
+ }
23
+ renameSync(path, `${path}.1`);
24
+ }
@@ -0,0 +1,66 @@
1
+ # Antigravity's screen. Trust and permission are case-insensitive. The slash
2
+ # menu's arrow line and the esc footer are chrome, so exit-typed stays unsent.
3
+ format: 1
4
+ cli: antigravity
5
+ binary: agy
6
+ process_names:
7
+ - agy
8
+ tested:
9
+ from: '1.2.16'
10
+ to: '1.2.16'
11
+ unattended:
12
+ - --dangerously-skip-permissions
13
+ rules: first-message
14
+ login:
15
+ check:
16
+ - models
17
+ hint: agy
18
+ exit: /exit
19
+ timeouts:
20
+ idle: 90s
21
+ exit: 30s
22
+ models:
23
+ option:
24
+ - --model
25
+ ids:
26
+ - match: '^gemini-([0-9]+(?:\.[0-9]+)?)-(flash|pro)(?:-(high|medium|low))?$'
27
+ ignore_case: true
28
+ model: 'Gemini {2:title}'
29
+ version: '{1}'
30
+ status_model:
31
+ - match: '(?:^|[\s·|])Gemini\s+([0-9]+(?:\.[0-9]+)*)\s+(Flash|Pro)\b'
32
+ ignore_case: true
33
+ model: 'Gemini {2:title}'
34
+ version: '{1}'
35
+ screen:
36
+ chrome:
37
+ - '^\s*↑/↓ Navigate.*$'
38
+ - '^esc to cancel\s+Gemini\s+[0-9].*$'
39
+ trust:
40
+ ignore_case: true
41
+ rules:
42
+ - all:
43
+ - 'Do you trust the contents of this project\?'
44
+ - '>\s*Yes, I trust this folder'
45
+ below_last_rule: 'enter Confirm'
46
+ permission:
47
+ ignore_case: true
48
+ rules:
49
+ - all:
50
+ - 'Requesting permission for:'
51
+ - '^\s*>\s*[0-9]\.\s+Yes\b'
52
+ below_last_rule: '↑/↓ Navigate'
53
+ working:
54
+ - any: ['\bGenerating\.\.\.']
55
+ composer:
56
+ mode: two-rules-footer-below
57
+ ignore_case: true
58
+ prompt: '^\s*>\s*'
59
+ rule: '^\s*[─━]{8,}\s*$'
60
+ footers:
61
+ - '\? for shortcuts'
62
+ - 'Gemini\s+[0-9]'
63
+ - 'esc to cancel'
64
+ - '>\s*/exit\s+Exit'
65
+ placeholders:
66
+ - equals: ''
@@ -0,0 +1,69 @@
1
+ # Claude Code's screen. The questions are written out in both cases: the old
2
+ # classifier ignored case on those two lines and not on the "1. Yes" choice, and
3
+ # ignore_case would apply to the whole rule.
4
+ format: 1
5
+ cli: claude-code
6
+ binary: claude
7
+ process_names:
8
+ - claude
9
+ tested:
10
+ from: '2.1.288'
11
+ to: '2.1.288'
12
+ unattended:
13
+ - --dangerously-skip-permissions
14
+ rules:
15
+ option: --append-system-prompt
16
+ login:
17
+ check:
18
+ - auth
19
+ - status
20
+ hint: claude auth login
21
+ exit: /exit
22
+ timeouts:
23
+ idle: 90s
24
+ exit: 30s
25
+ models:
26
+ option:
27
+ - --model
28
+ ids:
29
+ - match: '^claude-(opus|sonnet|haiku|fable)-([0-9]+)-([0-9]{1,2})(?:-[0-9]{8})?$'
30
+ model: 'Claude {1:title}'
31
+ version: '{2}.{3}'
32
+ - match: '^claude-(opus|sonnet|haiku|fable)-([0-9]+)(?:-[0-9]{8})?$'
33
+ model: 'Claude {1:title}'
34
+ version: '{2}'
35
+ status_model:
36
+ - match: '(?:^|[\s·|])(Opus|Sonnet|Haiku|Fable)\s+([0-9]+(?:\.[0-9]+)*)(?:$|[\s·|])'
37
+ model: 'Claude {1}'
38
+ version: '{2}'
39
+ screen:
40
+ trust:
41
+ - all:
42
+ - '(?:[Dd][Oo] [Yy][Oo][Uu] [Tt][Rr][Uu][Ss][Tt] (?:[Tt][Hh][Ii][Ss]|[Tt][Hh][Ee]) [Ff][Oo][Ll][Dd][Ee][Rr]\?|[Oo][Nn][Ee] [Yy][Oo][Uu] [Tt][Rr][Uu][Ss][Tt]\?)'
43
+ - '^\s*[❯›>]?\s*1\.\s+Yes\b'
44
+ # A quoted question above the composer's rule is not a dialog. The footer is the
45
+ # captured last line and needs the question. A Yes/No choice below the last rule
46
+ # is the other shape; the mark is optional, as on an unmarked "2. No".
47
+ permission:
48
+ - all:
49
+ - 'Do you want to (proceed|make this edit|create|allow)'
50
+ footer: 'Esc to cancel · Tab to amend'
51
+ below_last_rule: 'Esc to cancel · Tab to amend'
52
+ - below_last_rule: '^\s*❯?\s*[0-9]\. (Yes|No)\b'
53
+ question:
54
+ - any:
55
+ - 'Enter to select'
56
+ - '↑/↓ to navigate'
57
+ - 'Enter to confirm'
58
+ - 'Esc to cancel'
59
+ working:
60
+ - any:
61
+ - '\besc to interrupt\b'
62
+ - '^[^\S\n]*[✢✳✶✻✼✽][^\n]*…\s*\(\s*\d'
63
+ composer:
64
+ mode: box-to-rule
65
+ prompt: '^\s*[❯›>]'
66
+ rule: '^\s*[─━]{8,}'
67
+ placeholders:
68
+ - equals: ''
69
+ - prefix: 'Try "'
@@ -0,0 +1,77 @@
1
+ # Codex's screen. An empty box is idle, as the old classifier treated it; the
2
+ # grey suggestion is the other placeholder.
3
+ format: 1
4
+ cli: codex
5
+ binary: codex
6
+ process_names:
7
+ - codex
8
+ tested:
9
+ from: '0.157.0'
10
+ to: '0.157.0'
11
+ unattended:
12
+ - -a
13
+ - never
14
+ - -s
15
+ - danger-full-access
16
+ - --no-daemon
17
+ - --no-alt-screen
18
+ rules: first-message
19
+ login:
20
+ check:
21
+ - login
22
+ - status
23
+ hint: codex login
24
+ exit: /exit
25
+ timeouts:
26
+ idle: 90s
27
+ exit: 30s
28
+ models:
29
+ option:
30
+ - -m
31
+ - --model
32
+ ids:
33
+ - match: '^gpt-([0-9]+(?:\.[0-9]+)?)-(astra|sol|luna|terra)$'
34
+ ignore_case: true
35
+ model: 'GPT {2:title}'
36
+ version: '{1}'
37
+ quota:
38
+ - account: openai
39
+ match: '\bweekly ([0-9]+)% left\b'
40
+ left: '{1}%'
41
+ window: weekly
42
+ status_model:
43
+ - match: '^\s+GPT-([0-9]+(?:\.[0-9]+)?)-([Aa][Ss][Tt][Rr][Aa]|[Ss][Oo][Ll]|[Ll][Uu][Nn][Aa]|[Tt][Ee][Rr][Rr][Aa])\s[^·]*·'
44
+ model: 'GPT {2:title}'
45
+ version: '{1}'
46
+ # A later footer that is not one of those four names is unread, as the old id map was.
47
+ - match: '^\s+GPT-[0-9][\w.-]*\s[^·]*·'
48
+ model: '{1}'
49
+ version: '{1}'
50
+ screen:
51
+ unknown:
52
+ - any: ['\bmodel:\s+loading\b']
53
+ trust:
54
+ - all:
55
+ - 'Trust this folder\?'
56
+ - '^\s*›?\s*1\. Trust and continue\s*$'
57
+ footer: 'enter continue · esc quit'
58
+ permission:
59
+ - all:
60
+ - '^\s*Would you like to run the following command\?\s*$'
61
+ - '^\s*›?\s*1\. Yes, proceed \(y\)\s*$'
62
+ - '^\s*›?\s*2\. No, and tell Codex what to do differently \(esc\)\s*$'
63
+ footer: 'Press enter to confirm or esc to cancel'
64
+ question:
65
+ - all:
66
+ - 'Update available ·'
67
+ - '^\s*›?\s*1\. Update now\b'
68
+ footer: 'enter continue · esc skip'
69
+ working:
70
+ - any: ['esc to interrupt']
71
+ composer:
72
+ mode: status-last
73
+ status_line: '^\s+GPT-[0-9][\w.-]*\s+[^·]*·'
74
+ prompt: '^\s*›(?:\s|$)'
75
+ placeholders:
76
+ - equals: ''
77
+ - equals: Ask Codex to do anything
@@ -0,0 +1,68 @@
1
+ # Cursor's screen. "To resume this session:" is unknown, and the core reads
2
+ # unknown before trust. The braille range is written as characters: \u is refused.
3
+ format: 1
4
+ cli: cursor
5
+ binary: cursor-agent
6
+ process_names:
7
+ - cursor-agent
8
+ tested:
9
+ from: '2026.10.01'
10
+ to: '2026.10.01'
11
+ unattended:
12
+ - --force
13
+ - --sandbox
14
+ - disabled
15
+ rules: first-message
16
+ login:
17
+ check:
18
+ - status
19
+ hint: cursor-agent login
20
+ exit: /exit
21
+ timeouts:
22
+ idle: 90s
23
+ exit: 30s
24
+ models:
25
+ option:
26
+ - --model
27
+ ids:
28
+ - match: '^(?:cursor-)?grok-([0-9]+(?:\.[0-9]+)*)(?:-(?:xhigh|high|medium|low)(?:-fast)?|-fast)?$'
29
+ ignore_case: true
30
+ model: Grok
31
+ version: '{1}'
32
+ status_model:
33
+ - match: '(?:^|\s)Grok\s+([0-9]+(?:\.[0-9]+)*)\b'
34
+ model: Grok
35
+ version: '{1}'
36
+ screen:
37
+ unknown:
38
+ - any: ['To resume this session:']
39
+ trust:
40
+ - all:
41
+ - 'Do you trust the contents of this'
42
+ - 'directory\?'
43
+ - '\[a\] Trust this workspace'
44
+ - '\[q\] Quit'
45
+ none_after:
46
+ anchor: 'Use arrow keys to navigate, Enter to'
47
+ patterns:
48
+ - '^\s*→'
49
+ - '^\s+Grok\s+[0-9]'
50
+ working:
51
+ - any:
52
+ - 'ctrl\+c to stop'
53
+ - '^\s*[⠀-⣿]+\s+(Working|Thinking)\b'
54
+ composer:
55
+ mode: status-then-one
56
+ status_line: '^\s+Grok\s+[0-9]'
57
+ prompt: '^\s*→'
58
+ strip_suffix: '\s*ctrl\+c to stop\s*$'
59
+ placeholders:
60
+ - equals: ''
61
+ - equals: Plan, search, build anything
62
+ - equals: Add a follow-up
63
+ fallback:
64
+ - all:
65
+ - match: '^\s*→\s+\S'
66
+ except: ['\sExit\s*$']
67
+ - '^\s*→?\s*/\S+\s+Exit\s*$'
68
+ kind: unsent
@@ -0,0 +1 @@
1
+ export { profileFor } from './profile.ts';
@@ -0,0 +1 @@
1
+ export { profileFor } from "./profile.js";