@damian87/omp 0.13.0 → 0.15.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.
- package/.github/skills/schedule/SKILL.md +27 -2
- package/.github/skills/verify-byok/SKILL.md +50 -0
- package/README.md +91 -5
- package/catalog/capabilities.json +23 -0
- package/catalog/skills-general.json +25 -0
- package/dist/src/cli.js +147 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/copilot/update-prompt.d.ts +62 -0
- package/dist/src/copilot/update-prompt.js +149 -0
- package/dist/src/copilot/update-prompt.js.map +1 -0
- package/dist/src/gateway/desktop-notify.d.ts +56 -0
- package/dist/src/gateway/desktop-notify.js +183 -0
- package/dist/src/gateway/desktop-notify.js.map +1 -0
- package/dist/src/schedule/commands.d.ts +13 -0
- package/dist/src/schedule/commands.js +24 -1
- package/dist/src/schedule/commands.js.map +1 -1
- package/dist/src/schedule/deep-link.d.ts +18 -0
- package/dist/src/schedule/deep-link.js +41 -0
- package/dist/src/schedule/deep-link.js.map +1 -0
- package/dist/src/schedule/runner.d.ts +10 -0
- package/dist/src/schedule/runner.js +36 -0
- package/dist/src/schedule/runner.js.map +1 -1
- package/dist/src/schedule/types.d.ts +16 -0
- package/docs/research/2026-06-22-schedule-desktop-notifications.md +193 -0
- package/package.json +4 -2
- package/plugin.json +1 -1
|
@@ -37,14 +37,39 @@ the `omp schedule …` commands on the user's behalf.
|
|
|
37
37
|
```bash
|
|
38
38
|
omp schedule add --id <id> --cron "<expr>" --prompt "<text>" \
|
|
39
39
|
[--allow-all-tools] [--cwd <dir>] [--model <m>] [--timeout <ms>] \
|
|
40
|
-
[--max-runs <n>] [--ttl-hours <h>]
|
|
40
|
+
[--max-runs <n>] [--ttl-hours <h>] \
|
|
41
|
+
[--notify-target slack:<ID>] [--notify-desktop] [--notify-open-omp] --json
|
|
41
42
|
```
|
|
42
43
|
Jobs auto-expire after 72h by default (`--ttl-hours`) — set a longer TTL or a
|
|
43
44
|
`--max-runs` cap as needed. Use `--dry-run` to preview the OS entry first.
|
|
45
|
+
|
|
46
|
+
**End-of-run notifications (all opt-in, default off; failures never affect the job):**
|
|
47
|
+
- `--notify-target slack:<C|G|D|U…>` — post the run summary to Slack (needs
|
|
48
|
+
`SLACK_BOT_TOKEN`; falls back to `SLACK_HOME_CHANNEL` when no target).
|
|
49
|
+
- `--notify-desktop` — fire a native desktop notification (job id + status +
|
|
50
|
+
one-line summary). Transport per OS: **macOS → `osascript`** (the only path
|
|
51
|
+
that reliably displays on Sequoia; shown under "Script Editor", **not
|
|
52
|
+
clickable**); **Linux/Windows → node-notifier** (notify-send / SnoreToast).
|
|
53
|
+
- `--notify-open-omp` — make the notification's click open an interactive `omp`
|
|
54
|
+
session in the schedule state root (the SessionStart banner then surfaces the
|
|
55
|
+
latest result). **Requires a click-capable transport**, which on macOS means a
|
|
56
|
+
system `terminal-notifier` enabled via `OMP_NOTIFY_USE_TERMINAL_NOTIFIER=1`
|
|
57
|
+
(`brew install terminal-notifier`). Note: terminal-notifier does **not**
|
|
58
|
+
display on some macOS Sequoia builds — if notifications stop appearing,
|
|
59
|
+
unset that env to fall back to osascript (display-only). Disable desktop
|
|
60
|
+
notifications entirely with `OMP_DISABLE_DESKTOP_NOTIFY=1`.
|
|
61
|
+
|
|
62
|
+
Slack and desktop are independent and can be combined on one job.
|
|
44
63
|
4. **Confirm** by listing: `omp schedule list --json`.
|
|
45
64
|
5. **Trigger now** to test it once: `omp schedule run-now --id <id>`.
|
|
46
65
|
6. **Inspect** results: `omp schedule status --id <id> --json` (recent results
|
|
47
|
-
are also surfaced automatically at the start of future sessions).
|
|
66
|
+
are also surfaced automatically at the start of future sessions). To pull up
|
|
67
|
+
the latest run with full context by id — e.g. after seeing a desktop
|
|
68
|
+
notification titled `schedule: <id>` — run `omp schedule open <id>`, which
|
|
69
|
+
prints the latest status, summary, and the full captured output. Add `--tmux`
|
|
70
|
+
to instead drop into an interactive `omp` session (auto-wrapped in tmux) rooted
|
|
71
|
+
at the project; the SessionStart banner surfaces *recent* scheduled runs there
|
|
72
|
+
(not pinned to `<id>`) — for this id's exact output, use plain `omp schedule open <id>`.
|
|
48
73
|
7. **Remove** when done: `omp schedule remove --id <id>` (fully uninstalls the OS
|
|
49
74
|
entry; do NOT delete `.omp/state/schedule/` by hand).
|
|
50
75
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-byok
|
|
3
|
+
description: Verify an omp change end-to-end — static gate (build/tsc/tests/lint/catalog) plus a live BYOK run that drives Copilot/teams on a real model and captures evidence. Use before merging any PR that touches hooks, comms, team, launch, or skills.
|
|
4
|
+
argument-hint: "<branch-or-PR to verify>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# verify-byok — evidence-based verification for omp changes
|
|
8
|
+
|
|
9
|
+
**Invocation:** `/verify-byok <branch-or-PR>`
|
|
10
|
+
|
|
11
|
+
Prove a change actually works, not just that it compiles. Two gates: a **static** gate (cheap, always) and a **live BYOK** gate (drives real Copilot CLI sessions on a Bring-Your-Own-Key model so no GitHub quota is needed). Report **real command output**, never claims. Be honest about what you did NOT test.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
Before merging any PR — especially ones touching `hooks/`, `scripts/*.mjs`, `src/comms`, `src/team`, `src/copilot/launch`, `src/copilot/trust`, or `.github/skills`. Anything whose behavior only appears at runtime (tmux submit keys, hook firing, trust dialog, model tool-calls) needs the live gate.
|
|
15
|
+
|
|
16
|
+
## Prerequisites (one-time, persistent)
|
|
17
|
+
- `~/.omp/.env` holds BYOK: `COPILOT_PROVIDER_BASE_URL=https://openrouter.ai/api/v1`, `COPILOT_PROVIDER_TYPE=openai`, `COPILOT_PROVIDER_API_KEY=…`, `COPILOT_MODEL=…`, `COPILOT_PROVIDER_MODEL_ID=…`. omp auto-loads it; `~/.zshrc` sourcing it makes direct `copilot` BYOK too.
|
|
18
|
+
- Free model `openai/gpt-oss-120b:free` exercises plumbing but botches structured edits/`apply_patch` and narrates instead of acting — use a capable model (`anthropic/claude-sonnet-4.5` + `COPILOT_PROVIDER_MODEL_ID=claude-sonnet-4`) when verifying that real tasks complete.
|
|
19
|
+
- Folder trust: a session only skips the "Do you trust this folder?" dialog if the cwd is in `~/.copilot/config.json#trustedFolders` (`--yolo` does NOT skip it). `omp` auto-adds it on launch; `/private/tmp` is pre-trusted.
|
|
20
|
+
|
|
21
|
+
## Static gate (run from the branch worktree)
|
|
22
|
+
```bash
|
|
23
|
+
npm run build # tsc
|
|
24
|
+
npx tsc -p tsconfig.json --noEmit # type-clean
|
|
25
|
+
npx vitest run # ALL must pass — note the count
|
|
26
|
+
node dist/src/cli.js lint:skills --root . # 0 issues
|
|
27
|
+
node dist/src/cli.js catalog validate # PASS
|
|
28
|
+
```
|
|
29
|
+
A merged-overlap PR? Confirm the test count rose (new tests survived the rebase) and old behavior's tests still pass.
|
|
30
|
+
|
|
31
|
+
## Live BYOK gate
|
|
32
|
+
Build the branch CLI first (`npm run build`); installed `omp` may be older. Drive Copilot inside tmux (each pane is a pty). **Submit with the `Enter` key name, never `C-m`** — Copilot ≥1.0.61 ignores `C-m`.
|
|
33
|
+
|
|
34
|
+
1. **Launch + reach a model:** `tmux new-session -d -s vbyok -c <dir>` → send `omp` (or `omp --madmax` for bypass) → wait for `/ commands`. Confirm the model line shows your BYOK model and `Session: 0 AIC used` (0 AI Credits = not on GitHub quota). Send a prompt, send `Enter`, confirm a real reply.
|
|
35
|
+
2. **Exercise the changed surface** — pick the user-visible behavior the change claims:
|
|
36
|
+
- hooks → tail `<cwd>/.omp/state/hooks.log` for the events firing;
|
|
37
|
+
- team → run a 2-worker team, confirm files/artifacts on disk and the script's `🎉 All N agents completed!` (read the raw stdout, not the leader's summary);
|
|
38
|
+
- cost/minify → pipe a postToolUse payload to `scripts/post-tool-use.mjs` and check `modifiedResult` + raw preserved + ledger `savedTokens` + `omp cost`.
|
|
39
|
+
3. **Capture evidence**: `tmux capture-pane -p -t <session>` plus the on-disk artifact. Keep transcripts.
|
|
40
|
+
|
|
41
|
+
## Reporting (mandatory)
|
|
42
|
+
- PASS/FAIL table with the actual captured output (model line, reply, artifact contents, ledger numbers).
|
|
43
|
+
- State the model used and that no GitHub quota was consumed.
|
|
44
|
+
- **Honest limitations**: heuristics (e.g. pane-scrape completion detection), model-quality effects (free model botching edits), anything gated/conditional, and anything you did NOT exercise.
|
|
45
|
+
- Never report "works" without exercising the user-visible surface — verify before asserting.
|
|
46
|
+
|
|
47
|
+
## Anti-patterns
|
|
48
|
+
- Trusting a subagent's "Complete." — re-run the gate yourself.
|
|
49
|
+
- `C-m` to submit to Copilot (use `Enter`).
|
|
50
|
+
- Concluding from a green build alone — runtime bugs (env propagation, trust dialog, submit keys) pass the build and fail live.
|
package/README.md
CHANGED
|
@@ -57,6 +57,83 @@ That's it.
|
|
|
57
57
|
- **Chat bridge** — `omp gateway` runs long-lived chat connectors (Slack today, more next) so you can DM Copilot from anywhere
|
|
58
58
|
- **Lifecycle hooks** — `sessionStart`, `userPromptSubmitted`, `preToolUse`, `postToolUse`, `postToolUseFailure`, `sessionEnd`, `errorOccurred`
|
|
59
59
|
- **Doctor included** — `omp doctor` verifies plugin manifest, skills discovery, hooks, and the underlying `copilot` CLI in one shot
|
|
60
|
+
- **Self-update** — when a newer release is published, `omp` (and `omp --version`) offers to update in a TTY; `omp update` self-updates the CLI and refreshes the Copilot plugin so both stay in lockstep. Never prompts in CI / `--json` / non-TTY; opt out with `OMP_NO_UPDATE_CHECK=1`
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
|
|
66
|
+
oh-my-copilot is two things working together: a **shell CLI** (`omp`) that wraps and scripts the GitHub Copilot CLI, and a **Copilot plugin** that ships in-session slash skills, custom agents, and native lifecycle hooks. Both feed the same orchestration modes, the same file-based memory, and the same `.omp` state — so whether you drive from the shell, an in-session `/skill`, Slack, or cron, you hit one coherent system.
|
|
67
|
+
|
|
68
|
+
```mermaid
|
|
69
|
+
flowchart TB
|
|
70
|
+
subgraph Surfaces["① Where you drive it"]
|
|
71
|
+
SH["Shell CLI<br/><code>omp</code> / <code>omp --madmax</code>"]
|
|
72
|
+
IDE["In-session <code>/skills</code><br/>(Copilot plugin)"]
|
|
73
|
+
GW["Gateway<br/>Slack / messaging"]
|
|
74
|
+
CRON["Cron<br/><code>omp schedule</code>"]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
subgraph Copilot["② GitHub Copilot CLI (wrapped)"]
|
|
78
|
+
CP["copilot session"]
|
|
79
|
+
HK["Lifecycle hooks<br/>sessionStart · sessionEnd · agentStop<br/>pre/postToolUse · userPromptSubmitted"]
|
|
80
|
+
PS["Plugin skills + agents<br/>ralph · ralplan · team · council<br/>code-review · tdd · research · …"]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
subgraph Orchestration["③ Orchestration modes"]
|
|
84
|
+
direction LR
|
|
85
|
+
RALPH["ralph<br/>PRD verify/fix loop"]
|
|
86
|
+
UW["ultrawork<br/>parallel fan-out"]
|
|
87
|
+
UQA["ultraqa<br/>QA cycles"]
|
|
88
|
+
AP["autopilot"]
|
|
89
|
+
TEAM["team<br/>tmux multi-agent"]
|
|
90
|
+
COUNCIL["council<br/>weighted consensus"]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
subgraph Learning["④ Memory & learning loop"]
|
|
94
|
+
direction LR
|
|
95
|
+
MR["memory-review<br/>cheap model, end-of-session"]
|
|
96
|
+
PM["project-memory<br/>directives + notes"]
|
|
97
|
+
SE["self-evolve<br/>skill drafts"]
|
|
98
|
+
DL["daily-log"]
|
|
99
|
+
IM["instructions-memory<br/>→ copilot-instructions.md"]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
subgraph Store["⑤ Config & state"]
|
|
103
|
+
direction LR
|
|
104
|
+
G["~/.omp<br/>global config + .env"]
|
|
105
|
+
P[".omp/<br/>project config · memory<br/>cost · trace · mode-state"]
|
|
106
|
+
SST["~/.copilot/session-state<br/>events.jsonl transcripts"]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
SH --> CP
|
|
110
|
+
GW --> CP
|
|
111
|
+
CRON --> CP
|
|
112
|
+
IDE --> PS
|
|
113
|
+
CP --> HK
|
|
114
|
+
PS --> Orchestration
|
|
115
|
+
SH --> Orchestration
|
|
116
|
+
HK -- "agentStop drives the loop" --> Orchestration
|
|
117
|
+
Orchestration --> P
|
|
118
|
+
|
|
119
|
+
HK -- "sessionEnd (detached)" --> MR
|
|
120
|
+
SH -- "wrapper fallback (headless -p)" --> MR
|
|
121
|
+
MR -- reads transcript --> SST
|
|
122
|
+
MR -- "facts" --> PM
|
|
123
|
+
MR -- "procedures" --> SE
|
|
124
|
+
MR -- "rules (gated)" --> PM
|
|
125
|
+
PM --> IM
|
|
126
|
+
DL --> IM
|
|
127
|
+
IM -- "injected next session" --> CP
|
|
128
|
+
|
|
129
|
+
G -. config .-> MR
|
|
130
|
+
P -. config .-> MR
|
|
131
|
+
Orchestration -. cost/trace .-> P
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**The flow:** you launch a Copilot session from any surface ①. It runs through the wrapped Copilot CLI ②, where plugin hooks and skills can spin up orchestration modes ③ (ralph/ultrawork/ultraqa/team/council). When the session ends, the **learning loop** ④ fires — a cheap model reviews the transcript and writes durable **notes**, gated **directives**, and **skill drafts**, which `instructions-memory` injects into the *next* session so it starts smarter. Everything persists in layered config/state ⑤: global `~/.omp`, per-project `.omp/`, and Copilot's own session transcripts.
|
|
135
|
+
|
|
136
|
+
> The learning loop is **opt-in** (`omp config set memory-mode on`) and runs on a cheap model (`gpt-5-mini` by default) — the expensive reasoning already happened in your main session. See [docs/memory-mode.md](docs/memory-mode.md).
|
|
60
137
|
|
|
61
138
|
---
|
|
62
139
|
|
|
@@ -182,7 +259,8 @@ Notes:
|
|
|
182
259
|
|
|
183
260
|
```bash
|
|
184
261
|
omp --help
|
|
185
|
-
omp version
|
|
262
|
+
omp version # prints version; in a TTY, offers to self-update if one is available
|
|
263
|
+
omp update # self-update the CLI (npm) + refresh the Copilot plugin
|
|
186
264
|
omp doctor # verify install + copilot binary
|
|
187
265
|
omp list # show discovered skills and agents
|
|
188
266
|
omp setup [--dry-run] [--scope project|user]
|
|
@@ -205,9 +283,10 @@ omp gateway notify --text "<msg>" [--target slack:C…|G…|D…|U… [:thread_t
|
|
|
205
283
|
omp slack serve # deprecated alias of `gateway serve --only slack`
|
|
206
284
|
omp slack doctor [--json] # deprecated alias of `gateway status --only slack`
|
|
207
285
|
omp env init [--force] # write ~/.omp/.env (interactive Slack token setup)
|
|
208
|
-
omp schedule add --id <id> --cron "*/15 * * * *" --prompt "<text>" [--allow-all-tools] [--cwd <dir>] [--model <m>] [--timeout <ms>] [--max-runs N] [--ttl-hours H] [--notify-target slack:U0123ABCD] [--dry-run]
|
|
286
|
+
omp schedule add --id <id> --cron "*/15 * * * *" --prompt "<text>" [--allow-all-tools] [--cwd <dir>] [--model <m>] [--timeout <ms>] [--max-runs N] [--ttl-hours H] [--notify-target slack:U0123ABCD] [--notify-desktop] [--notify-open-omp] [--dry-run]
|
|
209
287
|
omp schedule list # registered jobs + OS-install status
|
|
210
288
|
omp schedule status <id> # last run + result summary
|
|
289
|
+
omp schedule open <id> [--tmux] # print this id's latest status + full output (--tmux: open an omp session)
|
|
211
290
|
omp schedule run-now <id> # trigger one run immediately
|
|
212
291
|
omp schedule remove <id> # uninstall the OS entry + delete the job
|
|
213
292
|
omp goal set "<objective>" | read [--json]
|
|
@@ -227,6 +306,8 @@ Environment overrides:
|
|
|
227
306
|
- `OMP_COPILOT_BIN` — alternate `copilot` binary
|
|
228
307
|
- `OMP_BIN` — absolute path to the `omp` wrapper written into OS-scheduler entries (overrides `which omp`)
|
|
229
308
|
- `OMP_SKIP_USER_ENV` — when `1`, skip auto-loading `~/.omp/.env` (useful for hermetic CI runs)
|
|
309
|
+
- `OMP_DISABLE_DESKTOP_NOTIFY` — when set, suppress all `--notify-desktop` notifications
|
|
310
|
+
- `OMP_NOTIFY_USE_TERMINAL_NOTIFIER` — when set (+ a system `terminal-notifier` on PATH), use it for desktop notifications so the click can open omp; otherwise macOS uses `osascript` (display-only)
|
|
230
311
|
|
|
231
312
|
**Scheduled jobs** register a durable per-job entry with the OS scheduler (macOS launchd,
|
|
232
313
|
Linux systemd-user timers, or a managed `crontab` block as a cross-platform fallback) that
|
|
@@ -234,8 +315,13 @@ invokes `omp schedule run --id <id>` on the cron schedule. Each tick spawns a fr
|
|
|
234
315
|
session; overlapping runs are locked out and every run is killed at its `--timeout`
|
|
235
316
|
(default 5 min). Jobs default to **read-only** (`--allow-all-tools` is opt-in and prints a
|
|
236
317
|
warning) and auto-expire after 72h unless `--ttl-hours`/`--max-runs` say otherwise. Recent
|
|
237
|
-
run results are surfaced automatically at the start of new Copilot sessions
|
|
238
|
-
`omp schedule
|
|
318
|
+
run results are surfaced automatically at the start of new Copilot sessions, and
|
|
319
|
+
`omp schedule open <id>` prints any job's latest status + full captured output on demand.
|
|
320
|
+
Opt into end-of-run **notifications** (default off; failures never affect the job): `--notify-target slack:…`
|
|
321
|
+
posts to Slack, and `--notify-desktop` fires a native notification (job id + status + one-line
|
|
322
|
+
summary) — on macOS via `osascript` (display-only; for a clickable notification that opens omp,
|
|
323
|
+
set `OMP_NOTIFY_USE_TERMINAL_NOTIFIER=1` with a system `terminal-notifier` and add `--notify-open-omp`).
|
|
324
|
+
Always use `omp schedule remove`, never delete `.omp/state/schedule/` by hand, so the OS entry is
|
|
239
325
|
uninstalled cleanly.
|
|
240
326
|
|
|
241
327
|
### Chat bridge: drive Copilot from Slack
|
|
@@ -269,7 +355,7 @@ omp grows in vertical slices. Items aren't pinned to specific semver versions
|
|
|
269
355
|
|
|
270
356
|
### Already shipped
|
|
271
357
|
|
|
272
|
-
- **Scheduled tasks** (v0.6.0) — durable local cron: `omp schedule add --id pr-watch --cron "*/15 * * * *" --prompt "…"` plus `/schedule` in-session. Each job registers an OS-scheduler entry (launchd / systemd-user / crontab fallback) that fires a fresh agent session, survives reboot, locks out overlap, and surfaces results at the next session start.
|
|
358
|
+
- **Scheduled tasks** (v0.6.0) — durable local cron: `omp schedule add --id pr-watch --cron "*/15 * * * *" --prompt "…"` plus `/schedule` in-session. Each job registers an OS-scheduler entry (launchd / systemd-user / crontab fallback) that fires a fresh agent session, survives reboot, locks out overlap, and surfaces results at the next session start. Opt-in end-of-run notifications (`--notify-target` Slack, `--notify-desktop` native) and `omp schedule open <id>` to pull up a run's full output by id.
|
|
273
359
|
- **Chat bridge — Slack inbound** (v0.8.0) — `omp gateway` runs long-lived chat connectors that forward messages into a running Copilot tmux session and post replies back. Slack is the first connector (Socket Mode, no public URL). `omp env init` walks you through one-time token setup; tokens live in `~/.omp/.env` (auto-loaded on every invocation). See [`docs/slack-setup.md`](docs/slack-setup.md).
|
|
274
360
|
- **Slack outbound — `omp gateway notify`** — stateless REST `chat.postMessage` from any process (cron `--notify-target`, in-session `/slack <message>`, ad-hoc `omp gateway notify --text "..."`). Default destination from `SLACK_HOME_CHANNEL`; explicit `--target slack:C…/G…/D…/U…` overrides; `U…` auto-resolves to a DM via `conversations.open`.
|
|
275
361
|
- **Weighted-consensus council** — multi-model council with role weights + minority report. Via `omp council` or `/weighted-consensus`.
|
|
@@ -908,6 +908,29 @@
|
|
|
908
908
|
"notes": "Use /slack from .github/skills/slack/SKILL.md."
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
"id": "verify-byok",
|
|
914
|
+
"name": "verify-byok",
|
|
915
|
+
"title": "BYOK verification",
|
|
916
|
+
"category": "verification",
|
|
917
|
+
"summary": "Static + live BYOK verification of omp changes with evidence capture.",
|
|
918
|
+
"notes": "Maintainer verification skill: static gate plus a live Copilot/team run on a BYOK model.",
|
|
919
|
+
"defaultCommand": "verify-byok",
|
|
920
|
+
"phase1": true,
|
|
921
|
+
"sourceSkill": "verify-byok",
|
|
922
|
+
"providers": {
|
|
923
|
+
"copilot": "supported"
|
|
924
|
+
},
|
|
925
|
+
"support": {
|
|
926
|
+
"copilot": "native"
|
|
927
|
+
},
|
|
928
|
+
"providerSupport": {
|
|
929
|
+
"copilot": {
|
|
930
|
+
"state": "native",
|
|
931
|
+
"notes": "Use /verify-byok from .github/skills/verify-byok/SKILL.md."
|
|
932
|
+
}
|
|
933
|
+
}
|
|
911
934
|
}
|
|
912
935
|
]
|
|
913
936
|
}
|
|
@@ -528,6 +528,31 @@
|
|
|
528
528
|
},
|
|
529
529
|
"projection": "project-skill",
|
|
530
530
|
"phase1": true
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"name": "verify-byok",
|
|
534
|
+
"capabilityId": "verify-byok",
|
|
535
|
+
"capabilityIds": [
|
|
536
|
+
"verify-byok"
|
|
537
|
+
],
|
|
538
|
+
"source": ".github/skills/verify-byok/SKILL.md",
|
|
539
|
+
"sourcePath": ".github/skills/verify-byok/SKILL.md",
|
|
540
|
+
"canonicalPath": ".github/skills/verify-byok/SKILL.md",
|
|
541
|
+
"description": "Verify an omp change with a static gate plus a live BYOK run.",
|
|
542
|
+
"summary": "Verify an omp change with a static gate plus a live BYOK run.",
|
|
543
|
+
"support": "project-skill",
|
|
544
|
+
"aliases": [],
|
|
545
|
+
"slashCommands": [
|
|
546
|
+
"verify-byok"
|
|
547
|
+
],
|
|
548
|
+
"projections": {
|
|
549
|
+
"copilot": {
|
|
550
|
+
"command": "/verify-byok",
|
|
551
|
+
"state": "supported"
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
"projection": "project-skill",
|
|
555
|
+
"phase1": true
|
|
531
556
|
}
|
|
532
557
|
]
|
|
533
558
|
}
|
package/dist/src/cli.js
CHANGED
|
@@ -16,6 +16,72 @@ function flagValue(args, flag) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
return args[index + 1];
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Interactive only when both streams are TTYs, we're not emitting JSON, and
|
|
21
|
+
* we're not in CI (which may allocate a pseudo-TTY but must never block on a
|
|
22
|
+
* prompt). Honors the same no-block contract as update-prompt.ts.
|
|
23
|
+
*/
|
|
24
|
+
function isInteractive(json) {
|
|
25
|
+
if (process.env.CI?.trim())
|
|
26
|
+
return false;
|
|
27
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY) && !json;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Provide an UpdatePromptIO backed by readline. The interface is created lazily
|
|
31
|
+
* on the first `ask` (so no-update launches never touch the TTY) and always
|
|
32
|
+
* closed before we return, freeing stdin for a subsequent copilot launch.
|
|
33
|
+
*/
|
|
34
|
+
async function withUpdateIO(fn) {
|
|
35
|
+
let rl;
|
|
36
|
+
const io = {
|
|
37
|
+
print: (line) => console.log(line),
|
|
38
|
+
ask: async (prompt) => {
|
|
39
|
+
if (!rl) {
|
|
40
|
+
const readline = await import("node:readline/promises");
|
|
41
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
}
|
|
43
|
+
const answer = await rl.question(prompt);
|
|
44
|
+
// Release stdin immediately so the update/plugin child processes that
|
|
45
|
+
// follow (stdio: "inherit") never contend with an open readline.
|
|
46
|
+
rl.close();
|
|
47
|
+
rl = undefined;
|
|
48
|
+
return answer;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
return await fn(io);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
rl?.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Re-exec the freshly-installed omp after a self-update so the launch runs the
|
|
60
|
+
* new version. `npm i -g` overwrote the entrypoint at `process.argv[1]` in
|
|
61
|
+
* place, so re-running it with the same node executes the new code. The child
|
|
62
|
+
* inherits stdio (so copilot's TUI works) and gets `OMP_NO_UPDATE_CHECK=1` to
|
|
63
|
+
* guarantee it can't loop back into the update prompt. Returns null when the
|
|
64
|
+
* entrypoint can't be resolved, so the caller falls back to a normal launch.
|
|
65
|
+
*/
|
|
66
|
+
async function reexecAfterUpdate(argv) {
|
|
67
|
+
const entry = process.argv[1];
|
|
68
|
+
if (!entry)
|
|
69
|
+
return undefined;
|
|
70
|
+
const { spawn } = await import("node:child_process");
|
|
71
|
+
return await new Promise((resolve) => {
|
|
72
|
+
const child = spawn(process.execPath, [entry, ...argv], {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
env: { ...process.env, OMP_NO_UPDATE_CHECK: "1" },
|
|
75
|
+
});
|
|
76
|
+
child.on("error", () => resolve({ ok: false, exitCode: 127, message: "re-exec failed" }));
|
|
77
|
+
child.on("close", (code) => {
|
|
78
|
+
// Match launchCopilot's convention: a signal-terminated child yields a
|
|
79
|
+
// null code — treat that as a non-zero exit, not success.
|
|
80
|
+
const exitCode = typeof code === "number" ? code : 1;
|
|
81
|
+
resolve({ ok: exitCode === 0, exitCode, message: `relaunched omp exit=${exitCode}` });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
19
85
|
function printResult(result, json) {
|
|
20
86
|
if (json || typeof result.output === "object") {
|
|
21
87
|
console.log(JSON.stringify(result.output ?? { ok: result.ok, message: result.message }, null, 2));
|
|
@@ -26,7 +92,8 @@ function printResult(result, json) {
|
|
|
26
92
|
}
|
|
27
93
|
}
|
|
28
94
|
function help() {
|
|
29
|
-
return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot] [--hooks]\n cost [--json] [--session <id>] [--days <n>]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n comms status [--session <name>] [--json] (is copilot on + online? auto-discovers session)\n comms send --text "<prompt>" [--force] [--session <name>] [--json]\n comms recv [--wait] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n comms ask --text "<prompt>" [--force] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n gateway serve [--only <name>[,<name>]] (run all configured connectors; today: slack)\n gateway status [--json] [--only <name>[,...]] (per-connector readiness; no sockets opened)\n gateway doctor [--json] [--only <name>[,...]] (alias for 'gateway status')\n gateway notify --text "<msg>" [--target slack:C\\|D\\|G\\|U... [:thread_ts]] [--thread-ts <ts>] [--json]\n (one-shot outbound Slack post; falls back to SLACK_HOME_CHANNEL)\n slack serve (deprecated alias for 'gateway serve --only slack')\n slack doctor [--json] (deprecated alias for 'gateway status --only slack')\n env init [--force] (interactive: write ~/.omp/.env with Slack tokens + optional SLACK_HOME_CHANNEL)\n non-interactive: set OMP_INIT_BOT_TOKEN/OMP_INIT_APP_TOKEN/OMP_INIT_HOME_CHANNEL\n (env vars preferred over --bot-token/--app-token/--home-channel flags)\n (--session is optional when exactly one omp-<digits> tmux session is running)\n${registeredCommandHelpLines().join("\n")}\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron "<expr>" --prompt "<text>" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--notify-target slack:<ID>] [--
|
|
95
|
+
return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n update (self-update: npm i -g @damian87/omp@latest)\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot] [--hooks]\n cost [--json] [--session <id>] [--days <n>]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n comms status [--session <name>] [--json] (is copilot on + online? auto-discovers session)\n comms send --text "<prompt>" [--force] [--session <name>] [--json]\n comms recv [--wait] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n comms ask --text "<prompt>" [--force] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n gateway serve [--only <name>[,<name>]] (run all configured connectors; today: slack)\n gateway status [--json] [--only <name>[,...]] (per-connector readiness; no sockets opened)\n gateway doctor [--json] [--only <name>[,...]] (alias for 'gateway status')\n gateway notify --text "<msg>" [--target slack:C\\|D\\|G\\|U... [:thread_ts]] [--thread-ts <ts>] [--json]\n (one-shot outbound Slack post; falls back to SLACK_HOME_CHANNEL)\n slack serve (deprecated alias for 'gateway serve --only slack')\n slack doctor [--json] (deprecated alias for 'gateway status --only slack')\n env init [--force] (interactive: write ~/.omp/.env with Slack tokens + optional SLACK_HOME_CHANNEL)\n non-interactive: set OMP_INIT_BOT_TOKEN/OMP_INIT_APP_TOKEN/OMP_INIT_HOME_CHANNEL\n (env vars preferred over --bot-token/--app-token/--home-channel flags)\n (--session is optional when exactly one omp-<digits> tmux session is running)\n${registeredCommandHelpLines().join("\n")}\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron "<expr>" --prompt "<text>" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--notify-target slack:<ID>] [--notify-desktop] [--notify-open-omp] [--dry-run] [--json]
|
|
96
|
+
(--notify-desktop: native OS notification on completion [macOS uses osascript]; --notify-open-omp: click opens an omp session in the state root — needs OMP_NOTIFY_USE_TERMINAL_NOTIFIER=1 + terminal-notifier on macOS)\n schedule list [--json]\n schedule status <id> [--json]\n schedule open <id> [--tmux] [--json] (show this id's latest status + full output; --tmux instead opens an interactive omp session in the project — recent runs show via the startup banner)\n schedule run-now <id> [--json]\n schedule remove <id> [--json]\n goal set "<objective>" [--json]\n goal read [--json]\n memory sync [--json] (render goal+directives into copilot-instructions.md)\n config get [--json] | config set memory-mode on|off | config set memory-review-model <slug> | config set memory-review-min-messages <n> [--global]\n (--global writes ~/.omp/config.json; applies to every project. project .omp/config.json overrides it)\n memory-review --session <uuid|latest> [--model <slug>] [--json] (cheap-model end-of-session review; opt-in via memory-mode)\n daily-log set-goal "<text>" [--json]\n daily-log add "<text>" [--json]\n daily-log read [--days <n>] [--json]\n daily-log prune [--keep-days <n>] [--json]\n state write <key> <val> [--ttl <s>] | read|delete|status <key> | list | cleanup [--json]\n project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" | prune-notes --keep <n>|--older-than <days> [--json]\n trace timeline [<sessionId>] [--limit <n>] | summary [<sessionId>] | add <sessionId> <event> [<json>] [--json]\n catalog list [--json]\n catalog validate [--json]\n catalog capability <id> [--json]\n project inspect [--json]\n skill install <skill-dir> [--root <repo>] [--scope project|user] [--dry-run] [--json]\n lint:skills [--root <repo>]\n sync:dry-run [--root <repo>]\n jira:dry-run [--root <repo>]\n jira render <plan-file> [--root <repo>] [--json]\n jira apply <ticket-key-or-plan-file> --comment|--update|--transition|--link [--dry-run] [--json]\n`;
|
|
30
97
|
}
|
|
31
98
|
async function resolveExistingInputPath(value) {
|
|
32
99
|
const { existsSync } = await import("node:fs");
|
|
@@ -79,15 +146,40 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
79
146
|
const versionCheckUrl = pathToFileURL(join(packageRootFromImportMeta(import.meta.url), "scripts", "lib", "version-check.mjs")).href;
|
|
80
147
|
const { checkForUpdate, formatUpdateNotice } = (await import(versionCheckUrl));
|
|
81
148
|
const cwd = flagValue(argv, "--root") ?? process.cwd();
|
|
82
|
-
const
|
|
149
|
+
const stateDir = join(ompRoot(cwd), ".omp", "state");
|
|
150
|
+
const skipCheck = Boolean(process.env.OMP_NO_UPDATE_CHECK?.trim());
|
|
83
151
|
if (json) {
|
|
152
|
+
const update = skipCheck ? null : await checkForUpdate({ stateDir });
|
|
84
153
|
return { ok: true, output: { ...info, update } };
|
|
85
154
|
}
|
|
155
|
+
// Interactive TTY: print the version, then offer to self-update.
|
|
156
|
+
if (isInteractive(json)) {
|
|
157
|
+
console.log(formatVersionInfo(info));
|
|
158
|
+
const { maybePromptUpdate } = await import("./copilot/update-prompt.js");
|
|
159
|
+
await withUpdateIO((io) => maybePromptUpdate({ cwd, io, interactive: true, importMetaUrl: import.meta.url }));
|
|
160
|
+
return { ok: true };
|
|
161
|
+
}
|
|
162
|
+
// Non-interactive: keep the passive notice behavior (honoring the opt-out).
|
|
163
|
+
const update = skipCheck ? null : await checkForUpdate({ stateDir });
|
|
86
164
|
const message = update
|
|
87
165
|
? `${formatVersionInfo(info)}\n\n${formatUpdateNotice(update.current, update.latest)}`
|
|
88
166
|
: formatVersionInfo(info);
|
|
89
167
|
return { ok: true, message };
|
|
90
168
|
}
|
|
169
|
+
if (group === "update") {
|
|
170
|
+
const { runSelfUpdate, updateCopilotPlugin } = await import("./copilot/update-prompt.js");
|
|
171
|
+
const ok = await runSelfUpdate();
|
|
172
|
+
if (!ok) {
|
|
173
|
+
return { ok: false, message: "Update failed; run manually: npm i -g @damian87/omp@latest" };
|
|
174
|
+
}
|
|
175
|
+
const plugin = await updateCopilotPlugin();
|
|
176
|
+
const pluginMsg = plugin === "updated"
|
|
177
|
+
? "Copilot plugin updated."
|
|
178
|
+
: plugin === "failed"
|
|
179
|
+
? "Copilot plugin update failed; run: copilot plugin update oh-my-copilot"
|
|
180
|
+
: "Copilot plugin: skipped (copilot CLI not found).";
|
|
181
|
+
return { ok: true, message: `omp CLI updated.\n${pluginMsg}\nre-run \`omp\`.` };
|
|
182
|
+
}
|
|
91
183
|
// Auto-load ~/.omp/.env so subcommands that read process.env (slack tokens,
|
|
92
184
|
// OMP_*, COPILOT_TMUX_SESSION, etc.) work from any cwd without `source .env`.
|
|
93
185
|
// Shell exports take precedence — see src/env/dotenv.ts.
|
|
@@ -98,6 +190,27 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
98
190
|
if (!group || BARE_LAUNCH_FLAGS.has(group)) {
|
|
99
191
|
const { launchCopilot } = await import("./copilot/launch.js");
|
|
100
192
|
const launchCwd = flagValue(argv, "--root") ?? process.cwd();
|
|
193
|
+
// Truly-bare `omp` in a TTY: offer to self-update, then show the
|
|
194
|
+
// first-run hint, before handing the terminal to copilot. `--madmax`/
|
|
195
|
+
// `--yolo` and any non-TTY launch are never blocked.
|
|
196
|
+
if (!group && isInteractive(json)) {
|
|
197
|
+
const { maybePromptUpdate, maybeWelcome } = await import("./copilot/update-prompt.js");
|
|
198
|
+
const outcome = await withUpdateIO((io) => maybePromptUpdate({ cwd: launchCwd, io, interactive: true, importMetaUrl: import.meta.url }));
|
|
199
|
+
// The running process still holds the OLD code in memory. After a
|
|
200
|
+
// successful self-update, re-exec the freshly-installed omp so this
|
|
201
|
+
// launch uses the new version instead of stale wrapper logic.
|
|
202
|
+
if (outcome.updated) {
|
|
203
|
+
const reexec = await reexecAfterUpdate(argv);
|
|
204
|
+
if (reexec)
|
|
205
|
+
return reexec;
|
|
206
|
+
// Fall through to a normal launch if re-exec wasn't possible.
|
|
207
|
+
}
|
|
208
|
+
maybeWelcome({
|
|
209
|
+
cwd: launchCwd,
|
|
210
|
+
io: { print: (line) => console.log(line), ask: async () => undefined },
|
|
211
|
+
interactive: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
101
214
|
const beforeSessions = await snapshotSessionsForReview();
|
|
102
215
|
const result = await launchCopilot({
|
|
103
216
|
args: argv,
|
|
@@ -1363,6 +1476,8 @@ async function handleScheduleCommand(argv, json) {
|
|
|
1363
1476
|
allowAllTools: hasFlag(argv, "--allow-all-tools"),
|
|
1364
1477
|
dryRun: hasFlag(argv, "--dry-run"),
|
|
1365
1478
|
notifyTarget,
|
|
1479
|
+
notifyDesktop: hasFlag(argv, "--notify-desktop"),
|
|
1480
|
+
notifyOpenOmp: hasFlag(argv, "--notify-open-omp"),
|
|
1366
1481
|
});
|
|
1367
1482
|
return json
|
|
1368
1483
|
? { ok: result.ok, exitCode: result.ok ? 0 : 1, output: result }
|
|
@@ -1399,10 +1514,39 @@ async function handleScheduleCommand(argv, json) {
|
|
|
1399
1514
|
? { ok: result.ok, exitCode: result.ok ? 0 : 1, output: result }
|
|
1400
1515
|
: { ok: result.ok, exitCode: result.ok ? 0 : 1, message: result.message };
|
|
1401
1516
|
}
|
|
1517
|
+
if (command === "open" && targetId) {
|
|
1518
|
+
const r = mod.openScheduleResult(cwd, targetId);
|
|
1519
|
+
if (!r.ok)
|
|
1520
|
+
return { ok: false, exitCode: 1, output: json ? r : undefined, message: r.error };
|
|
1521
|
+
// --tmux: drop into an interactive omp session (auto-wrapped in tmux by
|
|
1522
|
+
// launchCopilot) rooted at the project. The SessionStart [SCHEDULE RESULTS]
|
|
1523
|
+
// banner surfaces RECENT unseen results across all jobs (not pinned to <id>,
|
|
1524
|
+
// and cursor-gated) — for this id's exact output use `schedule open <id>`
|
|
1525
|
+
// without --tmux. We resolve <id> first only to fail fast on a bad id.
|
|
1526
|
+
if (hasFlag(argv, "--tmux")) {
|
|
1527
|
+
const { launchCopilot } = await import("./copilot/launch.js");
|
|
1528
|
+
const result = await launchCopilot({ args: [], cwd, env: { ...process.env, OMP_FORCE_TMUX_WRAP: "1" } });
|
|
1529
|
+
return {
|
|
1530
|
+
ok: result.ok,
|
|
1531
|
+
exitCode: result.exitCode,
|
|
1532
|
+
message: result.ok ? undefined : `failed to launch omp in tmux (exit ${result.exitCode})`,
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
if (json)
|
|
1536
|
+
return { ok: true, output: r };
|
|
1537
|
+
const j = r.job;
|
|
1538
|
+
const header = `${j.id} — ${j.lastStatus ?? "(never run)"} @ ${j.lastRunAt ?? "-"}\n${j.lastSummary ?? ""}`.trim();
|
|
1539
|
+
const body = r.logContent
|
|
1540
|
+
? `\n\n--- full output (${j.lastLogPath}) ---\n${r.logContent}`
|
|
1541
|
+
: j.lastRunAt
|
|
1542
|
+
? `\n(log not found: ${j.lastLogPath ?? "n/a"})`
|
|
1543
|
+
: "\n(no run recorded yet)";
|
|
1544
|
+
return { ok: true, message: header + body };
|
|
1545
|
+
}
|
|
1402
1546
|
return {
|
|
1403
1547
|
ok: false,
|
|
1404
1548
|
exitCode: 1,
|
|
1405
|
-
message: 'Unknown schedule subcommand. Try: schedule add --id <id> --cron "<expr>" --prompt "<text>" | list | status <id> | remove <id> | run-now <id>',
|
|
1549
|
+
message: 'Unknown schedule subcommand. Try: schedule add --id <id> --cron "<expr>" --prompt "<text>" | list | status <id> | open <id> | remove <id> | run-now <id>',
|
|
1406
1550
|
};
|
|
1407
1551
|
}
|
|
1408
1552
|
function isEntrypoint() {
|