@drisp/cli 0.4.5 → 0.5.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.
package/dist/cli.js CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunk-HXBCZAP7.js";
3
- import {
4
- channelSidecarDir,
5
- rotateGatewayToken
6
- } from "./chunk-M44KEGM7.js";
7
3
  import {
8
4
  CREDENTIAL_SOURCES_TRIED,
9
5
  EXEC_EXIT_CODE,
@@ -15,6 +11,8 @@ import {
15
11
  buildProbeConfigs,
16
12
  classifyFailure,
17
13
  connect,
14
+ createDashboardDecisionInbox,
15
+ createDashboardFeedPublisher,
18
16
  createFeedMapper,
19
17
  createRelayPermissionCallback,
20
18
  createRelayQuestionCallback,
@@ -47,15 +45,13 @@ import {
47
45
  lookupCredential,
48
46
  makeSkippedProbe,
49
47
  messageGlyphs,
48
+ normalizeHarnessOverride,
50
49
  probeSkipReason,
51
50
  processRegistry,
52
51
  progressGlyphs,
53
- readAttachmentMirror,
54
52
  readGatewayClientConfig,
55
- readPidLock,
56
53
  register,
57
54
  registerCleanupOnExit,
58
- removeAttachmentMirror,
59
55
  resolveClaudeBinary,
60
56
  resolveClaudeSettingsSurfacePaths,
61
57
  resolveHarnessAdapter,
@@ -70,13 +66,21 @@ import {
70
66
  startSessionBridge,
71
67
  supportsSessionApproval,
72
68
  todoGlyphSet,
73
- writeAttachmentMirror,
74
69
  writeGatewayClientConfig,
75
70
  wsClientOptionsForEndpoint
76
- } from "./chunk-PJUDHH4R.js";
71
+ } from "./chunk-K53YMYTG.js";
77
72
  import {
78
73
  generateId as generateId2
79
74
  } from "./chunk-BTKQ67RE.js";
75
+ import {
76
+ rotateGatewayToken
77
+ } from "./chunk-MRAM6EYI.js";
78
+ import {
79
+ readAttachmentMirror,
80
+ readPidLock,
81
+ removeAttachmentMirror,
82
+ writeAttachmentMirror
83
+ } from "./chunk-ZVOGOZNT.js";
80
84
  import {
81
85
  dashboardClientConfigPath,
82
86
  disableTelemetry,
@@ -97,7 +101,7 @@ import {
97
101
  trackTelemetryOptedOut,
98
102
  writeDashboardClientConfig,
99
103
  writeGatewayTrace
100
- } from "./chunk-4CRZXLIP.js";
104
+ } from "./chunk-BTY7MYYT.js";
101
105
  import {
102
106
  McpOptionsStep,
103
107
  StepSelector,
@@ -132,7 +136,7 @@ import {
132
136
  useWorkflowSessionController,
133
137
  writeGlobalConfig,
134
138
  writeProjectConfig
135
- } from "./chunk-5VK2ZMVV.js";
139
+ } from "./chunk-A54HGVML.js";
136
140
 
137
141
  // src/app/entry/cli.tsx
138
142
  import { render } from "ink";
@@ -1742,14 +1746,65 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1742
1746
  const abortRef = useRef(new AbortController());
1743
1747
  const feedEventsRef = useRef([]);
1744
1748
  const notifiedRuntimeErrorRef = useRef(null);
1749
+ const dashboardFeedPublisher = useMemo3(
1750
+ () => options?.dashboardFeedPublisher ?? createDashboardFeedPublisher(),
1751
+ [options?.dashboardFeedPublisher]
1752
+ );
1753
+ const dashboardDecisionInboxRef = useRef(
1754
+ options?.dashboardDecisionInbox ?? null
1755
+ );
1745
1756
  rulesRef.current = rules;
1746
1757
  feedEventsRef.current = feedEvents;
1747
1758
  sessionStoreRef.current = sessionStore;
1759
+ if (options?.dashboardDecisionInbox) {
1760
+ dashboardDecisionInboxRef.current = options.dashboardDecisionInbox;
1761
+ }
1762
+ const resolveAthenaSessionId = useCallback5(
1763
+ (fallback) => {
1764
+ if (options?.athenaSessionId) return options.athenaSessionId;
1765
+ try {
1766
+ const sessionId = sessionStoreRef.current?.getAthenaSession().id;
1767
+ if (sessionId) return sessionId;
1768
+ } catch {
1769
+ }
1770
+ return fallback;
1771
+ },
1772
+ [options?.athenaSessionId]
1773
+ );
1748
1774
  useEffect(() => {
1749
1775
  return () => {
1750
1776
  sessionStoreRef.current = void 0;
1751
1777
  };
1752
1778
  }, []);
1779
+ useEffect(() => {
1780
+ const athenaSessionId = resolveAthenaSessionId();
1781
+ if (!athenaSessionId) return;
1782
+ if (!dashboardDecisionInboxRef.current) {
1783
+ if (!readDashboardClientConfig()) return;
1784
+ dashboardDecisionInboxRef.current = createDashboardDecisionInbox();
1785
+ }
1786
+ const inbox = dashboardDecisionInboxRef.current;
1787
+ const applyPending = () => {
1788
+ const rows = inbox.pendingForSession({ athenaSessionId, limit: 25 });
1789
+ for (const row of rows) {
1790
+ runtime.sendDecision(row.requestId, row.decision);
1791
+ inbox.markConsumed({ id: row.id });
1792
+ }
1793
+ };
1794
+ applyPending();
1795
+ const interval = setInterval(
1796
+ applyPending,
1797
+ options?.dashboardDecisionPollIntervalMs ?? 1e3
1798
+ );
1799
+ return () => {
1800
+ clearInterval(interval);
1801
+ };
1802
+ }, [
1803
+ runtime,
1804
+ options?.dashboardDecisionInbox,
1805
+ options?.dashboardDecisionPollIntervalMs,
1806
+ resolveAthenaSessionId
1807
+ ]);
1753
1808
  const resetSession = useCallback5(() => {
1754
1809
  const newMapper = createFeedMapper();
1755
1810
  mapperRef.current = newMapper;
@@ -1860,18 +1915,37 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1860
1915
  },
1861
1916
  [runtime, dequeueQuestion]
1862
1917
  );
1863
- const emitNotification = useCallback5((message, title) => {
1864
- const mapper = mapperRef.current;
1865
- const syntheticRuntime = buildSyntheticNotificationEvent(
1866
- mapper,
1867
- message,
1868
- title
1869
- );
1870
- const newEvents = mapper.mapEvent(syntheticRuntime);
1871
- if (!abortRef.current.signal.aborted) {
1872
- feedStoreRef.current.pushEvents(newEvents);
1873
- }
1874
- }, []);
1918
+ const publishDashboardFeedEvents = useCallback5(
1919
+ (feedEventsToPublish, runtimeEvent) => {
1920
+ if (feedEventsToPublish.length === 0) return;
1921
+ const athenaSessionId = resolveAthenaSessionId(
1922
+ feedEventsToPublish[0]?.session_id ?? runtimeEvent?.sessionId
1923
+ );
1924
+ if (!athenaSessionId) return;
1925
+ dashboardFeedPublisher.publish({
1926
+ origin: options?.dashboardOrigin ?? "local",
1927
+ athenaSessionId,
1928
+ feedEvents: feedEventsToPublish
1929
+ });
1930
+ },
1931
+ [dashboardFeedPublisher, options?.dashboardOrigin, resolveAthenaSessionId]
1932
+ );
1933
+ const emitNotification = useCallback5(
1934
+ (message, title) => {
1935
+ const mapper = mapperRef.current;
1936
+ const syntheticRuntime = buildSyntheticNotificationEvent(
1937
+ mapper,
1938
+ message,
1939
+ title
1940
+ );
1941
+ const newEvents = mapper.mapEvent(syntheticRuntime);
1942
+ if (!abortRef.current.signal.aborted) {
1943
+ feedStoreRef.current.pushEvents(newEvents);
1944
+ publishDashboardFeedEvents(newEvents, syntheticRuntime);
1945
+ }
1946
+ },
1947
+ [publishDashboardFeedEvents]
1948
+ );
1875
1949
  const refreshRuntimeStatus = useCallback5(
1876
1950
  (notify = false) => {
1877
1951
  const nextIsServerRunning = runtime.getStatus() === "running";
@@ -1956,6 +2030,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1956
2030
  }
1957
2031
  }
1958
2032
  feedStoreRef.current.pushEvents(newFeedEvents);
2033
+ publishDashboardFeedEvents(newFeedEvents, runtimeEvent);
1959
2034
  }
1960
2035
  } finally {
1961
2036
  doneCause();
@@ -1991,6 +2066,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1991
2066
  });
1992
2067
  if (feedEvent) {
1993
2068
  feedStoreRef.current.pushEvents([feedEvent]);
2069
+ publishDashboardFeedEvents([feedEvent]);
1994
2070
  if (feedEvent.kind === "permission.decision" && feedEvent.cause?.hook_request_id) {
1995
2071
  dequeuePermission(feedEvent.cause.hook_request_id);
1996
2072
  }
@@ -2014,7 +2090,8 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
2014
2090
  dequeueQuestion,
2015
2091
  refreshRuntimeStatus,
2016
2092
  options?.relayPermission,
2017
- options?.relayQuestion
2093
+ options?.relayQuestion,
2094
+ publishDashboardFeedEvents
2018
2095
  ]);
