@damian87/omp 0.7.0 → 0.9.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.
- package/.github/skills/slack/SKILL.md +82 -0
- package/README.md +61 -34
- package/catalog/capabilities.json +46 -0
- package/catalog/skills-general.json +26 -0
- package/dist/src/cli.js +365 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/copilot/version.js +10 -0
- package/dist/src/copilot/version.js.map +1 -1
- package/dist/src/env/dotenv.d.ts +41 -0
- package/dist/src/env/dotenv.js +112 -0
- package/dist/src/env/dotenv.js.map +1 -0
- package/dist/src/env/init.d.ts +56 -0
- package/dist/src/env/init.js +294 -0
- package/dist/src/env/init.js.map +1 -0
- package/dist/src/gateway/connector.d.ts +37 -0
- package/dist/src/gateway/connector.js +12 -0
- package/dist/src/gateway/connector.js.map +1 -0
- package/dist/src/gateway/connectors/slack.d.ts +69 -0
- package/dist/src/gateway/connectors/slack.js +159 -0
- package/dist/src/gateway/connectors/slack.js.map +1 -0
- package/dist/src/gateway/notify.d.ts +35 -0
- package/dist/src/gateway/notify.js +261 -0
- package/dist/src/gateway/notify.js.map +1 -0
- package/dist/src/gateway/registry.d.ts +29 -0
- package/dist/src/gateway/registry.js +37 -0
- package/dist/src/gateway/registry.js.map +1 -0
- package/dist/src/gateway/runtime.d.ts +58 -0
- package/dist/src/gateway/runtime.js +105 -0
- package/dist/src/gateway/runtime.js.map +1 -0
- package/dist/src/gateway/target-parser.d.ts +76 -0
- package/dist/src/gateway/target-parser.js +105 -0
- package/dist/src/gateway/target-parser.js.map +1 -0
- package/dist/src/jira.js +2 -14
- package/dist/src/jira.js.map +1 -1
- package/dist/src/schedule/commands.js +1 -0
- package/dist/src/schedule/commands.js.map +1 -1
- package/dist/src/schedule/runner.d.ts +9 -0
- package/dist/src/schedule/runner.js +31 -1
- package/dist/src/schedule/runner.js.map +1 -1
- package/dist/src/schedule/types.d.ts +9 -0
- package/dist/src/slack/config.d.ts +32 -0
- package/dist/src/slack/config.js +52 -0
- package/dist/src/slack/config.js.map +1 -0
- package/dist/src/slack/handler.d.ts +48 -0
- package/dist/src/slack/handler.js +68 -0
- package/dist/src/slack/handler.js.map +1 -0
- package/dist/src/slack/serve.d.ts +8 -0
- package/dist/src/slack/serve.js +7 -0
- package/dist/src/slack/serve.js.map +1 -0
- package/dist/src/team/tmux.d.ts +1 -0
- package/dist/src/team/tmux.js +9 -0
- package/dist/src/team/tmux.js.map +1 -1
- package/docs/slack-setup.md +177 -0
- package/package.json +13 -4
- package/plugin.json +12 -4
- package/scripts/lib/version-check.mjs +3 -0
- package/dist/src/mcp/server.d.ts +0 -10
- package/dist/src/mcp/server.js +0 -44
- package/dist/src/mcp/server.js.map +0 -1
- package/dist/src/mcp/tools/daily-log.d.ts +0 -2
- package/dist/src/mcp/tools/daily-log.js +0 -148
- package/dist/src/mcp/tools/daily-log.js.map +0 -1
- package/dist/src/mcp/tools/index.d.ts +0 -9
- package/dist/src/mcp/tools/index.js +0 -15
- package/dist/src/mcp/tools/index.js.map +0 -1
- package/dist/src/mcp/tools/notepad.d.ts +0 -2
- package/dist/src/mcp/tools/notepad.js +0 -135
- package/dist/src/mcp/tools/notepad.js.map +0 -1
- package/dist/src/mcp/tools/project-memory.d.ts +0 -2
- package/dist/src/mcp/tools/project-memory.js +0 -91
- package/dist/src/mcp/tools/project-memory.js.map +0 -1
- package/dist/src/mcp/tools/shared-memory.d.ts +0 -2
- package/dist/src/mcp/tools/shared-memory.js +0 -148
- package/dist/src/mcp/tools/shared-memory.js.map +0 -1
- package/dist/src/mcp/tools/state.d.ts +0 -2
- package/dist/src/mcp/tools/state.js +0 -107
- package/dist/src/mcp/tools/state.js.map +0 -1
- package/dist/src/mcp/tools/trace.d.ts +0 -10
- package/dist/src/mcp/tools/trace.js +0 -102
- package/dist/src/mcp/tools/trace.js.map +0 -1
- package/dist/src/mcp/types.d.ts +0 -29
- package/dist/src/mcp/types.js +0 -7
- package/dist/src/mcp/types.js.map +0 -1
- package/dist/test/catalog.test.d.ts +0 -1
- package/dist/test/catalog.test.js +0 -21
- package/dist/test/catalog.test.js.map +0 -1
- package/dist/test/jira.test.d.ts +0 -1
- package/dist/test/jira.test.js +0 -26
- package/dist/test/jira.test.js.map +0 -1
- package/dist/test/lint.test.d.ts +0 -1
- package/dist/test/lint.test.js +0 -9
- package/dist/test/lint.test.js.map +0 -1
- package/dist/test/sync.test.d.ts +0 -1
- package/dist/test/sync.test.js +0 -15
- package/dist/test/sync.test.js.map +0 -1
package/dist/src/cli.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
4
5
|
import { findCapability, loadCatalogBundle, validateCatalogBundle } from "./catalog.js";
|
|
5
6
|
import { findRegisteredCommand, registeredCommandHelpLines } from "./commands/registry.js";
|
|
6
|
-
import {
|
|
7
|
+
import { loadOmpEnv } from "./env/dotenv.js";
|
|
8
|
+
import { ompRoot } from "./omp-root.js";
|
|
9
|
+
import { inspectProject, packageRootFromImportMeta } from "./project.js";
|
|
7
10
|
function hasFlag(args, flag) {
|
|
8
11
|
return args.includes(flag);
|
|
9
12
|
}
|
|
@@ -23,7 +26,7 @@ function printResult(result, json) {
|
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
function help() {
|
|
26
|
-
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]\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${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] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\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 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>" [--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`;
|
|
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]\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>] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\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 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>" [--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`;
|
|
27
30
|
}
|
|
28
31
|
async function resolveExistingInputPath(value) {
|
|
29
32
|
const { existsSync } = await import("node:fs");
|
|
@@ -46,8 +49,22 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
46
49
|
if (group === "version" || group === "--version" || group === "-v") {
|
|
47
50
|
const { getVersionInfo, formatVersionInfo } = await import("./copilot/version.js");
|
|
48
51
|
const info = getVersionInfo({ importMetaUrl: import.meta.url });
|
|
49
|
-
|
|
52
|
+
const versionCheckUrl = pathToFileURL(join(packageRootFromImportMeta(import.meta.url), "scripts", "lib", "version-check.mjs")).href;
|
|
53
|
+
const { checkForUpdate, formatUpdateNotice } = (await import(versionCheckUrl));
|
|
54
|
+
const cwd = flagValue(argv, "--root") ?? process.cwd();
|
|
55
|
+
const update = await checkForUpdate({ stateDir: join(ompRoot(cwd), ".omp", "state") });
|
|
56
|
+
if (json) {
|
|
57
|
+
return { ok: true, output: { ...info, update } };
|
|
58
|
+
}
|
|
59
|
+
const message = update
|
|
60
|
+
? `${formatVersionInfo(info)}\n\n${formatUpdateNotice(update.current, update.latest)}`
|
|
61
|
+
: formatVersionInfo(info);
|
|
62
|
+
return { ok: true, message };
|
|
50
63
|
}
|
|
64
|
+
// Auto-load ~/.omp/.env so subcommands that read process.env (slack tokens,
|
|
65
|
+
// OMP_*, COPILOT_TMUX_SESSION, etc.) work from any cwd without `source .env`.
|
|
66
|
+
// Shell exports take precedence — see src/env/dotenv.ts.
|
|
67
|
+
loadOmpEnv();
|
|
51
68
|
// Bare `omp` (no subcommand) launches copilot directly with permissions
|
|
52
69
|
// bypass OFF; `omp --madmax`/`--yolo` launch with bypass ON. For the bare
|
|
53
70
|
// case argv is empty, so normalizeCopilotLaunchArgs emits no --yolo.
|
|
@@ -122,6 +139,18 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
122
139
|
if (group === "council") {
|
|
123
140
|
return await handleCouncilCommand(argv, json);
|
|
124
141
|
}
|
|
142
|
+
if (group === "comms") {
|
|
143
|
+
return await handleCommsCommand(argv, json);
|
|
144
|
+
}
|
|
145
|
+
if (group === "slack") {
|
|
146
|
+
return await handleSlackCommand(argv, json);
|
|
147
|
+
}
|
|
148
|
+
if (group === "gateway") {
|
|
149
|
+
return await handleGatewayCommand(argv, json);
|
|
150
|
+
}
|
|
151
|
+
if (group === "env") {
|
|
152
|
+
return await handleEnvCommand(argv, json);
|
|
153
|
+
}
|
|
125
154
|
if (group === "ralph") {
|
|
126
155
|
return await handleModeCommand("ralph", argv, json);
|
|
127
156
|
}
|
|
@@ -575,6 +604,330 @@ async function handleCouncilCommand(argv, json) {
|
|
|
575
604
|
lines.push(`Artifacts: ${result.tmpDir}`);
|
|
576
605
|
return { ok: result.ok, exitCode: result.ok ? 0 : 1, message: lines.join("\n") };
|
|
577
606
|
}
|
|
607
|
+
async function handleCommsCommand(argv, json) {
|
|
608
|
+
const [, command] = argv;
|
|
609
|
+
const { resolveSession } = await import("./comms/resolve-session.js");
|
|
610
|
+
// `--session` present but with a missing/empty/flag-like value must fail loud,
|
|
611
|
+
// never silently fall through to env/discovery and target an unintended session.
|
|
612
|
+
const flagSession = flagValue(argv, "--session");
|
|
613
|
+
if (hasFlag(argv, "--session") &&
|
|
614
|
+
(flagSession === undefined || flagSession === "" || flagSession.startsWith("-"))) {
|
|
615
|
+
return { ok: false, exitCode: 1, message: "invalid or missing --session value" };
|
|
616
|
+
}
|
|
617
|
+
const resolved = resolveSession({
|
|
618
|
+
flag: flagSession,
|
|
619
|
+
env: process.env.COPILOT_TMUX_SESSION,
|
|
620
|
+
});
|
|
621
|
+
if (!resolved.ok) {
|
|
622
|
+
return json
|
|
623
|
+
? {
|
|
624
|
+
ok: false,
|
|
625
|
+
exitCode: 1,
|
|
626
|
+
output: { ok: false, error: resolved.error, candidates: resolved.candidates },
|
|
627
|
+
}
|
|
628
|
+
: { ok: false, exitCode: 1, message: resolved.error };
|
|
629
|
+
}
|
|
630
|
+
const session = resolved.session;
|
|
631
|
+
const sessionSource = resolved.source;
|
|
632
|
+
// Guard against a --session value that looks like a flag (e.g. --session --foo).
|
|
633
|
+
if (sessionSource === "flag" && session.startsWith("-")) {
|
|
634
|
+
return { ok: false, exitCode: 1, message: `invalid --session name: ${session}` };
|
|
635
|
+
}
|
|
636
|
+
const { commsStatus, commsSend, commsRecv, commsAsk } = await import("./comms/index.js");
|
|
637
|
+
if (command === "status") {
|
|
638
|
+
const r = await commsStatus(session);
|
|
639
|
+
return json
|
|
640
|
+
? { ok: r.ok, output: { ...r, source: sessionSource } }
|
|
641
|
+
: {
|
|
642
|
+
ok: r.ok,
|
|
643
|
+
message: `session=${session} source=${sessionSource} on=${r.exists} online=${r.online} ready=${r.ready} busy=${r.busy}`,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
if (command === "send") {
|
|
647
|
+
const text = flagValue(argv, "--text");
|
|
648
|
+
if (!text) {
|
|
649
|
+
return { ok: false, exitCode: 1, message: 'comms send requires --text "<prompt>"' };
|
|
650
|
+
}
|
|
651
|
+
const r = await commsSend(session, text, {}, { force: hasFlag(argv, "--force") });
|
|
652
|
+
return json
|
|
653
|
+
? { ok: r.ok, exitCode: r.ok ? 0 : 1, output: r }
|
|
654
|
+
: {
|
|
655
|
+
ok: r.ok,
|
|
656
|
+
exitCode: r.ok ? 0 : 1,
|
|
657
|
+
message: r.ok ? `sent to ${session}` : `send failed: ${r.error}`,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (command === "recv") {
|
|
661
|
+
let lines;
|
|
662
|
+
let timeoutMs;
|
|
663
|
+
try {
|
|
664
|
+
lines = parsePositiveIntFlag(flagValue(argv, "--lines"), "--lines");
|
|
665
|
+
timeoutMs = parsePositiveIntFlag(flagValue(argv, "--timeout"), "--timeout");
|
|
666
|
+
}
|
|
667
|
+
catch (err) {
|
|
668
|
+
return { ok: false, exitCode: 1, message: String(err instanceof Error ? err.message : err) };
|
|
669
|
+
}
|
|
670
|
+
const r = await commsRecv(session, {}, { wait: hasFlag(argv, "--wait"), lines, timeoutMs });
|
|
671
|
+
if (json) {
|
|
672
|
+
return { ok: r.ok && !r.timedOut, exitCode: r.ok && !r.timedOut ? 0 : 1, output: r };
|
|
673
|
+
}
|
|
674
|
+
if (!r.ok) {
|
|
675
|
+
return { ok: false, exitCode: 1, message: `recv failed: ${r.error}` };
|
|
676
|
+
}
|
|
677
|
+
if (r.timedOut) {
|
|
678
|
+
return { ok: false, exitCode: 1, message: `recv timed out waiting for copilot\n${r.text ?? ""}` };
|
|
679
|
+
}
|
|
680
|
+
return { ok: true, exitCode: 0, message: r.text ?? "" };
|
|
681
|
+
}
|
|
682
|
+
if (command === "ask") {
|
|
683
|
+
const text = flagValue(argv, "--text");
|
|
684
|
+
if (!text) {
|
|
685
|
+
return { ok: false, exitCode: 1, message: 'comms ask requires --text "<prompt>"' };
|
|
686
|
+
}
|
|
687
|
+
let lines;
|
|
688
|
+
let timeoutMs;
|
|
689
|
+
try {
|
|
690
|
+
lines = parsePositiveIntFlag(flagValue(argv, "--lines"), "--lines");
|
|
691
|
+
timeoutMs = parsePositiveIntFlag(flagValue(argv, "--timeout"), "--timeout");
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
return { ok: false, exitCode: 1, message: String(err instanceof Error ? err.message : err) };
|
|
695
|
+
}
|
|
696
|
+
const r = await commsAsk(session, text, {}, { force: hasFlag(argv, "--force"), lines, timeoutMs });
|
|
697
|
+
if (json) {
|
|
698
|
+
return { ok: r.ok && !r.timedOut, exitCode: r.ok && !r.timedOut ? 0 : 1, output: r };
|
|
699
|
+
}
|
|
700
|
+
if (!r.ok) {
|
|
701
|
+
return { ok: false, exitCode: 1, message: `ask failed: ${r.error}` };
|
|
702
|
+
}
|
|
703
|
+
if (r.timedOut) {
|
|
704
|
+
return { ok: false, exitCode: 1, message: `ask timed out waiting for copilot\n${r.text ?? ""}` };
|
|
705
|
+
}
|
|
706
|
+
return { ok: true, exitCode: 0, message: r.text ?? "" };
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
exitCode: 1,
|
|
711
|
+
message: 'Unknown comms subcommand. Try: comms status | send --text "<prompt>" | recv [--wait] | ask --text "<prompt>"',
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
async function handleEnvCommand(argv, json) {
|
|
715
|
+
const [, command] = argv;
|
|
716
|
+
if (command !== "init") {
|
|
717
|
+
return {
|
|
718
|
+
ok: false,
|
|
719
|
+
exitCode: 1,
|
|
720
|
+
message: "Unknown env subcommand. Try: env init [--force] [--bot-token x] [--app-token x]",
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
const { runEnvInit } = await import("./env/init.js");
|
|
724
|
+
const readline = await import("node:readline/promises");
|
|
725
|
+
// Preferred non-interactive path: env vars. Putting secrets in argv leaks
|
|
726
|
+
// them to shell history, ps output, and CI logs — so we read the safe
|
|
727
|
+
// input from env vars first, then fall back to flags (with a warning).
|
|
728
|
+
const botEnv = process.env.OMP_INIT_BOT_TOKEN;
|
|
729
|
+
const appEnv = process.env.OMP_INIT_APP_TOKEN;
|
|
730
|
+
const sessionEnv = process.env.OMP_INIT_SESSION;
|
|
731
|
+
const usersEnv = process.env.OMP_INIT_USERS;
|
|
732
|
+
const homeChannelEnv = process.env.OMP_INIT_HOME_CHANNEL;
|
|
733
|
+
const botFlag = flagValue(argv, "--bot-token");
|
|
734
|
+
const appFlag = flagValue(argv, "--app-token");
|
|
735
|
+
const sessionFlag = flagValue(argv, "--session");
|
|
736
|
+
const usersFlag = flagValue(argv, "--users");
|
|
737
|
+
const homeChannelFlag = flagValue(argv, "--home-channel");
|
|
738
|
+
if ((botFlag !== undefined || appFlag !== undefined) && process.env.OMP_INIT_NO_WARN !== "1") {
|
|
739
|
+
console.error("warning: --bot-token/--app-token leak via shell history and `ps`. " +
|
|
740
|
+
"Prefer OMP_INIT_BOT_TOKEN / OMP_INIT_APP_TOKEN env vars (set OMP_INIT_NO_WARN=1 to silence).");
|
|
741
|
+
}
|
|
742
|
+
const botToken = botEnv ?? botFlag;
|
|
743
|
+
const appToken = appEnv ?? appFlag;
|
|
744
|
+
const session = sessionEnv ?? sessionFlag;
|
|
745
|
+
const users = usersEnv ?? usersFlag;
|
|
746
|
+
const homeChannel = homeChannelEnv ?? homeChannelFlag;
|
|
747
|
+
// Non-interactive when stdin isn't a TTY OR when any answer is already
|
|
748
|
+
// supplied (via env or flag). Partial answers are allowed; missing required
|
|
749
|
+
// ones will be rejected by runEnvInit's validator.
|
|
750
|
+
const anyAnswer = botToken !== undefined ||
|
|
751
|
+
appToken !== undefined ||
|
|
752
|
+
session !== undefined ||
|
|
753
|
+
users !== undefined ||
|
|
754
|
+
homeChannel !== undefined;
|
|
755
|
+
const force = hasFlag(argv, "--force");
|
|
756
|
+
let result;
|
|
757
|
+
if (anyAnswer || !process.stdin.isTTY) {
|
|
758
|
+
result = await runEnvInit({
|
|
759
|
+
io: {
|
|
760
|
+
// In non-interactive / --json mode we still must not corrupt stdout
|
|
761
|
+
// with diagnostic prints. Route everything but the JSON result to
|
|
762
|
+
// stderr so callers can pipe stdout safely.
|
|
763
|
+
print: (line) => (json ? console.error(line) : console.log(line)),
|
|
764
|
+
warn: (line) => console.error(line),
|
|
765
|
+
ask: async () => undefined,
|
|
766
|
+
},
|
|
767
|
+
force,
|
|
768
|
+
answers: {
|
|
769
|
+
slackBotToken: botToken ?? "",
|
|
770
|
+
slackAppToken: appToken ?? "",
|
|
771
|
+
copilotTmuxSession: session ?? "",
|
|
772
|
+
slackAllowedUsers: users ?? "",
|
|
773
|
+
slackHomeChannel: homeChannel ?? "",
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
779
|
+
try {
|
|
780
|
+
result = await runEnvInit({
|
|
781
|
+
io: {
|
|
782
|
+
print: (line) => console.log(line),
|
|
783
|
+
warn: (line) => console.error(line),
|
|
784
|
+
ask: (prompt) => rl.question(prompt),
|
|
785
|
+
},
|
|
786
|
+
force,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
rl.close();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (json) {
|
|
794
|
+
return {
|
|
795
|
+
ok: result.ok,
|
|
796
|
+
exitCode: result.ok ? 0 : 1,
|
|
797
|
+
output: { ok: result.ok, path: result.path, reason: result.reason },
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
ok: result.ok,
|
|
802
|
+
exitCode: result.ok ? 0 : 1,
|
|
803
|
+
message: result.ok ? `wrote ${result.path}` : (result.reason ?? "env init failed"),
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
async function handleSlackCommand(argv, json) {
|
|
807
|
+
// Backwards-compatible alias: `omp slack <cmd>` forwards to the generalized
|
|
808
|
+
// gateway runtime (scoped to slack). `slack doctor --json` keeps its legacy
|
|
809
|
+
// flat output shape so existing scripts that parsed it don't break — see
|
|
810
|
+
// `src/cli.ts` pre-refactor at 5c08a88.
|
|
811
|
+
const [, command, ...rest] = argv;
|
|
812
|
+
if (command === "serve") {
|
|
813
|
+
return await handleGatewayCommand(["gateway", "serve", "--only", "slack", ...rest], json);
|
|
814
|
+
}
|
|
815
|
+
if (command === "doctor" || command === "status") {
|
|
816
|
+
const hasBot = !!process.env.SLACK_BOT_TOKEN;
|
|
817
|
+
const hasApp = !!process.env.SLACK_APP_TOKEN;
|
|
818
|
+
const { resolveSession } = await import("./comms/resolve-session.js");
|
|
819
|
+
const resolved = resolveSession({ env: process.env.COPILOT_TMUX_SESSION });
|
|
820
|
+
const startable = hasBot && hasApp;
|
|
821
|
+
const ready = startable && resolved.ok;
|
|
822
|
+
const output = {
|
|
823
|
+
botToken: hasBot,
|
|
824
|
+
appToken: hasApp,
|
|
825
|
+
copilotSession: resolved.ok ? resolved.session : null,
|
|
826
|
+
copilotError: resolved.ok ? undefined : resolved.error,
|
|
827
|
+
ready,
|
|
828
|
+
};
|
|
829
|
+
return json
|
|
830
|
+
? { ok: startable, exitCode: startable ? 0 : 1, output }
|
|
831
|
+
: {
|
|
832
|
+
ok: startable,
|
|
833
|
+
exitCode: startable ? 0 : 1,
|
|
834
|
+
message: `bot_token=${hasBot} app_token=${hasApp} copilot_session=${output.copilotSession ?? `(none: ${output.copilotError ?? "unknown"})`} ready=${ready}`,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
ok: false,
|
|
839
|
+
exitCode: 1,
|
|
840
|
+
message: "Unknown slack subcommand. Try: slack serve | slack doctor",
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function handleGatewayCommand(argv, json) {
|
|
844
|
+
const [, command] = argv;
|
|
845
|
+
const { buildConnectors, KNOWN_CONNECTORS } = await import("./gateway/registry.js");
|
|
846
|
+
const { runGateway, getGatewayStatus, parseOnlyFlag } = await import("./gateway/runtime.js");
|
|
847
|
+
const only = parseOnlyFlag(flagValue(argv, "--only"));
|
|
848
|
+
if (only) {
|
|
849
|
+
const unknown = only.filter((n) => !KNOWN_CONNECTORS.includes(n));
|
|
850
|
+
if (unknown.length > 0) {
|
|
851
|
+
return {
|
|
852
|
+
ok: false,
|
|
853
|
+
exitCode: 1,
|
|
854
|
+
message: `unknown connector(s): ${unknown.join(", ")} — known: ${KNOWN_CONNECTORS.join(", ")}`,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (command === "serve") {
|
|
859
|
+
const built = buildConnectors(only);
|
|
860
|
+
if (built.connectors.length === 0) {
|
|
861
|
+
const reason = built.warnings.length > 0
|
|
862
|
+
? built.warnings.join("; ")
|
|
863
|
+
: "no connectors configured — set SLACK_BOT_TOKEN+SLACK_APP_TOKEN or pass --only <name>";
|
|
864
|
+
return { ok: false, exitCode: 1, message: reason };
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
await runGateway({ connectors: built.connectors });
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
return {
|
|
871
|
+
ok: false,
|
|
872
|
+
exitCode: 1,
|
|
873
|
+
message: `gateway failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
return { ok: true, message: "gateway stopped" };
|
|
877
|
+
}
|
|
878
|
+
if (command === "status" || command === "doctor") {
|
|
879
|
+
const built = buildConnectors(only);
|
|
880
|
+
const rows = built.doctors.map((d) => {
|
|
881
|
+
const s = d.doctor();
|
|
882
|
+
return { name: d.name, ready: s.ready, detail: s.detail };
|
|
883
|
+
});
|
|
884
|
+
const ready = rows.length > 0 && rows.every((r) => r.ready);
|
|
885
|
+
if (json) {
|
|
886
|
+
return {
|
|
887
|
+
ok: ready,
|
|
888
|
+
exitCode: ready ? 0 : 1,
|
|
889
|
+
output: { ready, connectors: rows, warnings: built.warnings },
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const lines = [
|
|
893
|
+
`ready=${ready}`,
|
|
894
|
+
...rows.map((r) => ` ${r.name}: ready=${r.ready}${r.detail ? ` (${r.detail})` : ""}`),
|
|
895
|
+
...built.warnings.map((w) => ` warning: ${w}`),
|
|
896
|
+
];
|
|
897
|
+
return { ok: ready, exitCode: ready ? 0 : 1, message: lines.join("\n") };
|
|
898
|
+
}
|
|
899
|
+
if (command === "notify") {
|
|
900
|
+
const text = flagValue(argv, "--text");
|
|
901
|
+
if (!text || !text.trim()) {
|
|
902
|
+
return { ok: false, exitCode: 1, message: 'gateway notify requires --text "<message>"' };
|
|
903
|
+
}
|
|
904
|
+
const target = flagValue(argv, "--target");
|
|
905
|
+
const threadTs = flagValue(argv, "--thread-ts");
|
|
906
|
+
const { notify } = await import("./gateway/notify.js");
|
|
907
|
+
const result = await notify({ text, target, threadTs });
|
|
908
|
+
if (json) {
|
|
909
|
+
return {
|
|
910
|
+
ok: result.ok,
|
|
911
|
+
exitCode: result.ok ? 0 : 1,
|
|
912
|
+
output: result,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
ok: result.ok,
|
|
917
|
+
exitCode: result.ok ? 0 : 1,
|
|
918
|
+
message: result.ok
|
|
919
|
+
? `posted to ${result.channel} (ts=${result.ts}${result.openedIm ? ", opened IM" : ""})`
|
|
920
|
+
: `notify failed [${result.code}]: ${result.reason}`,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
// Silence the unused warning emitted when no command path is hit.
|
|
924
|
+
void getGatewayStatus;
|
|
925
|
+
return {
|
|
926
|
+
ok: false,
|
|
927
|
+
exitCode: 1,
|
|
928
|
+
message: "Unknown gateway subcommand. Try: gateway serve | status | doctor | notify --text \"...\"",
|
|
929
|
+
};
|
|
930
|
+
}
|
|
578
931
|
const TEAM_SPEC_RE = /^(\d+):([\w-]+)$/;
|
|
579
932
|
async function handleTeamCommand(argv, json) {
|
|
580
933
|
const [, command, value, extra] = argv;
|
|
@@ -859,6 +1212,14 @@ async function handleScheduleCommand(argv, json) {
|
|
|
859
1212
|
catch (err) {
|
|
860
1213
|
return { ok: false, exitCode: 1, message: String(err instanceof Error ? err.message : err) };
|
|
861
1214
|
}
|
|
1215
|
+
const notifyTarget = flagValue(argv, "--notify-target");
|
|
1216
|
+
if (notifyTarget) {
|
|
1217
|
+
const { parseTarget } = await import("./gateway/target-parser.js");
|
|
1218
|
+
const parsed = parseTarget(notifyTarget);
|
|
1219
|
+
if (!parsed.ok) {
|
|
1220
|
+
return { ok: false, exitCode: 1, message: `--notify-target: ${parsed.error}` };
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
862
1223
|
const result = mod.addScheduleJob(cwd, {
|
|
863
1224
|
id,
|
|
864
1225
|
cron,
|
|
@@ -871,6 +1232,7 @@ async function handleScheduleCommand(argv, json) {
|
|
|
871
1232
|
ttlHours,
|
|
872
1233
|
allowAllTools: hasFlag(argv, "--allow-all-tools"),
|
|
873
1234
|
dryRun: hasFlag(argv, "--dry-run"),
|
|
1235
|
+
notifyTarget,
|
|
874
1236
|
});
|
|
875
1237
|
return json
|
|
876
1238
|
? { ok: result.ok, exitCode: result.ok ? 0 : 1, output: result }
|