team 0.1.1 → 0.1.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 (55) hide show
  1. package/README.md +20 -5
  2. package/dist/ansi.d.ts +13 -0
  3. package/dist/ansi.js +75 -0
  4. package/dist/approve/approval.d.ts +11 -0
  5. package/dist/approve/approval.js +75 -8
  6. package/dist/approve/fingerprint.d.ts +15 -4
  7. package/dist/approve/fingerprint.js +37 -12
  8. package/dist/budgets/gate.d.ts +5 -4
  9. package/dist/budgets/gate.js +16 -12
  10. package/dist/budgets/readings.d.ts +51 -7
  11. package/dist/budgets/readings.js +106 -16
  12. package/dist/budgets/table.d.ts +2 -2
  13. package/dist/budgets/table.js +24 -11
  14. package/dist/commands/add.js +11 -4
  15. package/dist/commands/doctor.d.ts +3 -1
  16. package/dist/commands/doctor.js +4 -2
  17. package/dist/commands/down.d.ts +2 -2
  18. package/dist/commands/down.js +8 -1
  19. package/dist/commands/remove.d.ts +2 -0
  20. package/dist/commands/remove.js +21 -2
  21. package/dist/commands/status.js +3 -2
  22. package/dist/commands/up.d.ts +3 -1
  23. package/dist/commands/up.js +9 -5
  24. package/dist/commands/watch.d.ts +2 -0
  25. package/dist/commands/watch.js +73 -17
  26. package/dist/file/types.d.ts +5 -0
  27. package/dist/file/validate.js +31 -11
  28. package/dist/herdr.d.ts +4 -0
  29. package/dist/herdr.js +40 -11
  30. package/dist/launch/agent.d.ts +1 -0
  31. package/dist/launch/agent.js +12 -0
  32. package/dist/launch/deliver.d.ts +3 -1
  33. package/dist/launch/deliver.js +10 -1
  34. package/dist/launch/execute.d.ts +2 -2
  35. package/dist/launch/execute.js +15 -2
  36. package/dist/launch/rules.d.ts +4 -2
  37. package/dist/launch/rules.js +17 -3
  38. package/dist/profiles/antigravity.yaml +13 -0
  39. package/dist/profiles/claude-code.yaml +30 -0
  40. package/dist/profiles/cursor.yaml +4 -1
  41. package/dist/state.d.ts +7 -4
  42. package/dist/state.js +37 -0
  43. package/dist/status/statusline.js +3 -1
  44. package/dist/watch/check.d.ts +1 -0
  45. package/dist/watch/checks/budget.js +4 -1
  46. package/dist/watch/pass.d.ts +3 -2
  47. package/dist/watch/pass.js +52 -21
  48. package/dist/watch/screen-core.d.ts +1 -1
  49. package/dist/watch/screen-core.js +89 -33
  50. package/dist/watch/screen-data.d.ts +9 -0
  51. package/dist/watch/screen-file.js +25 -7
  52. package/dist/watch/screen.d.ts +3 -2
  53. package/dist/watch/screen.js +9 -4
  54. package/examples/team.yaml +8 -0
  55. package/package.json +1 -1
@@ -50,14 +50,16 @@ function stageOf(node) {
50
50
  }
