pi-crew 0.10.6 → 0.11.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 (44) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/index.mjs +34850 -34889
  3. package/package.json +170 -169
  4. package/scripts/watch-agent-transcript.mjs +120 -0
  5. package/src/extension/crew-input-router.ts +2 -0
  6. package/src/extension/crew-vibes/config.ts +1 -56
  7. package/src/extension/crew-vibes/figures.ts +5 -84
  8. package/src/extension/crew-vibes/font-detect.ts +1 -1
  9. package/src/extension/crew-vibes/index.ts +36 -328
  10. package/src/extension/crew-vibes/render.ts +2 -58
  11. package/src/extension/help.ts +1 -0
  12. package/src/extension/registration/command-registration.ts +5 -1
  13. package/src/extension/registration/commands/schedules.ts +212 -0
  14. package/src/extension/registration/commands/shared.ts +56 -1
  15. package/src/extension/registration/lifecycle-handlers.ts +16 -2
  16. package/src/extension/registration/schedule-toast-bridge.ts +109 -0
  17. package/src/extension/registration/viewers.ts +66 -1
  18. package/src/extension/team-tool/handle-schedule.ts +120 -1
  19. package/src/runtime/scheduling/scheduler.ts +120 -6
  20. package/src/runtime/settings-store.ts +28 -2
  21. package/src/state/coordination/locks.ts +17 -2
  22. package/src/ui/agents-jobs-browser.ts +785 -0
  23. package/src/ui/dashboard-panes/agents-pane.ts +7 -4
  24. package/src/ui/dashboard-panes/schedules-pane.ts +154 -0
  25. package/src/ui/inline-panel/crew-editor.ts +19 -6
  26. package/src/ui/inline-panel/index.ts +6 -0
  27. package/src/ui/key-utils.ts +3 -0
  28. package/src/ui/keybinding-map.ts +56 -2
  29. package/src/ui/mascot.ts +2 -1
  30. package/src/ui/overlays/agent-picker-overlay.ts +3 -3
  31. package/src/ui/overlays/confirm-overlay.ts +3 -2
  32. package/src/ui/overlays/mailbox-compose-overlay.ts +4 -3
  33. package/src/ui/overlays/mailbox-detail-overlay.ts +3 -3
  34. package/src/ui/run-dashboard.ts +257 -6
  35. package/src/ui/settings-overlay.ts +3 -3
  36. package/src/ui/transcript-viewer.ts +3 -2
  37. package/src/ui/widget/index.ts +61 -88
  38. package/src/ui/widget/widget-formatters.ts +7 -4
  39. package/src/ui/widget/widget-renderer.ts +134 -174
  40. package/src/ui/widget/widget-types.ts +0 -7
  41. package/src/utils/relative-time.ts +33 -0
  42. package/src/extension/crew-vibes/footer.ts +0 -313
  43. package/src/extension/crew-vibes/speed.ts +0 -286
  44. package/src/ui/dock-footer.ts +0 -49
