@automagik/omni 2.260922.2 → 2.260922.4
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/db/drizzle/0078_instances_slack_oauth_identity.sql +39 -0
- package/db/drizzle/meta/_journal.json +7 -0
- package/dist/commands/slack.d.ts +74 -2
- package/dist/commands/slack.d.ts.map +1 -1
- package/dist/index.js +303 -4
- package/dist/sdk/client.d.ts +6 -0
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/index.js +25 -0
- package/dist/sdk/types.generated.d.ts +297 -0
- package/dist/sdk/types.generated.d.ts.map +1 -1
- package/dist/server/index.js +20400 -18768
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- Slack OAuth identity on instances (wish: slack-personal-oauth).
|
|
2
|
+
--
|
|
3
|
+
-- The one-click Slack install (OAuth v2) provisions one instance per
|
|
4
|
+
-- (workspace, authorizing user) and must find that instance again when the
|
|
5
|
+
-- same person re-authorizes, so the callback upserts by a persisted Slack
|
|
6
|
+
-- identity instead of by name. The connection method records how the
|
|
7
|
+
-- credentials arrived, so the manual pasted-token path keeps its behavior.
|
|
8
|
+
--
|
|
9
|
+
-- instances.slack_team_id Slack workspace id (T…); also filled for
|
|
10
|
+
-- pasted-token instances by the
|
|
11
|
+
-- instance.connected listener
|
|
12
|
+
-- instances.slack_user_id authorizing user id (U…); null for
|
|
13
|
+
-- bot-mode instances (keyed by team only)
|
|
14
|
+
-- instances.slack_connection_method 'manual' | 'oauth'; NULL reads as 'manual'
|
|
15
|
+
-- instances_slack_identity_idx composite index on (team, user) for the
|
|
16
|
+
-- identity finder a follow-up adds. It
|
|
17
|
+
-- serves NO query today: the callback's
|
|
18
|
+
-- upsert still scans `instances.list`,
|
|
19
|
+
-- which is capped at 1,000 Slack rows and
|
|
20
|
+
-- is the documented trigger for a
|
|
21
|
+
-- `findBySlackIdentity` service finder.
|
|
22
|
+
-- The index ships here so that finder, and
|
|
23
|
+
-- the uniqueness work tracked alongside
|
|
24
|
+
-- it, need no second migration.
|
|
25
|
+
--
|
|
26
|
+
-- All nullable; existing rows are untouched. Not a credential column.
|
|
27
|
+
--
|
|
28
|
+
-- Hand-written following the 0043/0044/0052 precedent (additive, idempotent).
|
|
29
|
+
-- No explicit BEGIN/COMMIT — the boot migrator executes this file on a pooled
|
|
30
|
+
-- postgres-js connection, which rejects raw transaction control.
|
|
31
|
+
|
|
32
|
+
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "slack_team_id" varchar(32);
|
|
33
|
+
|
|
34
|
+
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "slack_user_id" varchar(32);
|
|
35
|
+
|
|
36
|
+
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "slack_connection_method" varchar(16);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS "instances_slack_identity_idx"
|
|
39
|
+
ON "instances" ("slack_team_id", "slack_user_id");
|
|
@@ -547,6 +547,13 @@
|
|
|
547
547
|
"when": 1787700000000,
|
|
548
548
|
"tag": "0077_instances_close_contact_config",
|
|
549
549
|
"breakpoints": true
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
"idx": 78,
|
|
553
|
+
"version": "7",
|
|
554
|
+
"when": 1787800000000,
|
|
555
|
+
"tag": "0078_instances_slack_oauth_identity",
|
|
556
|
+
"breakpoints": true
|
|
550
557
|
}
|
|
551
558
|
]
|
|
552
559
|
}
|
package/dist/commands/slack.d.ts
CHANGED
|
@@ -3,10 +3,82 @@
|
|
|
3
3
|
*
|
|
4
4
|
* omni slack dm <instance> <userId> — resolve/open the DM channel
|
|
5
5
|
* omni slack search <instance> <query> — full-text search (user token only)
|
|
6
|
+
* omni slack app setup — register the deployment Slack app
|
|
7
|
+
* omni slack app status — is the deployment app configured?
|
|
8
|
+
* omni slack connect — one-click OAuth install from a terminal
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
* than under `omni messages`.
|
|
10
|
+
* None has a cross-channel equivalent, which is why they live here rather
|
|
11
|
+
* than under `omni messages` / `omni instances`.
|
|
12
|
+
*
|
|
13
|
+
* Exit codes across this group: 0 success, 2 usage, 3 API failure.
|
|
14
|
+
*
|
|
15
|
+
* Secrets (client secret, signing secret, app-level token) are accepted ONLY
|
|
16
|
+
* from a piped stdin (`--*-stdin`) or an interactive prompt on stderr — there
|
|
17
|
+
* is deliberately no `--client-secret <value>` style flag, because a value on
|
|
18
|
+
* the command line lands in the shell history and in `ps`. Anything this file
|
|
19
|
+
* echoes back goes through `maskSecret` first. `--*-stdin` is the
|
|
20
|
+
* non-echoing path; the prompt is the convenience path.
|
|
9
21
|
*/
|
|
22
|
+
import type { OmniClient } from '@omni/sdk';
|
|
10
23
|
import { Command } from 'commander';
|
|
24
|
+
/** Every handler returns the process exit code it earned (0 — failures exit through `output.error`). */
|
|
25
|
+
interface CommandResult {
|
|
26
|
+
readonly exitCode: number;
|
|
27
|
+
readonly instanceId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Mask a secret for display. Local to this file on purpose: `instances.ts`
|
|
31
|
+
* masks API *body fields*, this masks the values `slack app setup` collected
|
|
32
|
+
* before they reach the API, and the two must be free to diverge.
|
|
33
|
+
*/
|
|
34
|
+
declare function maskSecret(secret: string): string;
|
|
35
|
+
/** Side effects `slack app setup` needs — injected so the test never touches the real stdin. */
|
|
36
|
+
interface SetupPorts {
|
|
37
|
+
readonly readStdin: () => Promise<string>;
|
|
38
|
+
readonly prompt: (question: string) => Promise<string>;
|
|
39
|
+
}
|
|
40
|
+
/** Side effects `slack connect` needs — injected so the test never waits or launches anything. */
|
|
41
|
+
interface ConnectPorts {
|
|
42
|
+
readonly openBrowser: (url: string) => Promise<boolean>;
|
|
43
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
44
|
+
readonly now: () => number;
|
|
45
|
+
readonly intervalMs: number;
|
|
46
|
+
}
|
|
47
|
+
interface SetupOptions {
|
|
48
|
+
readonly publicUrl?: string;
|
|
49
|
+
readonly clientId?: string;
|
|
50
|
+
readonly clientSecretStdin?: boolean;
|
|
51
|
+
readonly signingSecretStdin?: boolean;
|
|
52
|
+
readonly appTokenStdin?: boolean;
|
|
53
|
+
readonly nonInteractive?: boolean;
|
|
54
|
+
}
|
|
55
|
+
declare function handleAppSetup(client: OmniClient, opts: SetupOptions, ports?: SetupPorts): Promise<CommandResult>;
|
|
56
|
+
declare function handleAppStatus(client: OmniClient): Promise<CommandResult>;
|
|
57
|
+
interface ConnectOptions {
|
|
58
|
+
readonly mode?: string;
|
|
59
|
+
/** commander sets this to `false` for `--no-open`. */
|
|
60
|
+
readonly open?: boolean;
|
|
61
|
+
readonly timeout?: string;
|
|
62
|
+
}
|
|
63
|
+
declare function handleConnect(client: OmniClient, opts: ConnectOptions, ports?: ConnectPorts): Promise<CommandResult>;
|
|
11
64
|
export declare function createSlackCommand(): Command;
|
|
65
|
+
export declare const __testables: {
|
|
66
|
+
handleAppSetup: typeof handleAppSetup;
|
|
67
|
+
handleAppStatus: typeof handleAppStatus;
|
|
68
|
+
handleConnect: typeof handleConnect;
|
|
69
|
+
maskSecret: typeof maskSecret;
|
|
70
|
+
SETTING_KEYS: {
|
|
71
|
+
readonly clientId: "slack.app.client_id";
|
|
72
|
+
readonly clientSecret: "slack.app.client_secret";
|
|
73
|
+
readonly signingSecret: "slack.app.signing_secret";
|
|
74
|
+
readonly appToken: "slack.app.app_token";
|
|
75
|
+
readonly publicUrl: "server.public_url";
|
|
76
|
+
};
|
|
77
|
+
EXIT_OK: number;
|
|
78
|
+
EXIT_USAGE: number;
|
|
79
|
+
EXIT_API: number;
|
|
80
|
+
POLL_INTERVAL_MS: number;
|
|
81
|
+
DEFAULT_TIMEOUT_SECONDS: number;
|
|
82
|
+
};
|
|
83
|
+
export {};
|
|
12
84
|
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/commands/slack.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/commands/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,wGAAwG;AACxG,UAAU,aAAa;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAMD;;;;GAIG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG1C;AAkDD,gGAAgG;AAChG,UAAU,UAAU;IAClB,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD;AAED,kGAAkG;AAClG,UAAU,YAAY;IACpB,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAkDD,UAAU,YAAY;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AAgJD,iBAAe,cAAc,CAC3B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,YAAY,EAClB,KAAK,GAAE,UAA8B,GACpC,OAAO,CAAC,aAAa,CAAC,CAcxB;AAED,iBAAe,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAGzE;AAMD,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA0CD,iBAAe,aAAa,CAC1B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,cAAc,EACpB,KAAK,GAAE,YAAkC,GACxC,OAAO,CAAC,aAAa,CAAC,CAgDxB;AAgCD,wBAAgB,kBAAkB,IAAI,OAAO,CAkE5C;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;CAWvB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -82328,6 +82328,9 @@ var init_schema2 = __esm(() => {
|
|
|
82328
82328
|
slackAuthMode: varchar("slack_auth_mode", { length: 10 }),
|
|
82329
82329
|
slackAppToken: text("slack_app_token"),
|
|
82330
82330
|
slackSigningSecret: text("slack_signing_secret"),
|
|
82331
|
+
slackTeamId: varchar("slack_team_id", { length: 32 }),
|
|
82332
|
+
slackUserId: varchar("slack_user_id", { length: 32 }),
|
|
82333
|
+
slackConnectionMethod: varchar("slack_connection_method", { length: 16 }),
|
|
82331
82334
|
telegramBotToken: text("telegram_bot_token"),
|
|
82332
82335
|
telegramReactionLevel: varchar("telegram_reaction_level", { length: 20 }).notNull().default("off"),
|
|
82333
82336
|
gupshupCallbackUrl: text("gupshup_callback_url"),
|
|
@@ -82449,6 +82452,7 @@ var init_schema2 = __esm(() => {
|
|
|
82449
82452
|
isDefaultIdx: index("instances_is_default_idx").on(table3.isDefault),
|
|
82450
82453
|
agentIdIdx: index("instances_agent_id_idx").on(table3.agentId),
|
|
82451
82454
|
metaPhoneNumberIdx: index("instances_meta_phone_number_idx").on(table3.metaPhoneNumberId),
|
|
82455
|
+
slackIdentityIdx: index("instances_slack_identity_idx").on(table3.slackTeamId, table3.slackUserId),
|
|
82452
82456
|
chainModeCheck: check("instances_chain_mode_check", sql`${table3.chainMode} IN ('off', 'forward', 'bidirectional')`)
|
|
82453
82457
|
}));
|
|
82454
82458
|
whatsappTemplates = pgTable("whatsapp_templates", {
|
|
@@ -137204,6 +137208,31 @@ function createOmniClient(config) {
|
|
|
137204
137208
|
if (!resp.ok)
|
|
137205
137209
|
throw OmniApiError.from(json, resp.status);
|
|
137206
137210
|
return json?.data ?? [];
|
|
137211
|
+
},
|
|
137212
|
+
async appStatus() {
|
|
137213
|
+
const resp = await apiFetch(`${baseUrl}/api/v2/slack/app`);
|
|
137214
|
+
const json = await resp.json();
|
|
137215
|
+
if (!resp.ok)
|
|
137216
|
+
throw OmniApiError.from(json, resp.status);
|
|
137217
|
+
return json;
|
|
137218
|
+
},
|
|
137219
|
+
async oauthStart(body) {
|
|
137220
|
+
const resp = await apiFetch(`${baseUrl}/api/v2/slack/oauth/start`, {
|
|
137221
|
+
method: "POST",
|
|
137222
|
+
headers: { "Content-Type": "application/json" },
|
|
137223
|
+
body: JSON.stringify(body)
|
|
137224
|
+
});
|
|
137225
|
+
const json = await resp.json();
|
|
137226
|
+
if (!resp.ok)
|
|
137227
|
+
throw OmniApiError.from(json, resp.status);
|
|
137228
|
+
return json;
|
|
137229
|
+
},
|
|
137230
|
+
async oauthResult(nonce) {
|
|
137231
|
+
const resp = await apiFetch(`${baseUrl}/api/v2/slack/oauth/result/${encodeURIComponent(nonce)}`);
|
|
137232
|
+
const json = await resp.json();
|
|
137233
|
+
if (!resp.ok)
|
|
137234
|
+
throw OmniApiError.from(json, resp.status);
|
|
137235
|
+
return json;
|
|
137207
137236
|
}
|
|
137208
137237
|
},
|
|
137209
137238
|
followUp: {
|
|
@@ -137424,7 +137453,7 @@ import { fileURLToPath } from "url";
|
|
|
137424
137453
|
// package.json
|
|
137425
137454
|
var package_default = {
|
|
137426
137455
|
name: "@automagik/omni",
|
|
137427
|
-
version: "2.260922.
|
|
137456
|
+
version: "2.260922.4",
|
|
137428
137457
|
repository: {
|
|
137429
137458
|
type: "git",
|
|
137430
137459
|
url: "git+https://github.com/automagik-dev/omni.git",
|
|
@@ -151184,9 +151213,275 @@ async function runSetupAgent(options3) {
|
|
|
151184
151213
|
}
|
|
151185
151214
|
|
|
151186
151215
|
// src/commands/slack.ts
|
|
151216
|
+
import { createInterface as createInterface3 } from "readline";
|
|
151187
151217
|
init_output();
|
|
151218
|
+
var EXIT_OK = 0;
|
|
151219
|
+
var EXIT_USAGE = 2;
|
|
151220
|
+
var EXIT_API = 3;
|
|
151221
|
+
var SETTING_KEYS = {
|
|
151222
|
+
clientId: "slack.app.client_id",
|
|
151223
|
+
clientSecret: "slack.app.client_secret",
|
|
151224
|
+
signingSecret: "slack.app.signing_secret",
|
|
151225
|
+
appToken: "slack.app.app_token",
|
|
151226
|
+
publicUrl: "server.public_url"
|
|
151227
|
+
};
|
|
151228
|
+
var POLL_INTERVAL_MS2 = 2000;
|
|
151229
|
+
var DEFAULT_TIMEOUT_SECONDS = 240;
|
|
151230
|
+
function maskSecret2(secret) {
|
|
151231
|
+
if (secret.length <= 12)
|
|
151232
|
+
return "****";
|
|
151233
|
+
return `****${secret.slice(-4)}`;
|
|
151234
|
+
}
|
|
151235
|
+
async function readStdinText2() {
|
|
151236
|
+
if (process.stdin.isTTY) {
|
|
151237
|
+
throw new Error('a --*-stdin flag requires piped stdin (e.g. `printf %s "$SECRET" | omni slack app setup ...`)');
|
|
151238
|
+
}
|
|
151239
|
+
const chunks = [];
|
|
151240
|
+
for await (const chunk of process.stdin) {
|
|
151241
|
+
chunks.push(Buffer.from(chunk));
|
|
151242
|
+
}
|
|
151243
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
151244
|
+
}
|
|
151245
|
+
async function promptLine2(question) {
|
|
151246
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
151247
|
+
try {
|
|
151248
|
+
const answer = await new Promise((resolve4) => {
|
|
151249
|
+
rl.question(`${question}: `, resolve4);
|
|
151250
|
+
});
|
|
151251
|
+
return answer.trim();
|
|
151252
|
+
} finally {
|
|
151253
|
+
rl.close();
|
|
151254
|
+
}
|
|
151255
|
+
}
|
|
151256
|
+
async function openBrowser(url) {
|
|
151257
|
+
const cmd = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
|
|
151258
|
+
try {
|
|
151259
|
+
const proc = Bun.spawn({ cmd, stdout: "ignore", stderr: "ignore" });
|
|
151260
|
+
return await proc.exited === 0;
|
|
151261
|
+
} catch {
|
|
151262
|
+
return false;
|
|
151263
|
+
}
|
|
151264
|
+
}
|
|
151265
|
+
var defaultSetupPorts = { readStdin: readStdinText2, prompt: promptLine2 };
|
|
151266
|
+
var defaultConnectPorts = {
|
|
151267
|
+
openBrowser,
|
|
151268
|
+
sleep: (ms) => new Promise((resolve4) => setTimeout(resolve4, ms)),
|
|
151269
|
+
now: () => Date.now(),
|
|
151270
|
+
intervalMs: POLL_INTERVAL_MS2
|
|
151271
|
+
};
|
|
151272
|
+
function messageOf(err2) {
|
|
151273
|
+
return err2 instanceof Error ? err2.message : "Unknown error";
|
|
151274
|
+
}
|
|
151275
|
+
function codeOf(err2) {
|
|
151276
|
+
if (typeof err2 === "object" && err2 !== null && "code" in err2) {
|
|
151277
|
+
const code = err2.code;
|
|
151278
|
+
if (typeof code === "string")
|
|
151279
|
+
return code;
|
|
151280
|
+
}
|
|
151281
|
+
return;
|
|
151282
|
+
}
|
|
151283
|
+
async function fetchAppStatus(client) {
|
|
151284
|
+
try {
|
|
151285
|
+
return await client.slack.appStatus();
|
|
151286
|
+
} catch (err2) {
|
|
151287
|
+
error(`Failed to read the Slack app status: ${messageOf(err2)}`, undefined, EXIT_API);
|
|
151288
|
+
}
|
|
151289
|
+
}
|
|
151290
|
+
function printAppStatus(status) {
|
|
151291
|
+
keyValue("configured", status.configured);
|
|
151292
|
+
keyValue("missing", status.missing);
|
|
151293
|
+
keyValue("redirectUrl", status.redirectUrl);
|
|
151294
|
+
keyValue("manifestUrl", status.manifestUrl);
|
|
151295
|
+
if (!status.configured) {
|
|
151296
|
+
info(`Not configured yet \u2014 missing: ${status.missing.join(", ")}. Run: omni slack app setup`);
|
|
151297
|
+
}
|
|
151298
|
+
}
|
|
151299
|
+
var SECRET_REQUESTS = [
|
|
151300
|
+
{ field: "clientSecret", flag: "--client-secret-stdin", question: "Slack app client secret" },
|
|
151301
|
+
{ field: "signingSecret", flag: "--signing-secret-stdin", question: "Slack app signing secret" },
|
|
151302
|
+
{ field: "appToken", flag: "--app-token-stdin", question: "Slack app-level token (xapp-\u2026)" }
|
|
151303
|
+
];
|
|
151304
|
+
function wantsStdin(opts, field) {
|
|
151305
|
+
if (field === "clientSecret")
|
|
151306
|
+
return opts.clientSecretStdin === true;
|
|
151307
|
+
if (field === "signingSecret")
|
|
151308
|
+
return opts.signingSecretStdin === true;
|
|
151309
|
+
return opts.appTokenStdin === true;
|
|
151310
|
+
}
|
|
151311
|
+
async function readSecretsFromStdin(requests, ports) {
|
|
151312
|
+
const collected = new Map;
|
|
151313
|
+
if (requests.length === 0)
|
|
151314
|
+
return collected;
|
|
151315
|
+
let text3;
|
|
151316
|
+
try {
|
|
151317
|
+
text3 = await ports.readStdin();
|
|
151318
|
+
} catch (err2) {
|
|
151319
|
+
error(messageOf(err2), undefined, EXIT_USAGE);
|
|
151320
|
+
}
|
|
151321
|
+
const lines = text3.split(/\r?\n/).map((line3) => line3.trim()).filter((line3) => line3.length > 0);
|
|
151322
|
+
if (lines.length < requests.length) {
|
|
151323
|
+
const flags = requests.map((r6) => r6.flag).join(", ");
|
|
151324
|
+
error(`stdin carried ${lines.length} non-empty line(s) but ${requests.length} were requested (${flags}), one line per flag in that order`, undefined, EXIT_USAGE);
|
|
151325
|
+
}
|
|
151326
|
+
requests.forEach((request, index2) => collected.set(request.field, lines[index2]));
|
|
151327
|
+
return collected;
|
|
151328
|
+
}
|
|
151329
|
+
async function resolvePlainValue(given, question, flag2, opts, ports) {
|
|
151330
|
+
const trimmed = given?.trim();
|
|
151331
|
+
if (trimmed)
|
|
151332
|
+
return trimmed;
|
|
151333
|
+
if (opts.nonInteractive) {
|
|
151334
|
+
error(`--non-interactive requires ${flag2}`, undefined, EXIT_USAGE);
|
|
151335
|
+
}
|
|
151336
|
+
const answer = (await ports.prompt(question)).trim();
|
|
151337
|
+
if (!answer) {
|
|
151338
|
+
error(`${flag2} is required`, undefined, EXIT_USAGE);
|
|
151339
|
+
}
|
|
151340
|
+
return answer;
|
|
151341
|
+
}
|
|
151342
|
+
async function resolveSetupValues(opts, ports) {
|
|
151343
|
+
const requested = SECRET_REQUESTS.filter((request) => wantsStdin(opts, request.field));
|
|
151344
|
+
const fromStdin = await readSecretsFromStdin(requested, ports);
|
|
151345
|
+
const publicUrl = await resolvePlainValue(opts.publicUrl, "Public base URL of this deployment (https://\u2026)", "--public-url <url>", opts, ports);
|
|
151346
|
+
const clientId = await resolvePlainValue(opts.clientId, "Slack app client id", "--client-id <id>", opts, ports);
|
|
151347
|
+
const secrets2 = new Map(fromStdin);
|
|
151348
|
+
for (const request of SECRET_REQUESTS) {
|
|
151349
|
+
if (secrets2.has(request.field))
|
|
151350
|
+
continue;
|
|
151351
|
+
if (opts.nonInteractive) {
|
|
151352
|
+
error(`--non-interactive requires ${request.flag}`, undefined, EXIT_USAGE);
|
|
151353
|
+
}
|
|
151354
|
+
const answer = (await ports.prompt(request.question)).trim();
|
|
151355
|
+
if (!answer) {
|
|
151356
|
+
error(`${request.flag} is required`, undefined, EXIT_USAGE);
|
|
151357
|
+
}
|
|
151358
|
+
secrets2.set(request.field, answer);
|
|
151359
|
+
}
|
|
151360
|
+
return {
|
|
151361
|
+
publicUrl,
|
|
151362
|
+
clientId,
|
|
151363
|
+
clientSecret: secrets2.get("clientSecret") ?? "",
|
|
151364
|
+
signingSecret: secrets2.get("signingSecret") ?? "",
|
|
151365
|
+
appToken: secrets2.get("appToken") ?? ""
|
|
151366
|
+
};
|
|
151367
|
+
}
|
|
151368
|
+
async function writeAppSettings(client, values2) {
|
|
151369
|
+
const entries = [
|
|
151370
|
+
[SETTING_KEYS.clientId, values2.clientId, false],
|
|
151371
|
+
[SETTING_KEYS.clientSecret, values2.clientSecret, true],
|
|
151372
|
+
[SETTING_KEYS.signingSecret, values2.signingSecret, true],
|
|
151373
|
+
[SETTING_KEYS.appToken, values2.appToken, true],
|
|
151374
|
+
[SETTING_KEYS.publicUrl, values2.publicUrl, false]
|
|
151375
|
+
];
|
|
151376
|
+
for (const [key, value] of entries) {
|
|
151377
|
+
try {
|
|
151378
|
+
await client.settings.set(key, value);
|
|
151379
|
+
} catch (err2) {
|
|
151380
|
+
error(`Failed to write ${key}: ${messageOf(err2)}`, undefined, EXIT_API);
|
|
151381
|
+
}
|
|
151382
|
+
}
|
|
151383
|
+
success(`Wrote ${entries.length} Slack app settings`);
|
|
151384
|
+
list(entries.map(([key, value, secret]) => ({ key, value: secret ? maskSecret2(value) : value })));
|
|
151385
|
+
}
|
|
151386
|
+
async function handleAppSetup(client, opts, ports = defaultSetupPorts) {
|
|
151387
|
+
const before = await fetchAppStatus(client);
|
|
151388
|
+
if (before.manifestUrl) {
|
|
151389
|
+
info("Create (or update) the Slack app from this pre-filled manifest, then come back:");
|
|
151390
|
+
keyValue("manifestUrl", before.manifestUrl);
|
|
151391
|
+
} else {
|
|
151392
|
+
info("The pre-filled manifest link appears once server.public_url is known \u2014 this setup writes it.");
|
|
151393
|
+
}
|
|
151394
|
+
const values2 = await resolveSetupValues(opts, ports);
|
|
151395
|
+
await writeAppSettings(client, values2);
|
|
151396
|
+
printAppStatus(await fetchAppStatus(client));
|
|
151397
|
+
return { exitCode: EXIT_OK };
|
|
151398
|
+
}
|
|
151399
|
+
async function handleAppStatus(client) {
|
|
151400
|
+
printAppStatus(await fetchAppStatus(client));
|
|
151401
|
+
return { exitCode: EXIT_OK };
|
|
151402
|
+
}
|
|
151403
|
+
function resolveMode(opts) {
|
|
151404
|
+
if (opts.mode === undefined)
|
|
151405
|
+
return;
|
|
151406
|
+
if (opts.mode === "user" || opts.mode === "bot")
|
|
151407
|
+
return opts.mode;
|
|
151408
|
+
error(`--mode must be 'user' or 'bot' (got '${opts.mode}')`, undefined, EXIT_USAGE);
|
|
151409
|
+
}
|
|
151410
|
+
function resolveTimeoutSeconds(opts) {
|
|
151411
|
+
if (opts.timeout === undefined)
|
|
151412
|
+
return DEFAULT_TIMEOUT_SECONDS;
|
|
151413
|
+
const seconds = Number(opts.timeout);
|
|
151414
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
151415
|
+
error(`--timeout must be a positive number of seconds (got '${opts.timeout}')`, undefined, EXIT_USAGE);
|
|
151416
|
+
}
|
|
151417
|
+
return seconds;
|
|
151418
|
+
}
|
|
151419
|
+
async function pollOauthResult(client, nonce, ports, timeoutMs) {
|
|
151420
|
+
const deadline = ports.now() + timeoutMs;
|
|
151421
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutMs / Math.max(1, ports.intervalMs)));
|
|
151422
|
+
for (let attempt = 0;attempt < maxPolls; attempt++) {
|
|
151423
|
+
await ports.sleep(ports.intervalMs);
|
|
151424
|
+
let result;
|
|
151425
|
+
try {
|
|
151426
|
+
result = await client.slack.oauthResult(nonce);
|
|
151427
|
+
} catch (err2) {
|
|
151428
|
+
error(`Failed to read the install result: ${messageOf(err2)}`, undefined, EXIT_API);
|
|
151429
|
+
}
|
|
151430
|
+
if (result.status !== "pending")
|
|
151431
|
+
return result;
|
|
151432
|
+
if (ports.now() >= deadline)
|
|
151433
|
+
break;
|
|
151434
|
+
}
|
|
151435
|
+
return;
|
|
151436
|
+
}
|
|
151437
|
+
async function handleConnect(client, opts, ports = defaultConnectPorts) {
|
|
151438
|
+
const mode = resolveMode(opts);
|
|
151439
|
+
const timeoutSeconds = resolveTimeoutSeconds(opts);
|
|
151440
|
+
let started;
|
|
151441
|
+
try {
|
|
151442
|
+
started = await client.slack.oauthStart({ entry: "cli", ...mode ? { mode } : {} });
|
|
151443
|
+
} catch (err2) {
|
|
151444
|
+
if (codeOf(err2) === "SLACK_APP_NOT_CONFIGURED") {
|
|
151445
|
+
error(`The deployment Slack app is not configured: ${messageOf(err2)}. Run: omni slack app setup`, undefined, EXIT_API);
|
|
151446
|
+
}
|
|
151447
|
+
error(`Failed to start the Slack install: ${messageOf(err2)}`, undefined, EXIT_API);
|
|
151448
|
+
}
|
|
151449
|
+
info("Approve the install in Slack \u2014 open this URL if a browser does not:");
|
|
151450
|
+
keyValue("authorizeUrl", started.authorizeUrl);
|
|
151451
|
+
keyValue("nonce", started.nonce);
|
|
151452
|
+
keyValue("expiresAt", started.expiresAt);
|
|
151453
|
+
if (opts.open !== false) {
|
|
151454
|
+
const launched = await ports.openBrowser(started.authorizeUrl);
|
|
151455
|
+
if (!launched)
|
|
151456
|
+
info("Could not open a browser here \u2014 copy the URL above.");
|
|
151457
|
+
}
|
|
151458
|
+
const result = await pollOauthResult(client, started.nonce, ports, timeoutSeconds * 1000);
|
|
151459
|
+
if (!result) {
|
|
151460
|
+
error(`Timed out after ${timeoutSeconds}s waiting for Slack. The pending install (nonce ${started.nonce}) is single-use and cannot be resumed \u2014 start over with: omni slack connect`, undefined, EXIT_API);
|
|
151461
|
+
}
|
|
151462
|
+
if (result.status === "error") {
|
|
151463
|
+
error(`Slack install failed: ${result.message}`, { code: result.code }, EXIT_API);
|
|
151464
|
+
}
|
|
151465
|
+
if (result.status !== "done") {
|
|
151466
|
+
error(`Slack install did not complete (status: ${result.status})`, undefined, EXIT_API);
|
|
151467
|
+
}
|
|
151468
|
+
success(`Slack connected: instance ${result.instanceId}`);
|
|
151469
|
+
keyValue("instanceId", result.instanceId);
|
|
151470
|
+
keyValue("status", result.status);
|
|
151471
|
+
return { exitCode: EXIT_OK, instanceId: result.instanceId };
|
|
151472
|
+
}
|
|
151473
|
+
function createSlackAppCommand() {
|
|
151474
|
+
const app = new Command("app").description("Deployment-level Slack app registration (one app, every workspace)");
|
|
151475
|
+
app.command("setup").description("Register the deployment Slack app: print the manifest link, collect the values, write the settings").option("--public-url <url>", "Public base URL of this deployment (https://\u2026)").option("--client-id <id>", "Slack app client id").option("--client-secret-stdin", "Read the client secret from stdin (one line)").option("--signing-secret-stdin", "Read the signing secret from stdin (one line)").option("--app-token-stdin", "Read the app-level token from stdin (one line)").option("--non-interactive", "Never prompt: every value must arrive by flag or stdin").action(async (opts) => {
|
|
151476
|
+
await handleAppSetup(getClient(), opts);
|
|
151477
|
+
});
|
|
151478
|
+
app.command("status").description("Is the deployment Slack app configured? Shows missing keys, redirect URL and manifest link.").action(async () => {
|
|
151479
|
+
await handleAppStatus(getClient());
|
|
151480
|
+
});
|
|
151481
|
+
return app;
|
|
151482
|
+
}
|
|
151188
151483
|
function createSlackCommand() {
|
|
151189
|
-
const cmd = new Command("slack").description("Slack-only operations: open a DM, search messages (#889)");
|
|
151484
|
+
const cmd = new Command("slack").description("Slack-only operations: connect a workspace, open a DM, search messages (#889)");
|
|
151190
151485
|
cmd.command("dm <instance> <userId>").description("Resolve the DM channel id for a Slack user (U\u2026). Idempotent.").action(async (instance3, userId) => {
|
|
151191
151486
|
try {
|
|
151192
151487
|
const instanceId = await resolveInstanceId(instance3);
|
|
@@ -151219,6 +151514,10 @@ function createSlackCommand() {
|
|
|
151219
151514
|
error(`Search failed: ${err2 instanceof Error ? err2.message : "Unknown error"}`, undefined, 3);
|
|
151220
151515
|
}
|
|
151221
151516
|
});
|
|
151517
|
+
cmd.addCommand(createSlackAppCommand());
|
|
151518
|
+
cmd.command("connect").description("Connect a Slack workspace through the deployment app (opens Slack, then waits for the callback)").option("--mode <mode>", "Authorize as the person ('user', default) or as the workspace bot ('bot')").option("--no-open", "Do not try to open a browser; just print the URL").option("--timeout <seconds>", `Seconds to wait for the callback (default ${DEFAULT_TIMEOUT_SECONDS})`).action(async (opts) => {
|
|
151519
|
+
await handleConnect(getClient(), opts);
|
|
151520
|
+
});
|
|
151222
151521
|
return cmd;
|
|
151223
151522
|
}
|
|
151224
151523
|
|
|
@@ -152995,7 +153294,7 @@ function createTurnsCommand() {
|
|
|
152995
153294
|
// src/commands/update.ts
|
|
152996
153295
|
import { existsSync as existsSync20 } from "fs";
|
|
152997
153296
|
import { join as join19 } from "path";
|
|
152998
|
-
import { createInterface as
|
|
153297
|
+
import { createInterface as createInterface4 } from "readline";
|
|
152999
153298
|
init_source();
|
|
153000
153299
|
init_config();
|
|
153001
153300
|
|
|
@@ -153667,7 +153966,7 @@ async function restartServicesAndVerify(servicesToRestart, latest, options3, dia
|
|
|
153667
153966
|
}
|
|
153668
153967
|
}
|
|
153669
153968
|
async function promptConfirm(question) {
|
|
153670
|
-
const rl =
|
|
153969
|
+
const rl = createInterface4({
|
|
153671
153970
|
input: process.stdin,
|
|
153672
153971
|
output: process.stdout
|
|
153673
153972
|
});
|
package/dist/sdk/client.d.ts
CHANGED
|
@@ -2588,6 +2588,12 @@ export declare function createOmniClient(config: OmniClientConfig): {
|
|
|
2588
2588
|
count?: number;
|
|
2589
2589
|
page?: number;
|
|
2590
2590
|
}): Promise<Record<string, unknown>[]>;
|
|
2591
|
+
/** Deployment Slack app status: configured?, missing setting keys, redirect + manifest URLs. */
|
|
2592
|
+
appStatus(): Promise<components["schemas"]["SlackAppStatus"]>;
|
|
2593
|
+
/** Start a one-click OAuth install; open `authorizeUrl` in a browser, then poll `oauthResult(nonce)`. */
|
|
2594
|
+
oauthStart(body: components["schemas"]["SlackOAuthStartBody"]): Promise<components["schemas"]["SlackOAuthStart"]>;
|
|
2595
|
+
/** Single-use outcome of an install: `pending` until the callback lands, then `done` or `error` once. */
|
|
2596
|
+
oauthResult(nonce: string): Promise<components["schemas"]["SlackOAuthResult"]>;
|
|
2591
2597
|
};
|
|
2592
2598
|
followUp: {
|
|
2593
2599
|
getAgent(id: string): Promise<components["schemas"]["FollowUpSequenceConfig"] | null>;
|