51
51
  function ruleOf(node, ignoreCase) {
52
52
  const entries = mapping(node, 'a rule');
53
- only(entries, ['any', 'all', 'footer', 'below_last_rule', 'none_after']);
53
+ only(entries, ['any', 'all', 'footer', 'on_footer', 'below_last_rule', 'without_rule', 'none_after']);
54
54
  if (entries.length === 0)
55
55
  fail(node.line, 'a rule has no primitive');
56
56
  const rule = {};
57
57
  const any = optional(entries, 'any');
58
58
  const all = optional(entries, 'all');
59
59
  const footer = optional(entries, 'footer');
60
+ const onFooter = optional(entries, 'on_footer');
60
61
  const below = optional(entries, 'below_last_rule');
62
+ const withoutRule = optional(entries, 'without_rule');
61
63
  const noneAfter = optional(entries, 'none_after');
62
64
  if (any)
63
65
  rule.any = patternsOf(any.value, 'any', ignoreCase);
@@ -65,8 +67,12 @@ function ruleOf(node, ignoreCase) {
65
67
  rule.all = patternsOf(all.value, 'all', ignoreCase);
66
68
  if (footer)
67
69
  rule.footer = stringOf(footer.value) ?? fail(footer.line, '"footer" must be a string');
70
+ if (onFooter)
71
+ rule.onFooter = boolOf(onFooter.value, 'on_footer');
68
72
  if (below)
69
73
  rule.belowLastRule = patternOf(stringOf(below.value) ?? fail(below.line, '"below_last_rule" must be a string'), ignoreCase, below.line);
74
+ if (withoutRule)
75
+ rule.withoutRule = boolOf(withoutRule.value, 'without_rule');
70
76
  if (noneAfter)
71
77
  rule.noneAfter = noneAfterOf(noneAfter.value, ignoreCase);
72
78
  return rule;
@@ -116,16 +122,20 @@ function composerOf(node) {
116
122
  const entries = mapping(node, 'composer');
117
123
  const mode = required(entries, 'mode', node.line);
118
124
  const name = stringOf(mode.value);
125
+ // How the CLI renders its greyed suggestions, read off the input line's styling. Optional:
126
+ // a composer whose suggestions plain text already names needs none of it.
127
+ const style = optional(entries, 'placeholder_style');
128
+ const placeholderStyle = style ? placeholderStyleOf(style) : undefined;
119
129
  if (name === 'box-to-rule') {
120
- only(entries, ['mode', 'prompt', 'rule', 'placeholders']);
121
- return { mode: name, prompt: regexField(entries, 'prompt', node.line), rule: regexField(entries, 'rule', node.line), placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value) };
130
+ only(entries, ['mode', 'prompt', 'rule', 'placeholders', 'placeholder_style']);
131
+ return { mode: name, prompt: regexField(entries, 'prompt', node.line), rule: regexField(entries, 'rule', node.line), placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value), placeholderStyle };
122
132
  }
123
133
  if (name === 'status-last') {
124
- only(entries, ['mode', 'status_line', 'prompt', 'placeholders']);
125
- return { mode: name, statusLine: regexField(entries, 'status_line', node.line), prompt: regexField(entries, 'prompt', node.line), placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value) };
134
+ only(entries, ['mode', 'status_line', 'prompt', 'placeholders', 'placeholder_style']);
135
+ return { mode: name, statusLine: regexField(entries, 'status_line', node.line), prompt: regexField(entries, 'prompt', node.line), placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value), placeholderStyle };
126
136
  }
127
137
  if (name === 'status-then-one') {
128
- only(entries, ['mode', 'status_line', 'prompt', 'placeholders', 'strip_suffix', 'fallback']);
138
+ only(entries, ['mode', 'status_line', 'prompt', 'placeholders', 'placeholder_style', 'strip_suffix', 'fallback']);
129
139
  const suffix = optional(entries, 'strip_suffix');
130
140
  const fallback = required(entries, 'fallback', node.line);
131
141
  return {
@@ -133,12 +143,13 @@ function composerOf(node) {
133
143
  statusLine: regexField(entries, 'status_line', node.line),
134
144
  prompt: regexField(entries, 'prompt', node.line),
135
145
  placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value),
146
+ placeholderStyle,
136
147
  stripSuffix: suffix ? patternOf(stringOf(suffix.value) ?? fail(suffix.line, '"strip_suffix" must be a string'), false, suffix.line) : null,
137
148
  fallback: fallbackOf(fallback.value),
138
149
  };
139
150
  }
140
151
  if (name === 'two-rules-footer-below') {
141
- only(entries, ['mode', 'ignore_case', 'prompt', 'rule', 'footers', 'placeholders']);
152
+ only(entries, ['mode', 'ignore_case', 'prompt', 'rule', 'footers', 'placeholders', 'placeholder_style']);
142
153
  const flag = optional(entries, 'ignore_case');
143
154
  const ignoreCase = flag ? boolOf(flag.value, 'ignore_case') : false;
144
155
  const footers = required(entries, 'footers', node.line);
@@ -155,10 +166,17 @@ function composerOf(node) {
155
166
  return patternOf(text, ignoreCase, item.line);
156
167
  }),
157
168
  placeholders: placeholdersOf(required(entries, 'placeholders', node.line).value),
169
+ placeholderStyle,
158
170
  };
159
171
  }
160
172
  fail(mode.line, `"mode" must be box-to-rule, status-last, status-then-one or two-rules-footer-below`);
161
173
  }
