@drisp/cli 0.4.5 → 0.4.7

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,
@@ -50,12 +48,9 @@ import {
50
48
  probeSkipReason,
51
49
  processRegistry,
52
50
  progressGlyphs,
53
- readAttachmentMirror,
54
51
  readGatewayClientConfig,
55
- readPidLock,
56
52
  register,
57
53
  registerCleanupOnExit,
58
- removeAttachmentMirror,
59
54
  resolveClaudeBinary,
60
55
  resolveClaudeSettingsSurfacePaths,
61
56
  resolveHarnessAdapter,
@@ -70,13 +65,21 @@ import {
70
65
  startSessionBridge,
71
66
  supportsSessionApproval,
72
67
  todoGlyphSet,
73
- writeAttachmentMirror,
74
68
  writeGatewayClientConfig,
75
69
  wsClientOptionsForEndpoint
76
- } from "./chunk-PJUDHH4R.js";
70
+ } from "./chunk-TQKNZNDP.js";
77
71
  import {
78
72
  generateId as generateId2
79
73
  } from "./chunk-BTKQ67RE.js";
74
+ import {
75
+ rotateGatewayToken
76
+ } from "./chunk-D4W7RB25.js";
77
+ import {
78
+ readAttachmentMirror,
79
+ readPidLock,
80
+ removeAttachmentMirror,
81
+ writeAttachmentMirror
82
+ } from "./chunk-ZVOGOZNT.js";
80
83
  import {
81
84
  dashboardClientConfigPath,
82
85
  disableTelemetry,
@@ -97,7 +100,7 @@ import {
97
100
  trackTelemetryOptedOut,
98
101
  writeDashboardClientConfig,
99
102
  writeGatewayTrace
100
- } from "./chunk-4CRZXLIP.js";
103
+ } from "./chunk-WRHKXH5M.js";
101
104
  import {
102
105
  McpOptionsStep,
103
106
  StepSelector,
@@ -132,7 +135,7 @@ import {
132
135
  useWorkflowSessionController,
133
136
  writeGlobalConfig,
134
137
  writeProjectConfig
135
- } from "./chunk-5VK2ZMVV.js";
138
+ } from "./chunk-A54HGVML.js";
136
139
 
137
140
  // src/app/entry/cli.tsx
138
141
  import { render } from "ink";
@@ -1742,14 +1745,65 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1742
1745
  const abortRef = useRef(new AbortController());
1743
1746
  const feedEventsRef = useRef([]);
1744
1747
  const notifiedRuntimeErrorRef = useRef(null);
1748
+ const dashboardFeedPublisher = useMemo3(
1749
+ () => options?.dashboardFeedPublisher ?? createDashboardFeedPublisher(),
1750
+ [options?.dashboardFeedPublisher]
1751
+ );
1752
+ const dashboardDecisionInboxRef = useRef(
1753
+ options?.dashboardDecisionInbox ?? null
1754
+ );
1745
1755
  rulesRef.current = rules;
1746
1756
  feedEventsRef.current = feedEvents;
1747
1757
  sessionStoreRef.current = sessionStore;
1758
+ if (options?.dashboardDecisionInbox) {
1759
+ dashboardDecisionInboxRef.current = options.dashboardDecisionInbox;
1760
+ }
1761
+ const resolveAthenaSessionId = useCallback5(
1762
+ (fallback) => {
1763
+ if (options?.athenaSessionId) return options.athenaSessionId;
1764
+ try {
1765
+ const sessionId = sessionStoreRef.current?.getAthenaSession().id;
1766
+ if (sessionId) return sessionId;
1767
+ } catch {
1768
+ }
1769
+ return fallback;
1770
+ },
1771
+ [options?.athenaSessionId]
1772
+ );
1748
1773
  useEffect(() => {
1749
1774
  return () => {
1750
1775
  sessionStoreRef.current = void 0;
1751
1776
  };
1752
1777
  }, []);
1778
+ useEffect(() => {
1779
+ const athenaSessionId = resolveAthenaSessionId();
1780
+ if (!athenaSessionId) return;
1781
+ if (!dashboardDecisionInboxRef.current) {
1782
+ if (!readDashboardClientConfig()) return;
1783
+ dashboardDecisionInboxRef.current = createDashboardDecisionInbox();
1784
+ }
1785
+ const inbox = dashboardDecisionInboxRef.current;
1786
+ const applyPending = () => {
1787
+ const rows = inbox.pendingForSession({ athenaSessionId, limit: 25 });
1788
+ for (const row of rows) {
1789
+ runtime.sendDecision(row.requestId, row.decision);
1790
+ inbox.markConsumed({ id: row.id });
1791
+ }
1792
+ };
1793
+ applyPending();
1794
+ const interval = setInterval(
1795
+ applyPending,
1796
+ options?.dashboardDecisionPollIntervalMs ?? 1e3
1797
+ );
1798
+ return () => {
1799
+ clearInterval(interval);
1800
+ };
1801
+ }, [
1802
+ runtime,
1803
+ options?.dashboardDecisionInbox,
1804
+ options?.dashboardDecisionPollIntervalMs,
1805
+ resolveAthenaSessionId
1806
+ ]);
1753
1807
  const resetSession = useCallback5(() => {
1754
1808
  const newMapper = createFeedMapper();
1755
1809
  mapperRef.current = newMapper;
@@ -1860,18 +1914,37 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1860
1914
  },
1861
1915
  [runtime, dequeueQuestion]
1862
1916
  );
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
- }, []);
1917
+ const publishDashboardFeedEvents = useCallback5(
1918
+ (feedEventsToPublish, runtimeEvent) => {
1919
+ if (feedEventsToPublish.length === 0) return;
1920
+ const athenaSessionId = resolveAthenaSessionId(
1921
+ feedEventsToPublish[0]?.session_id ?? runtimeEvent?.sessionId
1922
+ );
1923
+ if (!athenaSessionId) return;
1924
+ dashboardFeedPublisher.publish({
1925
+ origin: options?.dashboardOrigin ?? "local",
1926
+ athenaSessionId,
1927
+ feedEvents: feedEventsToPublish
1928
+ });
1929
+ },
1930
+ [dashboardFeedPublisher, options?.dashboardOrigin, resolveAthenaSessionId]
1931
+ );
1932
+ const emitNotification = useCallback5(
1933
+ (message, title) => {
1934
+ const mapper = mapperRef.current;
1935
+ const syntheticRuntime = buildSyntheticNotificationEvent(
1936
+ mapper,
1937
+ message,
1938
+ title
1939
+ );
1940
+ const newEvents = mapper.mapEvent(syntheticRuntime);
1941
+ if (!abortRef.current.signal.aborted) {
1942
+ feedStoreRef.current.pushEvents(newEvents);
1943
+ publishDashboardFeedEvents(newEvents, syntheticRuntime);
1944
+ }
1945
+ },
1946
+ [publishDashboardFeedEvents]
1947
+ );
1875
1948
  const refreshRuntimeStatus = useCallback5(
1876
1949
  (notify = false) => {
1877
1950
  const nextIsServerRunning = runtime.getStatus() === "running";
@@ -1956,6 +2029,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1956
2029
  }
1957
2030
  }
1958
2031
  feedStoreRef.current.pushEvents(newFeedEvents);
2032
+ publishDashboardFeedEvents(newFeedEvents, runtimeEvent);
1959
2033
  }
1960
2034
  } finally {
1961
2035
  doneCause();
@@ -1991,6 +2065,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
1991
2065
  });
1992
2066
  if (feedEvent) {
1993
2067
  feedStoreRef.current.pushEvents([feedEvent]);
2068
+ publishDashboardFeedEvents([feedEvent]);
1994
2069
  if (feedEvent.kind === "permission.decision" && feedEvent.cause?.hook_request_id) {
1995
2070
  dequeuePermission(feedEvent.cause.hook_request_id);
1996
2071
  }
@@ -2014,7 +2089,8 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
2014
2089
  dequeueQuestion,
2015
2090
  refreshRuntimeStatus,
2016
2091
  options?.relayPermission,
2017
- options?.relayQuestion
2092
+ options?.relayQuestion,
2093
+ publishDashboardFeedEvents
2018
2094
  ]);