package/package.json CHANGED
@@ -1,171 +1,172 @@
1
1
  {
2
- "name": "pi-crew",
3
- "version": "0.10.6",
4
- "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
- "author": "baphuongna",
6
- "license": "MIT",
7
- "engines": {
8
- "node": ">=22.0.0"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/baphuongna/pi-crew.git"
13
- },
14
- "homepage": "https://github.com/baphuongna/pi-crew#readme",
15
- "bugs": {
16
- "url": "https://github.com/baphuongna/pi-crew/issues"
17
- },
18
- "type": "module",
19
- "bin": {
20
- "pi-crew": "install.mjs"
21
- },
22
- "keywords": [
23
- "pi-package",
24
- "pi",
25
- "pi-coding-agent",
26
- "teams",
27
- "agents",
28
- "multi-agent",
29
- "orchestration"
30
- ],
31
- "files": [
32
- "src/",
33
- "!src/benchmark",
34
- "dist/index.mjs",
35
- "index.ts",
36
- "install.mjs",
37
- "agents/",
38
- "teams/",
39
- "workflows/",
40
- "skills/**",
41
- "themes/",
42
- "assets/crew-vibes.ttf",
43
- "scripts/clean-strip-types.mjs",
44
- "scripts/postinstall.mjs",
45
- "scripts/install-crew-vibes-font.mjs",
46
- "scripts/build-crew-vibes-font.py",
47
- "scripts/pty_probe.py",
48
- "README.md",
49
- "AGENTS.md",
50
- "docs/actions-reference.md",
51
- "docs/commands-reference.md",
52
- "docs/troubleshooting.md",
53
- "docs/usage.md",
54
- "docs/architecture.md",
55
- "docs/dynamic-workflows.md",
56
- "docs/runtime-flow.md",
57
- "docs/resource-formats.md",
58
- "docs/publishing.md",
59
- "docs/migration-v0.4-v0.5.md",
60
- "tsconfig.json",
61
- "schema.json",
62
- "types/",
63
- "CHANGELOG.md",
64
- "LICENSE",
65
- "NOTICE.md",
66
- "scripts/resource-sampler.mjs",
67
- "scripts/analyze-run.mjs"
68
- ],
69
- "scripts": {
70
- "check": "npm run ci",
71
- "ci": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
72
- "ci:fast": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm run test:critical && npm pack --dry-run",
73
- "check:lockfile-sync": "node scripts/check-lockfile-sync.mjs",
74
- "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
75
- "check:bundle-staleness": "node scripts/check-bundle-staleness.mjs",
76
- "check:bundle-size": "node scripts/check-bundle-size.mjs",
77
- "check:conflict-markers": "node scripts/check-conflict-markers.mjs",
78
- "check:decision-drift": "node scripts/check-decision-drift.mjs",
79
- "check:env-vars": "node scripts/check-env-vars.mjs",
80
- "check:event-types": "node scripts/check-event-types-registry.mjs",
81
- "typecheck": "tsc --noEmit && node --experimental-strip-types -e \"await import('./index.ts'); console.log('strip-types import ok')\"",
82
- "lint": "biome check --linter-enabled=true --formatter-enabled=false --max-diagnostics=50 .",
83
- "format:check": "biome format .",
84
- "test": "npm run test:unit && npm run test:integration",
85
- "test:full": "npm run test:unit && npm run test:integration && npm run test:integration:slow",
86
- "test:unit": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=180000 --test-force-exit 'test/unit/**/*.test.ts'",
87
- "test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/runtime/broker/crew-broker-handshake.test.ts test/unit/runtime/broker/crew-broker-stale-socket.test.ts test/unit/runtime/broker/crew-broker-feature-flag.test.ts test/unit/runtime/broker/crew-broker-server-gate.test.ts test/unit/runtime/broker/crew-broker-client-fallback.test.ts test/unit/runtime/broker/crew-broker-mailbox-observer.test.ts test/unit/runtime/broker/crew-broker-close-during-reconnect.test.ts test/unit/runtime/broker/crew-broker-steer-dedup.test.ts test/unit/runtime/broker/crew-broker-symlink-steering.test.ts test/unit/ui/keybinding-map.parity.test.ts test/unit/ui/pi-tui-dispatch-probe.test.ts test/unit/utils/session-utils-extract.test.ts test/unit/config/config-schema-sync.test.ts test/unit/runtime/child-pi/child-pi-env-spread.test.ts",
88
- "test:watch": "node scripts/test-runner.mjs --watch --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
89
- "test:integration": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=300000 test/integration/*.test.ts",
90
- "test:integration:slow": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=600000 test/integration/slow/*.test.ts",
91
- "test:system": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 --test-force-exit test/system/*.e2e.test.ts",
92
- "test:smoke": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 test/smoke/*.smoke.ts",
93
- "test:spike": "node scripts/test-runner.mjs --test-concurrency=2 --test-timeout=120000 --test-force-exit test/runtime/scratchpad/*.test.ts",
94
- "test:functional": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=120000 test/functional/pi-crew-functional.test.ts",
95
- "test:platform": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=60000 test/platform/*.test.ts",
96
- "build:bundle": "node scripts/build-bundle.mjs",
97
- "test:bundle": "node --experimental-strip-types --test --test-force-exit test/unit/bundle-load.test.ts",
98
- "watch:bundle": "node scripts/watch-bundle.mjs",
99
- "build:bundle:once": "node scripts/watch-bundle.mjs --once",
100
- "dev": "node scripts/dev-runner.mjs",
101
- "postinstall": "node scripts/postinstall.mjs",
102
- "install:crew-font": "node scripts/install-crew-vibes-font.mjs",
103
- "build:crew-font": "python scripts/build-crew-vibes-font.py",
104
- "bench": "node scripts/run-bench.mjs",
105
- "bench:check": "node scripts/bench-check.mjs",
106
- "test:new": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
107
- "test:changed": "node scripts/test-changed.mjs",
108
- "bench:capture": "node scripts/run-bench.mjs && node -e \"require('node:fs').copyFileSync('test/bench/results.json','test/bench/baseline.json')\"",
109
- "profile:startup": "node scripts/profile-startup.mjs",
110
- "smoke:pi": "pi install .",
111
- "smoke:release": "node scripts/release-smoke.mjs",
112
- "prepack": "node scripts/clean-strip-types.mjs",
113
- "test:fast": "npm run test:critical && npm run test:bundle",
114
- "check:wc-gate": "node scripts/wc-gate.mjs"
115
- },
116
- "exports": {
117
- "./schema.json": "./schema.json",
118
- "./workflow": {
119
- "types": "./types/dwf.d.ts"
120
- }
121
- },
122
- "pi": {
123
- "extensions": [
124
- "./index.ts"
125
- ],
126
- "skills": [
127
- "./skills"
128
- ]
129
- },
130
- "peerDependencies": {
131
- "@earendil-works/pi-agent-core": "*",
132
- "@earendil-works/pi-ai": "*",
133
- "@earendil-works/pi-coding-agent": "*",
134
- "@earendil-works/pi-tui": "*"
135
- },
136
- "dependencies": {
137
- "@sinclair/typebox": "^0.34.50",
138
- "acorn": "^8.17.0",
139
- "ajv": "^8.20.0",
140
- "cli-highlight": "^2.1.11",
141
- "diff": "^5.2.0",
142
- "esbuild": "^0.28.1",
143
- "jiti": "^2.7.0",
144
- "yaml": "^2.9.0"
145
- },
146
- "devDependencies": {
147
- "@biomejs/biome": "^2.5.3",
148
- "@earendil-works/pi-agent-core": "^0.84.0",
149
- "@earendil-works/pi-ai": "^0.84.0",
150
- "@earendil-works/pi-coding-agent": "^0.84.0",
151
- "@earendil-works/pi-tui": "^0.84.0",
152
- "@types/node": "^25.9.5",
153
- "tsx": "^4.23.0",
154
- "typescript": "^7.0.2"
155
- },
156
- "peerDependenciesMeta": {
157
- "@earendil-works/pi-agent-core": {
158
- "optional": true
159
- },
160
- "@earendil-works/pi-ai": {
161
- "optional": true
162
- },
163
- "@earendil-works/pi-coding-agent": {
164
- "optional": true
165
- },
166
- "@earendil-works/pi-tui": {
167
- "optional": true
168
- }
169
- },
170
- "readmeFilename": "README.md"
2
+ "name": "pi-crew",
3
+ "version": "0.11.0",
4
+ "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
+ "author": "baphuongna",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.0.0"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/baphuongna/pi-crew.git"
13
+ },
14
+ "homepage": "https://github.com/baphuongna/pi-crew#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/baphuongna/pi-crew/issues"
17
+ },
18
+ "type": "module",
19
+ "bin": {
20
+ "pi-crew": "install.mjs"
21
+ },
22
+ "keywords": [
23
+ "pi-package",
24
+ "pi",
25
+ "pi-coding-agent",
26
+ "teams",
27
+ "agents",
28
+ "multi-agent",
29
+ "orchestration"
30
+ ],
31
+ "files": [
32
+ "src/",
33
+ "!src/benchmark",
34
+ "dist/index.mjs",
35
+ "index.ts",
36
+ "install.mjs",
37
+ "agents/",
38
+ "teams/",
39
+ "workflows/",
40
+ "skills/**",
41
+ "themes/",
42
+ "assets/crew-vibes.ttf",
43
+ "scripts/clean-strip-types.mjs",
44
+ "scripts/postinstall.mjs",
45
+ "scripts/install-crew-vibes-font.mjs",
46
+ "scripts/build-crew-vibes-font.py",
47
+ "scripts/pty_probe.py",
48
+ "README.md",
49
+ "AGENTS.md",
50
+ "docs/actions-reference.md",
51
+ "docs/commands-reference.md",
52
+ "docs/troubleshooting.md",
53
+ "docs/usage.md",
54
+ "docs/architecture.md",
55
+ "docs/dynamic-workflows.md",
56
+ "docs/runtime-flow.md",
57
+ "docs/resource-formats.md",
58
+ "docs/publishing.md",
59
+ "docs/migration-v0.4-v0.5.md",
60
+ "tsconfig.json",
61
+ "schema.json",
62
+ "types/",
63
+ "CHANGELOG.md",
64
+ "LICENSE",
65
+ "NOTICE.md",
66
+ "scripts/resource-sampler.mjs",
67
+ "scripts/analyze-run.mjs",
68
+ "scripts/watch-agent-transcript.mjs"
69
+ ],
70
+ "scripts": {
71
+ "check": "npm run ci",
72
+ "ci": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
73
+ "ci:fast": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm run test:critical && npm pack --dry-run",
74
+ "check:lockfile-sync": "node scripts/check-lockfile-sync.mjs",
75
+ "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
76
+ "check:bundle-staleness": "node scripts/check-bundle-staleness.mjs",
77
+ "check:bundle-size": "node scripts/check-bundle-size.mjs",
78
+ "check:conflict-markers": "node scripts/check-conflict-markers.mjs",
79
+ "check:decision-drift": "node scripts/check-decision-drift.mjs",
80
+ "check:env-vars": "node scripts/check-env-vars.mjs",
81
+ "check:event-types": "node scripts/check-event-types-registry.mjs",
82
+ "typecheck": "tsc --noEmit && node --experimental-strip-types -e \"await import('./index.ts'); console.log('strip-types import ok')\"",
83
+ "lint": "biome check --linter-enabled=true --formatter-enabled=false --max-diagnostics=50 .",
84
+ "format:check": "biome format .",
85
+ "test": "npm run test:unit && npm run test:integration",
86
+ "test:full": "npm run test:unit && npm run test:integration && npm run test:integration:slow",
87
+ "test:unit": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=180000 --test-force-exit 'test/unit/**/*.test.ts'",
88
+ "test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/runtime/broker/crew-broker-handshake.test.ts test/unit/runtime/broker/crew-broker-stale-socket.test.ts test/unit/runtime/broker/crew-broker-feature-flag.test.ts test/unit/runtime/broker/crew-broker-server-gate.test.ts test/unit/runtime/broker/crew-broker-client-fallback.test.ts test/unit/runtime/broker/crew-broker-mailbox-observer.test.ts test/unit/runtime/broker/crew-broker-close-during-reconnect.test.ts test/unit/runtime/broker/crew-broker-steer-dedup.test.ts test/unit/runtime/broker/crew-broker-symlink-steering.test.ts test/unit/ui/keybinding-map.parity.test.ts test/unit/ui/pi-tui-dispatch-probe.test.ts test/unit/utils/session-utils-extract.test.ts test/unit/config/config-schema-sync.test.ts test/unit/runtime/child-pi/child-pi-env-spread.test.ts",
89
+ "test:watch": "node scripts/test-runner.mjs --watch --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
90
+ "test:integration": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=300000 test/integration/*.test.ts",
91
+ "test:integration:slow": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=600000 test/integration/slow/*.test.ts",
92
+ "test:system": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 --test-force-exit test/system/*.e2e.test.ts",
93
+ "test:smoke": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 test/smoke/*.smoke.ts",
94
+ "test:spike": "node scripts/test-runner.mjs --test-concurrency=2 --test-timeout=120000 --test-force-exit test/runtime/scratchpad/*.test.ts",
95
+ "test:functional": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=120000 test/functional/pi-crew-functional.test.ts",
96
+ "test:platform": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=60000 test/platform/*.test.ts",
97
+ "build:bundle": "node scripts/build-bundle.mjs",
98
+ "test:bundle": "node --experimental-strip-types --test --test-force-exit test/unit/bundle-load.test.ts",
99
+ "watch:bundle": "node scripts/watch-bundle.mjs",
100
+ "build:bundle:once": "node scripts/watch-bundle.mjs --once",
101
+ "dev": "node scripts/dev-runner.mjs",
102
+ "postinstall": "node scripts/postinstall.mjs",
103
+ "install:crew-font": "node scripts/install-crew-vibes-font.mjs",
104
+ "build:crew-font": "python scripts/build-crew-vibes-font.py",
105
+ "bench": "node scripts/run-bench.mjs",
106
+ "bench:check": "node scripts/bench-check.mjs",
107
+ "test:new": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
108
+ "test:changed": "node scripts/test-changed.mjs",
109
+ "bench:capture": "node scripts/run-bench.mjs && node -e \"require('node:fs').copyFileSync('test/bench/results.json','test/bench/baseline.json')\"",
110
+ "profile:startup": "node scripts/profile-startup.mjs",
111
+ "smoke:pi": "pi install .",
112
+ "smoke:release": "node scripts/release-smoke.mjs",
113
+ "prepack": "node scripts/clean-strip-types.mjs",
114
+ "test:fast": "npm run test:critical && npm run test:bundle",
115
+ "check:wc-gate": "node scripts/wc-gate.mjs"
116
+ },
117
+ "exports": {
118
+ "./schema.json": "./schema.json",
119
+ "./workflow": {
120
+ "types": "./types/dwf.d.ts"
121
+ }
122
+ },
123
+ "pi": {
124
+ "extensions": [
125
+ "./index.ts"
126
+ ],
127
+ "skills": [
128
+ "./skills"
129
+ ]
130
+ },
131
+ "peerDependencies": {
132
+ "@earendil-works/pi-agent-core": "*",
133
+ "@earendil-works/pi-ai": "*",
134
+ "@earendil-works/pi-coding-agent": "*",
135
+ "@earendil-works/pi-tui": "*"
136
+ },
137
+ "dependencies": {
138
+ "@sinclair/typebox": "^0.34.50",
139
+ "acorn": "^8.17.0",
140
+ "ajv": "^8.20.0",
141
+ "cli-highlight": "^2.1.11",
142
+ "diff": "^5.2.0",
143
+ "esbuild": "^0.28.1",
144
+ "jiti": "^2.7.0",
145
+ "yaml": "^2.9.0"
146
+ },
147
+ "devDependencies": {
148
+ "@biomejs/biome": "^2.5.3",
149
+ "@earendil-works/pi-agent-core": "^0.84.0",
150
+ "@earendil-works/pi-ai": "^0.84.0",
151
+ "@earendil-works/pi-coding-agent": "^0.84.0",
152
+ "@earendil-works/pi-tui": "^0.84.0",
153
+ "@types/node": "^25.9.5",
154
+ "tsx": "^4.23.0",
155
+ "typescript": "^7.0.2"
156
+ },
157
+ "peerDependenciesMeta": {
158
+ "@earendil-works/pi-agent-core": {
159
+ "optional": true
160
+ },
161
+ "@earendil-works/pi-ai": {
162
+ "optional": true
163
+ },
164
+ "@earendil-works/pi-coding-agent": {
165
+ "optional": true
166
+ },
167
+ "@earendil-works/pi-tui": {
168
+ "optional": true
169
+ }
170
+ },
171
+ "readmeFilename": "README.md"
171
172
  }
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // Watch a pi session transcript (JSONL stream events) and print one readable
3
+ // line per message — the "session subagent" pane companion for the Agents &
4
+ // Jobs browser (`p` key). Unlike `tail -F` on the raw file, every line here is
5
+ // a formatted summary (role badge + preview), so a tmux/herdr pane shows WHAT
6
+ // the agent is doing, not walls of JSON.
7
+ //
8
+ // Usage: node watch-agent-transcript.mjs <transcript.jsonl>
9
+ import { open as openFile } from "node:fs/promises";
10
+ import { basename } from "node:path";
11
+
12
+ const path = process.argv[2];
13
+ if (!path) {
14
+ console.error("usage: node watch-agent-transcript.mjs <transcript.jsonl>");
15
+ process.exit(1);
16
+ }
17
+
18
+ const WIDTH = Math.min(200, Number(process.env.COLUMNS || 160) || 160);
19
+
20
+ function oneLine(text) {
21
+ return String(text ?? "")
22
+ .replace(/\s+/g, " ")
23
+ .trim();
24
+ }
25
+
26
+ function clip(text, max = WIDTH - 10) {
27
+ const s = oneLine(text);
28
+ return s.length > max ? `${s.slice(0, max - 1)}…` : s;
29
+ }
30
+
31
+ function stamp(iso) {
32
+ const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/.exec(iso ?? "");
33
+ return m ? `${m[2]}` : "";
34
+ }
35
+
36
+ function renderEvent(evt) {
37
+ if (evt.type === "session") return "── session start ──";
38
+ if (evt.type === "agent_start") return "── agent start ──";
39
+ if (evt.type !== "message_end" || !evt.message) return null;
40
+ const msg = evt.message;
41
+ const who =
42
+ msg.role === "assistant" ? "🤖" : msg.role === "user" ? "👤" : msg.role === "toolResult" ? "📤" : (msg.role ?? "?");
43
+ const parts = Array.isArray(msg.content) ? msg.content : [{ type: "text", text: String(msg.content ?? "") }];
44
+ const out = [];
45
+ for (const part of parts) {
46
+ if (part.type === "thinking") out.push(`💭 ${clip(part.thinking, 120)}`);
47
+ else if (part.type === "text") out.push(`💬 ${clip(part.text)}`);
48
+ else if (part.type === "tool_use") {
49
+ const input = typeof part.input === "object" ? JSON.stringify(part.input) : String(part.input ?? "");
50
+ out.push(`🔧 ${part.name ?? "tool"} ${clip(input, 100)}`);
51
+ } else if (part.type === "tool_result") {
52
+ const content = typeof part.content === "string" ? part.content : JSON.stringify(part.content ?? "");
53
+ out.push(`📤 ${clip(content, 120)}`);
54
+ }
55
+ }
56
+ if (out.length === 0) return null;
57
+ return `${stamp(evt.timestamp ?? msg.timestamp) || " "} ${who} ${out.join(" │ ")}`;
58
+ }
59
+
60
+ // Clear the pane first — herdr/tmux panes boot via the shell, so the echoed
61
+ // prompt + full node command would otherwise sit above the watcher output.
62
+ process.stdout.write("\x1b[2J\x1b[H");
63
+ console.log(`── watching ${basename(path)} ──`);
64
+
65
+ let offset = 0;
66
+ let buffer = "";
67
+ const POLL_MS = 400;
68
+ let waitingSince = 0;
69
+ let lastWaitingLog = 0;
70
+
71
+ function logWaiting(now) {
72
+ if (!waitingSince) waitingSince = now;
73
+ if (now - lastWaitingLog >= 2500) {
74
+ const waitedS = Math.round((now - waitingSince) / 1000);
75
+ console.log(`… waiting for ${basename(path)} to appear (${waitedS}s)`);
76
+ lastWaitingLog = now;
77
+ }
78
+ }
79
+
80
+ async function pump() {
81
+ try {
82
+ const fh = await openFile(path, "r");
83
+ try {
84
+ const stat = await fh.stat();
85
+ if (waitingSince) {
86
+ console.log(`✓ transcript appeared (waited ${Math.round((Date.now() - waitingSince) / 1000)}s)`);
87
+ waitingSince = 0;
88
+ }
89
+ if (stat.size < offset) offset = 0; // truncated/rotated
90
+ if (stat.size > offset) {
91
+ const len = stat.size - offset;
92
+ const buf = Buffer.alloc(len);
93
+ await fh.read(buf, 0, len, offset);
94
+ offset += len;
95
+ buffer += buf.toString("utf8");
96
+ const lines = buffer.split("\n");
97
+ buffer = lines.pop() ?? "";
98
+ for (const line of lines) {
99
+ const trimmed = line.trim();
100
+ if (!trimmed) continue;
101
+ try {
102
+ const rendered = renderEvent(JSON.parse(trimmed));
103
+ if (rendered) console.log(rendered);
104
+ } catch {
105
+ console.log(`· ${clip(trimmed)}`);
106
+ }
107
+ }
108
+ }
109
+ } finally {
110
+ await fh.close();
111
+ }
112
+ } catch {
113
+ // File not there yet (cold-start window): visible waiting state so the
114
+ // pane explains itself instead of looking dead.
115
+ logWaiting(Date.now());
116
+ }
117
+ }
118
+
119
+ setInterval(pump, POLL_MS);
120
+ await pump();
@@ -34,6 +34,8 @@ export const CREW_PHRASES: ReadonlyArray<{ phrase: string; command: string }> =
34
34
  { phrase: "crew commands", command: "/team-help" },
35
35
  { phrase: "crew doctor", command: "/team-doctor" },
36
36
  { phrase: "crew diagnose", command: "/team-doctor" },
37
+ { phrase: "crew schedule", command: "/schedules" },
38
+ { phrase: "scheduled jobs", command: "/schedules" },
37
39
  { phrase: "teams", command: "/teams" },
38
40
  ];
39
41
 
@@ -4,12 +4,11 @@ import { getCrewEnv } from "../../config/env-vars.ts";
4
4
  import { hasCrewFontFile, isWebTerminal } from "./font-detect.ts";
5
5
 
6
6
  /**
7
- * Self-contained config for the crew-vibes module (speed + capacity meters).
7
+ * Self-contained config for the crew-vibes module (provider quota).
8
8
  * Stored in its own JSON file so it never touches the strict typebox schema
9
9
  * used by the rest of pi-crew.
10
10
  */
11
11
 
12
- export const SPEED_STATUS_ID = "pi-crew-speed";
13
12
  export const CAPACITY_STATUS_ID = "pi-crew-bar";
14
13
  export const PROVIDER_STATUS_ID = "pi-crew-bar";
15
14
 
@@ -23,21 +22,6 @@ export function configPath(): string {
23
22
 
24
23
  export type TokenDisplay = "off" | "tokens" | "percentage";
25
24
 
26
- export interface SpeedConfig {
27
- enabled: boolean;
28
- footer: boolean;
29
- indicator: boolean;
30
- label: string;
31
- renderIntervalMs: number;
32
- slidingWindowMs: number;
33
- minReliableDurationMs: number;
34
- maxDisplayTokS: number;
35
- defaultIntervalMs: number;
36
- minIntervalMs: number;
37
- maxIntervalMs: number;
38
- scale: number;
39
- }
40
-
41
25
  export interface CapacityConfig {
42
26
  enabled: boolean;
43
27
  tokenDisplay: TokenDisplay;
@@ -51,26 +35,11 @@ export interface CapacityConfig {
51
35
 
52
36
  export interface CrewVibesConfig {
53
37
  enabled: boolean;
54
- speed: SpeedConfig;
55
38
  capacity: CapacityConfig;
56
39
  }
57
40
 
58
41
  export const DEFAULT_CONFIG: CrewVibesConfig = {
59
42
  enabled: true,
60
- speed: {
61
- enabled: true,
62
- footer: false,
63
- indicator: true,
64
- label: "tok/s",
65
- renderIntervalMs: 250,
66
- slidingWindowMs: 1000,
67
- minReliableDurationMs: 1000,
68
- maxDisplayTokS: 500,
69
- defaultIntervalMs: 167,
70
- minIntervalMs: 50,
71
- maxIntervalMs: 250,
72
- scale: 6000,
73
- },
74
43
  capacity: {
75
44
  enabled: true,
76
45
  tokenDisplay: "tokens",
@@ -133,29 +102,6 @@ function tokenDisplayFrom(raw: unknown, fallback: TokenDisplay): TokenDisplay {
133
102
  return raw === "off" || raw === "tokens" || raw === "percentage" ? raw : fallback;
134
103
  }
135
104
 
136
- function normalizeSpeed(raw: unknown): SpeedConfig {
137
- const input = asRecord(raw);
138
- const speed: SpeedConfig = {
139
- enabled: boolFrom(input.enabled, DEFAULT_CONFIG.speed.enabled),
140
- footer: boolFrom(input.footer, DEFAULT_CONFIG.speed.footer),
141
- indicator: boolFrom(input.indicator, DEFAULT_CONFIG.speed.indicator),
142
- label: stringFrom(input.label, DEFAULT_CONFIG.speed.label),
143
- renderIntervalMs: positiveFrom(input.renderIntervalMs, DEFAULT_CONFIG.speed.renderIntervalMs),
144
- slidingWindowMs: positiveFrom(input.slidingWindowMs, DEFAULT_CONFIG.speed.slidingWindowMs),
145
- minReliableDurationMs: positiveFrom(input.minReliableDurationMs, DEFAULT_CONFIG.speed.minReliableDurationMs),
146
- maxDisplayTokS: positiveFrom(input.maxDisplayTokS, DEFAULT_CONFIG.speed.maxDisplayTokS),
147
- defaultIntervalMs: positiveFrom(input.defaultIntervalMs, DEFAULT_CONFIG.speed.defaultIntervalMs),
148
- minIntervalMs: positiveFrom(input.minIntervalMs, DEFAULT_CONFIG.speed.minIntervalMs),
149
- maxIntervalMs: positiveFrom(input.maxIntervalMs, DEFAULT_CONFIG.speed.maxIntervalMs),
150
- scale: positiveFrom(input.scale, DEFAULT_CONFIG.speed.scale),
151
- };
152
- if (speed.minIntervalMs > speed.maxIntervalMs) {
153
- speed.minIntervalMs = DEFAULT_CONFIG.speed.minIntervalMs;
154
- speed.maxIntervalMs = DEFAULT_CONFIG.speed.maxIntervalMs;
155
- }
156
- return speed;
157
- }
158
-
159
105
  function normalizeCapacity(raw: unknown): CapacityConfig {
160
106
  const input = asRecord(raw);
161
107
  return {
@@ -174,7 +120,6 @@ export function normalizeConfig(raw: unknown): CrewVibesConfig {
174
120
  const input = asRecord(raw);
175
121
  return {
176
122
  enabled: boolFrom(input.enabled, DEFAULT_CONFIG.enabled),
177
- speed: normalizeSpeed(input.speed),
178
123
  capacity: normalizeCapacity(input.capacity),
179
124
  };
180
125
  }