174
+ function placeholderStyleOf(entry) {
175
+ const text = stringOf(entry.value);
176
+ if (text !== 'dim')
177
+ fail(entry.line, '"placeholder_style" must be dim');
178
+ return text;
179
+ }
162
180
  function fallbackOf(node) {
163
181
  if (node.kind !== 'seq')
164
182
  fail(node.line, '"fallback" must be a list');
@@ -19,6 +19,7 @@ export declare function classify(cli: string, lines: string[]): Screen;
19
19
  export declare function classifyComposer(cli: string, lines: string[]): Screen;
20
20
  export declare function readScreen(cli: string, screen: string | undefined): Screen;
21
21
  /** The one line a quota figure may come from: the composer's own status row, in the pane's last
22
- * 20 lines. Null for a CLI with no status line, and for a window that shows no status row — a
23
- * dialog, a question, a trust screen, a shell or an unknown one reads no figures. */
22
+ * 20 lines, read plain — a figure is never styled. Null for a CLI with no status line, and for
23
+ * a window that shows no status row — a dialog, a question, a trust screen, a shell or an
24
+ * unknown one reads no figures. */
24
25
  export declare function statusRow(cli: string, screen: string | undefined): string | null;
@@ -1,6 +1,9 @@
1
1
  // What a pane's visible text shows, read against the shapes of its CLI. A screen that matches no
2
2
  // shape is "unknown": never ready, never idle, and never grounds for typing anything.
3
+ // The text may keep its ANSI styling, as `herdr pane read --format ansi` reads it: matching
4
+ // runs on the plain form, and the styling tells a greyed suggestion from typed text.
3
5
  import { readFileSync } from 'node:fs';
6
+ import { stripSgr } from "../ansi.js";
4
7
  import { classifyLines, composeLines, statusRowOf } from "./screen-core.js";
5
8
  import { loadScreen } from "./screen-file.js";
6
9
  function load(name) {
@@ -13,7 +16,8 @@ const DATA = {
13
16
  cursor: load('cursor'),
14
17
  antigravity: load('antigravity'),
15
18
  };
16
- // The window every pattern sees: the pane's last 20 lines, each trimmed at the end.
19
+ // The window every pattern sees: the pane's last 20 lines. Styled lines are trimmed only past
20
+ // their last escape; the core trims each line's plain form for matching.
17
21
  function windowOf(lines) {
18
22
  return lines.map((line) => line.trimEnd()).slice(-20);
19
23
  }
@@ -37,11 +41,12 @@ export function readScreen(cli, screen) {
37
41
  return classify(cli, screen.split('\n'));
38
42
  }
39
43
  /** The one line a quota figure may come from: the composer's own status row, in the pane's last
40
- * 20 lines. Null for a CLI with no status line, and for a window that shows no status row — a
41
- * dialog, a question, a trust screen, a shell or an unknown one reads no figures. */
44
+ * 20 lines, read plain — a figure is never styled. Null for a CLI with no status line, and for
45
+ * a window that shows no status row — a dialog, a question, a trust screen, a shell or an
46
+ * unknown one reads no figures. */
42
47
  export function statusRow(cli, screen) {
43
48
  const data = DATA[cli];
44
49
  if (data === undefined || screen === undefined)
45
50
  return null;
46
- return statusRowOf(data, windowOf(screen.split('\n')));
51
+ return statusRowOf(data, windowOf(stripSgr(screen).split('\n')));
47
52
  }
@@ -49,6 +49,7 @@ seats:
49
49
  label: codex
50
50
  cli: codex
51
51
  vendor: openai
52
+ account: openai-team # the seat's account, when one vendor has two; absent, its vendor
52
53
  model: GPT Comet
53
54
  version: "3"
54
55
  display: GPT-3 Comet # the vendor's spelling, for the signature
@@ -74,3 +75,10 @@ seats:
74
75
  version: "1"
75
76
  launch: nimbus-claude # a launcher on the PATH, holding the account's key and endpoint
76
77
  count: 3
78
+
79
+ budgets: # the owner's: what is budgeted, and how long a figure stays fresh
80
+ accounts:
81
+ openai-team: # the account codex-beacon spends
82
+ kind: subscription
83
+ reserve: 20% # refuse a launch on a figure inside it
84
+ sources: [status_line] # Codex's status line carries its weekly figure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Set up, change and watch a project's team of AI agents from one file",
5
5
  "keywords": [
6
6
  "ai-agents",