2019
2095
  const items = useMemo3(() => {
2020
2096
  const done = startPerfStage("state.derive", {
@@ -2111,6 +2187,7 @@ function HookProvider({
2111
2187
  runtimeFactory = createRuntime,
2112
2188
  allowedTools,
2113
2189
  athenaSessionId,
2190
+ attachmentId,
2114
2191
  children
2115
2192
  }) {
2116
2193
  const runtime = useMemo4(
@@ -2158,6 +2235,7 @@ function HookProvider({
2158
2235
  void startSessionBridge({
2159
2236
  runtimeId: athenaSessionId,
2160
2237
  defaultAgentId: "main",
2238
+ ...attachmentId !== void 0 ? { attachmentId } : {},
2161
2239
  signal: controller.signal
2162
2240
  }).then((bridge) => {
2163
2241
  if (!bridge) return;
@@ -2174,7 +2252,7 @@ function HookProvider({
2174
2252
  return null;
2175
2253
  });
2176
2254
  };
2177
- }, [athenaSessionId]);
2255
+ }, [athenaSessionId, attachmentId]);
2178
2256
  useEffect2(() => {
2179
2257
  return () => {
2180
2258
  sessionStore.close();
@@ -10951,6 +11029,350 @@ function executeCommand(command, args, ctx) {
10951
11029
  }
10952
11030
  }
10953
11031
 
11032
+ // src/app/runner/assignmentHandler.ts
11033
+ function parseRunSpec(value) {
11034
+ if (typeof value !== "object" || value === null) return null;
11035
+ const obj = value;
11036
+ const prompt = obj["prompt"];
11037
+ if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
11038
+ const env2 = obj["env"];
11039
+ const workflow = obj["workflow"];
11040
+ return {
11041
+ prompt,
11042
+ sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
11043
+ projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
11044
+ workflow: typeof workflow === "object" && workflow !== null && typeof workflow["ref"] === "string" ? { ref: workflow["ref"] } : void 0,
11045
+ env: typeof env2 === "object" && env2 !== null ? Object.fromEntries(
11046
+ Object.entries(env2).filter(
11047
+ (entry) => typeof entry[1] === "string"
11048
+ )
11049
+ ) : void 0,
11050
+ timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
11051
+ };
11052
+ }
11053
+ function workflowNameFromRef(ref) {
11054
+ if (!ref) return void 0;
11055
+ const [name] = ref.split("@", 1);
11056
+ return name && name.length > 0 ? name : void 0;
11057
+ }
11058
+ function eventKindOf(event) {
11059
+ if (event.type === "exec.completed") {
11060
+ const data = event.data;
11061
+ return data?.success === false ? "error" : "completion";
11062
+ }
11063
+ return typeof event.type === "string" && event.type.length > 0 ? event.type : "progress";
11064
+ }
11065
+ function eventPayloadOf(event) {
11066
+ if (event.type === "exec.completed") {
11067
+ const data = event.data;
11068
+ if (data?.success === false) {
11069
+ return {
11070
+ ...typeof event.data === "object" && event.data !== null ? event.data : {},
11071
+ message: typeof data.failure === "object" && data.failure !== null && typeof data.failure.message === "string" ? data.failure.message : "remote execution failed"
11072
+ };
11073
+ }
11074
+ }
11075
+ return event.data ?? null;
11076
+ }
11077
+ function withEnv(env2, fn) {
11078
+ if (!env2 || Object.keys(env2).length === 0) return fn();
11079
+ const previous = /* @__PURE__ */ new Map();
11080
+ for (const [key, value] of Object.entries(env2)) {
11081
+ previous.set(key, process.env[key]);
11082
+ process.env[key] = value;
11083
+ }
11084
+ return fn().finally(() => {
11085
+ for (const [key, value] of previous) {
11086
+ if (value === void 0) {
11087
+ delete process.env[key];
11088
+ } else {
11089
+ process.env[key] = value;
11090
+ }
11091
+ }
11092
+ });
11093
+ }
11094
+ async function executeAssignment(input) {
11095
+ const {
11096
+ envelope,
11097
+ bridge,
11098
+ dispatchId,
11099
+ location,
11100
+ projectDir: fallbackProjectDir = process.cwd(),
11101
+ runExecFn = runExec,
11102
+ bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11103
+ now = Date.now,
11104
+ abortSignal
11105
+ } = input;
11106
+ const runId = envelope.runId;
11107
+ let seq = 0;
11108
+ let terminalSent = false;
11109
+ let deferredFailedCompletion = null;
11110
+ let lastTerminalFailureMessage = null;
11111
+ const nextSeq = () => {
11112
+ seq += 1;
11113
+ return seq;
11114
+ };
11115
+ const sendProgress = async (kind, payload, ts = now()) => {
11116
+ await bridge.sendRunEvent({
11117
+ location,
11118
+ runId,
11119
+ seq: nextSeq(),
11120
+ ts,
11121
+ kind,
11122
+ payload
11123
+ });
11124
+ };
11125
+ const sendTerminal = async (eventKind, payload, ts = now()) => {
11126
+ if (terminalSent) return;
11127
+ terminalSent = true;
11128
+ const envelopeText = JSON.stringify({
11129
+ kind: "run_event",
11130
+ runId,
11131
+ seq: nextSeq(),
11132
+ ts,
11133
+ eventKind,
11134
+ payload
11135
+ });
11136
+ await bridge.completeTurn({
11137
+ dispatchId,
11138
+ location,
11139
+ text: envelopeText,
11140
+ idempotencyKey: `run_event:${runId}:terminal`
11141
+ });
11142
+ };
11143
+ await sendProgress("progress", { message: "assignment received" });
11144
+ const spec = parseRunSpec(envelope.runSpec);
11145
+ if (!spec) {
11146
+ await sendTerminal("error", { message: "remote assignment missing prompt" });
11147
+ return;
11148
+ }
11149
+ const projectDir = spec.projectDir ?? fallbackProjectDir;
11150
+ let runtimeConfig;
11151
+ try {
11152
+ runtimeConfig = bootstrapRuntimeConfigFn({
11153
+ projectDir,
11154
+ showSetup: false,
11155
+ isolationPreset: "minimal",
11156
+ workflowOverride: workflowNameFromRef(spec.workflow?.ref)
11157
+ });
11158
+ } catch (err) {
11159
+ await sendTerminal("error", {
11160
+ message: err instanceof Error ? err.message : String(err)
11161
+ });
11162
+ return;
11163
+ }
11164
+ for (const warning of runtimeConfig.warnings) {
11165
+ await sendProgress("warning", { message: warning });
11166
+ }
11167
+ let buffered = "";
11168
+ const pendingProgress = [];
11169
+ const stdout = {
11170
+ write(chunk) {
11171
+ buffered += chunk;
11172
+ let newline = buffered.indexOf("\n");
11173
+ while (newline >= 0) {
11174
+ const line = buffered.slice(0, newline).trim();
11175
+ buffered = buffered.slice(newline + 1);
11176
+ if (line.length > 0) {
11177
+ try {
11178
+ const event = JSON.parse(line);
11179
+ const data = event.data;
11180
+ if (event.type === "exec.completed" && data?.success === false) {
11181
+ deferredFailedCompletion = event;
11182
+ } else if (event.type === "exec.completed") {
11183
+ deferredFailedCompletion = event;
11184
+ } else {
11185
+ pendingProgress.push(
11186
+ sendProgress(eventKindOf(event), eventPayloadOf(event), now())
11187
+ );
11188
+ }
11189
+ } catch {
11190
+ pendingProgress.push(sendProgress("progress", { line }));
11191
+ }
11192
+ }
11193
+ newline = buffered.indexOf("\n");
11194
+ }
11195
+ return true;
11196
+ }
11197
+ };
11198
+ const stderr = {
11199
+ write(chunk) {
11200
+ const text = chunk.trim();
11201
+ if (text.length > 0) pendingProgress.push(sendProgress("stderr", { text }));
11202
+ return true;
11203
+ }
11204
+ };
11205
+ try {
11206
+ await withEnv(spec.env, async () => {
11207
+ const result = await runExecFn({
11208
+ prompt: spec.prompt,
11209
+ projectDir,
11210
+ harness: runtimeConfig.harness,
11211
+ athenaSessionId: spec.sessionId ?? `athena-${runId}`,
11212
+ isolationConfig: runtimeConfig.isolationConfig,
11213
+ pluginMcpConfig: runtimeConfig.pluginMcpConfig,
11214
+ workflow: runtimeConfig.workflow,
11215
+ workflowPlan: runtimeConfig.workflowPlan,
11216
+ json: true,
11217
+ verbose: false,
11218
+ ephemeral: false,
11219
+ timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
11220
+ signal: abortSignal,
11221
+ stdout,
11222
+ stderr
11223
+ });
11224
+ await Promise.all(pendingProgress);
11225
+ if (deferredFailedCompletion) {
11226
+ const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
11227
+ const ts = typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now();
11228
+ const success = data.success !== false;
11229
+ const eventKind = success ? "completion" : "error";
11230
+ const message = !success ? result.failure?.message ?? eventPayloadOf(deferredFailedCompletion).message ?? "remote execution failed" : void 0;
11231
+ if (message) lastTerminalFailureMessage = message;
11232
+ await sendTerminal(
11233
+ eventKind,
11234
+ success ? {
11235
+ ...data,
11236
+ exitCode: result.exitCode,
11237
+ athenaSessionId: result.athenaSessionId,
11238
+ adapterSessionId: result.adapterSessionId,
11239
+ finalMessage: result.finalMessage,
11240
+ tokens: result.tokens,
11241
+ durationMs: result.durationMs,
11242
+ success: true
11243
+ } : {
11244
+ ...data,
11245
+ success: result.success,
11246
+ exitCode: result.exitCode,
11247
+ athenaSessionId: result.athenaSessionId,
11248
+ adapterSessionId: result.adapterSessionId,
11249
+ finalMessage: result.finalMessage,
11250
+ tokens: result.tokens,
11251
+ durationMs: result.durationMs,
11252
+ message
11253
+ },
11254
+ ts
11255
+ );
11256
+ return;
11257
+ }
11258
+ if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
11259
+ await sendTerminal("error", {
11260
+ success: result.success,
11261
+ exitCode: result.exitCode,
11262
+ athenaSessionId: result.athenaSessionId,
11263
+ adapterSessionId: result.adapterSessionId,
11264
+ finalMessage: result.finalMessage,
11265
+ tokens: result.tokens,
11266
+ durationMs: result.durationMs,
11267
+ message: result.failure.message
11268
+ });
11269
+ return;
11270
+ }
11271
+ await sendTerminal("completion", {
11272
+ success: result.success,
11273
+ exitCode: result.exitCode,
11274
+ athenaSessionId: result.athenaSessionId,
11275
+ adapterSessionId: result.adapterSessionId,
11276
+ finalMessage: result.finalMessage,
11277
+ tokens: result.tokens,
11278
+ durationMs: result.durationMs
11279
+ });
11280
+ });
11281
+ } catch (err) {
11282
+ await Promise.all(pendingProgress);
11283
+ await sendTerminal("error", {
11284
+ message: err instanceof Error ? err.message : String(err)
11285
+ });
11286
+ }
11287
+ }
11288
+
11289
+ // src/app/runner/envelope.ts
11290
+ function parseRunnerEnvelope(text) {
11291
+ let value;
11292
+ try {
11293
+ value = JSON.parse(text);
11294
+ } catch {
11295
+ return null;
11296
+ }
11297
+ if (typeof value !== "object" || value === null) return null;
11298
+ const obj = value;
11299
+ const runId = obj["runId"];
11300
+ if (typeof runId !== "string" || runId.length === 0) return null;
11301
+ const kind = obj["kind"];
11302
+ if (kind === "job_assignment") {
11303
+ return { kind, runId, runSpec: obj["runSpec"] };
11304
+ }
11305
+ if (kind === "cancel") {
11306
+ return { kind, runId };
11307
+ }
11308
+ return null;
11309
+ }
11310
+
11311
+ // src/app/runner/runnerSession.ts
11312
+ function createRunnerSession(opts) {
11313
+ const {
11314
+ bridge,
11315
+ projectDir,
11316
+ runExecFn = runExec,
11317
+ bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11318
+ now = Date.now
11319
+ } = opts;
11320
+ const inflight = /* @__PURE__ */ new Map();
11321
+ function startAssignment(envelope, dispatchId, location) {
11322
+ const controller = new AbortController();
11323
+ inflight.set(envelope.runId, controller);
11324
+ return executeAssignment({
11325
+ envelope,
11326
+ bridge,
11327
+ dispatchId,
11328
+ location,
11329
+ projectDir,
11330
+ runExecFn,
11331
+ bootstrapRuntimeConfigFn,
11332
+ now,
11333
+ abortSignal: controller.signal
11334
+ }).finally(() => {
11335
+ if (inflight.get(envelope.runId) === controller) {
11336
+ inflight.delete(envelope.runId);
11337
+ }
11338
+ });
11339
+ }
11340
+ return {
11341
+ handleDispatch(input) {
11342
+ const envelope = parseRunnerEnvelope(input.text);
11343
+ if (!envelope) {
11344
+ return { recognised: false, completed: Promise.resolve() };
11345
+ }
11346
+ if (envelope.kind === "job_assignment") {
11347
+ const completed = startAssignment(
11348
+ envelope,
11349
+ input.dispatchId,
11350
+ input.location
11351
+ );
11352
+ return { recognised: true, completed };
11353
+ }
11354
+ const controller = inflight.get(envelope.runId);
11355
+ if (controller) controller.abort();
11356
+ return { recognised: true, completed: Promise.resolve() };
11357
+ }
11358
+ };
11359
+ }
11360
+
11361
+ // src/app/runner/dispatchRouter.ts
11362
+ function makeDispatchRouter(opts) {
11363
+ return (payload) => {
11364
+ if (opts.runnerSession) {
11365
+ const result = opts.runnerSession.handleDispatch({
11366
+ text: payload.inbound.text,
11367
+ dispatchId: payload.dispatchId,
11368
+ location: payload.inbound.location
11369
+ });
11370
+ if (result.recognised) return;
11371
+ }
11372
+ opts.fallback(payload);
11373
+ };
11374
+ }
11375
+
10954
11376
  // src/ui/components/SessionPicker.tsx
10955
11377
  import { useState as useState11 } from "react";
10956
11378
  import { Box as Box11, Text as Text17, useInput as useInput12, useStdout as useStdout5 } from "ink";
@@ -13321,17 +13743,25 @@ function AppContent({
13321
13743
  [addMessage, currentSessionId, feedEvents.length, spawnHarness]
13322
13744
  );
13323
13745
  submitDispatchAsTurnRef.current = submitDispatchAsTurn;
13746
+ const runnerSession = useMemo17(
13747
+ () => sessionBridge ? createRunnerSession({ bridge: sessionBridge, projectDir }) : null,
13748
+ [sessionBridge, projectDir]
13749
+ );
13324
13750
  useEffect14(() => {
13325
13751
  if (!sessionBridge) return;
13326
- const off = sessionBridge.onTurnDispatch((payload) => {
13327
- if (isHarnessRunningRef.current || pendingDispatchRef.current) {
13328
- queuedDispatchRef.current = payload;
13329
- return;
13752
+ const router = makeDispatchRouter({
13753
+ runnerSession,
13754
+ fallback: (payload) => {
13755
+ if (isHarnessRunningRef.current || pendingDispatchRef.current) {
13756
+ queuedDispatchRef.current = payload;
13757
+ return;
13758
+ }
13759
+ submitDispatchAsTurnRef.current?.(payload);
13330
13760
  }
13331
- submitDispatchAsTurnRef.current?.(payload);
13332
13761
  });
13762
+ const off = sessionBridge.onTurnDispatch(router);
13333
13763
  return off;
13334
- }, [sessionBridge]);
13764
+ }, [sessionBridge, runnerSession]);
13335
13765
  useEffect14(() => {
13336
13766
  if (isHarnessRunning) return;
13337
13767
  if (pendingDispatchRef.current) return;
@@ -14366,6 +14796,7 @@ function App({
14366
14796
  isolationPreset,
14367
14797
  ascii,
14368
14798
  athenaSessionId: initialAthenaSessionId,
14799
+ attachmentId,
14369
14800
  initialTelemetryDiagnosticsConsent
14370
14801
  }) {
14371
14802
  const [clearCount, setClearCount] = useState18(0);
@@ -14581,6 +15012,7 @@ function App({
14581
15012
  runtimeState.isolation?.allowedTools
14582
15013
  ),
14583
15014
  athenaSessionId,
15015
+ ...attachmentId !== void 0 ? { attachmentId } : {},
14584
15016
  children: /* @__PURE__ */ jsx24(
14585
15017
  AppContent,
14586
15018
  {
@@ -15417,446 +15849,74 @@ function runChannelCommand(input, deps = {}) {
15417
15849
 
15418
15850
  // src/app/entry/dashboardCommand.ts
15419
15851
  import crypto3 from "crypto";
15420
- import { spawn as spawn2 } from "child_process";
15421
- import fs7 from "fs";
15852
+ import { spawn } from "child_process";
15853
+ import fs6 from "fs";
15422
15854
  import { createRequire } from "module";
15423
15855
  import os5 from "os";
15424
- import path7 from "path";
15425
- import { fileURLToPath as fileURLToPath2 } from "url";
15856
+ import path6 from "path";
15857
+ import { fileURLToPath } from "url";
15426
15858
 
15427
- // src/app/entry/gatewayCommand.ts
15428
- import { spawn } from "child_process";
15859
+ // src/infra/daemon/serviceUnit.ts
15429
15860
  import fs5 from "fs";
15861
+ import os4 from "os";
15430
15862
  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");
15863
+ function installServiceUnit(options) {
15864
+ const platform = options.platform ?? process.platform;
15865
+ const env2 = options.env ?? process.env;
15866
+ const home = env2["HOME"] ?? os4.homedir();
15867
+ if (platform === "darwin") {
15868
+ const target = options.targetPath ?? path5.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
15869
+ const paths = daemonStatePaths(env2);
15870
+ const plist = renderLaunchdPlist({
15871
+ label: "ai.drisp.daemon",
15872
+ nodeBinary: options.nodeBinary,
15873
+ daemonEntry: options.daemonEntry,
15874
+ workingDirectory: home,
15875
+ stdoutPath: paths.logPath,
15876
+ stderrPath: paths.logPath
15877
+ });
15878
+ writeIfChanged(target, plist);
15879
+ return {
15880
+ ok: true,
15881
+ platform: "darwin",
15882
+ path: target,
15883
+ loadCommand: `launchctl load -w ${target}`,
15884
+ startCommand: "launchctl start ai.drisp.daemon"
15885
+ };
15886
+ }
15887
+ if (platform === "linux") {
15888
+ const target = options.targetPath ?? path5.join(home, ".config", "systemd", "user", "drisp-daemon.service");
15889
+ const unit = renderSystemdUnit({
15890
+ description: "Drisp dashboard runtime daemon",
15891
+ nodeBinary: options.nodeBinary,
15892
+ daemonEntry: options.daemonEntry
15893
+ });
15894
+ writeIfChanged(target, unit);
15895
+ return {
15896
+ ok: true,
15897
+ platform: "linux",
15898
+ path: target,
15899
+ loadCommand: "systemctl --user daemon-reload",
15900
+ startCommand: "systemctl --user enable --now drisp-daemon.service"
15901
+ };
15902
+ }
15903
+ return {
15904
+ ok: false,
15905
+ platform: "unsupported",
15906
+ message: `service install not supported on ${platform}`
15907
+ };
15451
15908
  }
15452
- function readToken(tokenPath) {
15909
+ function writeIfChanged(target, content) {
15910
+ const dir = path5.dirname(target);
15911
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
15912
+ let existing = null;
15453
15913
  try {
15454
- return fs5.readFileSync(tokenPath, "utf-8").trim();
15914
+ existing = fs5.readFileSync(target, "utf-8");
15455
15915
  } 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;
15916
+ if (err.code !== "ENOENT") throw err;
15463
15917
  }
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 });
15918
+ if (existing === content) return;
15919
+ fs5.writeFileSync(target, content, { mode: 384 });
15860
15920
  }
15861
15921
  function renderLaunchdPlist(input) {
15862
15922
  const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
@@ -15912,7 +15972,7 @@ function escapeXml(input) {
15912
15972
  }
15913
15973
 
15914
15974
  // src/app/entry/dashboardCommand.ts
15915
- var USAGE5 = `Usage: athena dashboard <subcommand> [options]
15975
+ var USAGE4 = `Usage: athena dashboard <subcommand> [options]
15916
15976
 
15917
15977
  Subcommands:
15918
15978
  pair <token> --url <dashboard-origin> [--name <machine-name>]
@@ -15938,20 +15998,13 @@ Subcommands:
15938
15998
  runner is bound to this instance.
15939
15999
  list Print the local attachment mirror (the runners the dashboard
15940
16000
  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
16001
  connect Deprecated alias for "daemon foreground".
15948
16002
  unpair Stop the daemon, revoke the refresh token, and remove the
15949
16003
  local config.
15950
16004
 
15951
16005
  Options:
15952
16006
  --url <origin> Dashboard origin (required for pair)
15953
- --runner <id> Runner id (required for "console enable"/"console
15954
- link", optional for "doctor")
16007
+ --runner <id> Runner id (optional for "doctor")
15955
16008
  --name <name> Friendly machine name (optional, defaults to hostname)
15956
16009
  --tail N Number of trailing log lines (default 20)
15957
16010
  --follow Stream new log lines until interrupted
@@ -15964,7 +16017,7 @@ var cachedVersion = null;
15964
16017
  function readPackageVersion() {
15965
16018
  if (cachedVersion !== null) return cachedVersion;
15966
16019
  try {
15967
- const injected = "0.4.5";
16020
+ const injected = "0.4.7";
15968
16021
  if (typeof injected === "string" && injected.length > 0) {
15969
16022
  cachedVersion = injected;
15970
16023
  return cachedVersion;
@@ -16013,14 +16066,14 @@ async function runDashboardCommand(input, deps = {}) {
16013
16066
  const packageVersion = deps.packageVersion ?? readPackageVersion();
16014
16067
  const { subcommand, subcommandArgs, flags } = input;
16015
16068
  if (!subcommand || subcommand === "help" || subcommand === "--help") {
16016
- logOut(USAGE5);
16069
+ logOut(USAGE4);
16017
16070
  return 0;
16018
16071
  }
16019
16072
  if (subcommand === "pair") {
16020
16073
  const token = subcommandArgs[0];
16021
16074
  if (!token) {
16022
16075
  logError("dashboard pair: missing pairing token");
16023
- logError(USAGE5);
16076
+ logError(USAGE4);
16024
16077
  return 2;
16025
16078
  }
16026
16079
  if (subcommandArgs.length > 1) {
@@ -16047,7 +16100,6 @@ async function runDashboardCommand(input, deps = {}) {
16047
16100
  hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
16048
16101
  capabilities: {
16049
16102
  instanceSocket: true,
16050
- consoleAdapter: true,
16051
16103
  runtimeDaemon: true,
16052
16104
  cliVersion: packageVersion,
16053
16105
  // Legacy field — older dashboards read `version`. Drop in a
@@ -16762,166 +16814,13 @@ async function runDashboardCommand(input, deps = {}) {
16762
16814
  return reportDoctor2(runnerOk);
16763
16815
  }
16764
16816
  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;
16817
+ const message = "dashboard console is deprecated; paired dashboard feed sync now routes dashboard UI and channel decisions.";
16818
+ if (flags.json) {
16819
+ logOut(JSON.stringify({ ok: false, deprecated: true, message }));
16820
+ } else {
16821
+ logError(message);
16920
16822
  }
16921
- logError(
16922
- `dashboard console: unknown subcommand ${sub ? sub : "(missing)"}. Expected "enable <runnerId>" or "link <runnerId>".`
16923
- );
16924
- return 2;
16823
+ return 1;
16925
16824
  }
16926
16825
  if (subcommand === "list") {
16927
16826
  if (subcommandArgs.length > 0) {
@@ -17080,7 +16979,7 @@ async function runDashboardCommand(input, deps = {}) {
17080
16979
  return 0;
17081
16980
  }
17082
16981
  logError(`Unknown dashboard subcommand: ${subcommand}`);
17083
- logError(USAGE5);
16982
+ logError(USAGE4);
17084
16983
  return 2;
17085
16984
  }
17086
16985
  function defaultWaitForShutdown() {
@@ -17092,36 +16991,21 @@ function defaultWaitForShutdown() {
17092
16991
  };
17093
16992
  process.once("SIGINT", onSignal);
17094
16993
  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
- };
16994
+ });
17111
16995
  }
17112
16996
  function resolveDaemonEntry() {
17113
16997
  let here;
17114
16998
  try {
17115
- here = fileURLToPath2(import.meta.url);
16999
+ here = fileURLToPath(import.meta.url);
17116
17000
  } catch {
17117
17001
  return null;
17118
17002
  }
17119
17003
  const candidates = [
17120
- path7.join(path7.dirname(here), "dashboard-daemon.js"),
17004
+ path6.join(path6.dirname(here), "dashboard-daemon.js"),
17121
17005
  // When invoked via `npm run start`, `here` may be the unbundled source
17122
17006
  // path. Walk up until we hit a `dist/` sibling.
17123
- path7.join(
17124
- path7.dirname(here),
17007
+ path6.join(
17008
+ path6.dirname(here),
17125
17009
  "..",
17126
17010
  "..",
17127
17011
  "..",
@@ -17131,7 +17015,7 @@ function resolveDaemonEntry() {
17131
17015
  ];
17132
17016
  for (const candidate of candidates) {
17133
17017
  try {
17134
- fs7.accessSync(candidate, fs7.constants.R_OK);
17018
+ fs6.accessSync(candidate, fs6.constants.R_OK);
17135
17019
  return candidate;
17136
17020
  } catch {
17137
17021
  }
@@ -17189,7 +17073,7 @@ async function defaultStartRuntimeDaemon(opts) {
17189
17073
  }
17190
17074
  let child;
17191
17075
  try {
17192
- child = spawn2(process.execPath, [entry], {
17076
+ child = spawn(process.execPath, [entry], {
17193
17077
  detached: true,
17194
17078
  stdio: "ignore",
17195
17079
  env: buildDaemonEnv(process.env)
@@ -17316,14 +17200,14 @@ async function defaultTailDaemonLog(opts) {
17316
17200
  let stream2 = null;
17317
17201
  let watcher = null;
17318
17202
  try {
17319
- const stat = fs7.statSync(paths.logPath);
17203
+ const stat = fs6.statSync(paths.logPath);
17320
17204
  const size = stat.size;
17321
17205
  const buf = Buffer.alloc(Math.min(size, opts.tail * 1024));
17322
- const fd = fs7.openSync(paths.logPath, "r");
17206
+ const fd = fs6.openSync(paths.logPath, "r");
17323
17207
  try {
17324
- fs7.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
17208
+ fs6.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
17325
17209
  } finally {
17326
- fs7.closeSync(fd);
17210
+ fs6.closeSync(fd);
17327
17211
  }
17328
17212
  const lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
17329
17213
  const tail = lines.slice(-opts.tail);
@@ -17337,253 +17221,612 @@ async function defaultTailDaemonLog(opts) {
17337
17221
  `dashboard logs: log file ${paths.logPath} does not exist yet
17338
17222
  `
17339
17223
  );
17340
- return opts.follow ? 0 : 1;
17224
+ return opts.follow ? 0 : 1;
17225
+ }
17226
+ throw err;
17227
+ }
17228
+ let position = fs6.statSync(paths.logPath).size;
17229
+ return await new Promise((resolve) => {
17230
+ let pollTimer = null;
17231
+ const drain = () => {
17232
+ try {
17233
+ const stat = fs6.statSync(paths.logPath);
17234
+ if (stat.size < position) {
17235
+ position = 0;
17236
+ }
17237
+ if (stat.size > position) {
17238
+ const fd = fs6.openSync(paths.logPath, "r");
17239
+ const buf = Buffer.alloc(stat.size - position);
17240
+ try {
17241
+ fs6.readSync(fd, buf, 0, buf.length, position);
17242
+ } finally {
17243
+ fs6.closeSync(fd);
17244
+ }
17245
+ position = stat.size;
17246
+ process.stdout.write(buf);
17247
+ }
17248
+ } catch (err) {
17249
+ if (err.code !== "ENOENT") {
17250
+ process.stderr.write(
17251
+ `dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
17252
+ `
17253
+ );
17254
+ }
17255
+ }
17256
+ };
17257
+ try {
17258
+ watcher = fs6.watch(paths.logPath, { persistent: true }, () => {
17259
+ drain();
17260
+ });
17261
+ } catch {
17262
+ pollTimer = setInterval(drain, 500);
17263
+ pollTimer.unref();
17264
+ }
17265
+ const onSignal = () => {
17266
+ if (watcher) {
17267
+ watcher.close();
17268
+ watcher = null;
17269
+ }
17270
+ if (pollTimer) {
17271
+ clearInterval(pollTimer);
17272
+ pollTimer = null;
17273
+ }
17274
+ if (stream2) {
17275
+ stream2.close();
17276
+ stream2 = null;
17277
+ }
17278
+ resolve(0);
17279
+ };
17280
+ process.once("SIGINT", onSignal);
17281
+ process.once("SIGTERM", onSignal);
17282
+ });
17283
+ }
17284
+ function formatDuration2(seconds) {
17285
+ if (!Number.isFinite(seconds) || seconds < 0) return "?";
17286
+ if (seconds < 60) return `${seconds}s`;
17287
+ if (seconds < 3600) {
17288
+ const m = Math.floor(seconds / 60);
17289
+ const s = seconds % 60;
17290
+ return s > 0 ? `${m}m${s}s` : `${m}m`;
17291
+ }
17292
+ if (seconds < 86400) {
17293
+ const h2 = Math.floor(seconds / 3600);
17294
+ const m = Math.floor(seconds % 3600 / 60);
17295
+ return m > 0 ? `${h2}h${m}m` : `${h2}h`;
17296
+ }
17297
+ const d = Math.floor(seconds / 86400);
17298
+ const h = Math.floor(seconds % 86400 / 3600);
17299
+ return h > 0 ? `${d}d${h}h` : `${d}d`;
17300
+ }
17301
+ async function fetchRunnerHealth(fetchImpl, dashboardUrl, runnerId, token) {
17302
+ const url = new URL(
17303
+ `/api/runners/${encodeURIComponent(runnerId)}`,
17304
+ dashboardUrl
17305
+ ).toString();
17306
+ let response;
17307
+ try {
17308
+ response = await fetchImpl(url, {
17309
+ method: "GET",
17310
+ headers: {
17311
+ authorization: `Bearer ${token.accessToken}`,
17312
+ accept: "application/json"
17313
+ }
17314
+ });
17315
+ } catch (err) {
17316
+ return {
17317
+ id: runnerId,
17318
+ matches: false,
17319
+ error: `request failed: ${err instanceof Error ? err.message : String(err)}`
17320
+ };
17321
+ }
17322
+ if (!response.ok) {
17323
+ return {
17324
+ id: runnerId,
17325
+ matches: false,
17326
+ error: `dashboard returned ${response.status}`
17327
+ };
17328
+ }
17329
+ let body;
17330
+ try {
17331
+ body = await response.json();
17332
+ } catch (err) {
17333
+ return {
17334
+ id: runnerId,
17335
+ matches: false,
17336
+ error: `invalid response body: ${err instanceof Error ? err.message : String(err)}`
17337
+ };
17338
+ }
17339
+ const obj = typeof body === "object" && body !== null ? body : {};
17340
+ const executionTarget = typeof obj["executionTarget"] === "string" ? obj["executionTarget"] : void 0;
17341
+ const remoteInstanceId = typeof obj["remoteInstanceId"] === "string" ? obj["remoteInstanceId"] : void 0;
17342
+ const matches = executionTarget === "remote" && remoteInstanceId === token.instanceId;
17343
+ const reasons = [];
17344
+ if (executionTarget !== "remote") {
17345
+ reasons.push(
17346
+ `executionTarget=${executionTarget ?? "unset"} (expected "remote")`
17347
+ );
17348
+ }
17349
+ if (remoteInstanceId !== token.instanceId) {
17350
+ reasons.push(
17351
+ `remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
17352
+ );
17353
+ }
17354
+ return {
17355
+ id: runnerId,
17356
+ matches,
17357
+ ...executionTarget !== void 0 ? { executionTarget } : {},
17358
+ ...remoteInstanceId !== void 0 ? { remoteInstanceId } : {},
17359
+ ...reasons.length > 0 ? { error: reasons.join("; ") } : {}
17360
+ };
17361
+ }
17362
+ function defaultInstallServiceUnit() {
17363
+ const entry = resolveDaemonEntry();
17364
+ if (!entry) {
17365
+ return {
17366
+ ok: false,
17367
+ platform: "unsupported",
17368
+ message: "cannot resolve dashboard-daemon.js entry path"
17369
+ };
17370
+ }
17371
+ return installServiceUnit({
17372
+ daemonEntry: entry,
17373
+ nodeBinary: process.execPath
17374
+ });
17375
+ }
17376
+ function compareSemver(a, b) {
17377
+ const parseN = (s) => {
17378
+ const parts = s.replace(/^v/, "").split("-")[0].split(".");
17379
+ return [
17380
+ Number.parseInt(parts[0] ?? "0", 10) || 0,
17381
+ Number.parseInt(parts[1] ?? "0", 10) || 0,
17382
+ Number.parseInt(parts[2] ?? "0", 10) || 0
17383
+ ];
17384
+ };
17385
+ const av = parseN(a);
17386
+ const bv = parseN(b);
17387
+ for (let i = 0; i < 3; i += 1) {
17388
+ if (av[i] < bv[i]) return -1;
17389
+ if (av[i] > bv[i]) return 1;
17390
+ }
17391
+ return 0;
17392
+ }
17393
+ async function safeReadError(response) {
17394
+ try {
17395
+ const text = await response.text();
17396
+ if (text.length === 0) return "";
17397
+ try {
17398
+ const parsed = JSON.parse(text);
17399
+ if (typeof parsed === "object" && parsed !== null && typeof parsed["error"] === "string") {
17400
+ return parsed["error"];
17401
+ }
17402
+ } catch {
17403
+ }
17404
+ return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
17405
+ } catch {
17406
+ return "";
17407
+ }
17408
+ }
17409
+ function parsePairResponse(raw) {
17410
+ if (typeof raw !== "object" || raw === null) {
17411
+ throw new Error("expected object");
17412
+ }
17413
+ const obj = raw;
17414
+ const instanceId = obj["instanceId"];
17415
+ const refreshToken = obj["refreshToken"];
17416
+ if (typeof instanceId !== "string" || instanceId.length === 0) {
17417
+ throw new Error("missing instanceId");
17418
+ }
17419
+ if (typeof refreshToken !== "string" || refreshToken.length === 0) {
17420
+ throw new Error("missing refreshToken");
17421
+ }
17422
+ return {
17423
+ instanceId,
17424
+ refreshToken,
17425
+ ...typeof obj["jti"] === "string" ? { jti: obj["jti"] } : {},
17426
+ ...typeof obj["accessToken"] === "string" ? { accessToken: obj["accessToken"] } : {},
17427
+ ...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
17428
+ ...Array.isArray(obj["runners"]) ? {
17429
+ runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
17430
+ } : {},
17431
+ ...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
17432
+ ...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
17433
+ };
17434
+ }
17435
+ function parseCapabilityAck(raw) {
17436
+ if (typeof raw !== "object" || raw === null) return {};
17437
+ const obj = raw;
17438
+ const ack = {};
17439
+ if (typeof obj["runtimeDaemon"] === "boolean") {
17440
+ ack.runtimeDaemon = obj["runtimeDaemon"];
17441
+ }
17442
+ if (typeof obj["instanceSocket"] === "boolean") {
17443
+ ack.instanceSocket = obj["instanceSocket"];
17444
+ }
17445
+ return ack;
17446
+ }
17447
+ function parsePairedRunner(raw) {
17448
+ if (typeof raw !== "object" || raw === null) return null;
17449
+ const obj = raw;
17450
+ const runnerId = obj["runnerId"];
17451
+ if (typeof runnerId !== "string" || runnerId.length === 0) return null;
17452
+ return {
17453
+ runnerId,
17454
+ ...typeof obj["name"] === "string" ? { name: obj["name"] } : {},
17455
+ ...typeof obj["executionTarget"] === "string" ? { executionTarget: obj["executionTarget"] } : {},
17456
+ ...typeof obj["remoteInstanceId"] === "string" ? { remoteInstanceId: obj["remoteInstanceId"] } : {}
17457
+ };
17458
+ }
17459
+
17460
+ // src/app/entry/gatewayCommand.ts
17461
+ import { spawn as spawn2 } from "child_process";
17462
+ import fs7 from "fs";
17463
+ import path7 from "path";
17464
+ import { fileURLToPath as fileURLToPath2 } from "url";
17465
+ var USAGE5 = `Usage: athena-flow gateway <subcommand> [--json]
17466
+
17467
+ Subcommands:
17468
+ start Run the gateway daemon in foreground (only mode in this build).
17469
+ Options: [--bind <host:port>] [--insecure]
17470
+ [--tls-cert <path>] [--tls-key <path>]
17471
+ [--grace-period-ms <n>]
17472
+ status Print daemon pid, uptime, and version.
17473
+ probe Send a ping RPC and report reachability + latency.
17474
+ link Store a remote WS/WSS gateway endpoint for this user.
17475
+ unlink Restore local UDS gateway mode for this user.
17476
+ rotate-token Regenerate the gateway token file (server-side).
17477
+ Restart the daemon to drop existing connections; clients
17478
+ must re-run "athena gateway link --token <new>".
17479
+ reload-channels Reload channel sidecars without restarting the daemon.
17480
+ `;
17481
+ function defaultResolveDaemonEntry() {
17482
+ const here = path7.dirname(fileURLToPath2(import.meta.url));
17483
+ return path7.resolve(here, "athena-gateway.js");
17484
+ }
17485
+ function readToken(tokenPath) {
17486
+ try {
17487
+ return fs7.readFileSync(tokenPath, "utf-8").trim();
17488
+ } catch (err) {
17489
+ const code = err.code;
17490
+ if (code === "ENOENT") {
17491
+ throw new Error(
17492
+ `gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
17493
+ );
17341
17494
  }
17342
17495
  throw err;
17343
17496
  }
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
17497
  }
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`;
17498
+ function flagJson(args) {
17499
+ return args.includes("--json");
17500
+ }
17501
+ async function runGatewayCommand(input, deps = {}) {
17502
+ const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
17503
+ const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
17504
+ const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
17505
+ const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
17506
+ const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
17507
+ const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
17508
+ const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
17509
+ const connectGateway = deps.connectGateway ?? defaultConnectGateway;
17510
+ const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn2(process.execPath, [entry, ...args], { stdio: "inherit" }));
17511
+ const { subcommand, subcommandArgs } = input;
17512
+ if (!subcommand || subcommand === "help" || subcommand === "--help") {
17513
+ logOut(USAGE5);
17514
+ return 0;
17407
17515
  }
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`;
17516
+ if (subcommand === "start") {
17517
+ const entry = resolveDaemonEntry2();
17518
+ const child = spawnDaemon(entry, subcommandArgs);
17519
+ return await new Promise((resolve) => {
17520
+ child.once("exit", (code) => resolve(code ?? 0));
17521
+ child.once("error", (err) => {
17522
+ logError(`gateway start: failed to spawn daemon: ${err.message}`);
17523
+ resolve(1);
17524
+ });
17525
+ });
17412
17526
  }
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
- }
17527
+ if (subcommand === "link") {
17528
+ const parsed = parseLinkArgs(subcommandArgs);
17529
+ if (!parsed.ok) {
17530
+ logError(parsed.message);
17531
+ return 2;
17532
+ }
17533
+ writeClientConfig({
17534
+ mode: "remote",
17535
+ url: parsed.url,
17536
+ token: parsed.token,
17537
+ ...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
17430
17538
  });
17431
- } catch (err) {
17432
- return {
17433
- id: runnerId,
17434
- matches: false,
17435
- error: `request failed: ${err instanceof Error ? err.message : String(err)}`
17436
- };
17539
+ logOut(`gateway: linked remote endpoint ${parsed.url}`);
17540
+ return 0;
17437
17541
  }
17438
- if (!response.ok) {
17439
- return {
17440
- id: runnerId,
17441
- matches: false,
17442
- error: `dashboard returned ${response.status}`
17443
- };
17542
+ if (subcommand === "rotate-token") {
17543
+ const json = flagJson(subcommandArgs);
17544
+ const extras = subcommandArgs.filter((a) => a !== "--json");
17545
+ if (extras.length > 0) {
17546
+ logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
17547
+ return 2;
17548
+ }
17549
+ const tokenPath = resolveTokenPath();
17550
+ try {
17551
+ const newToken = rotateGatewayToken(tokenPath);
17552
+ if (json) {
17553
+ logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
17554
+ } else {
17555
+ logOut(newToken);
17556
+ logOut(
17557
+ `gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
17558
+ );
17559
+ }
17560
+ return 0;
17561
+ } catch (err) {
17562
+ const message = err instanceof Error ? err.message : String(err);
17563
+ if (json) {
17564
+ logOut(JSON.stringify({ ok: false, message }));
17565
+ } else {
17566
+ logError(`gateway rotate-token: ${message}`);
17567
+ }
17568
+ return 1;
17569
+ }
17444
17570
  }
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
- };
17571
+ if (subcommand === "unlink") {
17572
+ if (subcommandArgs.length > 0) {
17573
+ logError("gateway unlink does not accept arguments");
17574
+ return 2;
17575
+ }
17576
+ writeClientConfig({ mode: "local" });
17577
+ logOut("gateway: using local gateway endpoint");
17578
+ return 0;
17454
17579
  }
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
- );
17580
+ if (subcommand === "probe") {
17581
+ const json = flagJson(subcommandArgs);
17582
+ const socketPath = resolveSocketPath();
17583
+ const tokenPath = resolveTokenPath();
17584
+ const endpoint = readClientConfig();
17585
+ const startedAt = Date.now();
17586
+ try {
17587
+ const client = await connectGateway({
17588
+ endpoint,
17589
+ socketPath,
17590
+ tokenPath,
17591
+ timeoutMs: 3e3
17592
+ });
17593
+ const res = await client.request("ping", {});
17594
+ client.close();
17595
+ const latencyMs = Date.now() - startedAt;
17596
+ if (json) {
17597
+ logOut(
17598
+ JSON.stringify({
17599
+ ok: true,
17600
+ reachable: true,
17601
+ latency_ms: latencyMs,
17602
+ daemon_pid: res.daemonPid,
17603
+ daemon_uptime_ms: res.uptimeMs
17604
+ })
17605
+ );
17606
+ } else {
17607
+ logOut(
17608
+ `gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
17609
+ );
17610
+ }
17611
+ return 0;
17612
+ } catch (err) {
17613
+ return reportProbeFailure(err, json, logOut, logError);
17614
+ }
17464
17615
  }
17465
- if (remoteInstanceId !== token.instanceId) {
17466
- reasons.push(
17467
- `remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
17468
- );
17616
+ if (subcommand === "reload-channels") {
17617
+ const json = flagJson(subcommandArgs);
17618
+ const extras = subcommandArgs.filter((a) => a !== "--json");
17619
+ if (extras.length > 0) {
17620
+ logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
17621
+ return 2;
17622
+ }
17623
+ const socketPath = resolveSocketPath();
17624
+ const tokenPath = resolveTokenPath();
17625
+ const endpoint = readClientConfig();
17626
+ try {
17627
+ const client = await connectGateway({
17628
+ endpoint,
17629
+ socketPath,
17630
+ tokenPath,
17631
+ timeoutMs: 3e3
17632
+ });
17633
+ const res = await client.request("channels.reload", {});
17634
+ client.close();
17635
+ if (json) {
17636
+ logOut(JSON.stringify({ ok: true, ...res }));
17637
+ } else if (res.results.length === 0) {
17638
+ logOut("gateway: channels reloaded (no channel sidecars found)");
17639
+ } else {
17640
+ logOut("gateway: channels reloaded");
17641
+ for (const result of res.results) {
17642
+ logOut(formatChannelReloadResult(result));
17643
+ }
17644
+ }
17645
+ return 0;
17646
+ } catch (err) {
17647
+ return reportProbeFailure(err, json, logOut, logError);
17648
+ }
17469
17649
  }
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
- };
17650
+ if (subcommand === "status") {
17651
+ const json = flagJson(subcommandArgs);
17652
+ const socketPath = resolveSocketPath();
17653
+ const tokenPath = resolveTokenPath();
17654
+ const endpoint = readClientConfig();
17655
+ try {
17656
+ const client = await connectGateway({
17657
+ endpoint,
17658
+ socketPath,
17659
+ tokenPath,
17660
+ timeoutMs: 3e3
17661
+ });
17662
+ const res = await client.request("status", {});
17663
+ client.close();
17664
+ if (json) {
17665
+ logOut(JSON.stringify(res));
17666
+ } else {
17667
+ const listenerSummary = formatListenerSummary(res.listener);
17668
+ const channelSummary = formatChannelSummary(res.channels);
17669
+ const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
17670
+ logOut(
17671
+ `gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
17672
+ );
17673
+ }
17674
+ return 0;
17675
+ } catch (err) {
17676
+ return reportProbeFailure(err, json, logOut, logError);
17677
+ }
17486
17678
  }
17487
- return installServiceUnit({
17488
- daemonEntry: entry,
17489
- nodeBinary: process.execPath
17490
- });
17679
+ logError(`Unknown gateway subcommand: ${subcommand}`);
17680
+ logError(USAGE5);
17681
+ return 2;
17491
17682
  }
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;
17683
+ async function defaultConnectGateway(opts) {
17684
+ if (opts.endpoint.mode === "remote") {
17685
+ return connect({
17686
+ socketPath: opts.socketPath,
17687
+ token: opts.endpoint.token,
17688
+ timeoutMs: opts.timeoutMs,
17689
+ transport: createWsClientTransport(
17690
+ wsClientOptionsForEndpoint({
17691
+ url: opts.endpoint.url,
17692
+ timeoutMs: opts.timeoutMs,
17693
+ tlsCaPath: opts.endpoint.tlsCaPath
17694
+ })
17695
+ )
17696
+ });
17506
17697
  }
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();
17698
+ return connect({
17699
+ socketPath: opts.socketPath,
17700
+ token: readToken(opts.tokenPath),
17701
+ timeoutMs: opts.timeoutMs
17702
+ });
17518
17703
  }
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"];
17704
+ function parseLinkArgs(args) {
17705
+ const positional = [];
17706
+ let token;
17707
+ let tlsCaPath;
17708
+ for (let i = 0; i < args.length; i += 1) {
17709
+ const arg = args[i];
17710
+ if (arg === "--token") {
17711
+ const v = args[i + 1];
17712
+ if (!v || v.startsWith("--")) {
17713
+ return { ok: false, message: "gateway link --token requires a value" };
17527
17714
  }
17528
- } catch {
17715
+ token = v;
17716
+ i += 1;
17717
+ continue;
17529
17718
  }
17530
- return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
17531
- } catch {
17532
- return "";
17719
+ if (arg === "--tls-ca") {
17720
+ const v = args[i + 1];
17721
+ if (!v || v.startsWith("--")) {
17722
+ return { ok: false, message: "gateway link --tls-ca requires a path" };
17723
+ }
17724
+ tlsCaPath = v;
17725
+ i += 1;
17726
+ continue;
17727
+ }
17728
+ if (arg.startsWith("--token=")) {
17729
+ token = arg.slice("--token=".length);
17730
+ continue;
17731
+ }
17732
+ if (arg.startsWith("--tls-ca=")) {
17733
+ tlsCaPath = arg.slice("--tls-ca=".length);
17734
+ continue;
17735
+ }
17736
+ if (arg.startsWith("--")) {
17737
+ return { ok: false, message: `gateway link: unknown option ${arg}` };
17738
+ }
17739
+ positional.push(arg);
17533
17740
  }
17534
- }
17535
- function parsePairResponse(raw) {
17536
- if (typeof raw !== "object" || raw === null) {
17537
- throw new Error("expected object");
17741
+ const url = positional[0];
17742
+ if (!url) {
17743
+ return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
17538
17744
  }
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");
17745
+ if (positional.length > 1) {
17746
+ return { ok: false, message: "gateway link accepts exactly one URL" };
17544
17747
  }
17545
- if (typeof refreshToken !== "string" || refreshToken.length === 0) {
17546
- throw new Error("missing refreshToken");
17748
+ if (!isSupportedGatewayUrl(url)) {
17749
+ return { ok: false, message: "gateway link URL must use ws:// or wss://" };
17750
+ }
17751
+ if (!token) {
17752
+ return { ok: false, message: "gateway link requires --token <token>" };
17753
+ }
17754
+ if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
17755
+ return { ok: false, message: "gateway link --tls-ca requires a path" };
17547
17756
  }
17548
17757
  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"]) } : {}
17758
+ ok: true,
17759
+ url,
17760
+ token,
17761
+ ...tlsCaPath !== void 0 ? { tlsCaPath } : {}
17559
17762
  };
17560
17763
  }
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"];
17764
+ function reportProbeFailure(err, json, logOut, logError) {
17765
+ const message = err instanceof Error ? err.message : String(err);
17766
+ if (err instanceof GatewayUnreachableError) {
17767
+ if (json) {
17768
+ logOut(
17769
+ JSON.stringify({
17770
+ ok: false,
17771
+ reachable: false,
17772
+ reason: "unreachable",
17773
+ message
17774
+ })
17775
+ );
17776
+ } else {
17777
+ logError(`gateway: not reachable \u2014 ${message}`);
17778
+ }
17779
+ return 1;
17567
17780
  }
17568
- if (typeof obj["consoleAdapter"] === "boolean") {
17569
- ack.consoleAdapter = obj["consoleAdapter"];
17781
+ if (err instanceof GatewayUnauthorizedError) {
17782
+ if (json) {
17783
+ logOut(
17784
+ JSON.stringify({
17785
+ ok: false,
17786
+ reachable: true,
17787
+ reason: "unauthorized",
17788
+ message
17789
+ })
17790
+ );
17791
+ } else {
17792
+ logError(`gateway: unauthorized \u2014 ${message}`);
17793
+ }
17794
+ return 1;
17570
17795
  }
17571
- if (typeof obj["instanceSocket"] === "boolean") {
17572
- ack.instanceSocket = obj["instanceSocket"];
17796
+ if (json) {
17797
+ logOut(JSON.stringify({ ok: false, reason: "error", message }));
17798
+ } else {
17799
+ logError(`gateway: ${message}`);
17573
17800
  }
17574
- return ack;
17801
+ return 1;
17575
17802
  }
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
- };
17803
+ function formatRuntimeSummary(r) {
17804
+ if (!r) return " runtime=<none>";
17805
+ const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
17806
+ const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
17807
+ return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
17808
+ }
17809
+ function formatChannelReloadResult(result) {
17810
+ const suffix = result.reason ? `: ${result.reason}` : "";
17811
+ return ` ${result.id} ${result.action}${suffix}`;
17812
+ }
17813
+ function formatChannelSummary(channels) {
17814
+ if (channels.length === 0) return " channels=<none>";
17815
+ const parts = channels.map((channel) => {
17816
+ const note = channel.note ? `(${channel.note})` : "";
17817
+ return `${channel.id}:${channel.state}${note}`;
17818
+ });
17819
+ return ` channels=${parts.join(",")}`;
17820
+ }
17821
+ function formatListenerSummary(listener) {
17822
+ if (listener.kind === "uds") {
17823
+ return `listener=uds:${listener.socketPath}`;
17824
+ }
17825
+ const flags = [];
17826
+ if (listener.tls) flags.push("tls");
17827
+ if (listener.insecure) flags.push("insecure");
17828
+ const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
17829
+ return `listener=${listener.url}${suffix}`;
17587
17830
  }
17588
17831
 
17589
17832
  // src/app/entry/doctorCommand.ts
@@ -18283,7 +18526,7 @@ var cli = meow(
18283
18526
  workflow <sub> Manage workflows (install, list, search, remove, upgrade, use)
18284
18527
  marketplace <sub> Manage marketplace sources (add, remove, list)
18285
18528
  channel <sub> Manage external channels
18286
- dashboard <sub> Manage dashboard remote-instance pairing (pair, status, refresh, connect, unpair)
18529
+ dashboard <sub> Manage dashboard pairing and runtime daemon (pair, status, daemon, unpair)
18287
18530
  telemetry [action] Manage anonymous telemetry (enable/disable/status)
18288
18531
  doctor Diagnose Claude headless setup (use with --harness=claude)
18289
18532
 
@@ -18308,15 +18551,8 @@ var cli = meow(
18308
18551
  --bot-token Telegram bot token (channel telegram configure)
18309
18552
  --user-id Telegram allowed user id (channel telegram configure)
18310
18553
  --chat-id Telegram destination chat id (defaults to --user-id)
18311
- --token Gateway link token (gateway link)
18312
18554
  --url Dashboard origin (dashboard pair)
18313
18555
  --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
18556
  --dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (exec mode)
18321
18557
  --project Scope workflow command to project config (workflow use)
18322
18558
  --global Scope workflow command to global config (workflow use, default)
@@ -18476,6 +18712,9 @@ var cli = meow(
18476
18712
  },
18477
18713
  apiKey: {
18478
18714
  type: "string"
18715
+ },
18716
+ attachmentId: {
18717
+ type: "string"
18479
18718
  }
18480
18719
  }
18481
18720
  }
@@ -18525,7 +18764,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
18525
18764
  await exitWith(1);
18526
18765
  return;
18527
18766
  }
18528
- const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-2MC5A7W4.js");
18767
+ const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-X754ND4V.js");
18529
18768
  const { waitUntilExit } = render(
18530
18769
  /* @__PURE__ */ jsx25(
18531
18770
  WorkflowInstallWizard,
@@ -18788,6 +19027,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
18788
19027
  isolationPreset,
18789
19028
  ascii: cli.flags.ascii,
18790
19029
  showSetup,
19030
+ ...cli.flags.attachmentId !== void 0 ? { attachmentId: cli.flags.attachmentId } : {},
18791
19031
  initialTelemetryDiagnosticsConsent: globalConfig.telemetryDiagnostics
18792
19032
  }
18793
19033
  ),