@flytedesk/app-kit 5.0.0 → 6.0.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.
Files changed (39) hide show
  1. package/dist/auth/plugin.js +22 -2
  2. package/dist/auth/plugin.js.map +1 -1
  3. package/dist/auth/testing/fake-idp.d.ts +9 -0
  4. package/dist/auth/testing/fake-idp.js +33 -5
  5. package/dist/auth/testing/fake-idp.js.map +1 -1
  6. package/dist/bigquery/errors.d.ts +19 -0
  7. package/dist/bigquery/errors.js +41 -0
  8. package/dist/bigquery/errors.js.map +1 -1
  9. package/dist/bigquery/index.d.ts +2 -2
  10. package/dist/bigquery/index.js +1 -1
  11. package/dist/bigquery/index.js.map +1 -1
  12. package/dist/bigquery/query.js +14 -1
  13. package/dist/bigquery/query.js.map +1 -1
  14. package/dist/bigquery/schema.d.ts +5 -0
  15. package/dist/bigquery/schema.js +35 -0
  16. package/dist/bigquery/schema.js.map +1 -0
  17. package/dist/bigquery/types.d.ts +47 -2
  18. package/dist/chat/index.d.ts +52 -21
  19. package/dist/chat/index.js +50 -20
  20. package/dist/chat/index.js.map +1 -1
  21. package/dist/chat/mcp/entry.d.ts +37 -0
  22. package/dist/chat/mcp/entry.js +135 -0
  23. package/dist/chat/mcp/entry.js.map +1 -0
  24. package/dist/cli/is-running-as-main.d.ts +1 -0
  25. package/dist/cli/is-running-as-main.js +54 -0
  26. package/dist/cli/is-running-as-main.js.map +1 -0
  27. package/dist/cli/sync-engine.d.ts +1 -32
  28. package/dist/cli/sync-engine.js +7 -45
  29. package/dist/cli/sync-engine.js.map +1 -1
  30. package/dist/flags/evaluate.d.ts +43 -0
  31. package/dist/flags/evaluate.js +64 -0
  32. package/dist/flags/evaluate.js.map +1 -0
  33. package/dist/flags/index.d.ts +22 -6
  34. package/dist/flags/index.js +21 -6
  35. package/dist/flags/index.js.map +1 -1
  36. package/dist/flags/plugin.js +73 -7
  37. package/dist/flags/plugin.js.map +1 -1
  38. package/dist/flags/types.d.ts +25 -0
  39. package/package.json +3 -2
@@ -32,6 +32,20 @@ export interface BigQueryTableReference {
32
32
  datasetId?: string;
33
33
  tableId?: string;
34
34
  }
