Path: @/src
- Contains all source modules for the CLI: entry point, argument parsing, transport selection, error formatting, pagination, fuzzy method suggestion, the known-methods catalog, and the method metadata registry
- Compiles from
src/todist/via TypeScript (ES2022 target, Node16 module resolution)
- index.ts is the CLI entry point (shebang
#!/usr/bin/env node), compiled todist/index.jsand exposed as thenori-slackbinary via thebinfield in ../package.json. The compileddist/directory is produced at pack time by thepreparescript and shipped to the npm registry via thefilesallowlist -- see ../docs.md for the full packaging chain - transport.ts is the only module that knows how to reach Slack. It selects between proxy mode (a Nori Sessions broker, configured by
NORI_SLACK_PROXY_URL+NORI_SLACK_CONTEXT_TOKEN) and direct mode (SLACK_BOT_TOKENvia@slack/web-api); everything downstream works against itsTransportinterface - parse-args.ts and errors.ts are pure utility modules with no side effects; paginate.ts is transport-generic (no Slack SDK dependency). All are independently testable and tested in @/test
- methods.ts is a static data file; it is only used by the
list-methodssubcommand and has no effect on which methods the CLI can actually call
Entry point (index.ts)
- Sets up Commander with three subcommands:
list-methods,describe, and the default dynamic method handler - The dynamic handler: optionally reads JSON from stdin, parses CLI flags, merges params (CLI flags win over stdin), then branches into three paths:
--dry-run: short-circuits immediately after param resolution -- outputs a JSON preview withok,dry_run,method,params,transport,token_present,paginate, and optionally awarningfor unknown methods. Does not require credentials. Always exits 0.--paginate: resolves the transport viaresolveTransport(), then runsmergePages(paginatePages(transport, method, params))- Default: resolves the transport, then makes a single
transport.call(method, params)
- If
resolveTransport()returns null (no credentials in either mode), the handler emits theno_tokenerror envelope and exits 1 before any API path runs - When no arguments are provided (
process.argv.length <= 2), help text and error go to stderr and the process exits with code 2 - The
list-methodssubcommand supports two options that compose together:--namespace <ns>filters the method list to those starting with the given prefix (e.g.,chat.), and--descriptionschanges the output shape fromstring[]toArray<{ method, description }>by pulling descriptions fromgetMethodMetadata(). When--namespaceis provided, anamespacefield is added to the response JSON.
Transport selection (transport.ts)
detectTransportMode(env)returns'proxy' | 'direct' | 'none': proxy when bothNORI_SLACK_PROXY_URLandNORI_SLACK_CONTEXT_TOKENare non-empty, otherwise direct whenSLACK_BOT_TOKENis set, otherwise none. Proxy takes precedence over a bot tokenresolveTransport(env)returns aTransport({ mode, call(method, params) }) ornullwhen no credentials are available- Proxy
callPOSTs{ method, args }as JSON to<proxy-url>/method(trailing slashes are stripped from the configured URL first) with anauthorization: Bearer <context token>header. A 2xx response is returned as the raw Slack JSON body; a non-2xx response throwsProxyError(codenori_slack_proxy_error) carrying the HTTP status and the broker'serrormessage - Direct
callwrapsWebClient.apiCallfrom@slack/web-api
Pagination (paginate.ts)
paginatePages(transport, method, params)is an async generator that repeatedly calls the transport, followingresponse_metadata.next_cursorand terminating when the cursor is empty or missing. It replaces the oldWebClient.paginate()path so pagination works identically over both transportsmergePages(pages)takes anAsyncIterableof page objects and returns a single merged object- Array-valued keys are concatenated across pages; scalar/metadata keys (
ok,response_metadata,headers,warning) are overwritten with the last page's value - This design means the function works generically with any Slack method's response shape -- it does not need to know which key holds the data (e.g.,
channels,members,messages)
Argument parsing (parse-args.ts)
parseArgs(argv)walks the args array linearly, handling three patterns:--key value,--key=value, and standalone--flag(boolean true)kebabToSnakeconverts all flag names from CLI convention to Slack API conventioncoerceValueapplies type inference:"true"/"false"become booleans, numeric strings become numbers (except those with leading zeros), and strings starting with[or{are attempted as JSON parse
Error formatting (errors.ts)
formatError(error, sourceDir)returns aCliErrorobject with fields:ok,error,message,suggestion,source- Handles five specific error codes: the
@slack/web-apicodesslack_webapi_platform_error,slack_webapi_rate_limited_error, andslack_webapi_request_error, plus the customno_tokenand the proxy transport'snori_slack_proxy_error - For
nori_slack_proxy_error: broker messages of the form "An API error occurred: <code>" have the Slack platform code extracted and mapped through the sameSUGGESTIONStable as direct-mode platform errors; HTTP 401 maps toproxy_unauthorizedwith a context-token rotation suggestion; any other status maps toproxy_errorwith a suggestion about the session's access grant - Broker wire contract behind those mappings: 200 returns raw Slack JSON; error statuses (e.g., 401, 403, 404) return
{ error: message } - The
SUGGESTIONSmap provides agent-friendly remediation text for common Slack platform errors likechannel_not_found,not_in_channel,rate_limited, etc.
Fuzzy method suggestion (suggest.ts)
findSimilarMethods(input, methods?, maxResults?)returns up to 3 similar method names fromKNOWN_METHODSfor typo correction- Three-tier matching: exact match returns
[](no suggestions needed), case-insensitive exact match returns the correctly-cased method as a fast path, then Levenshtein distance ranking with a dynamic threshold ofmax(3, floor(input.length * 0.4)) - Levenshtein comparison is case-insensitive (both sides lowercased) to maximize match quality
- Used in
index.tsat two integration points: the--dry-runpath addssuggestionsarray and enrichedwarningto the JSON output, and the pre-API-call path emits a stderr warning with "Did you mean: X?"
Methods catalog (methods.ts)
KNOWN_METHODSis a static string array of Slack Web API methods available to bot tokens- Serves as a discoverability aid only; the comment in the file explicitly notes the CLI is not limited to these methods
Method metadata (method-metadata.ts)
METHOD_METADATAis a staticRecord<string, MethodMetadata>map providing parameter documentation for every method inKNOWN_METHODS- Each entry includes:
description,required_params,optional_params(bothRecord<string, string>mapping param name to human-readable description),supports_pagination(boolean), optionaldeprecatednotice, anddocs_url getMethodMetadata(method)looks up the map and returns the entry if present; for unknown methods, it returns a fallback with empty params and a generated docs URL- The
docsUrl()helper constructs URLs in the formhttps://api.slack.com/methods/{method} - The
describecommand in index.ts wrapsgetMethodMetadataoutput withok,method, andknownfields (whereknownistrueonly when the method has a curated entry inMETHOD_METADATA)
--json-input,--paginate, and--dry-runare consumed by Commander as known options; all other flags pass through viaallowUnknownOption()and are parsed byparseArgsfromprocess.argv- The raw args filter explicitly strips
--json-input,--paginate, and--dry-runbefore passing toparseArgs, preventing them from being sent as Slack API parameters - When both stdin JSON and CLI flags provide the same key, the CLI flag value wins due to spread order:
{ ...stdinParams, ...cliParams } - Non-flag arguments (tokens not starting with
--) are silently skipped byparseArgs-- they do not cause errors - Rate limit errors extract
retryAfterfrom the@slack/web-apierror object and include the retry duration in the message - The
--dry-runoutput'stoken_presentfield only reflectsSLACK_BOT_TOKEN; thetransportfield is the authoritative indicator of which mode would be used (proxy wins when both credential sets are present) - The missing-credentials error keeps the error code
no_tokenfor backward compatibility, but its message ("No Slack credentials provided.") and suggestion cover both credential sets
Created and maintained by Nori.