2019
2096
  const items = useMemo3(() => {
2020
2097
  const done = startPerfStage("state.derive", {
@@ -2111,6 +2188,7 @@ function HookProvider({
2111
2188
  runtimeFactory = createRuntime,
2112
2189
  allowedTools,
2113
2190
  athenaSessionId,
2191
+ attachmentId,
2114
2192
  children
2115
2193
  }) {
2116
2194
  const runtime = useMemo4(
@@ -2158,6 +2236,7 @@ function HookProvider({
2158
2236
  void startSessionBridge({
2159
2237
  runtimeId: athenaSessionId,
2160
2238
  defaultAgentId: "main",
2239
+ ...attachmentId !== void 0 ? { attachmentId } : {},
2161
2240
  signal: controller.signal
2162
2241
  }).then((bridge) => {
2163
2242
  if (!bridge) return;
@@ -2174,7 +2253,7 @@ function HookProvider({
2174
2253
  return null;
2175
2254
  });
2176
2255
  };
2177
- }, [athenaSessionId]);
2256
+ }, [athenaSessionId, attachmentId]);
2178
2257
  useEffect2(() => {
2179
2258
  return () => {
2180
2259
  sessionStore.close();
@@ -10951,6 +11030,352 @@ function executeCommand(command, args, ctx) {
10951
11030
  }
10952
11031
  }
10953
11032
 
11033
+ // src/app/runner/assignmentHandler.ts
11034
+ function parseRunSpec(value) {
11035
+ if (typeof value !== "object" || value === null) return null;
11036
+ const obj = value;
11037
+ const prompt = obj["prompt"];
11038
+ if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
11039
+ const env2 = obj["env"];
11040
+ const workflow = obj["workflow"];
11041
+ return {
11042
+ prompt,
11043
+ sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
11044
+ projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
11045
+ workflow: typeof workflow === "object" && workflow !== null && typeof workflow["ref"] === "string" ? { ref: workflow["ref"] } : void 0,
11046
+ harness: normalizeHarnessOverride(obj["harness"]),
11047
+ env: typeof env2 === "object" && env2 !== null ? Object.fromEntries(
11048
+ Object.entries(env2).filter(
11049
+ (entry) => typeof entry[1] === "string"
11050
+ )
11051
+ ) : void 0,
11052
+ timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
11053
+ };
11054
+ }
11055
+ function workflowNameFromRef(ref) {
11056
+ if (!ref) return void 0;
11057
+ const [name] = ref.split("@", 1);
11058
+ return name && name.length > 0 ? name : void 0;
11059
+ }
11060
+ function eventKindOf(event) {
11061
+ if (event.type === "exec.completed") {
11062
+ const data = event.data;
11063
+ return data?.success === false ? "error" : "completion";
11064
+ }
11065
+ return typeof event.type === "string" && event.type.length > 0 ? event.type : "progress";
11066
+ }
11067
+ function eventPayloadOf(event) {
11068
+ if (event.type === "exec.completed") {
11069
+ const data = event.data;
11070
+ if (data?.success === false) {
11071
+ return {
11072
+ ...typeof event.data === "object" && event.data !== null ? event.data : {},
11073
+ message: typeof data.failure === "object" && data.failure !== null && typeof data.failure.message === "string" ? data.failure.message : "remote execution failed"
11074
+ };
11075
+ }
11076
+ }
11077
+ return event.data ?? null;
11078
+ }
11079
+ function withEnv(env2, fn) {
11080
+ if (!env2 || Object.keys(env2).length === 0) return fn();
11081
+ const previous = /* @__PURE__ */ new Map();
11082
+ for (const [key, value] of Object.entries(env2)) {
11083
+ previous.set(key, process.env[key]);
11084
+ process.env[key] = value;
11085
+ }
11086
+ return fn().finally(() => {
11087
+ for (const [key, value] of previous) {
11088
+ if (value === void 0) {
11089
+ delete process.env[key];
11090
+ } else {
11091
+ process.env[key] = value;
11092
+ }
11093
+ }
11094
+ });
11095
+ }
11096
+ async function executeAssignment(input) {
11097
+ const {
11098
+ envelope,
11099
+ bridge,
11100
+ dispatchId,
11101
+ location,
11102
+ projectDir: fallbackProjectDir = process.cwd(),
11103
+ runExecFn = runExec,
11104
+ bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11105
+ now = Date.now,
11106
+ abortSignal
11107
+ } = input;
11108
+ const runId = envelope.runId;
11109
+ let seq = 0;
11110
+ let terminalSent = false;
11111
+ let deferredFailedCompletion = null;
11112
+ let lastTerminalFailureMessage = null;
11113
+ const nextSeq = () => {
11114
+ seq += 1;
11115
+ return seq;
11116
+ };
11117
+ const sendProgress = async (kind, payload, ts = now()) => {
11118
+ await bridge.sendRunEvent({
11119
+ location,
11120
+ runId,
11121
+ seq: nextSeq(),
11122
+ ts,
11123
+ kind,
11124
+ payload
11125
+ });
11126
+ };
11127
+ const sendTerminal = async (eventKind, payload, ts = now()) => {
11128
+ if (terminalSent) return;
11129
+ terminalSent = true;
11130
+ const envelopeText = JSON.stringify({
11131
+ kind: "run_event",
11132
+ runId,
11133
+ seq: nextSeq(),
11134
+ ts,
11135
+ eventKind,
11136
+ payload
11137
+ });
11138
+ await bridge.completeTurn({
11139
+ dispatchId,
11140
+ location,
11141
+ text: envelopeText,
11142
+ idempotencyKey: `run_event:${runId}:terminal`
11143
+ });
11144
+ };
11145
+ await sendProgress("progress", { message: "assignment received" });
11146
+ const spec = parseRunSpec(envelope.runSpec);
11147
+ if (!spec) {
11148
+ await sendTerminal("error", { message: "remote assignment missing prompt" });
11149
+ return;
11150
+ }
11151
+ const projectDir = spec.projectDir ?? fallbackProjectDir;
11152
+ let runtimeConfig;
11153
+ try {
11154
+ runtimeConfig = bootstrapRuntimeConfigFn({
11155
+ projectDir,
11156
+ showSetup: false,
11157
+ isolationPreset: "minimal",
11158
+ harnessOverride: spec.harness,
11159
+ workflowOverride: workflowNameFromRef(spec.workflow?.ref)
11160
+ });
11161
+ } catch (err) {
11162
+ await sendTerminal("error", {
11163
+ message: err instanceof Error ? err.message : String(err)
11164
+ });
11165
+ return;
11166
+ }
11167
+ for (const warning of runtimeConfig.warnings) {
11168
+ await sendProgress("warning", { message: warning });
11169
+ }
11170
+ let buffered = "";
11171
+ const pendingProgress = [];
11172
+ const stdout = {
11173
+ write(chunk) {
11174
+ buffered += chunk;
11175
+ let newline = buffered.indexOf("\n");
11176
+ while (newline >= 0) {
11177
+ const line = buffered.slice(0, newline).trim();
11178
+ buffered = buffered.slice(newline + 1);
11179
+ if (line.length > 0) {
11180
+ try {
11181
+ const event = JSON.parse(line);
11182
+ const data = event.data;
11183
+ if (event.type === "exec.completed" && data?.success === false) {
11184
+ deferredFailedCompletion = event;
11185
+ } else if (event.type === "exec.completed") {
11186
+ deferredFailedCompletion = event;
11187
+ } else {
11188
+ pendingProgress.push(
11189
+ sendProgress(eventKindOf(event), eventPayloadOf(event), now())
11190
+ );
11191
+ }
11192
+ } catch {
11193
+ pendingProgress.push(sendProgress("progress", { line }));
11194
+ }
11195
+ }
11196
+ newline = buffered.indexOf("\n");
11197
+ }
11198
+ return true;
11199
+ }
11200
+ };
11201
+ const stderr = {
11202
+ write(chunk) {
11203
+ const text = chunk.trim();
11204
+ if (text.length > 0) pendingProgress.push(sendProgress("stderr", { text }));
11205
+ return true;
11206
+ }
11207
+ };
11208
+ try {
11209
+ await withEnv(spec.env, async () => {
11210
+ const result = await runExecFn({
11211
+ prompt: spec.prompt,
11212
+ projectDir,
11213
+ harness: runtimeConfig.harness,
11214
+ athenaSessionId: spec.sessionId ?? `athena-${runId}`,
11215
+ isolationConfig: runtimeConfig.isolationConfig,
11216
+ pluginMcpConfig: runtimeConfig.pluginMcpConfig,
11217
+ workflow: runtimeConfig.workflow,
11218
+ workflowPlan: runtimeConfig.workflowPlan,
11219
+ json: true,
11220
+ verbose: false,
11221
+ ephemeral: false,
11222
+ timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
11223
+ signal: abortSignal,
11224
+ stdout,
11225
+ stderr
11226
+ });
11227
+ await Promise.all(pendingProgress);
11228
+ if (deferredFailedCompletion) {
11229
+ const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
11230
+ const ts = typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now();
11231
+ const success = data.success !== false;
11232
+ const eventKind = success ? "completion" : "error";
11233
+ const message = !success ? result.failure?.message ?? eventPayloadOf(deferredFailedCompletion).message ?? "remote execution failed" : void 0;
11234
+ if (message) lastTerminalFailureMessage = message;
11235
+ await sendTerminal(
11236
+ eventKind,
11237
+ success ? {
11238
+ ...data,
11239
+ exitCode: result.exitCode,
11240
+ athenaSessionId: result.athenaSessionId,
11241
+ adapterSessionId: result.adapterSessionId,
11242
+ finalMessage: result.finalMessage,
11243
+ tokens: result.tokens,
11244
+ durationMs: result.durationMs,
11245
+ success: true
11246
+ } : {
11247
+ ...data,
11248
+ success: result.success,
11249
+ exitCode: result.exitCode,
11250
+ athenaSessionId: result.athenaSessionId,
11251
+ adapterSessionId: result.adapterSessionId,
11252
+ finalMessage: result.finalMessage,
11253
+ tokens: result.tokens,
11254
+ durationMs: result.durationMs,
11255
+ message
11256
+ },
11257
+ ts
11258
+ );
11259
+ return;
11260
+ }
11261
+ if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
11262
+ await sendTerminal("error", {
11263
+ success: result.success,
11264
+ exitCode: result.exitCode,
11265
+ athenaSessionId: result.athenaSessionId,
11266
+ adapterSessionId: result.adapterSessionId,
11267
+ finalMessage: result.finalMessage,
11268
+ tokens: result.tokens,
11269
+ durationMs: result.durationMs,
11270
+ message: result.failure.message
11271
+ });
11272
+ return;
11273
+ }
11274
+ await sendTerminal("completion", {
11275
+ success: result.success,
11276
+ exitCode: result.exitCode,
11277
+ athenaSessionId: result.athenaSessionId,
11278
+ adapterSessionId: result.adapterSessionId,
11279
+ finalMessage: result.finalMessage,
11280
+ tokens: result.tokens,
11281
+ durationMs: result.durationMs
11282
+ });
11283
+ });
11284
+ } catch (err) {
11285
+ await Promise.all(pendingProgress);
11286
+ await sendTerminal("error", {
11287
+ message: err instanceof Error ? err.message : String(err)
11288
+ });
11289
+ }
11290
+ }
11291
+
11292
+ // src/app/runner/envelope.ts
11293
+ function parseRunnerEnvelope(text) {
11294
+ let value;
11295
+ try {
11296
+ value = JSON.parse(text);
11297
+ } catch {
11298
+ return null;
11299
+ }
11300
+ if (typeof value !== "object" || value === null) return null;
11301
+ const obj = value;
11302
+ const runId = obj["runId"];
11303
+ if (typeof runId !== "string" || runId.length === 0) return null;
11304
+ const kind = obj["kind"];
11305
+ if (kind === "job_assignment") {
11306
+ return { kind, runId, runSpec: obj["runSpec"] };
11307
+ }
11308
+ if (kind === "cancel") {
11309
+ return { kind, runId };
11310
+ }
11311
+ return null;
11312
+ }
11313
+
11314
+ // src/app/runner/runnerSession.ts
11315
+ function createRunnerSession(opts) {
11316
+ const {
11317
+ bridge,
11318
+ projectDir,
11319
+ runExecFn = runExec,
11320
+ bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11321
+ now = Date.now
11322
+ } = opts;
11323
+ const inflight = /* @__PURE__ */ new Map();
11324
+ function startAssignment(envelope, dispatchId, location) {
11325
+ const controller = new AbortController();
11326
+ inflight.set(envelope.runId, controller);
11327
+ return executeAssignment({
11328
+ envelope,
11329
+ bridge,
11330
+ dispatchId,
11331
+ location,
11332
+ projectDir,
11333
+ runExecFn,
11334
+ bootstrapRuntimeConfigFn,
11335
+ now,
11336
+ abortSignal: controller.signal
11337
+ }).finally(() => {
11338
+ if (inflight.get(envelope.runId) === controller) {
11339
+ inflight.delete(envelope.runId);
11340
+ }
11341
+ });
11342
+ }
11343
+ return {
11344
+ handleDispatch(input) {
11345
+ const envelope = parseRunnerEnvelope(input.text);
11346
+ if (!envelope) {
11347
+ return { recognised: false, completed: Promise.resolve() };
11348
+ }
11349
+ if (envelope.kind === "job_assignment") {
11350
+ const completed = startAssignment(
11351
+ envelope,
11352
+ input.dispatchId,
11353
+ input.location
11354
+ );
11355
+ return { recognised: true, completed };
11356
+ }
11357
+ const controller = inflight.get(envelope.runId);
11358
+ if (controller) controller.abort();
11359
+ return { recognised: true, completed: Promise.resolve() };
11360
+ }
11361
+ };
11362
+ }
11363
+
11364
+ // src/app/runner/dispatchRouter.ts
11365
+ function makeDispatchRouter(opts) {
11366
+ return (payload) => {
11367
+ if (opts.runnerSession) {
11368
+ const result = opts.runnerSession.handleDispatch({
11369
+ text: payload.inbound.text,
11370
+ dispatchId: payload.dispatchId,
11371
+ location: payload.inbound.location
11372
+ });
11373
+ if (result.recognised) return;
11374
+ }
11375
+ opts.fallback(payload);
11376
+ };
11377
+ }
11378
+
10954
11379
  // src/ui/components/SessionPicker.tsx
10955
11380
  import { useState as useState11 } from "react";
10956
11381
  import { Box as Box11, Text as Text17, useInput as useInput12, useStdout as useStdout5 } from "ink";
@@ -13321,17 +13746,25 @@ function AppContent({
13321
13746
  [addMessage, currentSessionId, feedEvents.length, spawnHarness]
13322
13747
  );
13323
13748
  submitDispatchAsTurnRef.current = submitDispatchAsTurn;
13749
+ const runnerSession = useMemo17(
13750
+ () => sessionBridge ? createRunnerSession({ bridge: sessionBridge, projectDir }) : null,
13751
+ [sessionBridge, projectDir]
13752
+ );
13324
13753
  useEffect14(() => {
13325
13754
  if (!sessionBridge) return;
13326
- const off = sessionBridge.onTurnDispatch((payload) => {
13327
- if (isHarnessRunningRef.current || pendingDispatchRef.current) {
13328
- queuedDispatchRef.current = payload;
13329
- return;
13755
+ const router = makeDispatchRouter({
13756
+ runnerSession,
13757
+ fallback: (payload) => {
13758
+ if (isHarnessRunningRef.current || pendingDispatchRef.current) {
13759
+ queuedDispatchRef.current = payload;
13760
+ return;
13761
+ }
13762
+ submitDispatchAsTurnRef.current?.(payload);
13330
13763
  }
13331
- submitDispatchAsTurnRef.current?.(payload);
13332
13764
  });
13765
+ const off = sessionBridge.onTurnDispatch(router);
13333
13766
  return off;
13334
- }, [sessionBridge]);
13767
+ }, [sessionBridge, runnerSession]);
13335
13768
  useEffect14(() => {
13336
13769
  if (isHarnessRunning) return;
13337
13770
  if (pendingDispatchRef.current) return;
@@ -14366,6 +14799,7 @@ function App({
14366
14799
  isolationPreset,
14367
14800
  ascii,
14368
14801
  athenaSessionId: initialAthenaSessionId,
14802
+ attachmentId,
14369
14803
  initialTelemetryDiagnosticsConsent
14370
14804
  }) {
14371
14805
  const [clearCount, setClearCount] = useState18(0);
@@ -14581,6 +15015,7 @@ function App({
14581
15015
  runtimeState.isolation?.allowedTools
14582
15016
  ),
14583
15017
  athenaSessionId,
15018
+ ...attachmentId !== void 0 ? { attachmentId } : {},
14584
15019
  children: /* @__PURE__ */ jsx24(
14585
15020
  AppContent,
14586
15021
  {
@@ -15417,446 +15852,74 @@ function runChannelCommand(input, deps = {}) {
15417
15852
 
15418
15853
  // src/app/entry/dashboardCommand.ts
15419
15854
  import crypto3 from "crypto";
15420
- import { spawn as spawn2 } from "child_process";
15421
- import fs7 from "fs";
15855
+ import { spawn } from "child_process";
15856
+ import fs6 from "fs";
15422
15857
  import { createRequire } from "module";
15423
15858
  import os5 from "os";
15424
- import path7 from "path";
15425
- import { fileURLToPath as fileURLToPath2 } from "url";
15859
+ import path6 from "path";
15860
+ import { fileURLToPath } from "url";
15426
15861
 
15427
- // src/app/entry/gatewayCommand.ts
15428
- import { spawn } from "child_process";
15862
+ // src/infra/daemon/serviceUnit.ts
15429
15863
  import fs5 from "fs";
15864
+ import os4 from "os";
15430
15865
  import path5 from "path";
15431
- import { fileURLToPath } from "url";
15432
- var USAGE4 = `Usage: athena-flow gateway <subcommand> [--json]
15433
-
15434
- Subcommands:
15435
- start Run the gateway daemon in foreground (only mode in this build).
15436
- Options: [--bind <host:port>] [--insecure]
15437
- [--tls-cert <path>] [--tls-key <path>]
15438
- [--grace-period-ms <n>]
15439
- status Print daemon pid, uptime, and version.
15440
- probe Send a ping RPC and report reachability + latency.
15441
- link Store a remote WS/WSS gateway endpoint for this user.
15442
- unlink Restore local UDS gateway mode for this user.
15443
- rotate-token Regenerate the gateway token file (server-side).
15444
- Restart the daemon to drop existing connections; clients
15445
- must re-run "athena gateway link --token <new>".
15446
- reload-channels Reload channel sidecars without restarting the daemon.
15447
- `;
15448
- function defaultResolveDaemonEntry() {
15449
- const here = path5.dirname(fileURLToPath(import.meta.url));
15450
- return path5.resolve(here, "athena-gateway.js");
15866
+ function installServiceUnit(options) {
15867
+ const platform = options.platform ?? process.platform;
15868
+ const env2 = options.env ?? process.env;
15869
+ const home = env2["HOME"] ?? os4.homedir();
15870
+ if (platform === "darwin") {
15871
+ const target = options.targetPath ?? path5.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
15872
+ const paths = daemonStatePaths(env2);
15873
+ const plist = renderLaunchdPlist({
15874
+ label: "ai.drisp.daemon",
15875
+ nodeBinary: options.nodeBinary,
15876
+ daemonEntry: options.daemonEntry,
15877
+ workingDirectory: home,
15878
+ stdoutPath: paths.logPath,
15879
+ stderrPath: paths.logPath
15880
+ });
15881
+ writeIfChanged(target, plist);
15882
+ return {
15883
+ ok: true,
15884
+ platform: "darwin",
15885
+ path: target,
15886
+ loadCommand: `launchctl load -w ${target}`,
15887
+ startCommand: "launchctl start ai.drisp.daemon"
15888
+ };
15889
+ }
15890
+ if (platform === "linux") {
15891
+ const target = options.targetPath ?? path5.join(home, ".config", "systemd", "user", "drisp-daemon.service");
15892
+ const unit = renderSystemdUnit({
15893
+ description: "Drisp dashboard runtime daemon",
15894
+ nodeBinary: options.nodeBinary,
15895
+ daemonEntry: options.daemonEntry
15896
+ });
15897
+ writeIfChanged(target, unit);
15898
+ return {
15899
+ ok: true,
15900
+ platform: "linux",
15901
+ path: target,
15902
+ loadCommand: "systemctl --user daemon-reload",
15903
+ startCommand: "systemctl --user enable --now drisp-daemon.service"
15904
+ };
15905
+ }
15906
+ return {
15907
+ ok: false,
15908
+ platform: "unsupported",
15909
+ message: `service install not supported on ${platform}`
15910
+ };
15451
15911
  }
15452
- function readToken(tokenPath) {
15912
+ function writeIfChanged(target, content) {
15913
+ const dir = path5.dirname(target);
15914
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
15915
+ let existing = null;
15453
15916
  try {
15454
- return fs5.readFileSync(tokenPath, "utf-8").trim();
15917
+ existing = fs5.readFileSync(target, "utf-8");
15455
15918
  } catch (err) {
15456
- const code = err.code;
15457
- if (code === "ENOENT") {
15458
- throw new Error(
15459
- `gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
15460
- );
15461
- }
15462
- throw err;
15919
+ if (err.code !== "ENOENT") throw err;
15463
15920
  }
15464
- }
15465
- function flagJson(args) {
15466
- return args.includes("--json");
15467
- }
15468
- async function runGatewayCommand(input, deps = {}) {
15469
- const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
15470
- const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
15471
- const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
15472
- const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
15473
- const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
15474
- const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
15475
- const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
15476
- const connectGateway = deps.connectGateway ?? defaultConnectGateway;
15477
- const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn(process.execPath, [entry, ...args], { stdio: "inherit" }));
15478
- const { subcommand, subcommandArgs } = input;
15479
- if (!subcommand || subcommand === "help" || subcommand === "--help") {
15480
- logOut(USAGE4);
15481
- return 0;
15482
- }
15483
- if (subcommand === "start") {
15484
- const entry = resolveDaemonEntry2();
15485
- const child = spawnDaemon(entry, subcommandArgs);
15486
- return await new Promise((resolve) => {
15487
- child.once("exit", (code) => resolve(code ?? 0));
15488
- child.once("error", (err) => {
15489
- logError(`gateway start: failed to spawn daemon: ${err.message}`);
15490
- resolve(1);
15491
- });
15492
- });
15493
- }
15494
- if (subcommand === "link") {
15495
- const parsed = parseLinkArgs(subcommandArgs);
15496
- if (!parsed.ok) {
15497
- logError(parsed.message);
15498
- return 2;
15499
- }
15500
- writeClientConfig({
15501
- mode: "remote",
15502
- url: parsed.url,
15503
- token: parsed.token,
15504
- ...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
15505
- });
15506
- logOut(`gateway: linked remote endpoint ${parsed.url}`);
15507
- return 0;
15508
- }
15509
- if (subcommand === "rotate-token") {
15510
- const json = flagJson(subcommandArgs);
15511
- const extras = subcommandArgs.filter((a) => a !== "--json");
15512
- if (extras.length > 0) {
15513
- logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
15514
- return 2;
15515
- }
15516
- const tokenPath = resolveTokenPath();
15517
- try {
15518
- const newToken = rotateGatewayToken(tokenPath);
15519
- if (json) {
15520
- logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
15521
- } else {
15522
- logOut(newToken);
15523
- logOut(
15524
- `gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
15525
- );
15526
- }
15527
- return 0;
15528
- } catch (err) {
15529
- const message = err instanceof Error ? err.message : String(err);
15530
- if (json) {
15531
- logOut(JSON.stringify({ ok: false, message }));
15532
- } else {
15533
- logError(`gateway rotate-token: ${message}`);
15534
- }
15535
- return 1;
15536
- }
15537
- }
15538
- if (subcommand === "unlink") {
15539
- if (subcommandArgs.length > 0) {
15540
- logError("gateway unlink does not accept arguments");
15541
- return 2;
15542
- }
15543
- writeClientConfig({ mode: "local" });
15544
- logOut("gateway: using local gateway endpoint");
15545
- return 0;
15546
- }
15547
- if (subcommand === "probe") {
15548
- const json = flagJson(subcommandArgs);
15549
- const socketPath = resolveSocketPath();
15550
- const tokenPath = resolveTokenPath();
15551
- const endpoint = readClientConfig();
15552
- const startedAt = Date.now();
15553
- try {
15554
- const client = await connectGateway({
15555
- endpoint,
15556
- socketPath,
15557
- tokenPath,
15558
- timeoutMs: 3e3
15559
- });
15560
- const res = await client.request("ping", {});
15561
- client.close();
15562
- const latencyMs = Date.now() - startedAt;
15563
- if (json) {
15564
- logOut(
15565
- JSON.stringify({
15566
- ok: true,
15567
- reachable: true,
15568
- latency_ms: latencyMs,
15569
- daemon_pid: res.daemonPid,
15570
- daemon_uptime_ms: res.uptimeMs
15571
- })
15572
- );
15573
- } else {
15574
- logOut(
15575
- `gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
15576
- );
15577
- }
15578
- return 0;
15579
- } catch (err) {
15580
- return reportProbeFailure(err, json, logOut, logError);
15581
- }
15582
- }
15583
- if (subcommand === "reload-channels") {
15584
- const json = flagJson(subcommandArgs);
15585
- const extras = subcommandArgs.filter((a) => a !== "--json");
15586
- if (extras.length > 0) {
15587
- logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
15588
- return 2;
15589
- }
15590
- const socketPath = resolveSocketPath();
15591
- const tokenPath = resolveTokenPath();
15592
- const endpoint = readClientConfig();
15593
- try {
15594
- const client = await connectGateway({
15595
- endpoint,
15596
- socketPath,
15597
- tokenPath,
15598
- timeoutMs: 3e3
15599
- });
15600
- const res = await client.request("channels.reload", {});
15601
- client.close();
15602
- if (json) {
15603
- logOut(JSON.stringify({ ok: true, ...res }));
15604
- } else if (res.results.length === 0) {
15605
- logOut("gateway: channels reloaded (no channel sidecars found)");
15606
- } else {
15607
- logOut("gateway: channels reloaded");
15608
- for (const result of res.results) {
15609
- logOut(formatChannelReloadResult(result));
15610
- }
15611
- }
15612
- return 0;
15613
- } catch (err) {
15614
- return reportProbeFailure(err, json, logOut, logError);
15615
- }
15616
- }
15617
- if (subcommand === "status") {
15618
- const json = flagJson(subcommandArgs);
15619
- const socketPath = resolveSocketPath();
15620
- const tokenPath = resolveTokenPath();
15621
- const endpoint = readClientConfig();
15622
- try {
15623
- const client = await connectGateway({
15624
- endpoint,
15625
- socketPath,
15626
- tokenPath,
15627
- timeoutMs: 3e3
15628
- });
15629
- const res = await client.request("status", {});
15630
- client.close();
15631
- if (json) {
15632
- logOut(JSON.stringify(res));
15633
- } else {
15634
- const listenerSummary = formatListenerSummary(res.listener);
15635
- const channelSummary = formatChannelSummary(res.channels);
15636
- const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
15637
- logOut(
15638
- `gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
15639
- );
15640
- }
15641
- return 0;
15642
- } catch (err) {
15643
- return reportProbeFailure(err, json, logOut, logError);
15644
- }
15645
- }
15646
- logError(`Unknown gateway subcommand: ${subcommand}`);
15647
- logError(USAGE4);
15648
- return 2;
15649
- }
15650
- async function defaultConnectGateway(opts) {
15651
- if (opts.endpoint.mode === "remote") {
15652
- return connect({
15653
- socketPath: opts.socketPath,
15654
- token: opts.endpoint.token,
15655
- timeoutMs: opts.timeoutMs,
15656
- transport: createWsClientTransport(
15657
- wsClientOptionsForEndpoint({
15658
- url: opts.endpoint.url,
15659
- timeoutMs: opts.timeoutMs,
15660
- tlsCaPath: opts.endpoint.tlsCaPath
15661
- })
15662
- )
15663
- });
15664
- }
15665
- return connect({
15666
- socketPath: opts.socketPath,
15667
- token: readToken(opts.tokenPath),
15668
- timeoutMs: opts.timeoutMs
15669
- });
15670
- }
15671
- function parseLinkArgs(args) {
15672
- const positional = [];
15673
- let token;
15674
- let tlsCaPath;
15675
- for (let i = 0; i < args.length; i += 1) {
15676
- const arg = args[i];
15677
- if (arg === "--token") {
15678
- const v = args[i + 1];
15679
- if (!v || v.startsWith("--")) {
15680
- return { ok: false, message: "gateway link --token requires a value" };
15681
- }
15682
- token = v;
15683
- i += 1;
15684
- continue;
15685
- }
15686
- if (arg === "--tls-ca") {
15687
- const v = args[i + 1];
15688
- if (!v || v.startsWith("--")) {
15689
- return { ok: false, message: "gateway link --tls-ca requires a path" };
15690
- }
15691
- tlsCaPath = v;
15692
- i += 1;
15693
- continue;
15694
- }
15695
- if (arg.startsWith("--token=")) {
15696
- token = arg.slice("--token=".length);
15697
- continue;
15698
- }
15699
- if (arg.startsWith("--tls-ca=")) {
15700
- tlsCaPath = arg.slice("--tls-ca=".length);
15701
- continue;
15702
- }
15703
- if (arg.startsWith("--")) {
15704
- return { ok: false, message: `gateway link: unknown option ${arg}` };
15705
- }
15706
- positional.push(arg);
15707
- }
15708
- const url = positional[0];
15709
- if (!url) {
15710
- return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
15711
- }
15712
- if (positional.length > 1) {
15713
- return { ok: false, message: "gateway link accepts exactly one URL" };
15714
- }
15715
- if (!isSupportedGatewayUrl(url)) {
15716
- return { ok: false, message: "gateway link URL must use ws:// or wss://" };
15717
- }
15718
- if (!token) {
15719
- return { ok: false, message: "gateway link requires --token <token>" };
15720
- }
15721
- if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
15722
- return { ok: false, message: "gateway link --tls-ca requires a path" };
15723
- }
15724
- return {
15725
- ok: true,
15726
- url,
15727
- token,
15728
- ...tlsCaPath !== void 0 ? { tlsCaPath } : {}
15729
- };
15730
- }
15731
- function reportProbeFailure(err, json, logOut, logError) {
15732
- const message = err instanceof Error ? err.message : String(err);
15733
- if (err instanceof GatewayUnreachableError) {
15734
- if (json) {
15735
- logOut(
15736
- JSON.stringify({
15737
- ok: false,
15738
- reachable: false,
15739
- reason: "unreachable",
15740
- message
15741
- })
15742
- );
15743
- } else {
15744
- logError(`gateway: not reachable \u2014 ${message}`);
15745
- }
15746
- return 1;
15747
- }
15748
- if (err instanceof GatewayUnauthorizedError) {
15749
- if (json) {
15750
- logOut(
15751
- JSON.stringify({
15752
- ok: false,
15753
- reachable: true,
15754
- reason: "unauthorized",
15755
- message
15756
- })
15757
- );
15758
- } else {
15759
- logError(`gateway: unauthorized \u2014 ${message}`);
15760
- }
15761
- return 1;
15762
- }
15763
- if (json) {
15764
- logOut(JSON.stringify({ ok: false, reason: "error", message }));
15765
- } else {
15766
- logError(`gateway: ${message}`);
15767
- }
15768
- return 1;
15769
- }
15770
- function formatRuntimeSummary(r) {
15771
- if (!r) return " runtime=<none>";
15772
- const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
15773
- const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
15774
- return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
15775
- }
15776
- function formatChannelReloadResult(result) {
15777
- const suffix = result.reason ? `: ${result.reason}` : "";
15778
- return ` ${result.id} ${result.action}${suffix}`;
15779
- }
15780
- function formatChannelSummary(channels) {
15781
- if (channels.length === 0) return " channels=<none>";
15782
- const parts = channels.map((channel) => {
15783
- const note = channel.note ? `(${channel.note})` : "";
15784
- return `${channel.id}:${channel.state}${note}`;
15785
- });
15786
- return ` channels=${parts.join(",")}`;
15787
- }
15788
- function formatListenerSummary(listener) {
15789
- if (listener.kind === "uds") {
15790
- return `listener=uds:${listener.socketPath}`;
15791
- }
15792
- const flags = [];
15793
- if (listener.tls) flags.push("tls");
15794
- if (listener.insecure) flags.push("insecure");
15795
- const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
15796
- return `listener=${listener.url}${suffix}`;
15797
- }
15798
-
15799
- // src/infra/daemon/serviceUnit.ts
15800
- import fs6 from "fs";
15801
- import os4 from "os";
15802
- import path6 from "path";
15803
- function installServiceUnit(options) {
15804
- const platform = options.platform ?? process.platform;
15805
- const env2 = options.env ?? process.env;
15806
- const home = env2["HOME"] ?? os4.homedir();
15807
- if (platform === "darwin") {
15808
- const target = options.targetPath ?? path6.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
15809
- const paths = daemonStatePaths(env2);
15810
- const plist = renderLaunchdPlist({
15811
- label: "ai.drisp.daemon",
15812
- nodeBinary: options.nodeBinary,
15813
- daemonEntry: options.daemonEntry,
15814
- workingDirectory: home,
15815
- stdoutPath: paths.logPath,
15816
- stderrPath: paths.logPath
15817
- });
15818
- writeIfChanged(target, plist);
15819
- return {
15820
- ok: true,
15821
- platform: "darwin",
15822
- path: target,
15823
- loadCommand: `launchctl load -w ${target}`,
15824
- startCommand: "launchctl start ai.drisp.daemon"
15825
- };
15826
- }
15827
- if (platform === "linux") {
15828
- const target = options.targetPath ?? path6.join(home, ".config", "systemd", "user", "drisp-daemon.service");
15829
- const unit = renderSystemdUnit({
15830
- description: "Drisp dashboard runtime daemon",
15831
- nodeBinary: options.nodeBinary,
15832
- daemonEntry: options.daemonEntry
15833
- });
15834
- writeIfChanged(target, unit);
15835
- return {
15836
- ok: true,
15837
- platform: "linux",
15838
- path: target,
15839
- loadCommand: "systemctl --user daemon-reload",
15840
- startCommand: "systemctl --user enable --now drisp-daemon.service"
15841
- };
15842
- }
15843
- return {
15844
- ok: false,
15845
- platform: "unsupported",
15846
- message: `service install not supported on ${platform}`
15847
- };
15848
- }
15849
- function writeIfChanged(target, content) {
15850
- const dir = path6.dirname(target);
15851
- fs6.mkdirSync(dir, { recursive: true, mode: 448 });
15852
- let existing = null;
15853
- try {
15854
- existing = fs6.readFileSync(target, "utf-8");
15855
- } catch (err) {
15856
- if (err.code !== "ENOENT") throw err;
15857
- }
15858
- if (existing === content) return;
15859
- fs6.writeFileSync(target, content, { mode: 384 });
15921
+ if (existing === content) return;
15922
+ fs5.writeFileSync(target, content, { mode: 384 });
15860
15923
  }
15861
15924
  function renderLaunchdPlist(input) {
15862
15925
  const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
@@ -15912,7 +15975,7 @@ function escapeXml(input) {
15912
15975
  }
15913
15976
 
15914
15977
  // src/app/entry/dashboardCommand.ts
15915
- var USAGE5 = `Usage: athena dashboard <subcommand> [options]
15978
+ var USAGE4 = `Usage: athena dashboard <subcommand> [options]
15916
15979
 
15917
15980
  Subcommands:
15918
15981
  pair <token> --url <dashboard-origin> [--name <machine-name>]
@@ -15938,20 +16001,13 @@ Subcommands:
15938
16001
  runner is bound to this instance.
15939
16002
  list Print the local attachment mirror (the runners the dashboard
15940
16003
  reported as bound to this instance at the last pair/refresh).
15941
- console enable <runnerId>
15942
- Configure the console channel for a runner (opinionated;
15943
- writes the sidecar and reloads the gateway).
15944
- console link <runnerId>
15945
- Primitive: write ~/.config/athena/channels/console.json for
15946
- the given runner. Prefer "console enable".
15947
16004
  connect Deprecated alias for "daemon foreground".
15948
16005
  unpair Stop the daemon, revoke the refresh token, and remove the
15949
16006
  local config.
15950
16007
 
15951
16008
  Options:
15952
16009
  --url <origin> Dashboard origin (required for pair)
15953
- --runner <id> Runner id (required for "console enable"/"console
15954
- link", optional for "doctor")
16010
+ --runner <id> Runner id (optional for "doctor")
15955
16011
  --name <name> Friendly machine name (optional, defaults to hostname)
15956
16012
  --tail N Number of trailing log lines (default 20)
15957
16013
  --follow Stream new log lines until interrupted
@@ -15964,7 +16020,7 @@ var cachedVersion = null;
15964
16020
  function readPackageVersion() {
15965
16021
  if (cachedVersion !== null) return cachedVersion;
15966
16022
  try {
15967
- const injected = "0.4.5";
16023
+ const injected = "0.5.0";
15968
16024
  if (typeof injected === "string" && injected.length > 0) {
15969
16025
  cachedVersion = injected;
15970
16026
  return cachedVersion;
@@ -16013,14 +16069,14 @@ async function runDashboardCommand(input, deps = {}) {
16013
16069
  const packageVersion = deps.packageVersion ?? readPackageVersion();
16014
16070
  const { subcommand, subcommandArgs, flags } = input;
16015
16071
  if (!subcommand || subcommand === "help" || subcommand === "--help") {
16016
- logOut(USAGE5);
16072
+ logOut(USAGE4);
16017
16073
  return 0;
16018
16074
  }
16019
16075
  if (subcommand === "pair") {
16020
16076
  const token = subcommandArgs[0];
16021
16077
  if (!token) {
16022
16078
  logError("dashboard pair: missing pairing token");
16023
- logError(USAGE5);
16079
+ logError(USAGE4);
16024
16080
  return 2;
16025
16081
  }
16026
16082
  if (subcommandArgs.length > 1) {
@@ -16047,7 +16103,6 @@ async function runDashboardCommand(input, deps = {}) {
16047
16103
  hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
16048
16104
  capabilities: {
16049
16105
  instanceSocket: true,
16050
- consoleAdapter: true,
16051
16106
  runtimeDaemon: true,
16052
16107
  cliVersion: packageVersion,
16053
16108
  // Legacy field — older dashboards read `version`. Drop in a
@@ -16762,166 +16817,13 @@ async function runDashboardCommand(input, deps = {}) {
16762
16817
  return reportDoctor2(runnerOk);
16763
16818
  }
16764
16819
  if (subcommand === "console") {
16765
- const sub = subcommandArgs[0];
16766
- if (sub === "enable") {
16767
- const runnerId = subcommandArgs[1];
16768
- if (!runnerId) {
16769
- logError("dashboard console enable: missing <runnerId>");
16770
- return 2;
16771
- }
16772
- if (subcommandArgs.length > 2) {
16773
- logError(
16774
- `dashboard console enable: unexpected argument ${subcommandArgs[2]}`
16775
- );
16776
- return 2;
16777
- }
16778
- const config = readConfig2();
16779
- if (!config) {
16780
- logError(
16781
- 'dashboard console enable: not paired. Run "drisp dashboard pair" first.'
16782
- );
16783
- return 1;
16784
- }
16785
- const refreshResult = await tryRefresh("refresh");
16786
- if (!refreshResult.ok) return refreshResult.code;
16787
- const health = await fetchRunnerHealth(
16788
- fetchImpl,
16789
- config.dashboardUrl,
16790
- runnerId,
16791
- refreshResult.token
16792
- );
16793
- if (!health.matches) {
16794
- logError(
16795
- `dashboard console enable: runner ${runnerId} is not bound to this instance${health.error ? ` (${health.error})` : ""}`
16796
- );
16797
- return 1;
16798
- }
16799
- return await runDashboardCommand(
16800
- {
16801
- subcommand: "console",
16802
- subcommandArgs: ["link", runnerId],
16803
- flags: input.flags
16804
- },
16805
- deps
16806
- );
16807
- }
16808
- if (sub === "link") {
16809
- const runnerId = subcommandArgs[1];
16810
- if (!runnerId) {
16811
- logError("dashboard console link: missing <runnerId>");
16812
- return 2;
16813
- }
16814
- if (subcommandArgs.length > 2) {
16815
- logError(
16816
- `dashboard console link: unexpected argument ${subcommandArgs[2]}`
16817
- );
16818
- return 2;
16819
- }
16820
- const config = readConfig2();
16821
- if (!config) {
16822
- logError(
16823
- 'dashboard console link: not paired. Run "drisp dashboard pair" first.'
16824
- );
16825
- return 1;
16826
- }
16827
- let brokerUrl;
16828
- try {
16829
- brokerUrl = consoleBrokerUrl(config.dashboardUrl, runnerId);
16830
- } catch (err) {
16831
- logError(
16832
- `dashboard console link: ${err instanceof Error ? err.message : String(err)}`
16833
- );
16834
- return 1;
16835
- }
16836
- const dir = (deps.channelDir ?? channelSidecarDir)();
16837
- const target = path7.join(dir, `console-${runnerId}.json`);
16838
- const legacyTarget = path7.join(dir, "console.json");
16839
- let previousBroker;
16840
- try {
16841
- const existing = JSON.parse(fs7.readFileSync(target, "utf-8"));
16842
- if (typeof existing.broker_url === "string") {
16843
- previousBroker = existing.broker_url;
16844
- }
16845
- } catch {
16846
- try {
16847
- const legacy = JSON.parse(fs7.readFileSync(legacyTarget, "utf-8"));
16848
- if (typeof legacy.broker_url === "string" && legacy.runner_id === runnerId) {
16849
- previousBroker = legacy.broker_url;
16850
- }
16851
- } catch {
16852
- }
16853
- }
16854
- try {
16855
- fs7.mkdirSync(dir, { recursive: true, mode: 448 });
16856
- } catch (err) {
16857
- logError(
16858
- `dashboard console link: failed to create ${dir}: ${err instanceof Error ? err.message : String(err)}`
16859
- );
16860
- return 1;
16861
- }
16862
- const payload = {
16863
- kind: "console",
16864
- instance_id: `console:${runnerId}`,
16865
- broker_url: brokerUrl,
16866
- runner_id: runnerId,
16867
- dashboard_config: true
16868
- };
16869
- const tmp = `${target}.tmp`;
16870
- try {
16871
- fs7.writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n", {
16872
- mode: 384
16873
- });
16874
- fs7.renameSync(tmp, target);
16875
- } catch (err) {
16876
- try {
16877
- fs7.unlinkSync(tmp);
16878
- } catch {
16879
- }
16880
- logError(
16881
- `dashboard console link: failed to write ${target}: ${err instanceof Error ? err.message : String(err)}`
16882
- );
16883
- return 1;
16884
- }
16885
- try {
16886
- const legacy = JSON.parse(fs7.readFileSync(legacyTarget, "utf-8"));
16887
- if (legacy.runner_id === runnerId) {
16888
- fs7.unlinkSync(legacyTarget);
16889
- }
16890
- } catch {
16891
- }
16892
- const reload = await (deps.reloadGatewayChannels ?? defaultReloadGatewayChannels)();
16893
- if (flags.json) {
16894
- logOut(
16895
- JSON.stringify({
16896
- ok: true,
16897
- runnerId,
16898
- brokerUrl,
16899
- path: target,
16900
- ...previousBroker ? { previousBrokerUrl: previousBroker } : {},
16901
- gatewayReload: reload
16902
- })
16903
- );
16904
- } else {
16905
- if (previousBroker && previousBroker !== brokerUrl) {
16906
- logOut(`console: replaced existing config (was: ${previousBroker})`);
16907
- }
16908
- logOut(`console: linked runner ${runnerId} at ${brokerUrl}`);
16909
- logOut(`console: wrote ${target}`);
16910
- if (reload.ok) {
16911
- logOut(`console: gateway channels reloaded (${reload.message})`);
16912
- } else {
16913
- logError(`console: gateway reload skipped: ${reload.message}`);
16914
- logOut(
16915
- "console: start or reload the gateway before using the Console tab."
16916
- );
16917
- }
16918
- }
16919
- return 0;
16820
+ const message = "dashboard console is deprecated; paired dashboard feed sync now routes dashboard UI and channel decisions.";
16821
+ if (flags.json) {
16822
+ logOut(JSON.stringify({ ok: false, deprecated: true, message }));
16823
+ } else {
16824
+ logError(message);
16920
16825
  }
16921
- logError(
16922
- `dashboard console: unknown subcommand ${sub ? sub : "(missing)"}. Expected "enable <runnerId>" or "link <runnerId>".`
16923
- );
16924
- return 2;
16826
+ return 1;
16925
16827
  }
16926
16828
  if (subcommand === "list") {
16927
16829
  if (subcommandArgs.length > 0) {
@@ -17080,7 +16982,7 @@ async function runDashboardCommand(input, deps = {}) {
17080
16982
  return 0;
17081
16983
  }
17082
16984
  logError(`Unknown dashboard subcommand: ${subcommand}`);
17083
- logError(USAGE5);
16985
+ logError(USAGE4);
17084
16986
  return 2;
17085
16987
  }
17086
16988
  function defaultWaitForShutdown() {
@@ -17092,36 +16994,21 @@ function defaultWaitForShutdown() {
17092
16994
  };
17093
16995
  process.once("SIGINT", onSignal);
17094
16996
  process.once("SIGTERM", onSignal);
17095
- });
17096
- }
17097
- async function defaultReloadGatewayChannels() {
17098
- const out = [];
17099
- const err = [];
17100
- const code = await runGatewayCommand(
17101
- { subcommand: "reload-channels", subcommandArgs: [] },
17102
- {
17103
- logOut: (m) => out.push(m),
17104
- logError: (m) => err.push(m)
17105
- }
17106
- );
17107
- return {
17108
- ok: code === 0,
17109
- message: (code === 0 ? out.join("\n") : err.join("\n")) || (code === 0 ? "gateway channels reloaded" : "gateway not reachable")
17110
- };
16997
+ });
17111
16998
  }
17112
16999
  function resolveDaemonEntry() {
17113
17000
  let here;
17114
17001
  try {
17115
- here = fileURLToPath2(import.meta.url);
17002
+ here = fileURLToPath(import.meta.url);
17116
17003
  } catch {
17117
17004
  return null;
17118
17005
  }
17119
17006
  const candidates = [
17120
- path7.join(path7.dirname(here), "dashboard-daemon.js"),
17007
+ path6.join(path6.dirname(here), "dashboard-daemon.js"),
17121
17008
  // When invoked via `npm run start`, `here` may be the unbundled source
17122
17009
  // path. Walk up until we hit a `dist/` sibling.
17123
- path7.join(
17124
- path7.dirname(here),
17010
+ path6.join(
17011
+ path6.dirname(here),
17125
17012
  "..",
17126
17013
  "..",
17127
17014
  "..",
@@ -17131,7 +17018,7 @@ function resolveDaemonEntry() {
17131
17018
  ];
17132
17019
  for (const candidate of candidates) {
17133
17020
  try {
17134
- fs7.accessSync(candidate, fs7.constants.R_OK);
17021
+ fs6.accessSync(candidate, fs6.constants.R_OK);
17135
17022
  return candidate;
17136
17023
  } catch {
17137
17024
  }
@@ -17189,7 +17076,7 @@ async function defaultStartRuntimeDaemon(opts) {
17189
17076
  }
17190
17077
  let child;
17191
17078
  try {
17192
- child = spawn2(process.execPath, [entry], {
17079
+ child = spawn(process.execPath, [entry], {
17193
17080
  detached: true,
17194
17081
  stdio: "ignore",
17195
17082
  env: buildDaemonEnv(process.env)
@@ -17316,14 +17203,14 @@ async function defaultTailDaemonLog(opts) {
17316
17203
  let stream2 = null;
17317
17204
  let watcher = null;
17318
17205
  try {
17319
- const stat = fs7.statSync(paths.logPath);
17206
+ const stat = fs6.statSync(paths.logPath);
17320
17207
  const size = stat.size;
17321
17208
  const buf = Buffer.alloc(Math.min(size, opts.tail * 1024));
17322
- const fd = fs7.openSync(paths.logPath, "r");
17209
+ const fd = fs6.openSync(paths.logPath, "r");
17323
17210
  try {
17324
- fs7.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
17211
+ fs6.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
17325
17212
  } finally {
17326
- fs7.closeSync(fd);
17213
+ fs6.closeSync(fd);
17327
17214
  }
17328
17215
  const lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
17329
17216
  const tail = lines.slice(-opts.tail);
@@ -17337,253 +17224,612 @@ async function defaultTailDaemonLog(opts) {
17337
17224
  `dashboard logs: log file ${paths.logPath} does not exist yet
17338
17225
  `
17339
17226
  );
17340
- return opts.follow ? 0 : 1;
17227
+ return opts.follow ? 0 : 1;
17228
+ }
17229
+ throw err;
17230
+ }
17231
+ let position = fs6.statSync(paths.logPath).size;
17232
+ return await new Promise((resolve) => {
17233
+ let pollTimer = null;
17234
+ const drain = () => {
17235
+ try {
17236
+ const stat = fs6.statSync(paths.logPath);
17237
+ if (stat.size < position) {
17238
+ position = 0;
17239
+ }
17240
+ if (stat.size > position) {
17241
+ const fd = fs6.openSync(paths.logPath, "r");
17242
+ const buf = Buffer.alloc(stat.size - position);
17243
+ try {
17244
+ fs6.readSync(fd, buf, 0, buf.length, position);
17245
+ } finally {
17246
+ fs6.closeSync(fd);
17247
+ }
17248
+ position = stat.size;
17249
+ process.stdout.write(buf);
17250
+ }
17251
+ } catch (err) {
17252
+ if (err.code !== "ENOENT") {
17253
+ process.stderr.write(
17254
+ `dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
17255
+ `
17256
+ );
17257
+ }
17258
+ }
17259
+ };
17260
+ try {
17261
+ watcher = fs6.watch(paths.logPath, { persistent: true }, () => {
17262
+ drain();
17263
+ });
17264
+ } catch {
17265
+ pollTimer = setInterval(drain, 500);
17266
+ pollTimer.unref();
17267
+ }
17268
+ const onSignal = () => {
17269
+ if (watcher) {
17270
+ watcher.close();
17271
+ watcher = null;
17272
+ }
17273
+ if (pollTimer) {
17274
+ clearInterval(pollTimer);
17275
+ pollTimer = null;
17276
+ }
17277
+ if (stream2) {
17278
+ stream2.close();
17279
+ stream2 = null;
17280
+ }
17281
+ resolve(0);
17282
+ };
17283
+ process.once("SIGINT", onSignal);
17284
+ process.once("SIGTERM", onSignal);
17285
+ });
17286
+ }
17287
+ function formatDuration2(seconds) {
17288
+ if (!Number.isFinite(seconds) || seconds < 0) return "?";
17289
+ if (seconds < 60) return `${seconds}s`;
17290
+ if (seconds < 3600) {
17291
+ const m = Math.floor(seconds / 60);
17292
+ const s = seconds % 60;
17293
+ return s > 0 ? `${m}m${s}s` : `${m}m`;
17294
+ }
17295
+ if (seconds < 86400) {
17296
+ const h2 = Math.floor(seconds / 3600);
17297
+ const m = Math.floor(seconds % 3600 / 60);
17298
+ return m > 0 ? `${h2}h${m}m` : `${h2}h`;
17299
+ }
17300
+ const d = Math.floor(seconds / 86400);
17301
+ const h = Math.floor(seconds % 86400 / 3600);
17302
+ return h > 0 ? `${d}d${h}h` : `${d}d`;
17303
+ }
17304
+ async function fetchRunnerHealth(fetchImpl, dashboardUrl, runnerId, token) {
17305
+ const url = new URL(
17306
+ `/api/runners/${encodeURIComponent(runnerId)}`,
17307
+ dashboardUrl
17308
+ ).toString();
17309
+ let response;
17310
+ try {
17311
+ response = await fetchImpl(url, {
17312
+ method: "GET",
17313
+ headers: {
17314
+ authorization: `Bearer ${token.accessToken}`,
17315
+ accept: "application/json"
17316
+ }
17317
+ });
17318
+ } catch (err) {
17319
+ return {
17320
+ id: runnerId,
17321
+ matches: false,
17322
+ error: `request failed: ${err instanceof Error ? err.message : String(err)}`
17323
+ };
17324
+ }
17325
+ if (!response.ok) {
17326
+ return {
17327
+ id: runnerId,
17328
+ matches: false,
17329
+ error: `dashboard returned ${response.status}`
17330
+ };
17331
+ }
17332
+ let body;
17333
+ try {
17334
+ body = await response.json();
17335
+ } catch (err) {
17336
+ return {
17337
+ id: runnerId,
17338
+ matches: false,
17339
+ error: `invalid response body: ${err instanceof Error ? err.message : String(err)}`
17340
+ };
17341
+ }
17342
+ const obj = typeof body === "object" && body !== null ? body : {};
17343
+ const executionTarget = typeof obj["executionTarget"] === "string" ? obj["executionTarget"] : void 0;
17344
+ const remoteInstanceId = typeof obj["remoteInstanceId"] === "string" ? obj["remoteInstanceId"] : void 0;
17345
+ const matches = executionTarget === "remote" && remoteInstanceId === token.instanceId;
17346
+ const reasons = [];
17347
+ if (executionTarget !== "remote") {
17348
+ reasons.push(
17349
+ `executionTarget=${executionTarget ?? "unset"} (expected "remote")`
17350
+ );
17351
+ }
17352
+ if (remoteInstanceId !== token.instanceId) {
17353
+ reasons.push(
17354
+ `remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
17355
+ );
17356
+ }
17357
+ return {
17358
+ id: runnerId,
17359
+ matches,
17360
+ ...executionTarget !== void 0 ? { executionTarget } : {},
17361
+ ...remoteInstanceId !== void 0 ? { remoteInstanceId } : {},
17362
+ ...reasons.length > 0 ? { error: reasons.join("; ") } : {}
17363
+ };
17364
+ }
17365
+ function defaultInstallServiceUnit() {
17366
+ const entry = resolveDaemonEntry();
17367
+ if (!entry) {
17368
+ return {
17369
+ ok: false,
17370
+ platform: "unsupported",
17371
+ message: "cannot resolve dashboard-daemon.js entry path"
17372
+ };
17373
+ }
17374
+ return installServiceUnit({
17375
+ daemonEntry: entry,
17376
+ nodeBinary: process.execPath
17377
+ });
17378
+ }
17379
+ function compareSemver(a, b) {
17380
+ const parseN = (s) => {
17381
+ const parts = s.replace(/^v/, "").split("-")[0].split(".");
17382
+ return [
17383
+ Number.parseInt(parts[0] ?? "0", 10) || 0,
17384
+ Number.parseInt(parts[1] ?? "0", 10) || 0,
17385
+ Number.parseInt(parts[2] ?? "0", 10) || 0
17386
+ ];
17387
+ };
17388
+ const av = parseN(a);
17389
+ const bv = parseN(b);
17390
+ for (let i = 0; i < 3; i += 1) {
17391
+ if (av[i] < bv[i]) return -1;
17392
+ if (av[i] > bv[i]) return 1;
17393
+ }
17394
+ return 0;
17395
+ }
17396
+ async function safeReadError(response) {
17397
+ try {
17398
+ const text = await response.text();
17399
+ if (text.length === 0) return "";
17400
+ try {
17401
+ const parsed = JSON.parse(text);
17402
+ if (typeof parsed === "object" && parsed !== null && typeof parsed["error"] === "string") {
17403
+ return parsed["error"];
17404
+ }
17405
+ } catch {
17406
+ }
17407
+ return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
17408
+ } catch {
17409
+ return "";
17410
+ }
17411
+ }
17412
+ function parsePairResponse(raw) {
17413
+ if (typeof raw !== "object" || raw === null) {
17414
+ throw new Error("expected object");
17415
+ }
17416
+ const obj = raw;
17417
+ const instanceId = obj["instanceId"];
17418
+ const refreshToken = obj["refreshToken"];
17419
+ if (typeof instanceId !== "string" || instanceId.length === 0) {
17420
+ throw new Error("missing instanceId");
17421
+ }
17422
+ if (typeof refreshToken !== "string" || refreshToken.length === 0) {
17423
+ throw new Error("missing refreshToken");
17424
+ }
17425
+ return {
17426
+ instanceId,
17427
+ refreshToken,
17428
+ ...typeof obj["jti"] === "string" ? { jti: obj["jti"] } : {},
17429
+ ...typeof obj["accessToken"] === "string" ? { accessToken: obj["accessToken"] } : {},
17430
+ ...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
17431
+ ...Array.isArray(obj["runners"]) ? {
17432
+ runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
17433
+ } : {},
17434
+ ...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
17435
+ ...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
17436
+ };
17437
+ }
17438
+ function parseCapabilityAck(raw) {
17439
+ if (typeof raw !== "object" || raw === null) return {};
17440
+ const obj = raw;
17441
+ const ack = {};
17442
+ if (typeof obj["runtimeDaemon"] === "boolean") {
17443
+ ack.runtimeDaemon = obj["runtimeDaemon"];
17444
+ }
17445
+ if (typeof obj["instanceSocket"] === "boolean") {
17446
+ ack.instanceSocket = obj["instanceSocket"];
17447
+ }
17448
+ return ack;
17449
+ }
17450
+ function parsePairedRunner(raw) {
17451
+ if (typeof raw !== "object" || raw === null) return null;
17452
+ const obj = raw;
17453
+ const runnerId = obj["runnerId"];
17454
+ if (typeof runnerId !== "string" || runnerId.length === 0) return null;
17455
+ return {
17456
+ runnerId,
17457
+ ...typeof obj["name"] === "string" ? { name: obj["name"] } : {},
17458
+ ...typeof obj["executionTarget"] === "string" ? { executionTarget: obj["executionTarget"] } : {},
17459
+ ...typeof obj["remoteInstanceId"] === "string" ? { remoteInstanceId: obj["remoteInstanceId"] } : {}
17460
+ };
17461
+ }
17462
+
17463
+ // src/app/entry/gatewayCommand.ts
17464
+ import { spawn as spawn2 } from "child_process";
17465
+ import fs7 from "fs";
17466
+ import path7 from "path";
17467
+ import { fileURLToPath as fileURLToPath2 } from "url";
17468
+ var USAGE5 = `Usage: athena-flow gateway <subcommand> [--json]
17469
+
17470
+ Subcommands:
17471
+ start Run the gateway daemon in foreground (only mode in this build).
17472
+ Options: [--bind <host:port>] [--insecure]
17473
+ [--tls-cert <path>] [--tls-key <path>]
17474
+ [--grace-period-ms <n>]
17475
+ status Print daemon pid, uptime, and version.
17476
+ probe Send a ping RPC and report reachability + latency.
17477
+ link Store a remote WS/WSS gateway endpoint for this user.
17478
+ unlink Restore local UDS gateway mode for this user.
17479
+ rotate-token Regenerate the gateway token file (server-side).
17480
+ Restart the daemon to drop existing connections; clients
17481
+ must re-run "athena gateway link --token <new>".
17482
+ reload-channels Reload channel sidecars without restarting the daemon.
17483
+ `;
17484
+ function defaultResolveDaemonEntry() {
17485
+ const here = path7.dirname(fileURLToPath2(import.meta.url));
17486
+ return path7.resolve(here, "athena-gateway.js");
17487
+ }
17488
+ function readToken(tokenPath) {
17489
+ try {
17490
+ return fs7.readFileSync(tokenPath, "utf-8").trim();
17491
+ } catch (err) {
17492
+ const code = err.code;
17493
+ if (code === "ENOENT") {
17494
+ throw new Error(
17495
+ `gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
17496
+ );
17341
17497
  }
17342
17498
  throw err;
17343
17499
  }
17344
- let position = fs7.statSync(paths.logPath).size;
17345
- return await new Promise((resolve) => {
17346
- let pollTimer = null;
17347
- const drain = () => {
17348
- try {
17349
- const stat = fs7.statSync(paths.logPath);
17350
- if (stat.size < position) {
17351
- position = 0;
17352
- }
17353
- if (stat.size > position) {
17354
- const fd = fs7.openSync(paths.logPath, "r");
17355
- const buf = Buffer.alloc(stat.size - position);
17356
- try {
17357
- fs7.readSync(fd, buf, 0, buf.length, position);
17358
- } finally {
17359
- fs7.closeSync(fd);
17360
- }
17361
- position = stat.size;
17362
- process.stdout.write(buf);
17363
- }
17364
- } catch (err) {
17365
- if (err.code !== "ENOENT") {
17366
- process.stderr.write(
17367
- `dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
17368
- `
17369
- );
17370
- }
17371
- }
17372
- };
17373
- try {
17374
- watcher = fs7.watch(paths.logPath, { persistent: true }, () => {
17375
- drain();
17376
- });
17377
- } catch {
17378
- pollTimer = setInterval(drain, 500);
17379
- pollTimer.unref?.();
17380
- }
17381
- const onSignal = () => {
17382
- if (watcher) {
17383
- watcher.close();
17384
- watcher = null;
17385
- }
17386
- if (pollTimer) {
17387
- clearInterval(pollTimer);
17388
- pollTimer = null;
17389
- }
17390
- if (stream2) {
17391
- stream2.close();
17392
- stream2 = null;
17393
- }
17394
- resolve(0);
17395
- };
17396
- process.once("SIGINT", onSignal);
17397
- process.once("SIGTERM", onSignal);
17398
- });
17399
17500
  }
17400
- function formatDuration2(seconds) {
17401
- if (!Number.isFinite(seconds) || seconds < 0) return "?";
17402
- if (seconds < 60) return `${seconds}s`;
17403
- if (seconds < 3600) {
17404
- const m = Math.floor(seconds / 60);
17405
- const s = seconds % 60;
17406
- return s > 0 ? `${m}m${s}s` : `${m}m`;
17501
+ function flagJson(args) {
17502
+ return args.includes("--json");
17503
+ }
17504
+ async function runGatewayCommand(input, deps = {}) {
17505
+ const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
17506
+ const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
17507
+ const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
17508
+ const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
17509
+ const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
17510
+ const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
17511
+ const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
17512
+ const connectGateway = deps.connectGateway ?? defaultConnectGateway;
17513
+ const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn2(process.execPath, [entry, ...args], { stdio: "inherit" }));
17514
+ const { subcommand, subcommandArgs } = input;
17515
+ if (!subcommand || subcommand === "help" || subcommand === "--help") {
17516
+ logOut(USAGE5);
17517
+ return 0;
17407
17518
  }
17408
- if (seconds < 86400) {
17409
- const h2 = Math.floor(seconds / 3600);
17410
- const m = Math.floor(seconds % 3600 / 60);
17411
- return m > 0 ? `${h2}h${m}m` : `${h2}h`;
17519
+ if (subcommand === "start") {
17520
+ const entry = resolveDaemonEntry2();
17521
+ const child = spawnDaemon(entry, subcommandArgs);
17522
+ return await new Promise((resolve) => {
17523
+ child.once("exit", (code) => resolve(code ?? 0));
17524
+ child.once("error", (err) => {
17525
+ logError(`gateway start: failed to spawn daemon: ${err.message}`);
17526
+ resolve(1);
17527
+ });
17528
+ });
17412
17529
  }
17413
- const d = Math.floor(seconds / 86400);
17414
- const h = Math.floor(seconds % 86400 / 3600);
17415
- return h > 0 ? `${d}d${h}h` : `${d}d`;
17416
- }
17417
- async function fetchRunnerHealth(fetchImpl, dashboardUrl, runnerId, token) {
17418
- const url = new URL(
17419
- `/api/runners/${encodeURIComponent(runnerId)}`,
17420
- dashboardUrl
17421
- ).toString();
17422
- let response;
17423
- try {
17424
- response = await fetchImpl(url, {
17425
- method: "GET",
17426
- headers: {
17427
- authorization: `Bearer ${token.accessToken}`,
17428
- accept: "application/json"
17429
- }
17530
+ if (subcommand === "link") {
17531
+ const parsed = parseLinkArgs(subcommandArgs);
17532
+ if (!parsed.ok) {
17533
+ logError(parsed.message);
17534
+ return 2;
17535
+ }
17536
+ writeClientConfig({
17537
+ mode: "remote",
17538
+ url: parsed.url,
17539
+ token: parsed.token,
17540
+ ...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
17430
17541
  });
17431
- } catch (err) {
17432
- return {
17433
- id: runnerId,
17434
- matches: false,
17435
- error: `request failed: ${err instanceof Error ? err.message : String(err)}`
17436
- };
17542
+ logOut(`gateway: linked remote endpoint ${parsed.url}`);
17543
+ return 0;
17437
17544
  }
17438
- if (!response.ok) {
17439
- return {
17440
- id: runnerId,
17441
- matches: false,
17442
- error: `dashboard returned ${response.status}`
17443
- };
17545
+ if (subcommand === "rotate-token") {
17546
+ const json = flagJson(subcommandArgs);
17547
+ const extras = subcommandArgs.filter((a) => a !== "--json");
17548
+ if (extras.length > 0) {
17549
+ logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
17550
+ return 2;
17551
+ }
17552
+ const tokenPath = resolveTokenPath();
17553
+ try {
17554
+ const newToken = rotateGatewayToken(tokenPath);
17555
+ if (json) {
17556
+ logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
17557
+ } else {
17558
+ logOut(newToken);
17559
+ logOut(
17560
+ `gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
17561
+ );
17562
+ }
17563
+ return 0;
17564
+ } catch (err) {
17565
+ const message = err instanceof Error ? err.message : String(err);
17566
+ if (json) {
17567
+ logOut(JSON.stringify({ ok: false, message }));
17568
+ } else {
17569
+ logError(`gateway rotate-token: ${message}`);
17570
+ }
17571
+ return 1;
17572
+ }
17444
17573
  }
17445
- let body;
17446
- try {
17447
- body = await response.json();
17448
- } catch (err) {
17449
- return {
17450
- id: runnerId,
17451
- matches: false,
17452
- error: `invalid response body: ${err instanceof Error ? err.message : String(err)}`
17453
- };
17574
+ if (subcommand === "unlink") {
17575
+ if (subcommandArgs.length > 0) {
17576
+ logError("gateway unlink does not accept arguments");
17577
+ return 2;
17578
+ }
17579
+ writeClientConfig({ mode: "local" });
17580
+ logOut("gateway: using local gateway endpoint");
17581
+ return 0;
17454
17582
  }
17455
- const obj = typeof body === "object" && body !== null ? body : {};
17456
- const executionTarget = typeof obj["executionTarget"] === "string" ? obj["executionTarget"] : void 0;
17457
- const remoteInstanceId = typeof obj["remoteInstanceId"] === "string" ? obj["remoteInstanceId"] : void 0;
17458
- const matches = executionTarget === "remote" && remoteInstanceId === token.instanceId;
17459
- const reasons = [];
17460
- if (executionTarget !== "remote") {
17461
- reasons.push(
17462
- `executionTarget=${executionTarget ?? "unset"} (expected "remote")`
17463
- );
17583
+ if (subcommand === "probe") {
17584
+ const json = flagJson(subcommandArgs);
17585
+ const socketPath = resolveSocketPath();
17586
+ const tokenPath = resolveTokenPath();
17587
+ const endpoint = readClientConfig();
17588
+ const startedAt = Date.now();
17589
+ try {
17590
+ const client = await connectGateway({
17591
+ endpoint,
17592
+ socketPath,
17593
+ tokenPath,
17594
+ timeoutMs: 3e3
17595
+ });
17596
+ const res = await client.request("ping", {});
17597
+ client.close();
17598
+ const latencyMs = Date.now() - startedAt;
17599
+ if (json) {
17600
+ logOut(
17601
+ JSON.stringify({
17602
+ ok: true,
17603
+ reachable: true,
17604
+ latency_ms: latencyMs,
17605
+ daemon_pid: res.daemonPid,
17606
+ daemon_uptime_ms: res.uptimeMs
17607
+ })
17608
+ );
17609
+ } else {
17610
+ logOut(
17611
+ `gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
17612
+ );
17613
+ }
17614
+ return 0;
17615
+ } catch (err) {
17616
+ return reportProbeFailure(err, json, logOut, logError);
17617
+ }
17464
17618
  }
17465
- if (remoteInstanceId !== token.instanceId) {
17466
- reasons.push(
17467
- `remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
17468
- );
17619
+ if (subcommand === "reload-channels") {
17620
+ const json = flagJson(subcommandArgs);
17621
+ const extras = subcommandArgs.filter((a) => a !== "--json");
17622
+ if (extras.length > 0) {
17623
+ logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
17624
+ return 2;
17625
+ }
17626
+ const socketPath = resolveSocketPath();
17627
+ const tokenPath = resolveTokenPath();
17628
+ const endpoint = readClientConfig();
17629
+ try {
17630
+ const client = await connectGateway({
17631
+ endpoint,
17632
+ socketPath,
17633
+ tokenPath,
17634
+ timeoutMs: 3e3
17635
+ });
17636
+ const res = await client.request("channels.reload", {});
17637
+ client.close();
17638
+ if (json) {
17639
+ logOut(JSON.stringify({ ok: true, ...res }));
17640
+ } else if (res.results.length === 0) {
17641
+ logOut("gateway: channels reloaded (no channel sidecars found)");
17642
+ } else {
17643
+ logOut("gateway: channels reloaded");
17644
+ for (const result of res.results) {
17645
+ logOut(formatChannelReloadResult(result));
17646
+ }
17647
+ }
17648
+ return 0;
17649
+ } catch (err) {
17650
+ return reportProbeFailure(err, json, logOut, logError);
17651
+ }
17469
17652
  }
17470
- return {
17471
- id: runnerId,
17472
- matches,
17473
- ...executionTarget !== void 0 ? { executionTarget } : {},
17474
- ...remoteInstanceId !== void 0 ? { remoteInstanceId } : {},
17475
- ...reasons.length > 0 ? { error: reasons.join("; ") } : {}
17476
- };
17477
- }
17478
- function defaultInstallServiceUnit() {
17479
- const entry = resolveDaemonEntry();
17480
- if (!entry) {
17481
- return {
17482
- ok: false,
17483
- platform: "unsupported",
17484
- message: "cannot resolve dashboard-daemon.js entry path"
17485
- };
17653
+ if (subcommand === "status") {
17654
+ const json = flagJson(subcommandArgs);
17655
+ const socketPath = resolveSocketPath();
17656
+ const tokenPath = resolveTokenPath();
17657
+ const endpoint = readClientConfig();
17658
+ try {
17659
+ const client = await connectGateway({
17660
+ endpoint,
17661
+ socketPath,
17662
+ tokenPath,
17663
+ timeoutMs: 3e3
17664
+ });
17665
+ const res = await client.request("status", {});
17666
+ client.close();
17667
+ if (json) {
17668
+ logOut(JSON.stringify(res));
17669
+ } else {
17670
+ const listenerSummary = formatListenerSummary(res.listener);
17671
+ const channelSummary = formatChannelSummary(res.channels);
17672
+ const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
17673
+ logOut(
17674
+ `gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
17675
+ );
17676
+ }
17677
+ return 0;
17678
+ } catch (err) {
17679
+ return reportProbeFailure(err, json, logOut, logError);
17680
+ }
17486
17681
  }
17487
- return installServiceUnit({
17488
- daemonEntry: entry,
17489
- nodeBinary: process.execPath
17490
- });
17682
+ logError(`Unknown gateway subcommand: ${subcommand}`);
17683
+ logError(USAGE5);
17684
+ return 2;
17491
17685
  }
17492
- function compareSemver(a, b) {
17493
- const parseN = (s) => {
17494
- const parts = s.replace(/^v/, "").split("-")[0].split(".");
17495
- return [
17496
- Number.parseInt(parts[0] ?? "0", 10) || 0,
17497
- Number.parseInt(parts[1] ?? "0", 10) || 0,
17498
- Number.parseInt(parts[2] ?? "0", 10) || 0
17499
- ];
17500
- };
17501
- const av = parseN(a);
17502
- const bv = parseN(b);
17503
- for (let i = 0; i < 3; i += 1) {
17504
- if (av[i] < bv[i]) return -1;
17505
- if (av[i] > bv[i]) return 1;
17686
+ async function defaultConnectGateway(opts) {
17687
+ if (opts.endpoint.mode === "remote") {
17688
+ return connect({
17689
+ socketPath: opts.socketPath,
17690
+ token: opts.endpoint.token,
17691
+ timeoutMs: opts.timeoutMs,
17692
+ transport: createWsClientTransport(
17693
+ wsClientOptionsForEndpoint({
17694
+ url: opts.endpoint.url,
17695
+ timeoutMs: opts.timeoutMs,
17696
+ tlsCaPath: opts.endpoint.tlsCaPath
17697
+ })
17698
+ )
17699
+ });
17506
17700
  }
17507
- return 0;
17508
- }
17509
- function consoleBrokerUrl(dashboardUrl, runnerId) {
17510
- const url = new URL(dashboardUrl);
17511
- if (url.protocol === "https:") url.protocol = "wss:";
17512
- else if (url.protocol === "http:") url.protocol = "ws:";
17513
- else throw new Error(`unsupported dashboard protocol: ${url.protocol}`);
17514
- url.pathname = `/api/runners/${encodeURIComponent(runnerId)}/console/adapter`;
17515
- url.search = "";
17516
- url.hash = "";
17517
- return url.toString();
17701
+ return connect({
17702
+ socketPath: opts.socketPath,
17703
+ token: readToken(opts.tokenPath),
17704
+ timeoutMs: opts.timeoutMs
17705
+ });
17518
17706
  }
17519
- async function safeReadError(response) {
17520
- try {
17521
- const text = await response.text();
17522
- if (text.length === 0) return "";
17523
- try {
17524
- const parsed = JSON.parse(text);
17525
- if (typeof parsed === "object" && parsed !== null && typeof parsed["error"] === "string") {
17526
- return parsed["error"];
17707
+ function parseLinkArgs(args) {
17708
+ const positional = [];
17709
+ let token;
17710
+ let tlsCaPath;
17711
+ for (let i = 0; i < args.length; i += 1) {
17712
+ const arg = args[i];
17713
+ if (arg === "--token") {
17714
+ const v = args[i + 1];
17715
+ if (!v || v.startsWith("--")) {
17716
+ return { ok: false, message: "gateway link --token requires a value" };
17527
17717
  }
17528
- } catch {
17718
+ token = v;
17719
+ i += 1;
17720
+ continue;
17529
17721
  }
17530
- return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
17531
- } catch {
17532
- return "";
17722
+ if (arg === "--tls-ca") {
17723
+ const v = args[i + 1];
17724
+ if (!v || v.startsWith("--")) {
17725
+ return { ok: false, message: "gateway link --tls-ca requires a path" };
17726
+ }
17727
+ tlsCaPath = v;
17728
+ i += 1;
17729
+ continue;
17730
+ }
17731
+ if (arg.startsWith("--token=")) {
17732
+ token = arg.slice("--token=".length);
17733
+ continue;
17734
+ }
17735
+ if (arg.startsWith("--tls-ca=")) {
17736
+ tlsCaPath = arg.slice("--tls-ca=".length);
17737
+ continue;
17738
+ }
17739
+ if (arg.startsWith("--")) {
17740
+ return { ok: false, message: `gateway link: unknown option ${arg}` };
17741
+ }
17742
+ positional.push(arg);
17533
17743
  }
17534
- }
17535
- function parsePairResponse(raw) {
17536
- if (typeof raw !== "object" || raw === null) {
17537
- throw new Error("expected object");
17744
+ const url = positional[0];
17745
+ if (!url) {
17746
+ return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
17538
17747
  }
17539
- const obj = raw;
17540
- const instanceId = obj["instanceId"];
17541
- const refreshToken = obj["refreshToken"];
17542
- if (typeof instanceId !== "string" || instanceId.length === 0) {
17543
- throw new Error("missing instanceId");
17748
+ if (positional.length > 1) {
17749
+ return { ok: false, message: "gateway link accepts exactly one URL" };
17544
17750
  }
17545
- if (typeof refreshToken !== "string" || refreshToken.length === 0) {
17546
- throw new Error("missing refreshToken");
17751
+ if (!isSupportedGatewayUrl(url)) {
17752
+ return { ok: false, message: "gateway link URL must use ws:// or wss://" };
17753
+ }
17754
+ if (!token) {
17755
+ return { ok: false, message: "gateway link requires --token <token>" };
17756
+ }
17757
+ if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
17758
+ return { ok: false, message: "gateway link --tls-ca requires a path" };
17547
17759
  }
17548
17760
  return {
17549
- instanceId,
17550
- refreshToken,
17551
- ...typeof obj["jti"] === "string" ? { jti: obj["jti"] } : {},
17552
- ...typeof obj["accessToken"] === "string" ? { accessToken: obj["accessToken"] } : {},
17553
- ...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
17554
- ...Array.isArray(obj["runners"]) ? {
17555
- runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
17556
- } : {},
17557
- ...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
17558
- ...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
17761
+ ok: true,
17762
+ url,
17763
+ token,
17764
+ ...tlsCaPath !== void 0 ? { tlsCaPath } : {}
17559
17765
  };
17560
17766
  }
17561
- function parseCapabilityAck(raw) {
17562
- if (typeof raw !== "object" || raw === null) return {};
17563
- const obj = raw;
17564
- const ack = {};
17565
- if (typeof obj["runtimeDaemon"] === "boolean") {
17566
- ack.runtimeDaemon = obj["runtimeDaemon"];
17767
+ function reportProbeFailure(err, json, logOut, logError) {
17768
+ const message = err instanceof Error ? err.message : String(err);
17769
+ if (err instanceof GatewayUnreachableError) {
17770
+ if (json) {
17771
+ logOut(
17772
+ JSON.stringify({
17773
+ ok: false,
17774
+ reachable: false,
17775
+ reason: "unreachable",
17776
+ message
17777
+ })
17778
+ );
17779
+ } else {
17780
+ logError(`gateway: not reachable \u2014 ${message}`);
17781
+ }
17782
+ return 1;
17567
17783
  }
17568
- if (typeof obj["consoleAdapter"] === "boolean") {
17569
- ack.consoleAdapter = obj["consoleAdapter"];
17784
+ if (err instanceof GatewayUnauthorizedError) {
17785
+ if (json) {
17786
+ logOut(
17787
+ JSON.stringify({
17788
+ ok: false,
17789
+ reachable: true,
17790
+ reason: "unauthorized",
17791
+ message
17792
+ })
17793
+ );
17794
+ } else {
17795
+ logError(`gateway: unauthorized \u2014 ${message}`);
17796
+ }
17797
+ return 1;
17570
17798
  }
17571
- if (typeof obj["instanceSocket"] === "boolean") {
17572
- ack.instanceSocket = obj["instanceSocket"];
17799
+ if (json) {
17800
+ logOut(JSON.stringify({ ok: false, reason: "error", message }));
17801
+ } else {
17802
+ logError(`gateway: ${message}`);
17573
17803
  }
17574
- return ack;
17804
+ return 1;
17575
17805
  }
17576
- function parsePairedRunner(raw) {
17577
- if (typeof raw !== "object" || raw === null) return null;
17578
- const obj = raw;
17579
- const runnerId = obj["runnerId"];
17580
- if (typeof runnerId !== "string" || runnerId.length === 0) return null;
17581
- return {
17582
- runnerId,
17583
- ...typeof obj["name"] === "string" ? { name: obj["name"] } : {},
17584
- ...typeof obj["executionTarget"] === "string" ? { executionTarget: obj["executionTarget"] } : {},
17585
- ...typeof obj["remoteInstanceId"] === "string" ? { remoteInstanceId: obj["remoteInstanceId"] } : {}
17586
- };
17806
+ function formatRuntimeSummary(r) {
17807
+ if (!r) return " runtime=<none>";
17808
+ const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
17809
+ const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
17810
+ return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
17811
+ }
17812
+ function formatChannelReloadResult(result) {
17813
+ const suffix = result.reason ? `: ${result.reason}` : "";
17814
+ return ` ${result.id} ${result.action}${suffix}`;
17815
+ }
17816
+ function formatChannelSummary(channels) {
17817
+ if (channels.length === 0) return " channels=<none>";
17818
+ const parts = channels.map((channel) => {
17819
+ const note = channel.note ? `(${channel.note})` : "";
17820
+ return `${channel.id}:${channel.state}${note}`;
17821
+ });
17822
+ return ` channels=${parts.join(",")}`;
17823
+ }
17824
+ function formatListenerSummary(listener) {
17825
+ if (listener.kind === "uds") {
17826
+ return `listener=uds:${listener.socketPath}`;
17827
+ }
17828
+ const flags = [];
17829
+ if (listener.tls) flags.push("tls");
17830
+ if (listener.insecure) flags.push("insecure");
17831
+ const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
17832
+ return `listener=${listener.url}${suffix}`;
17587
17833
  }
17588
17834
 
17589
17835
  // src/app/entry/doctorCommand.ts
@@ -18283,7 +18529,7 @@ var cli = meow(
18283
18529
  workflow <sub> Manage workflows (install, list, search, remove, upgrade, use)
18284
18530
  marketplace <sub> Manage marketplace sources (add, remove, list)
18285
18531
  channel <sub> Manage external channels
18286
- dashboard <sub> Manage dashboard remote-instance pairing (pair, status, refresh, connect, unpair)
18532
+ dashboard <sub> Manage dashboard pairing and runtime daemon (pair, status, daemon, unpair)
18287
18533
  telemetry [action] Manage anonymous telemetry (enable/disable/status)
18288
18534
  doctor Diagnose Claude headless setup (use with --harness=claude)
18289
18535
 
@@ -18308,15 +18554,8 @@ var cli = meow(
18308
18554
  --bot-token Telegram bot token (channel telegram configure)
18309
18555
  --user-id Telegram allowed user id (channel telegram configure)
18310
18556
  --chat-id Telegram destination chat id (defaults to --user-id)
18311
- --token Gateway link token (gateway link)
18312
18557
  --url Dashboard origin (dashboard pair)
18313
18558
  --name Friendly machine name (dashboard pair)
18314
- --tls-ca Gateway custom CA path (gateway link)
18315
- --tls-cert Gateway TLS certificate path (gateway start)
18316
- --tls-key Gateway TLS private key path (gateway start)
18317
- --bind Gateway listen address host:port (gateway start)
18318
- --insecure Allow plain WS on non-loopback trusted tunnels (gateway start)
18319
- --grace-period-ms Gateway reconnect grace period in milliseconds (gateway start)
18320
18559
  --dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (exec mode)
18321
18560
  --project Scope workflow command to project config (workflow use)
18322
18561
  --global Scope workflow command to global config (workflow use, default)
@@ -18476,6 +18715,9 @@ var cli = meow(
18476
18715
  },
18477
18716
  apiKey: {
18478
18717
  type: "string"
18718
+ },
18719
+ attachmentId: {
18720
+ type: "string"
18479
18721
  }
18480
18722
  }
18481
18723
  }
@@ -18525,7 +18767,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
18525
18767
  await exitWith(1);
18526
18768
  return;
18527
18769
  }
18528
- const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-2MC5A7W4.js");
18770
+ const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-X754ND4V.js");
18529
18771
  const { waitUntilExit } = render(
18530
18772
  /* @__PURE__ */ jsx25(
18531
18773
  WorkflowInstallWizard,
@@ -18788,6 +19030,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
18788
19030
  isolationPreset,
18789
19031
  ascii: cli.flags.ascii,
18790
19032
  showSetup,
19033
+ ...cli.flags.attachmentId !== void 0 ? { attachmentId: cli.flags.attachmentId } : {},
18791
19034
  initialTelemetryDiagnosticsConsent: globalConfig.telemetryDiagnostics
18792
19035
  }
18793
19036
  ),