Path: @/
- A TypeScript CLI that exposes the entire Slack Web API as a single command:
nori-slack <method> [--param value ...] - Designed for coding agents: all output is JSON on stdout, human-readable errors go to stderr
- Supports two transports, resolved in src/transport.ts: direct calls to Slack via the
@slack/web-apiWebClient, or a Nori Sessions broker proxy. Dispatch is dynamic in both modes -- the CLI is not limited to a fixed set of methods - Supports automatic cursor pagination via
--paginate, which fetches all pages and returns a single merged JSON response - Supports
--dry-runto preview resolved API requests without sending them -- designed as a safety net for coding agents to validate parameter resolution - Supports
describe <method>to look up parameter documentation for any Slack API method without requiring a token -- the metadata map covers all methods inKNOWN_METHODS, so agents always get full parameter documentation rather than a fallback
- Standalone repository (was originally imported from the
nori-integrationsmonorepo and now lives on its own). Distributed via the public npm registry asnori-slack-cli - The canonical install path is
npm install -g nori-slack-cli, which places thenori-slackbinary onPATH;npm linkfrom a local clone is retained for contributors - Two credential modes, no user OAuth flows: direct mode via the
SLACK_BOT_TOKENenvironment variable, and proxy mode viaNORI_SLACK_PROXY_URL+NORI_SLACK_CONTEXT_TOKEN(both must be set; Nori session machines export them). Proxy mode takes precedence when both credential sets are present - Proxy mode exists so Nori Sessions can route Slack calls through its broker's scoped access grants. It replaced a separate hand-rolled proxy client script in the sessions repo, consolidating two diverging implementations of the same command behind this one CLI
- The CLI is a thin wrapper -- it does not contain business logic, scheduling, or state management; it translates CLI flags into Slack API calls and returns the raw JSON response
- The pagination logic in src/paginate.ts is decoupled from the Slack SDK -- the cursor loop talks only to the
Transportinterface, and the merge step operates on anyAsyncIterableof page objects - User-facing installation and usage documentation lives in README.md
- Entry point is src/index.ts, which uses Commander.js with
allowUnknownOption()so arbitrary--flag valuepairs pass through without Commander rejecting them - The dynamic handler has three code paths:
--dry-runshort-circuits after param resolution (no credentials required, no API call, reports which transport would be used),--paginateruns the generic cursor looppaginatePages()+mergePages()from src/paginate.ts, and the default path makes a singletransport.call(). The transport is resolved once per invocation and both API paths route through it, so behavior (including pagination) is identical in proxy and direct mode - Two input modes: CLI flags (
--channel C123 --text "hi") and piped JSON via--json-input; when both are provided, CLI flags override stdin values - Two discovery subcommands that do not require credentials:
list-methodsoutputs known method names as JSON (supports--namespacefiltering and--descriptionsto include method descriptions), anddescribe <method>returns structured parameter documentation describeuses src/method-metadata.ts, a hand-curated static map covering every method inKNOWN_METHODS-- this is static because@slack/web-apierases parameter type information at compile time, so runtime introspection is not possible- For unknown methods (not in
KNOWN_METHODS),getMethodMetadata()returns a fallback entry with empty params and a generated docs URL, sodescribenever errors; theknownfield in the output distinguishes curated entries from fallbacks - When an unknown method is used, src/suggest.ts provides fuzzy matching via Levenshtein distance against
KNOWN_METHODS, surfacing "Did you mean?" suggestions; suggestions are non-blocking -- unknown methods still proceed to the API - Successful API responses and error responses both go to stdout as JSON; errors additionally write a human-readable line to stderr
- Exit codes:
0for success,1for API/token errors,2for missing args or invalid stdin JSON
The published npm artifact is assembled at pack time, not committed to git. The relevant package.json fields form a single chain that must stay in sync:
dist/ (gitignored)
└─ produced by `prepare: "npm run build"` (runs on npm pack / npm publish / npm install from tarball)
└─ made executable by `postbuild: "chmod +x dist/index.js"`
└─ included in the tarball by `files: ["dist"]`
└─ exposed as `nori-slack` via `bin: { "nori-slack": "./dist/index.js" }`
└─ verified end-to-end by test/packaging.test.ts on every `npm test`
test/packaging.test.tsrunsnpm pack, installs the resulting tarball into a tmpdir, and executes the installednori-slackbinary to confirmdist/actually ships. See test/docs.md for test-level detail- CI is defined in .github/workflows/pr-ci.yaml (on pull requests to
main) and .github/workflows/main-ci.yaml (on push tomain). Both mirrornori-registrarconventions: checkout,actions/setup-nodereading Node version from .nvmrc,npm install,npm run build,npm test - .nvmrc pins Node 22 to match the
nori-registrarbaseline
- Flag parsing in src/parse-args.ts converts
--kebab-casetosnake_casebecause the Slack API uses snake_case parameter names - Type coercion in
coerceValuehandles booleans ("true"/"false"), numbers (but preserves leading-zero strings like"007"), and inline JSON arrays/objects - A standalone
--flagwith no following value (or followed by another--flag) is treated as booleantrue - Error formatting in src/errors.ts maps Slack error codes to actionable suggestions (e.g.,
channel_not_foundsuggests runningconversations.list); unknown errors get a generic suggestion pointing to the source directory. Broker errors from proxy mode are normalized into the same envelope, including extracting Slack platform codes embedded in broker messages - Every error response includes a
sourcefield with the filesystem path to the CLI, so agents can locate the source code for debugging - The method metadata in src/method-metadata.ts marks
files.uploadas deprecated with a pointer to the two-stepfiles.getUploadURLExternal+files.completeUploadExternalflow - The CLI version string is currently duplicated: once in package.json
versionand once as a hardcoded argument to Commander's.version()call in src/index.ts. Both must be bumped together on release - Packaging invariant: anything that changes how the distributed artifact is produced must keep the
preparescript, thefilesallowlist,bin, and test/packaging.test.ts consistent. Concretely, any future change that removesprepare, removesfiles, emits generated code outsidedist/, or adds a second bin entry needs matching updates in the allowlist and the packaging test -- otherwisenpm install -g nori-slack-clisilently ships a broken binary (this was the exact0.1.0regression)
Created and maintained by Nori.