35
+ /** A result-schema field exactly as BigQuery's job statistics report it: the
36
+ * real SDK's `ITableFieldSchema`, where every property is optional. Only the
37
+ * raw boundary speaks this shape; callers get `BigQueryResultSchemaField`
38
+ * (name and type guaranteed) from `estimateQueryBytes`. */
39
+ export interface BigQueryRawSchemaField {
40
+ name?: string;
41
+ type?: string;
42
+ mode?: string;
43
+ fields?: BigQueryRawSchemaField[];
44
+ }
45
+ /** `IJobStatistics2.schema`, an `ITableSchema`. */
46
+ export interface BigQueryRawSchema {
47
+ fields?: BigQueryRawSchemaField[];
48
+ }
35
49
  export interface BigQueryJobStatistics {
36
50
  query?: {
37
51
  totalBytesProcessed?: string | number;
@@ -42,6 +56,9 @@ export interface BigQueryJobStatistics {
42
56
  statementType?: string;
43
57
  /** Every table the query touches — `IJobStatistics2.referencedTables`. */
44
58
  referencedTables?: BigQueryTableReference[];
59
+ /** The result schema BigQuery computed for the (dry-run) query —
60
+ * `IJobStatistics2.schema`, an `ITableSchema` (`{ fields }`). */
61
+ schema?: BigQueryRawSchema;
45
62
  };
46
63
  }
47
64
  export interface BigQueryJobMetadata {
@@ -92,6 +109,10 @@ export interface BigQueryCreateQueryJobOptions {
92
109
  * than run. Distinct from `EstimateQueryBytesOptions.maxBytesBilled`
93
110
  * (a pre-flight dry-run check this module enforces client-side). */
94
111
  maximumBytesBilled?: string;
112
+ /** BigQuery's own `configuration.jobTimeoutMs` — the job fails with
113
+ * `stopped` state once it runs longer than this, wall-clock, from job
114
+ * creation. The SDK converts this number to the wire's decimal string. */
115
+ jobTimeoutMs?: number;
95
116
  }
96
117
  export interface BigQueryExtractOptions {
97
118
  /** `@google-cloud/bigquery`'s own extract-job vocabulary — note this is
@@ -111,14 +132,25 @@ export interface GcsFileLike {
111
132
  };
112
133
  }
113
134
  /** One BigQuery table-schema field, as `createWriteStream`'s `schema` option
114
- * (and the REST API's `tables.insert`) accepts it. `fields` recurses for
115
- * RECORD/STRUCT columns. */
135
+ * (and the REST API's `tables.insert`) accepts it: load-job INPUT, where the
136
+ * caller must state `name` and `type`. `fields` recurses for RECORD/STRUCT
137
+ * columns. The schema BigQuery reports back is `BigQueryResultSchemaField`. */
116
138
  export interface BigQuerySchemaField {
117
139
  name: string;
118
140
  type: string;
119
141
  mode?: "NULLABLE" | "REQUIRED" | "REPEATED";
120
142
  fields?: BigQuerySchemaField[];
121
143
  }
144
+ /** One column of a query's result schema, normalised from BigQuery's own
145
+ * dry-run statistics: `name` and `type` are guaranteed (a field missing
146
+ * either is rejected with `MalformedResultSchemaError`); `mode` is BigQuery's
147
+ * string as reported. `fields` recurses for RECORD/STRUCT columns. */
148
+ export interface BigQueryResultSchemaField {
149
+ name: string;
150
+ type: string;
151
+ mode?: string;
152
+ fields?: BigQueryResultSchemaField[];
153
+ }
122
154
  export interface BigQueryLoadOptions {
123
155
  sourceFormat?: "NEWLINE_DELIMITED_JSON" | "CSV" | "AVRO" | "PARQUET";
124
156
  /** Full destination-table schema. Required when the table doesn't already
@@ -182,6 +214,11 @@ export interface RunQueryOptions {
182
214
  * belt to that check's suspenders, since data can grow between a dry
183
215
  * run and the real job, or a caller can skip the dry run entirely. */
184
216
  maximumBytesBilled?: number;
217
+ /** Fails the job with a typed `JobTimeoutError` once it runs longer than
218
+ * this many milliseconds, wall-clock from job creation — set on the job's
219
+ * own `configuration.jobTimeoutMs` (BigQuery enforces it server-side),
220
+ * not a client-side `Promise.race`. */
221
+ jobTimeoutMs?: number;
185
222
  }
186
223
  export interface QueryPage<T = Record<string, unknown>> {
187
224
  rows: T[];
@@ -197,6 +234,10 @@ export interface EstimateQueryBytesOptions {
197
234
  /** When given, `estimateQueryBytes` throws `DryRunBudgetExceededError`
198
235
  * instead of returning once the estimate exceeds this many bytes. */
199
236
  maxBytesBilled?: number;
237
+ /** Same `configuration.jobTimeoutMs` enforcement as `RunQueryOptions` —
238
+ * applies to the dry-run job itself, not the (never-run) query it
239
+ * estimates. */
240
+ jobTimeoutMs?: number;
200
241
  }
201
242
  export interface DryRunEstimate {
202
243
  totalBytesProcessed: number;
@@ -211,6 +252,10 @@ export interface DryRunEstimate {
211
252
  * checks every entry against its own read-only table allowlist before
212
253
  * ever submitting the real job. */
213
254
  referencedTables?: BigQueryTableReference[];
255
+ /** BigQuery's own result schema for the query, typed directly from the
256
+ * dry-run job's `statistics.query.schema` — never inferred client-side.
257
+ * `undefined` only when BigQuery's dry-run statistics omitted it. */
258
+ schema?: BigQueryResultSchemaField[];
214
259
  }
215
260
  export interface ExtractTableToGCSInput {
216
261
  datasetId: string;
@@ -41,32 +41,63 @@
41
41
  * async (request) => recordReply(request.body),
42
42
  * );
43
43
  *
44
- * Usage — the MCP server script the dispatched agent's clean room actually runs
45
- * (typically `dist/chat/mcp/entry.js`, wired through a `.danxbot/config/mcp-servers/
46
- * *.yml`):
44
+ * Usage — the MCP server the dispatched agent's clean room actually runs (AK-34): a
45
+ * `.danxbot/config/mcp-servers/*.yml` launches the PUBLISHED `app-kit-chat-mcp` bin
46
+ * directly, no vendored copy and no local install in the app's own repo. This
47
+ * package publishes FIVE bins and none is named `app-kit`, so `npx` needs
48
+ * `--package=` to say which package to fetch, separately from which bin to run —
49
+ * `npx -y @flytedesk/app-kit@<v> app-kit-chat-mcp` fails ("could not determine
50
+ * executable to run"); this is the form that actually works (verified against a real
51
+ * `npm pack` tarball run via `npx --package=<tgz>` from a clean directory with no
52
+ * `node_modules` — see `mcp/entry.test.ts`):
47
53
  *
48
- * import {
49
- * createChatMcpProtocol,
50
- * createChatMcpStdioServer,
51
- * createReplyTool,
52
- * } from "@flytedesk/app-kit/chat";
54
+ * server:
55
+ * command: npx
56
+ * args:
57
+ * - "-y"
58
+ * - "--package=@flytedesk/app-kit@6.0.0"
59
+ * - "app-kit-chat-mcp"
60
+ * - "--tools"
61
+ * - "${DANX_REPO_ROOT}/packages/audience-chat-mcp/src/chatTools.mjs"
53
62
  *
54
- * const protocol = createChatMcpProtocol({
55
- * serverName: "sms-app-chat",
56
- * tools: [
57
- * createReplyTool({ description: "...", deliver: postReplyToApi }),
58
- * // The extension point: register whatever app-specific tools the agent
59
- * // needs — each is just { name, description, inputSchema, handler }.
60
- * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
61
- * ],
62
- * });
63
- * createChatMcpStdioServer({ protocol }).start();
63
+ * `--tools <path>` MUST be absolute — danxbot's cwd for this process is its own
64
+ * clean room, never assumed to equal the app's repo root, hence the
65
+ * `${DANX_REPO_ROOT}`-prefixed path above rather than a bare relative one.
66
+ *
67
+ * The path names the ONE thing this package cannot supply itself: the app's own
68
+ * tools module. That module must NOT `import` anything from `@flytedesk/app-kit` —
69
+ * a bare package import cannot resolve from danxbot's unbuilt dispatch clone (no
70
+ * `node_modules`). Instead, `app-kit-chat-mcp` INJECTS the pieces the module needs as
71
+ * a `ChatMcpKit` argument, and the module returns a (optionally wrapped)
72
+ * `ChatMcpProtocol` rather than a plain options bag:
73
+ *
74
+ * // chatTools.mjs — the app's own file, never vendored, never copied, never
75
+ * // imports @flytedesk/app-kit as a VALUE (a type-only `// @ts-check` JSDoc
76
+ * // reference to BuildChatServer/ChatMcpKit is fine — see mcp/entry.ts's header)
77
+ * export default function buildChatServer(kit) {
78
+ * return kit.createChatMcpProtocol({
79
+ * serverName: "sms-app-chat",
80
+ * tools: [
81
+ * kit.createReplyTool({ description: "...", deliver: postReplyToApi }),
82
+ * // The extension point: register whatever app-specific tools the agent
83
+ * // needs — each is just { name, description, inputSchema, handler }.
84
+ * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
85
+ * ],
86
+ * });
87
+ * }
88
+ *
89
+ * `app-kit-chat-mcp` (`src/chat/mcp/entry.ts`, published as `dist/chat/mcp/entry.js`)
90
+ * loads that module, calls its default export with the kit, and starts the stdio
91
+ * loop on whatever `ChatMcpProtocol` it returns — see `entry.ts`'s own doc comment
92
+ * for the full contract, why it stays dependency-free, and how a consumer (e.g.
93
+ * media-planner's MP-169 `loggedProtocol`) wraps `handleMessage` before returning.
64
94
  */
65
- export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus } from "./launcher.js";
66
- export { callbackSecretMatches, requireCallbackSecret } from "./callback-secret.js";
95
+ export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus, } from "./launcher.js";
96
+ export { callbackSecretMatches, requireCallbackSecret, } from "./callback-secret.js";
67
97
  export { createChatMcpProtocol, createReplyTool, parseReplyArguments, FALLBACK_PROTOCOL_VERSION, JSON_RPC, } from "./mcp-protocol.js";
68
98
  export { createChatMcpStdioServer } from "./mcp-server.js";
69
99
  export { parseChatEnv, ChatEnvError } from "./env.js";
70
100
  export type { AppRegisteredTool, ChatEnv, ChatToolCallResult, ChatToolContent, DanxbotDispatchResult, DanxbotJobStatus, DanxbotLauncher, DanxbotLauncherOptions, } from "./types.js";
71
101
  export type { ChatMcpProtocol, ChatMcpProtocolOptions, ChatReplyPayload, ParseReplyArgumentsOptions, ReplyDeliverResult, ReplyToolOptions, } from "./mcp-protocol.js";
72
- export type { ChatMcpStdioServer, ChatMcpStdioServerOptions } from "./mcp-server.js";
102
+ export type { ChatMcpStdioServer, ChatMcpStdioServerOptions, } from "./mcp-server.js";
103
+ export type { BuildChatServer, ChatMcpKit } from "./mcp/entry.js";
@@ -41,29 +41,59 @@
41
41
  * async (request) => recordReply(request.body),
42
42
  * );
43
43
  *
44
- * Usage — the MCP server script the dispatched agent's clean room actually runs
45
- * (typically `dist/chat/mcp/entry.js`, wired through a `.danxbot/config/mcp-servers/
46
- * *.yml`):
44
+ * Usage — the MCP server the dispatched agent's clean room actually runs (AK-34): a
45
+ * `.danxbot/config/mcp-servers/*.yml` launches the PUBLISHED `app-kit-chat-mcp` bin
46
+ * directly, no vendored copy and no local install in the app's own repo. This
47
+ * package publishes FIVE bins and none is named `app-kit`, so `npx` needs
48
+ * `--package=` to say which package to fetch, separately from which bin to run —
49
+ * `npx -y @flytedesk/app-kit@<v> app-kit-chat-mcp` fails ("could not determine
50
+ * executable to run"); this is the form that actually works (verified against a real
51
+ * `npm pack` tarball run via `npx --package=<tgz>` from a clean directory with no
52
+ * `node_modules` — see `mcp/entry.test.ts`):
47
53
  *
48
- * import {
49
- * createChatMcpProtocol,
50
- * createChatMcpStdioServer,
51
- * createReplyTool,
52
- * } from "@flytedesk/app-kit/chat";
54
+ * server:
55
+ * command: npx
56
+ * args:
57
+ * - "-y"
58
+ * - "--package=@flytedesk/app-kit@6.0.0"
59
+ * - "app-kit-chat-mcp"
60
+ * - "--tools"
61
+ * - "${DANX_REPO_ROOT}/packages/audience-chat-mcp/src/chatTools.mjs"
53
62
  *
54
- * const protocol = createChatMcpProtocol({
55
- * serverName: "sms-app-chat",
56
- * tools: [
57
- * createReplyTool({ description: "...", deliver: postReplyToApi }),
58
- * // The extension point: register whatever app-specific tools the agent
59
- * // needs — each is just { name, description, inputSchema, handler }.
60
- * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
61
- * ],
62
- * });
63
- * createChatMcpStdioServer({ protocol }).start();
63
+ * `--tools <path>` MUST be absolute — danxbot's cwd for this process is its own
64
+ * clean room, never assumed to equal the app's repo root, hence the
65
+ * `${DANX_REPO_ROOT}`-prefixed path above rather than a bare relative one.
66
+ *
67
+ * The path names the ONE thing this package cannot supply itself: the app's own
68
+ * tools module. That module must NOT `import` anything from `@flytedesk/app-kit` —
69
+ * a bare package import cannot resolve from danxbot's unbuilt dispatch clone (no
70
+ * `node_modules`). Instead, `app-kit-chat-mcp` INJECTS the pieces the module needs as
71
+ * a `ChatMcpKit` argument, and the module returns a (optionally wrapped)
72
+ * `ChatMcpProtocol` rather than a plain options bag:
73
+ *
74
+ * // chatTools.mjs — the app's own file, never vendored, never copied, never
75
+ * // imports @flytedesk/app-kit as a VALUE (a type-only `// @ts-check` JSDoc
76
+ * // reference to BuildChatServer/ChatMcpKit is fine — see mcp/entry.ts's header)
77
+ * export default function buildChatServer(kit) {
78
+ * return kit.createChatMcpProtocol({
79
+ * serverName: "sms-app-chat",
80
+ * tools: [
81
+ * kit.createReplyTool({ description: "...", deliver: postReplyToApi }),
82
+ * // The extension point: register whatever app-specific tools the agent
83
+ * // needs — each is just { name, description, inputSchema, handler }.
84
+ * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
85
+ * ],
86
+ * });
87
+ * }
88
+ *
89
+ * `app-kit-chat-mcp` (`src/chat/mcp/entry.ts`, published as `dist/chat/mcp/entry.js`)
90
+ * loads that module, calls its default export with the kit, and starts the stdio
91
+ * loop on whatever `ChatMcpProtocol` it returns — see `entry.ts`'s own doc comment
92
+ * for the full contract, why it stays dependency-free, and how a consumer (e.g.
93
+ * media-planner's MP-169 `loggedProtocol`) wraps `handleMessage` before returning.
64
94
  */
65
- export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus } from "./launcher.js";
66
- export { callbackSecretMatches, requireCallbackSecret } from "./callback-secret.js";
95
+ export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus, } from "./launcher.js";
96
+ export { callbackSecretMatches, requireCallbackSecret, } from "./callback-secret.js";
67
97
  export { createChatMcpProtocol, createReplyTool, parseReplyArguments, FALLBACK_PROTOCOL_VERSION, JSON_RPC, } from "./mcp-protocol.js";
68
98
  export { createChatMcpStdioServer } from "./mcp-server.js";
69
99
  export { parseChatEnv, ChatEnvError } from "./env.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC5G,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AACH,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { createChatMcpProtocol, createReplyTool, parseReplyArguments, type ChatMcpProtocol } from "../mcp-protocol.js";
3
+ /**
4
+ * Everything a `--tools` module needs, injected rather than imported — see this
5
+ * file's own doc comment for why a runtime `import "@flytedesk/app-kit/chat"` from
6
+ * the module itself cannot work in danxbot's unbuilt dispatch clone.
7
+ */
8
+ export interface ChatMcpKit {
9
+ createReplyTool: typeof createReplyTool;
10
+ parseReplyArguments: typeof parseReplyArguments;
11
+ createChatMcpProtocol: typeof createChatMcpProtocol;
12
+ }
13
+ /** The contract a `--tools` module's default export must satisfy: given the kit,
14
+ * return a (possibly wrapped) `ChatMcpProtocol`. */
15
+ export type BuildChatServer = (kit: ChatMcpKit) => ChatMcpProtocol | Promise<ChatMcpProtocol>;
16
+ export declare class ChatMcpEntryError extends Error {
17
+ }
18
+ export interface ChatMcpEntryArgs {
19
+ /** Path to the app's tools module. MUST be absolute in real dispatch use (see this
20
+ * file's own doc comment) — resolved against `process.cwd()` only as a fallback
21
+ * for a genuinely relative path, which `path.resolve` leaves untouched when the
22
+ * input is already absolute. */
23
+ toolsPath: string;
24
+ }
25
+ /** Parse argv into `{toolsPath}`. Exported for direct unit testing. */
26
+ export declare function parseArgs(argv: string[]): ChatMcpEntryArgs;
27
+ /**
28
+ * Dynamically `import()` the app's tools module and validate its default export.
29
+ * Exported for direct unit testing (no process spawn needed to exercise the loading
30
+ * and validation logic — see entry.test.ts's own split between a fast in-process
31
+ * suite for this function and real stdio/npx spawn suites for the whole process).
32
+ */
33
+ export declare function loadBuildChatServer(toolsPath: string): Promise<BuildChatServer>;
34
+ /** Run the server: load the tools module, build the protocol (via the injected
35
+ * kit), and start stdio. Exported for direct unit testing of argument/validation
36
+ * failures without a process spawn. */
37
+ export declare function main(argv: string[]): Promise<void>;
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * app-kit-chat-mcp (AK-34) — a runnable stdio entry point for
4
+ * `@flytedesk/app-kit/chat`'s MCP server, so a consuming app's
5
+ * `.danxbot/config/mcp-servers/*.yml` can launch a real chat MCP server without
6
+ * vendoring a copy of `mcp-protocol.js`/`mcp-server.js` into its own repo.
7
+ *
8
+ * ## Launch form
9
+ *
10
+ * This package publishes FIVE bins and none is named `app-kit`, so npx needs
11
+ * `--package=` to say which package to fetch (separate from which bin to run):
12
+ *
13
+ * npx -y --package=@flytedesk/app-kit@<v> app-kit-chat-mcp --tools <abs path>
14
+ *
15
+ * `npx -y @flytedesk/app-kit@<v> app-kit-chat-mcp` (no `--package=`) fails —
16
+ * verified against a real `npm pack` tarball run via `npx --package=<tgz>` from a
17
+ * clean directory with no `node_modules` (see entry.test.ts's "packed via npm pack,
18
+ * run via npx --package" suite).
19
+ *
20
+ * `--tools <path>` is resolved against `process.cwd()`, which danxbot sets to its
21
+ * OWN clean-room working directory — an app's yml MUST pass a
22
+ * `${DANX_REPO_ROOT}`-absolute path, never a bare relative one.
23
+ *
24
+ * ## The `--tools` module — kit injection, not a package import
25
+ *
26
+ * The module must NOT `import` `@flytedesk/app-kit` itself — that cannot resolve
27
+ * from danxbot's unbuilt dispatch clone (no `node_modules`), and would just move the
28
+ * vendoring problem this card closes into "the app re-implements createReplyTool".
29
+ * Instead this file injects a `ChatMcpKit` (`createReplyTool`, `parseReplyArguments`,
30
+ * `createChatMcpProtocol` — the same values `@flytedesk/app-kit/chat` exports) as
31
+ * the module's one argument:
32
+ *
33
+ * export default function build(kit) {
34
+ * return kit.createChatMcpProtocol({
35
+ * serverName: "my-app-chat",
36
+ * tools: [kit.createReplyTool({ description: "...", deliver: postReply }), ...],
37
+ * });
38
+ * }
39
+ *
40
+ * The default export returns a `ChatMcpProtocol` (`{handleMessage}`), not a plain
41
+ * options bag, so it can wrap the built protocol before returning (e.g.
42
+ * media-planner's MP-169 `loggedProtocol`). A module wanting type-checking can
43
+ * reference the exported `BuildChatServer`/`ChatMcpKit` TYPES via a `// @ts-check`
44
+ * JSDoc `@type` comment — type-only, erased, resolves fine from the app's own dev
45
+ * environment; it must never become a value import.
46
+ *
47
+ * ## Dependency-free at runtime
48
+ *
49
+ * Node builtins + this package's own `mcp-protocol.js`/`mcp-server.js` (themselves
50
+ * dependency-free) + `../../cli/is-running-as-main.js` (also Node-builtins-only —
51
+ * deliberately not imported from `../../cli/sync-engine.js`, which pulls in
52
+ * `cross-spawn` for its own Prisma-shelling subcommands).
53
+ */
54
+ import { pathToFileURL } from "node:url";
55
+ import { resolve as resolvePath } from "node:path";
56
+ import { createChatMcpProtocol, createReplyTool, parseReplyArguments, } from "../mcp-protocol.js";
57
+ import { createChatMcpStdioServer } from "../mcp-server.js";
58
+ import { isRunningAsMain } from "../../cli/is-running-as-main.js";
59
+ const KIT = {
60
+ createReplyTool,
61
+ parseReplyArguments,
62
+ createChatMcpProtocol,
63
+ };
64
+ export class ChatMcpEntryError extends Error {
65
+ }
66
+ /** Parse argv into `{toolsPath}`. Exported for direct unit testing. */
67
+ export function parseArgs(argv) {
68
+ let toolsPath;
69
+ for (let i = 0; i < argv.length; i += 1) {
70
+ const arg = argv[i];
71
+ if (arg === "--tools") {
72
+ toolsPath = argv[i + 1];
73
+ i += 1;
74
+ }
75
+ else {
76
+ throw new ChatMcpEntryError(`app-kit-chat-mcp: unrecognized argument "${arg}"`);
77
+ }
78
+ }
79
+ if (toolsPath === undefined || toolsPath === "") {
80
+ throw new ChatMcpEntryError("app-kit-chat-mcp: missing required --tools <absolute path to the app's tools module>");
81
+ }
82
+ return { toolsPath };
83
+ }
84
+ /**
85
+ * Dynamically `import()` the app's tools module and validate its default export.
86
+ * Exported for direct unit testing (no process spawn needed to exercise the loading
87
+ * and validation logic — see entry.test.ts's own split between a fast in-process
88
+ * suite for this function and real stdio/npx spawn suites for the whole process).
89
+ */
90
+ export async function loadBuildChatServer(toolsPath) {
91
+ const absolutePath = resolvePath(process.cwd(), toolsPath);
92
+ let mod;
93
+ try {
94
+ mod = await import(pathToFileURL(absolutePath).href);
95
+ }
96
+ catch (cause) {
97
+ throw new ChatMcpEntryError(`app-kit-chat-mcp: could not load tools module "${toolsPath}" (resolved to ` +
98
+ `${absolutePath}): ${cause instanceof Error ? cause.message : String(cause)}`);
99
+ }
100
+ const candidate = mod.default;
101
+ if (typeof candidate !== "function") {
102
+ throw new ChatMcpEntryError(`app-kit-chat-mcp: tools module "${toolsPath}" must have a default export that is a ` +
103
+ `function returning a ChatMcpProtocol; got ${typeof candidate}.`);
104
+ }
105
+ return candidate;
106
+ }
107
+ /** Run the server: load the tools module, build the protocol (via the injected
108
+ * kit), and start stdio. Exported for direct unit testing of argument/validation
109
+ * failures without a process spawn. */
110
+ export async function main(argv) {
111
+ const { toolsPath } = parseArgs(argv);
112
+ const buildChatServer = await loadBuildChatServer(toolsPath);
113
+ let protocol;
114
+ try {
115
+ protocol = await buildChatServer(KIT);
116
+ }
117
+ catch (cause) {
118
+ throw new ChatMcpEntryError(`app-kit-chat-mcp: tools module "${toolsPath}" threw while building its chat server: ` +
119
+ `${cause instanceof Error ? cause.message : String(cause)}`);
120
+ }
121
+ if (protocol === null ||
122
+ typeof protocol !== "object" ||
123
+ typeof protocol.handleMessage !== "function") {
124
+ throw new ChatMcpEntryError(`app-kit-chat-mcp: tools module "${toolsPath}"'s default export must return a ` +
125
+ `ChatMcpProtocol ({handleMessage(message)}); got ${typeof protocol}.`);
126
+ }
127
+ createChatMcpStdioServer({ protocol }).start();
128
+ }
129
+ if (isRunningAsMain(import.meta.url)) {
130
+ main(process.argv.slice(2)).catch((err) => {
131
+ console.error(err instanceof ChatMcpEntryError ? err.message : err);
132
+ process.exitCode = 1;
133
+ });
134
+ }
135
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../src/chat/mcp/entry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function isRunningAsMain(moduleUrl: string): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Whether the module at `moduleUrl` (pass `import.meta.url`) is the one Node was
3
+ * invoked to run directly — the ESM successor to `require.main === module`, shared
4
+ * by every bin wrapper (trace-sync.ts/profile-sync.ts/flags-sync.ts/spa-server/cli.ts
5
+ * and src/chat/mcp/entry.ts) so there's one fix point instead of N copies of the
6
+ * same check. Node builtins ONLY (`node:fs`, `node:url`) — importing this file never
7
+ * drags in anything else, which is why src/chat/mcp/entry.ts imports it directly
8
+ * instead of importing `isRunningAsMain` from ./sync-engine.js: that file also pulls
9
+ * in `cross-spawn` (for its Prisma-shelling subcommands) at module scope, which would
10
+ * break entry.ts's own "Node builtins + this package's own chat modules only"
11
+ * dependency-free bar. sync-engine.ts re-exports this same function so its existing
12
+ * importers (trace-sync.ts et al.) are unaffected.
13
+ *
14
+ * A raw `import.meta.url === pathToFileURL(process.argv[1]).href` string comparison
15
+ * (the previous check) is NOT reliable: a package manager's bin shim can leave
16
+ * `argv[1]` pointing at a *symlink* while Node resolves the ESM module id through
17
+ * that symlink to the real target path for `import.meta.url` — so the two name the
18
+ * same file but compare unequal. This is not hypothetical: pnpm's
19
+ * `node_modules/<pkg>` is exactly such a symlink into its content-addressable
20
+ * store, and it reproduces on Windows for every real invocation path (`pnpm exec
21
+ * flags-sync init`, an npm-script `"flags-sync init"` run via `pnpm run`, etc.) —
22
+ * confirmed by instrumenting a real 0.3.0 install: `argv[1]` resolved to
23
+ * `...\node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js` (the symlinked
24
+ * path) while `import.meta.url` resolved to
25
+ * `...\node_modules\.pnpm\@flytedesk+app-kit@0.3.0_.../node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js`
26
+ * (the real store path). The mismatch meant `main()` was never called at all —
27
+ * every CLI entrypoint silently no-op'd: exit code 0, no output, nothing written.
28
+ *
29
+ * `realpathSync.native` (the real OS `realpath` syscall, not Node's pure-JS
30
+ * fallback — verified directly: the JS fallback leaves an on-disk casing mismatch
31
+ * unresolved, e.g. `RealFile.js` vs `realfile.js` compare unequal even though NTFS
32
+ * treats them as the same file, while `.native` correctly normalizes both to the
33
+ * same canonical path) resolves both sides to the same canonical filesystem path
34
+ * first — following symlinks AND normalizing casing — so the comparison is correct
35
+ * regardless of symlinks, path-separator style, or casing differences between how
36
+ * a shell/bin-shim sets `argv[1]` and how Node constructs `import.meta.url` for
37
+ * the same file.
38
+ */
39
+ import { realpathSync } from "node:fs";
40
+ import { fileURLToPath } from "node:url";
41
+ export function isRunningAsMain(moduleUrl) {
42
+ if (process.argv[1] === undefined)
43
+ return false;
44
+ try {
45
+ return (realpathSync.native(fileURLToPath(moduleUrl)) ===
46
+ realpathSync.native(process.argv[1]));
47
+ }
48
+ catch {
49
+ // Either path doesn't exist / isn't resolvable (e.g. a REPL or a bundler that
50
+ // gives import.meta.url a non-file URL) — definitely not "run directly" then.
51
+ return false;
52
+ }
53
+ }
54
+ //# sourceMappingURL=is-running-as-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-running-as-main.js","sourceRoot":"","sources":["../../src/cli/is-running-as-main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAChD,IAAI,CAAC;QACH,OAAO,CACL,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YAC7C,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CACrC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,8EAA8E;QAC9E,8EAA8E;QAC9E,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -1,35 +1,4 @@
1
- /**
2
- * Whether the module at `moduleUrl` (pass `import.meta.url`) is the one Node was
3
- * invoked to run directly — the ESM successor to `require.main === module`, shared
4
- * by every bin wrapper (trace-sync.ts/profile-sync.ts/flags-sync.ts) so there's one
5
- * fix point instead of three copies of the same check.
6
- *
7
- * A raw `import.meta.url === pathToFileURL(process.argv[1]).href` string comparison
8
- * (the previous check) is NOT reliable: a package manager's bin shim can leave
9
- * `argv[1]` pointing at a *symlink* while Node resolves the ESM module id through
10
- * that symlink to the real target path for `import.meta.url` — so the two name the
11
- * same file but compare unequal. This is not hypothetical: pnpm's
12
- * `node_modules/<pkg>` is exactly such a symlink into its content-addressable
13
- * store, and it reproduces on Windows for every real invocation path (`pnpm exec
14
- * flags-sync init`, an npm-script `"flags-sync init"` run via `pnpm run`, etc.) —
15
- * confirmed by instrumenting a real 0.3.0 install: `argv[1]` resolved to
16
- * `...\node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js` (the symlinked
17
- * path) while `import.meta.url` resolved to
18
- * `...\node_modules\.pnpm\@flytedesk+app-kit@0.3.0_.../node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js`
19
- * (the real store path). The mismatch meant `main()` was never called at all —
20
- * every CLI entrypoint silently no-op'd: exit code 0, no output, nothing written.
21
- *
22
- * `realpathSync.native` (the real OS `realpath` syscall, not Node's pure-JS
23
- * fallback — verified directly: the JS fallback leaves an on-disk casing mismatch
24
- * unresolved, e.g. `RealFile.js` vs `realfile.js` compare unequal even though NTFS
25
- * treats them as the same file, while `.native` correctly normalizes both to the
26
- * same canonical path) resolves both sides to the same canonical filesystem path
27
- * first — following symlinks AND normalizing casing — so the comparison is correct
28
- * regardless of symlinks, path-separator style, or casing differences between how
29
- * a shell/bin-shim sets `argv[1]` and how Node constructs `import.meta.url` for
30
- * the same file.
31
- */
32
- export declare function isRunningAsMain(moduleUrl: string): boolean;
1
+ export { isRunningAsMain } from "./is-running-as-main.js";
33
2
  /**
34
3
  * Everything that differs between `trace-sync` and `profile-sync`. `name` picks out
35
4
  * prisma/fragments/<name>.prisma, prisma/fragments/<name>.meta.json, and every
@@ -53,53 +53,15 @@
53
53
  */
54
54
  import { createHash } from "node:crypto";
55
55
  import spawn from "cross-spawn";
56
- import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
56
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
57
57
  import path from "node:path";
58
58
  import { fileURLToPath, pathToFileURL } from "node:url";
59
- /**
60
- * Whether the module at `moduleUrl` (pass `import.meta.url`) is the one Node was
61
- * invoked to run directly — the ESM successor to `require.main === module`, shared
62
- * by every bin wrapper (trace-sync.ts/profile-sync.ts/flags-sync.ts) so there's one
63
- * fix point instead of three copies of the same check.
64
- *
65
- * A raw `import.meta.url === pathToFileURL(process.argv[1]).href` string comparison
66
- * (the previous check) is NOT reliable: a package manager's bin shim can leave
67
- * `argv[1]` pointing at a *symlink* while Node resolves the ESM module id through
68
- * that symlink to the real target path for `import.meta.url` — so the two name the
69
- * same file but compare unequal. This is not hypothetical: pnpm's
70
- * `node_modules/<pkg>` is exactly such a symlink into its content-addressable
71
- * store, and it reproduces on Windows for every real invocation path (`pnpm exec
72
- * flags-sync init`, an npm-script `"flags-sync init"` run via `pnpm run`, etc.) —
73
- * confirmed by instrumenting a real 0.3.0 install: `argv[1]` resolved to
74
- * `...\node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js` (the symlinked
75
- * path) while `import.meta.url` resolved to
76
- * `...\node_modules\.pnpm\@flytedesk+app-kit@0.3.0_.../node_modules\@flytedesk\app-kit\dist\cli\flags-sync.js`
77
- * (the real store path). The mismatch meant `main()` was never called at all —
78
- * every CLI entrypoint silently no-op'd: exit code 0, no output, nothing written.
79
- *
80
- * `realpathSync.native` (the real OS `realpath` syscall, not Node's pure-JS
81
- * fallback — verified directly: the JS fallback leaves an on-disk casing mismatch
82
- * unresolved, e.g. `RealFile.js` vs `realfile.js` compare unequal even though NTFS
83
- * treats them as the same file, while `.native` correctly normalizes both to the
84
- * same canonical path) resolves both sides to the same canonical filesystem path
85
- * first — following symlinks AND normalizing casing — so the comparison is correct
86
- * regardless of symlinks, path-separator style, or casing differences between how
87
- * a shell/bin-shim sets `argv[1]` and how Node constructs `import.meta.url` for
88
- * the same file.
89
- */
90
- export function isRunningAsMain(moduleUrl) {
91
- if (process.argv[1] === undefined)
92
- return false;
93
- try {
94
- return (realpathSync.native(fileURLToPath(moduleUrl)) ===
95
- realpathSync.native(process.argv[1]));
96
- }
97
- catch {
98
- // Either path doesn't exist / isn't resolvable (e.g. a REPL or a bundler that
99
- // gives import.meta.url a non-file URL) — definitely not "run directly" then.
100
- return false;
101
- }
102
- }
59
+ // Re-exported so trace-sync.ts/profile-sync.ts/flags-sync.ts keep importing it from
60
+ // here unchanged — the implementation (and its symlink/casing bug history) now lives
61
+ // in its own Node-builtins-only module because src/chat/mcp/entry.ts needs the exact
62
+ // same check WITHOUT this file's `cross-spawn` import coming along for the ride. See
63
+ // is-running-as-main.ts's own doc comment for the full contract.
64
+ export { isRunningAsMain } from "./is-running-as-main.js";
103
65
  export class SyncCliError extends Error {
104
66
  }
105
67
  function readJson(filePath) {