@gravity-rail/cli 0.5.0 → 0.6.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/gr.js CHANGED
@@ -1,56 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import { CLIError, handleError, AuthRequiredError } from './chunk-XRBVAFCB.js';
3
+ import fs, { readFileSync, existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync, createWriteStream } from 'fs';
4
+ import path3, { join, resolve, dirname } from 'path';
2
5
  import { createServer } from 'http';
3
6
  import { randomBytes, createHash } from 'crypto';
4
- import { readFileSync, existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync, createWriteStream } from 'fs';
5
- import { join } from 'path';
6
7
  import { execSync, execFile, exec, spawn, spawnSync } from 'child_process';
7
8
  import * as readline from 'readline';
8
- import { homedir } from 'os';
9
+ import readline__default from 'readline';
10
+ import os, { homedir } from 'os';
9
11
  import { EventEmitter } from 'events';
10
12
 
11
- // src/lib/errors.ts
12
- var CLIError = class extends Error {
13
- constructor(message, exitCode = 1) {
14
- super(message);
15
- this.exitCode = exitCode;
16
- this.name = "CLIError";
17
- }
18
- };
19
- var AuthRequiredError = class extends CLIError {
20
- constructor(message = "Not authenticated. Run `gr login` or set GRAVITY_RAIL_API_KEY.") {
21
- super(message, 2);
22
- this.name = "AuthRequiredError";
23
- }
24
- };
25
- function handleError(err) {
26
- if (err instanceof CLIError) {
27
- process.stderr.write(`Error: ${err.message}
28
- `);
29
- process.exit(err.exitCode);
30
- }
31
- if (err instanceof Error) {
32
- process.stderr.write(`Error: ${err.message}
33
- `);
34
- if ("errors" in err && Array.isArray(err.errors)) {
35
- for (const e of err.errors) {
36
- process.stderr.write(` - ${e}
37
- `);
38
- }
39
- }
40
- if ("statusCode" in err && err.statusCode === 401) {
41
- process.stderr.write(
42
- `
43
- Hint: Run "gr login" to authenticate, or set GRAVITY_RAIL_API_KEY for non-interactive use.
44
- `
45
- );
46
- }
47
- process.exit(1);
48
- }
49
- process.stderr.write(`Error: ${String(err)}
50
- `);
51
- process.exit(1);
52
- }
53
-
54
13
  // src/lib/pagination.ts
55
14
  function isListResult(data) {
56
15
  if (!data || typeof data !== "object" || Array.isArray(data)) return false;
@@ -174,7 +133,7 @@ var COMMON_ARGS = [
174
133
  { name: "uuid", description: "Entity UUID" }
175
134
  ];
176
135
  function parseArgs(defs, argv) {
177
- const allDefs = [...COMMON_ARGS, ...defs];
136
+ const allDefs = [...defs, ...COMMON_ARGS];
178
137
  const result2 = {};
179
138
  for (const def of allDefs) {
180
139
  if (def.default !== void 0) {
@@ -201,8 +160,7 @@ function parseArgs(defs, argv) {
201
160
  const def = findDef(allDefs, flagPart);
202
161
  if (!def) {
203
162
  if ((flagPart.startsWith("--") || flagPart.startsWith("-")) && !KNOWN_GLOBAL_FLAGS.has(flagPart)) {
204
- process.stderr.write(`Warning: unknown flag "${flagPart}" (ignored)
205
- `);
163
+ throw new CLIError(`Unknown flag "${flagPart}"`);
206
164
  }
207
165
  continue;
208
166
  }
@@ -217,9 +175,11 @@ function parseArgs(defs, argv) {
217
175
  }
218
176
  }
219
177
  for (const def of allDefs) {
220
- if (def.required && result2[def.name] === void 0) {
221
- throw new CLIError(`Missing required argument: --${def.name}`);
222
- }
178
+ if (!def.required) continue;
179
+ if (result2[def.name] !== void 0) continue;
180
+ const aliasNames = [def.alias, ...def.aliases ?? []].filter(Boolean);
181
+ if (aliasNames.some((a) => result2[a] !== void 0)) continue;
182
+ throw new CLIError(`Missing required argument: --${def.name}`);
223
183
  }
224
184
  return result2;
225
185
  }
@@ -2392,7 +2352,7 @@ function parsePhoneNumber4() {
2392
2352
  return withMetadataArgument(parsePhoneNumber3, arguments);
2393
2353
  }
2394
2354
 
2395
- // ../sdk/dist/chunk-QZC4QPP2.js
2355
+ // ../sdk/dist/chunk-DX42AMVM.js
2396
2356
  function getCookieValue(name) {
2397
2357
  if (typeof document === "undefined") return void 0;
2398
2358
  const cookies = document.cookie.split(";");
@@ -2800,7 +2760,7 @@ ${formattedErrors}`, response.status);
2800
2760
  * Fetch from root path (not prefixed with /api/v2/)
2801
2761
  * Used for OAuth and other non-API endpoints
2802
2762
  */
2803
- async fetchRoot(path, options) {
2763
+ async fetchRoot(path4, options) {
2804
2764
  try {
2805
2765
  const headers2 = new Headers({
2806
2766
  ...options?.body instanceof FormData ? {} : { "Content-Type": "application/json" },
@@ -2820,7 +2780,7 @@ ${formattedErrors}`, response.status);
2820
2780
  if (this.orgToken) {
2821
2781
  headers2.set("X-Org-Token", this.orgToken);
2822
2782
  }
2823
- const fullUrl = `${this.baseUrl}/${path}`;
2783
+ const fullUrl = `${this.baseUrl}/${path4}`;
2824
2784
  const response = await this.fetchFunction(fullUrl, {
2825
2785
  ...options,
2826
2786
  ...this.apiKey ? {} : { credentials: "include" },
@@ -6788,7 +6748,7 @@ var OrgApi = class extends BaseApiClient {
6788
6748
  );
6789
6749
  }
6790
6750
  /**
6791
- * Purchase a phone number from Twilio for this org.
6751
+ * Purchase a phone number from Twilio for this org inventory (org admin only).
6792
6752
  */
6793
6753
  async purchasePhoneNumber(orgUuid, phoneNumber, numberType) {
6794
6754
  return this.fetch(`o/${orgUuid}/phone-numbers/purchase`, {
@@ -7034,22 +6994,22 @@ var OAuthConnectionsApi = class extends BaseApiClient {
7034
6994
  * Get the connection status for an OAuth provider
7035
6995
  */
7036
6996
  async getConnectionStatus(providerSlug) {
7037
- const path = getOAuthPath(providerSlug, "status");
7038
- return this.fetchRoot(path);
6997
+ const path4 = getOAuthPath(providerSlug, "status");
6998
+ return this.fetchRoot(path4);
7039
6999
  }
7040
7000
  /**
7041
7001
  * Get the authorization URL to start OAuth flow
7042
7002
  */
7043
7003
  async getAuthorizationUrl(providerSlug) {
7044
- const path = getOAuthPath(providerSlug, "authorize");
7045
- return this.fetchRoot(path);
7004
+ const path4 = getOAuthPath(providerSlug, "authorize");
7005
+ return this.fetchRoot(path4);
7046
7006
  }
7047
7007
  /**
7048
7008
  * Disconnect an OAuth provider
7049
7009
  */
7050
7010
  async disconnect(providerSlug) {
7051
- const path = getOAuthPath(providerSlug, "disconnect");
7052
- return this.fetchRoot(path, { method: "DELETE" });
7011
+ const path4 = getOAuthPath(providerSlug, "disconnect");
7012
+ return this.fetchRoot(path4, { method: "DELETE" });
7053
7013
  }
7054
7014
  };
7055
7015
  var OAuthApi = class extends BaseApiClient {
@@ -7211,31 +7171,6 @@ var PhoneNumbersApi = class extends BaseApiClient {
7211
7171
  body: JSON.stringify({ providerPhoneUuid })
7212
7172
  });
7213
7173
  }
7214
- async searchAvailablePhoneNumbers(wid, params) {
7215
- const searchParams = new URLSearchParams();
7216
- if (params.country) searchParams.set("country", params.country);
7217
- if (params.numberType) searchParams.set("number_type", params.numberType);
7218
- if (params.areaCode) searchParams.set("area_code", params.areaCode);
7219
- if (params.contains) searchParams.set("contains", params.contains);
7220
- if (params.city) searchParams.set("city", params.city);
7221
- if (params.state) searchParams.set("state", params.state);
7222
- if (params.limit) searchParams.set("limit", String(params.limit));
7223
- const qs = searchParams.toString();
7224
- return this.fetch(
7225
- `w/${wid}/phone-numbers/search${qs ? `?${qs}` : ""}`
7226
- );
7227
- }
7228
- async purchasePhoneNumber(wid, request, idempotencyKey) {
7229
- const headers2 = {};
7230
- if (idempotencyKey) {
7231
- headers2["Idempotency-Key"] = idempotencyKey;
7232
- }
7233
- return this.fetch(`w/${wid}/phone-numbers/purchase`, {
7234
- method: "POST",
7235
- body: JSON.stringify(request),
7236
- headers: headers2
7237
- });
7238
- }
7239
7174
  async releasePhoneNumber(wid, id) {
7240
7175
  return this.fetch(`w/${wid}/phone-numbers/${id}/release`, {
7241
7176
  method: "POST"
@@ -9991,8 +9926,6 @@ function bindCommunicationsMethods(target, phoneNumbers, inboxes, notificationRu
9991
9926
  target.getPhoneNumberVoicemails = phoneNumbers.getPhoneNumberVoicemails.bind(phoneNumbers);
9992
9927
  target.fetchVoicemailAudio = phoneNumbers.fetchVoicemailAudio.bind(phoneNumbers);
9993
9928
  target.updateVoicemailStatus = phoneNumbers.updateVoicemailStatus.bind(phoneNumbers);
9994
- target.searchAvailablePhoneNumbers = phoneNumbers.searchAvailablePhoneNumbers.bind(phoneNumbers);
9995
- target.purchasePhoneNumber = phoneNumbers.purchasePhoneNumber.bind(phoneNumbers);
9996
9929
  target.releasePhoneNumber = phoneNumbers.releasePhoneNumber.bind(phoneNumbers);
9997
9930
  target.getLinkablePhones = phoneNumbers.getLinkablePhones.bind(phoneNumbers);
9998
9931
  target.linkPhoneNumber = phoneNumbers.linkPhoneNumber.bind(phoneNumbers);
@@ -10940,8 +10873,6 @@ var GravityRailClient = class {
10940
10873
  getPhoneNumberVoicemails;
10941
10874
  fetchVoicemailAudio;
10942
10875
  updateVoicemailStatus;
10943
- searchAvailablePhoneNumbers;
10944
- purchasePhoneNumber;
10945
10876
  releasePhoneNumber;
10946
10877
  getLinkablePhones;
10947
10878
  linkPhoneNumber;
@@ -12990,11 +12921,87 @@ var ApiClient = class extends GravityRailClient {
12990
12921
  // ignore X-Org-Token entirely, so propagating an org token to adminApi is
12991
12922
  // dead code that would only obscure auth flow if admin route logic changes.
12992
12923
  };
12993
-
12994
- // src/cli-utils/config.ts
12924
+ var ENVIRONMENTS = {
12925
+ dev: {
12926
+ apiUrl: "https://api.gravityrail.test:5054",
12927
+ frontendUrl: "https://app.gravityrail.test:3000"
12928
+ },
12929
+ prod: {
12930
+ apiUrl: "https://api.gravityrail.com",
12931
+ frontendUrl: "https://app.gravityrail.com"
12932
+ },
12933
+ staging: {
12934
+ apiUrl: "https://api.gr-staging.com",
12935
+ frontendUrl: "https://app.gr-staging.com"
12936
+ }
12937
+ };
12995
12938
  var CALLBACK_PORT = 31337;
12996
12939
  var CALLBACK_PATH = "/oauth/callback";
12997
12940
  var REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
12941
+ function detectWorktreePorts() {
12942
+ let dir = resolve(process.cwd());
12943
+ let overridePath;
12944
+ while (true) {
12945
+ const candidate = join(dir, "docker-compose.override.yml");
12946
+ if (existsSync(candidate)) {
12947
+ overridePath = candidate;
12948
+ break;
12949
+ }
12950
+ const parent = dirname(dir);
12951
+ if (parent === dir) break;
12952
+ dir = parent;
12953
+ }
12954
+ if (!overridePath) return {};
12955
+ let content;
12956
+ try {
12957
+ content = readFileSync(overridePath, "utf-8");
12958
+ } catch {
12959
+ return {};
12960
+ }
12961
+ if (!content.includes("worktree-setup.sh")) return {};
12962
+ let apiPort;
12963
+ let frontendPort;
12964
+ for (const match of content.matchAll(/-\s*'(\d+):(\d+)'/g)) {
12965
+ const host = parseInt(match[1], 10);
12966
+ const container = parseInt(match[2], 10);
12967
+ if (container === 5050) apiPort = host;
12968
+ if (container === 3e3) frontendPort = host;
12969
+ }
12970
+ return { apiPort, frontendPort };
12971
+ }
12972
+ function getEnvironmentConfig(env) {
12973
+ if (env.startsWith("dev-cloud:")) {
12974
+ const namespace = env.slice("dev-cloud:".length);
12975
+ if (!namespace) {
12976
+ throw new Error("dev-cloud environment requires a namespace: --env dev-cloud:NAMESPACE");
12977
+ }
12978
+ return {
12979
+ apiUrl: `https://${namespace}-api.gravityrail-dev.com`,
12980
+ frontendUrl: `https://${namespace}-app.gravityrail-dev.com`
12981
+ };
12982
+ }
12983
+ const config = ENVIRONMENTS[env];
12984
+ if (!config) {
12985
+ throw new Error(
12986
+ `Invalid environment: ${env}. Use 'dev', 'staging', 'prod', or 'dev-cloud:NAMESPACE'.`
12987
+ );
12988
+ }
12989
+ const apiUrl = process.env.GRAVITY_RAIL_API_URL || config.apiUrl;
12990
+ const frontendUrl = process.env.GRAVITY_RAIL_FRONTEND_URL || config.frontendUrl;
12991
+ if (apiUrl !== config.apiUrl || frontendUrl !== config.frontendUrl) {
12992
+ return { apiUrl, frontendUrl };
12993
+ }
12994
+ if (env === "dev") {
12995
+ const ports = detectWorktreePorts();
12996
+ if (ports.apiPort || ports.frontendPort) {
12997
+ return {
12998
+ apiUrl: ports.apiPort ? `https://api.gravityrail.test:${ports.apiPort}` : config.apiUrl,
12999
+ frontendUrl: ports.frontendPort ? `https://app.gravityrail.test:${ports.frontendPort}` : config.frontendUrl
13000
+ };
13001
+ }
13002
+ }
13003
+ return config;
13004
+ }
12998
13005
  var CredentialsManager = class {
12999
13006
  cacheDir;
13000
13007
  apiUrl;
@@ -13124,7 +13131,7 @@ function openBrowser(url) {
13124
13131
  });
13125
13132
  }
13126
13133
  function startCallbackServer() {
13127
- return new Promise((resolve, reject) => {
13134
+ return new Promise((resolve2, reject) => {
13128
13135
  let authCode = null;
13129
13136
  let error = null;
13130
13137
  let receivedState = null;
@@ -13166,7 +13173,7 @@ function startCallbackServer() {
13166
13173
  </html>
13167
13174
  `);
13168
13175
  server.close();
13169
- resolve({ code: authCode, state: receivedState });
13176
+ resolve2({ code: authCode, state: receivedState });
13170
13177
  } else {
13171
13178
  res.writeHead(400);
13172
13179
  res.end("Missing code parameter");
@@ -13358,7 +13365,7 @@ async function selectFromList(items, options = {}) {
13358
13365
  return { item: null, isNew: false };
13359
13366
  }
13360
13367
  const displayItems = allowNew ? [NEW_ITEM_MARKER, ...items] : items;
13361
- return new Promise((resolve) => {
13368
+ return new Promise((resolve2) => {
13362
13369
  let selectedIndex = allowNew ? 1 : 0;
13363
13370
  let filterText = "";
13364
13371
  const getFilteredItems = () => {
@@ -13441,16 +13448,16 @@ async function selectFromList(items, options = {}) {
13441
13448
  if (filtered.length > 0 && selectedIndex < filtered.length) {
13442
13449
  const selected = filtered[selectedIndex];
13443
13450
  if (selected === NEW_ITEM_MARKER) {
13444
- resolve({ item: null, isNew: true });
13451
+ resolve2({ item: null, isNew: true });
13445
13452
  } else {
13446
- resolve({ item: selected, isNew: false });
13453
+ resolve2({ item: selected, isNew: false });
13447
13454
  }
13448
13455
  } else {
13449
- resolve({ item: null, isNew: false });
13456
+ resolve2({ item: null, isNew: false });
13450
13457
  }
13451
13458
  } else if (key.name === "escape" || key.ctrl && key.name === "c") {
13452
13459
  cleanup();
13453
- resolve({ item: null, isNew: false });
13460
+ resolve2({ item: null, isNew: false });
13454
13461
  } else if (key.name === "backspace") {
13455
13462
  filterText = filterText.slice(0, -1);
13456
13463
  selectedIndex = allowNew ? 0 : 0;
@@ -13560,29 +13567,82 @@ function clearScreen() {
13560
13567
  // src/cli-utils/agui.ts
13561
13568
  var AGUIClient = class {
13562
13569
  endpointUrl;
13570
+ baseUrl;
13571
+ workspaceUuid;
13572
+ chatId;
13563
13573
  accessToken;
13564
13574
  debug;
13565
13575
  model;
13576
+ appContext;
13566
13577
  constructor(options) {
13578
+ this.baseUrl = options.baseUrl;
13579
+ this.workspaceUuid = options.workspaceUuid ?? "";
13580
+ this.chatId = options.chatId ?? 0;
13581
+ if (!options.endpointUrl && (!options.workspaceUuid || options.chatId === void 0)) {
13582
+ throw new Error("AGUIClient requires workspaceUuid and chatId, or endpointUrl");
13583
+ }
13567
13584
  this.endpointUrl = options.endpointUrl ?? `${options.baseUrl}/api/v2/w/${options.workspaceUuid}/web-chat/${options.chatId}/chat/agui`;
13568
13585
  this.accessToken = options.accessToken;
13569
13586
  this.debug = options.debug ?? false;
13570
13587
  this.model = "model" in options ? options.model : "manager";
13588
+ this.appContext = options.appContext;
13589
+ }
13590
+ inferenceStatusUrl() {
13591
+ if (!this.workspaceUuid || this.chatId <= 0) return null;
13592
+ return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;
13593
+ }
13594
+ async waitForInferenceInactive(maxWaitMs = 6e4) {
13595
+ const url = this.inferenceStatusUrl();
13596
+ if (!url) return false;
13597
+ const deadline = Date.now() + maxWaitMs;
13598
+ while (Date.now() < deadline) {
13599
+ try {
13600
+ const res = await fetch(url, {
13601
+ headers: { Authorization: `Bearer ${this.accessToken}` }
13602
+ });
13603
+ if (res.ok) {
13604
+ const body = await res.json();
13605
+ if (!body.active) return true;
13606
+ }
13607
+ } catch (err) {
13608
+ if (this.debug) {
13609
+ console.log(
13610
+ `[DEBUG] waitForInferenceInactive fetch failed: ${err instanceof Error ? err.message : String(err)}`
13611
+ );
13612
+ }
13613
+ }
13614
+ await new Promise((r) => setTimeout(r, 2e3));
13615
+ }
13616
+ return false;
13571
13617
  }
13572
13618
  /**
13573
13619
  * Send messages and stream the response
13574
13620
  */
13575
13621
  async sendMessage(messages, onEvent, options) {
13622
+ return this.streamRequest({ messages }, onEvent, options);
13623
+ }
13624
+ /**
13625
+ * Stream an AG-UI request (initial messages or HITL resume).
13626
+ */
13627
+ async streamRequest(request, onEvent, options) {
13576
13628
  const body = JSON.stringify({
13577
13629
  ...this.model ? { model: this.model } : {},
13578
- messages,
13630
+ messages: request.messages ?? [],
13631
+ ...this.appContext ? { appContext: this.appContext } : {},
13632
+ ...request.resumeValue !== void 0 ? { resumeValue: request.resumeValue } : {},
13633
+ ...request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {},
13579
13634
  ...options?.autoApprove ? { context: { autoApprove: true } } : {}
13580
13635
  });
13581
13636
  if (this.debug) {
13582
13637
  console.log(`
13583
13638
  [DEBUG] POST ${this.endpointUrl}`);
13584
- console.log(`[DEBUG] Body: ${messages.length} message(s)`);
13639
+ console.log(
13640
+ `[DEBUG] Body: ${request.resumeValue !== void 0 ? "resume" : `${(request.messages ?? []).length} message(s)`}`
13641
+ );
13585
13642
  }
13643
+ await this.postStream(body, onEvent, options);
13644
+ }
13645
+ async postStream(body, onEvent, options, conflictRetries = 0) {
13586
13646
  const response = await fetch(this.endpointUrl, {
13587
13647
  method: "POST",
13588
13648
  headers: {
@@ -13595,12 +13655,21 @@ var AGUIClient = class {
13595
13655
  if (this.debug) {
13596
13656
  console.log(`[DEBUG] Response status: ${response.status}`);
13597
13657
  }
13658
+ if (response.status === 409 && conflictRetries < 3) {
13659
+ const ready = await this.waitForInferenceInactive();
13660
+ if (ready) {
13661
+ await this.postStream(body, onEvent, options, conflictRetries + 1);
13662
+ return;
13663
+ }
13664
+ }
13598
13665
  if (!response.ok) {
13599
13666
  const errorText = await response.text().catch(() => "");
13600
13667
  if (this.debug) {
13601
13668
  console.log(`[DEBUG] Error response: ${errorText}`);
13602
13669
  }
13603
- throw new Error(`Chat request failed: ${response.status}`);
13670
+ throw new Error(
13671
+ `Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ""}`
13672
+ );
13604
13673
  }
13605
13674
  const reader = response.body.getReader();
13606
13675
  const decoder = new TextDecoder();
@@ -13873,12 +13942,14 @@ function requireAuth(ctx) {
13873
13942
  return ctx;
13874
13943
  }
13875
13944
  function createPublicContext(opts) {
13876
- const apiUrl = opts.apiUrl || process.env.GRAVITY_RAIL_API_URL || "https://api.gravityrail.com";
13877
- const frontendUrl = opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com";
13878
- const envConfig = { apiUrl, frontendUrl };
13879
- const credentialsManager = new CredentialsManager(homedir(), apiUrl);
13945
+ const env = opts.env ?? "prod";
13946
+ const envConfig = opts.apiUrl ? {
13947
+ apiUrl: opts.apiUrl,
13948
+ frontendUrl: opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com"
13949
+ } : getEnvironmentConfig(env);
13950
+ const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);
13880
13951
  return {
13881
- env: "prod",
13952
+ env,
13882
13953
  envConfig,
13883
13954
  outputFormat: detectOutputFormat(opts.outputFormat),
13884
13955
  verbose: opts.verbose ?? false,
@@ -13927,32 +13998,48 @@ async function readJsonInput(args) {
13927
13998
  throw new CLIError(`Cannot read file: ${filePath}`);
13928
13999
  }
13929
14000
  }
13930
- if (!process.stdin.isTTY) {
13931
- return readStdin();
14001
+ if (process.stdin.isTTY === true) {
14002
+ return void 0;
13932
14003
  }
13933
- return void 0;
14004
+ return readStdinIfPiped();
13934
14005
  }
13935
- function readStdin() {
13936
- return new Promise((resolve, reject) => {
14006
+ function readStdinIfPiped() {
14007
+ return new Promise((resolve2, reject) => {
13937
14008
  let data = "";
13938
- process.stdin.setEncoding("utf-8");
13939
- process.stdin.on("data", (chunk) => {
13940
- data += chunk;
13941
- });
13942
- process.stdin.on("end", () => {
14009
+ let settled = false;
14010
+ const finish = (value) => {
14011
+ if (settled) return;
14012
+ settled = true;
14013
+ resolve2(value);
14014
+ };
14015
+ const flush = () => {
13943
14016
  if (!data.trim()) {
13944
- resolve(void 0);
14017
+ finish(void 0);
13945
14018
  return;
13946
14019
  }
13947
14020
  try {
13948
- resolve(JSON.parse(data));
14021
+ finish(JSON.parse(data));
13949
14022
  } catch {
13950
14023
  reject(new CLIError("Invalid JSON from stdin"));
13951
14024
  }
14025
+ };
14026
+ process.stdin.setEncoding("utf-8");
14027
+ process.stdin.on("data", (chunk) => {
14028
+ data += chunk;
13952
14029
  });
14030
+ process.stdin.on("end", flush);
13953
14031
  process.stdin.on("error", (err) => {
13954
14032
  reject(new CLIError(`Error reading stdin: ${err.message}`));
13955
14033
  });
14034
+ process.stdin.resume();
14035
+ setImmediate(() => {
14036
+ if (data) return;
14037
+ if (process.stdin.readableEnded) {
14038
+ flush();
14039
+ return;
14040
+ }
14041
+ finish(void 0);
14042
+ });
13956
14043
  });
13957
14044
  }
13958
14045
 
@@ -14358,6 +14445,17 @@ function requireNumber(args, key) {
14358
14445
  if (isNaN(num)) throw new Error(`Argument --${key} must be a number`);
14359
14446
  return num;
14360
14447
  }
14448
+ function requireNumberFrom(args, ...keys) {
14449
+ for (const key of keys) {
14450
+ const val = args[key];
14451
+ if (typeof val === "string") {
14452
+ const num = Number(val);
14453
+ if (!isNaN(num)) return num;
14454
+ }
14455
+ }
14456
+ const hint = keys.map((k) => `--${k}`).join(" or ");
14457
+ throw new Error(`Missing required argument: ${hint}`);
14458
+ }
14361
14459
 
14362
14460
  // src/domains/agent-tasks.ts
14363
14461
  var LIST_COLUMNS = [
@@ -14518,6 +14616,7 @@ var agentsDomain = {
14518
14616
  var COLUMNS2 = [
14519
14617
  COL.id(),
14520
14618
  COL.uuid(),
14619
+ { header: "TYPE", getValue: (item) => item.chatType ?? item.chat_type ?? "" },
14521
14620
  { header: "MEMBER", getValue: (item) => item.member_name ?? String(item.member_id ?? "") },
14522
14621
  COL.status(),
14523
14622
  { header: "CHANNEL", getValue: (item) => item.channel ?? "" },
@@ -14569,7 +14668,11 @@ var chatsDomain = {
14569
14668
  isFlag: true
14570
14669
  },
14571
14670
  { name: "is-test", description: "Show only test chats", isFlag: true },
14572
- { name: "member-id", description: "Filter by member ID" }
14671
+ { name: "member-id", description: "Filter by member ID" },
14672
+ {
14673
+ name: "chat-type",
14674
+ description: "Filter by chat type (assignment, manager, developer, member, contributor). Default: assignment"
14675
+ }
14573
14676
  ]
14574
14677
  }),
14575
14678
  // Messages
@@ -14578,7 +14681,7 @@ var chatsDomain = {
14578
14681
  description: "List messages for a chat. Use --limit/--offset to page; the API caps a single request at 100. Tool-call shell rows that the API filter excludes (parent_message_id IS NOT NULL or parent_call_id IS NOT NULL) are still hidden \u2014 use `gr chats inspect --id <chat>` for the full debug view.",
14579
14682
  requiresWorkspace: true,
14580
14683
  args: [
14581
- { name: "chat-id", description: "Chat ID", required: true },
14684
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
14582
14685
  {
14583
14686
  name: "limit",
14584
14687
  description: "Max messages to return (1-100, default 100)"
@@ -14601,7 +14704,7 @@ var chatsDomain = {
14601
14704
  }
14602
14705
  const result2 = await requireAuth(ctx).apiClient.getChatMessages(
14603
14706
  workspace,
14604
- requireNumber(args, "chat-id"),
14707
+ requireNumberFrom(args, "chat-id", "id"),
14605
14708
  limit,
14606
14709
  offset
14607
14710
  );
@@ -14614,7 +14717,7 @@ var chatsDomain = {
14614
14717
  mutates: true,
14615
14718
  requiresWorkspace: true,
14616
14719
  args: [
14617
- { name: "chat-id", description: "Chat ID", required: true },
14720
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
14618
14721
  { name: "stream", description: "Stream the AI response via AG-UI", isFlag: true }
14619
14722
  ],
14620
14723
  handler: async ({ ctx, workspace, args, data }) => {
@@ -14623,7 +14726,7 @@ var chatsDomain = {
14623
14726
  throw new Error(
14624
14727
  `Message content is required (pass --data '"your message"' or --data '{"content":"your message"}')`
14625
14728
  );
14626
- const chatId = requireNumber(args, "chat-id");
14729
+ const chatId = requireNumberFrom(args, "chat-id", "id");
14627
14730
  if (args.stream) {
14628
14731
  const aguiClient = new AGUIClient({
14629
14732
  baseUrl: ctx.envConfig.apiUrl,
@@ -14656,11 +14759,11 @@ var chatsDomain = {
14656
14759
  description: "Send an assistant message to a chat (does not trigger AI inference)",
14657
14760
  mutates: true,
14658
14761
  requiresWorkspace: true,
14659
- args: [{ name: "chat-id", description: "Chat ID", required: true }],
14762
+ args: [{ name: "chat-id", aliases: ["id"], description: "Chat ID", required: true }],
14660
14763
  handler: async ({ ctx, workspace, args, data }) => {
14661
14764
  const result2 = await requireAuth(ctx).apiClient.sendAssistantMessage(
14662
14765
  workspace,
14663
- requireNumber(args, "chat-id"),
14766
+ requireNumberFrom(args, "chat-id", "id"),
14664
14767
  data
14665
14768
  );
14666
14769
  outputSingle(result2, ctx, MESSAGE_COLUMNS2);
@@ -16723,10 +16826,20 @@ var eventRulesCrudNoList = crudCommands({
16723
16826
  ' "eventType":"journey_step:execute","actionType":"member:label",',
16724
16827
  ` "actionParams":{"addLabelIds":["<label-uuid>"]},"journeyStepId":<step-id>}'`,
16725
16828
  "",
16829
+ "Scheduled outbound (routine \u2192 chat \u2192 voice/SMS):",
16830
+ `gr events rules create -w $WID --allow-write --data '{"name":"AM voice check-in",`,
16831
+ ' "objectType":"routine","eventType":"routine:triggered","actionType":"chat:create",',
16832
+ ' "routineId":<routine-id>,',
16833
+ ' "actionParams":{"workflowId":<workflow-id>,"channel":"phone-voice",',
16834
+ ` "phoneNumberId":<workspace-phone-id>}}'`,
16835
+ "",
16836
+ ' PM SMS variant: use "channel":"phone-sms" and a second rule on the same routineId.',
16837
+ " Prereq: org phone assigned + gr phone-numbers link; workflow published with start task.",
16838
+ "",
16726
16839
  "Required: objectType, eventType, actionType",
16727
- "Optional: name, actionParams, conditions (CEL), journeyStepId, delay (sec), order",
16840
+ "Optional: name, actionParams, conditions (CEL), journeyStepId, routineId, delay (sec), order",
16728
16841
  "",
16729
- "Common eventTypes: journey_step:execute, journey:enrolled, journey:completed,",
16842
+ "Common eventTypes: journey_step:execute, journey:enrolled, routine:triggered,",
16730
16843
  " task:created, chat:created, data_record:created, member:created",
16731
16844
  "",
16732
16845
  "Common actionTypes: member:label, member:notify, webhook:send, email:send,",
@@ -19009,7 +19122,7 @@ var conciergeDomain = {
19009
19122
  path: ["concierge", "messages"],
19010
19123
  description: "Show messages for a concierge chat",
19011
19124
  requiresWorkspace: false,
19012
- args: [{ name: "id", description: "Chat ID", required: true }],
19125
+ args: [{ name: "id", aliases: ["chat-id"], description: "Chat ID", required: true }],
19013
19126
  handler: async ({ ctx, args }) => {
19014
19127
  const auth = requireAuth(ctx);
19015
19128
  const chatId = Number(args.id);
@@ -19642,15 +19755,15 @@ var DOMAIN_UUID_ARG = {
19642
19755
  function pluralize(count, singular, plural = `${singular}s`) {
19643
19756
  return count === 1 ? singular : plural;
19644
19757
  }
19645
- function emailVerifyCommand(path, description) {
19758
+ function emailVerifyCommand(path4, description) {
19646
19759
  return {
19647
- path,
19760
+ path: path4,
19648
19761
  description,
19649
19762
  mutates: true,
19650
19763
  requiresWorkspace: false,
19651
19764
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
19652
19765
  examples: [
19653
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19766
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19654
19767
  ],
19655
19768
  handler: async (hctx) => {
19656
19769
  const result2 = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainEmail(
@@ -19661,15 +19774,15 @@ function emailVerifyCommand(path, description) {
19661
19774
  }
19662
19775
  };
19663
19776
  }
19664
- function siteVerifyCommand(path, description) {
19777
+ function siteVerifyCommand(path4, description) {
19665
19778
  return {
19666
- path,
19779
+ path: path4,
19667
19780
  description,
19668
19781
  mutates: true,
19669
19782
  requiresWorkspace: false,
19670
19783
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
19671
19784
  examples: [
19672
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19785
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19673
19786
  ],
19674
19787
  handler: async (hctx) => {
19675
19788
  const result2 = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainSite(
@@ -19680,9 +19793,9 @@ function siteVerifyCommand(path, description) {
19680
19793
  }
19681
19794
  };
19682
19795
  }
19683
- function registerDomainCommand(path) {
19796
+ function registerDomainCommand(path4) {
19684
19797
  return {
19685
- path,
19798
+ path: path4,
19686
19799
  description: "Register an organization domain and return ownership TXT instructions",
19687
19800
  mutates: true,
19688
19801
  requiresWorkspace: false,
@@ -19691,7 +19804,7 @@ function registerDomainCommand(path) {
19691
19804
  { name: "domain", description: "Domain to register, e.g. example.com", required: true }
19692
19805
  ],
19693
19806
  examples: [
19694
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
19807
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
19695
19808
  ],
19696
19809
  handler: async (hctx) => {
19697
19810
  const result2 = await requireAuth(hctx.ctx).apiClient.registerOrgDomain(
@@ -20045,6 +20158,12 @@ var commands2 = [
20045
20158
  workspaceUuid
20046
20159
  );
20047
20160
  outputSingle(result2, hctx.ctx, PROVIDER_PHONE_COLUMNS);
20161
+ process.stdout.write(
20162
+ `
20163
+ Create a workspace phone linked to this provider number (sets Twilio webhooks):
20164
+ gr phone-numbers create -w ${workspaceUuid} --allow-write --data '{"name":"\u2026","providerPhoneUuid":"${providerPhoneUuid}"}'
20165
+ `
20166
+ );
20048
20167
  }
20049
20168
  },
20050
20169
  {
@@ -20148,6 +20267,31 @@ var phoneNumbersDomain = {
20148
20267
  outputData(result2, ctx, COLUMNS25);
20149
20268
  }
20150
20269
  },
20270
+ {
20271
+ path: ["phone-numbers", "link"],
20272
+ description: "Link a workspace phone number to a provider (org) phone",
20273
+ mutates: true,
20274
+ requiresWorkspace: true,
20275
+ args: [
20276
+ {
20277
+ name: "phone-number-id",
20278
+ aliases: ["id"],
20279
+ description: "Workspace phone number ID",
20280
+ required: true
20281
+ },
20282
+ { name: "provider-phone-uuid", description: "Provider phone UUID", required: true }
20283
+ ],
20284
+ examples: [
20285
+ "gr phone-numbers link -w $WID --id 1 --provider-phone-uuid <uuid> --allow-write"
20286
+ ],
20287
+ handler: async ({ ctx, workspace, args }) => {
20288
+ const auth = requireAuth(ctx);
20289
+ const id = requireNumberFrom(args, "phone-number-id", "id");
20290
+ const providerPhoneUuid = requireString(args, "provider-phone-uuid");
20291
+ const result2 = await auth.apiClient.linkPhoneNumber(workspace, id, providerPhoneUuid);
20292
+ outputData([result2], auth, COLUMNS25);
20293
+ }
20294
+ },
20151
20295
  {
20152
20296
  path: ["phone-numbers", "request"],
20153
20297
  description: "Request a phone number",
@@ -20166,7 +20310,12 @@ var phoneNumbersDomain = {
20166
20310
  description: "List calls for a phone number",
20167
20311
  requiresWorkspace: true,
20168
20312
  args: [
20169
- { name: "phone-number-id", description: "Phone number ID", required: true },
20313
+ {
20314
+ name: "phone-number-id",
20315
+ aliases: ["id"],
20316
+ description: "Phone number ID",
20317
+ required: true
20318
+ },
20170
20319
  { name: "page", description: "Page number" },
20171
20320
  { name: "page-size", description: "Page size" },
20172
20321
  { name: "status", description: "Filter by status" }
@@ -20175,7 +20324,7 @@ var phoneNumbersDomain = {
20175
20324
  const status = args.status;
20176
20325
  const result2 = await requireAuth(ctx).apiClient.getPhoneNumberCalls(
20177
20326
  workspace,
20178
- requireNumber(args, "phone-number-id"),
20327
+ requireNumberFrom(args, "phone-number-id", "id"),
20179
20328
  args.page ? Number(args.page) : void 0,
20180
20329
  args["page-size"] ? Number(args["page-size"]) : void 0,
20181
20330
  status ? { status } : void 0
@@ -20188,14 +20337,19 @@ var phoneNumbersDomain = {
20188
20337
  description: "List messages for a phone number",
20189
20338
  requiresWorkspace: true,
20190
20339
  args: [
20191
- { name: "phone-number-id", description: "Phone number ID", required: true },
20340
+ {
20341
+ name: "phone-number-id",
20342
+ aliases: ["id"],
20343
+ description: "Phone number ID",
20344
+ required: true
20345
+ },
20192
20346
  { name: "page", description: "Page number" },
20193
20347
  { name: "page-size", description: "Page size" }
20194
20348
  ],
20195
20349
  handler: async ({ ctx, workspace, args }) => {
20196
20350
  const result2 = await requireAuth(ctx).apiClient.getPhoneNumberMessages(
20197
20351
  workspace,
20198
- requireNumber(args, "phone-number-id"),
20352
+ requireNumberFrom(args, "phone-number-id", "id"),
20199
20353
  args.page ? Number(args.page) : void 0,
20200
20354
  args["page-size"] ? Number(args["page-size"]) : void 0
20201
20355
  );
@@ -20205,36 +20359,85 @@ var phoneNumbersDomain = {
20205
20359
  ]
20206
20360
  };
20207
20361
 
20362
+ // src/lib/resolve-org.ts
20363
+ async function resolveOrgUuidFromWorkspace(apiClient, workspaceUuid) {
20364
+ const workspace = await apiClient.getWorkspace(workspaceUuid);
20365
+ const orgUuid = workspace.organizationUuid;
20366
+ if (!orgUuid) {
20367
+ throw new CLIError(
20368
+ "Workspace has no organization; pass --org-uuid for organization-scoped commands"
20369
+ );
20370
+ }
20371
+ return orgUuid;
20372
+ }
20373
+ function normalizePronunciationCreatePayload(data) {
20374
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
20375
+ throw new CLIError("JSON body required with term and respelling (--data or --file)");
20376
+ }
20377
+ const raw = data;
20378
+ const term = raw.term ?? raw.word;
20379
+ const respelling = raw.respelling ?? raw.pronunciation;
20380
+ if (typeof term !== "string" || !term) {
20381
+ throw new CLIError("Payload requires term (alias: word)");
20382
+ }
20383
+ if (typeof respelling !== "string" || !respelling) {
20384
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
20385
+ }
20386
+ const payload = { term, respelling };
20387
+ if (raw.accountId !== void 0 && raw.accountId !== null) {
20388
+ payload.accountId = Number(raw.accountId);
20389
+ }
20390
+ return payload;
20391
+ }
20392
+ function normalizePronunciationUpdatePayload(data) {
20393
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
20394
+ throw new CLIError("JSON body required (--data or --file)");
20395
+ }
20396
+ const raw = data;
20397
+ const respelling = raw.respelling ?? raw.pronunciation;
20398
+ if (typeof respelling !== "string" || !respelling) {
20399
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
20400
+ }
20401
+ return { respelling };
20402
+ }
20403
+
20208
20404
  // src/domains/pronunciations.ts
20209
20405
  var COLUMNS26 = [
20210
20406
  COL.id(),
20211
- { header: "WORD", getValue: (item) => item.word ?? "" },
20212
- { header: "PRONUNCIATION", getValue: (item) => item.pronunciation ?? "" },
20213
- { header: "TYPE", getValue: (item) => item.type ?? "" }
20407
+ { header: "TERM", getValue: (item) => item.term ?? "" },
20408
+ { header: "RESPELLING", getValue: (item) => item.respelling ?? "" },
20409
+ { header: "SCOPE", getValue: (item) => item.scope ?? "" }
20410
+ ];
20411
+ var CREATE_EXAMPLES = [
20412
+ `gr pronunciations create -w $WID --data '{"term":"Jeeves","respelling":"JEEVZ"}' --allow-write`,
20413
+ " (aliases: word \u2192 term, pronunciation \u2192 respelling)"
20214
20414
  ];
20215
20415
  var pronunciationsDomain = {
20216
20416
  name: "pronunciations",
20217
- description: "Manage pronunciation entries",
20417
+ description: "Manage pronunciation entries (org-scoped; -w resolves organization)",
20218
20418
  commands: [
20219
20419
  {
20220
20420
  path: ["pronunciations", "list"],
20221
- description: "List pronunciation entries",
20421
+ description: "List pronunciation entries for the workspace organization",
20222
20422
  requiresWorkspace: true,
20223
20423
  handler: async ({ ctx, workspace }) => {
20224
- const result2 = await requireAuth(ctx).apiClient.getPronunciationEntries(workspace);
20424
+ const auth = requireAuth(ctx);
20425
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20426
+ const result2 = await auth.apiClient.getPronunciationEntries(orgUuid);
20225
20427
  outputData(result2, ctx, COLUMNS26);
20226
20428
  }
20227
20429
  },
20228
20430
  {
20229
20431
  path: ["pronunciations", "create"],
20230
- description: "Create a pronunciation entry",
20432
+ description: "Create a pronunciation entry (term + respelling)",
20231
20433
  mutates: true,
20232
20434
  requiresWorkspace: true,
20435
+ examples: CREATE_EXAMPLES,
20233
20436
  handler: async ({ ctx, workspace, data }) => {
20234
- const result2 = await requireAuth(ctx).apiClient.createPronunciationEntry(
20235
- workspace,
20236
- data
20237
- );
20437
+ const auth = requireAuth(ctx);
20438
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20439
+ const payload = normalizePronunciationCreatePayload(data);
20440
+ const result2 = await auth.apiClient.createPronunciationEntry(orgUuid, payload);
20238
20441
  outputSingle(result2, ctx, COLUMNS26);
20239
20442
  }
20240
20443
  },
@@ -20245,10 +20448,13 @@ var pronunciationsDomain = {
20245
20448
  requiresWorkspace: true,
20246
20449
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
20247
20450
  handler: async ({ ctx, workspace, args, data }) => {
20248
- const result2 = await requireAuth(ctx).apiClient.updatePronunciationEntry(
20249
- workspace,
20451
+ const auth = requireAuth(ctx);
20452
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20453
+ const payload = normalizePronunciationUpdatePayload(data);
20454
+ const result2 = await auth.apiClient.updatePronunciationEntry(
20455
+ orgUuid,
20250
20456
  requireNumber(args, "id"),
20251
- data
20457
+ payload
20252
20458
  );
20253
20459
  outputSingle(result2, ctx, COLUMNS26);
20254
20460
  }
@@ -20260,8 +20466,10 @@ var pronunciationsDomain = {
20260
20466
  requiresWorkspace: true,
20261
20467
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
20262
20468
  handler: async ({ ctx, workspace, args }) => {
20469
+ const auth = requireAuth(ctx);
20470
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20263
20471
  const id = requireNumber(args, "id");
20264
- await requireAuth(ctx).apiClient.deletePronunciationEntry(workspace, id);
20472
+ await auth.apiClient.deletePronunciationEntry(orgUuid, id);
20265
20473
  process.stdout.write(`Deleted pronunciation entry ${id}
20266
20474
  `);
20267
20475
  }
@@ -20281,7 +20489,8 @@ var pronunciationsDomain = {
20281
20489
  mutates: true,
20282
20490
  requiresWorkspace: true,
20283
20491
  handler: async ({ ctx, data }) => {
20284
- const result2 = await requireAuth(ctx).apiClient.createMyPronunciation(data);
20492
+ const payload = normalizePronunciationCreatePayload(data);
20493
+ const result2 = await requireAuth(ctx).apiClient.createMyPronunciation(payload);
20285
20494
  outputSingle(result2, ctx, COLUMNS26);
20286
20495
  }
20287
20496
  },
@@ -20289,11 +20498,17 @@ var pronunciationsDomain = {
20289
20498
  path: ["pronunciations", "suggest"],
20290
20499
  description: "Get pronunciation suggestions for a word",
20291
20500
  requiresWorkspace: true,
20292
- args: [{ name: "word", description: "Word to get suggestions for", required: true }],
20501
+ args: [
20502
+ {
20503
+ name: "word",
20504
+ aliases: ["term"],
20505
+ description: "Word to get suggestions for",
20506
+ required: true
20507
+ }
20508
+ ],
20293
20509
  handler: async ({ ctx, args }) => {
20294
- const result2 = await requireAuth(ctx).apiClient.suggestPronunciations(
20295
- requireString(args, "word")
20296
- );
20510
+ const word = typeof args.word === "string" ? args.word : typeof args.term === "string" ? args.term : requireString(args, "word");
20511
+ const result2 = await requireAuth(ctx).apiClient.suggestPronunciations(word);
20297
20512
  outputData(result2, ctx);
20298
20513
  }
20299
20514
  },
@@ -22250,6 +22465,327 @@ function openUrl(url) {
22250
22465
  }
22251
22466
  });
22252
22467
  }
22468
+ function buildCliConciergeAppContext() {
22469
+ const shell = process.env.SHELL ? path3.basename(process.env.SHELL) : void 0;
22470
+ return {
22471
+ clientSurface: "cli",
22472
+ channel: "text",
22473
+ cliContext: {
22474
+ shell,
22475
+ os: process.platform,
22476
+ cwdBasename: path3.basename(process.cwd())
22477
+ }
22478
+ };
22479
+ }
22480
+ var DEFAULT_MAX_READ_BYTES = 5e4;
22481
+ var ABSOLUTE_MAX_READ_BYTES = 1e6;
22482
+ function resolveSafePath(root, relativePath) {
22483
+ const rootResolved = path3.resolve(root);
22484
+ const target = path3.resolve(rootResolved, relativePath || ".");
22485
+ if (target !== rootResolved && !target.startsWith(rootResolved + path3.sep)) {
22486
+ return null;
22487
+ }
22488
+ let cursor = rootResolved;
22489
+ const rest = path3.relative(rootResolved, target);
22490
+ if (rest && rest !== ".") {
22491
+ for (const segment of rest.split(path3.sep)) {
22492
+ cursor = path3.join(cursor, segment);
22493
+ try {
22494
+ const lstat = fs.lstatSync(cursor);
22495
+ if (lstat.isSymbolicLink()) {
22496
+ return null;
22497
+ }
22498
+ } catch {
22499
+ break;
22500
+ }
22501
+ }
22502
+ }
22503
+ return target;
22504
+ }
22505
+ function executeCliLocalFileAction(root, interrupt, opts) {
22506
+ const safe = resolveSafePath(root, interrupt.path);
22507
+ if (!safe) {
22508
+ return { ok: false, error: "Path escapes trusted directory" };
22509
+ }
22510
+ try {
22511
+ switch (interrupt.action) {
22512
+ case "read_file": {
22513
+ if (!fs.existsSync(safe)) {
22514
+ return { ok: false, error: "File not found" };
22515
+ }
22516
+ const stat = fs.lstatSync(safe);
22517
+ if (stat.isSymbolicLink()) {
22518
+ return { ok: false, error: "Path escapes trusted directory" };
22519
+ }
22520
+ if (!stat.isFile()) {
22521
+ return { ok: false, error: "Not a file" };
22522
+ }
22523
+ const requested = Number.isFinite(interrupt.max_bytes) ? Math.max(1, Math.min(Number(interrupt.max_bytes), ABSOLUTE_MAX_READ_BYTES)) : DEFAULT_MAX_READ_BYTES;
22524
+ if (stat.size > requested) {
22525
+ const buf = fs.readFileSync(safe, { encoding: "utf8" });
22526
+ return {
22527
+ ok: true,
22528
+ content: buf.slice(0, requested) + `
22529
+
22530
+ [truncated at ${requested} bytes]`
22531
+ };
22532
+ }
22533
+ return { ok: true, content: fs.readFileSync(safe, "utf8") };
22534
+ }
22535
+ case "list_dir": {
22536
+ if (!fs.existsSync(safe)) {
22537
+ return { ok: false, error: "Directory not found" };
22538
+ }
22539
+ const entries = fs.readdirSync(safe, { withFileTypes: true });
22540
+ const lines = entries.slice(0, 200).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
22541
+ if (entries.length > 200) {
22542
+ lines.push(`... and ${entries.length - 200} more`);
22543
+ }
22544
+ return { ok: true, content: lines.join("\n") };
22545
+ }
22546
+ case "write_file": {
22547
+ if (!opts.allowWrite) {
22548
+ return { ok: false, error: "Write requires --allow-write" };
22549
+ }
22550
+ if (interrupt.content === void 0) {
22551
+ return { ok: false, error: "Missing content for write_file" };
22552
+ }
22553
+ fs.mkdirSync(path3.dirname(safe), { recursive: true });
22554
+ fs.writeFileSync(safe, interrupt.content, "utf8");
22555
+ return { ok: true, content: "written" };
22556
+ }
22557
+ default:
22558
+ return { ok: false, error: `Unknown action: ${interrupt.action}` };
22559
+ }
22560
+ } catch (e) {
22561
+ return { ok: false, error: sanitizeLocalFileError(e) };
22562
+ }
22563
+ }
22564
+ function sanitizeLocalFileError(e) {
22565
+ if (e && typeof e === "object" && "code" in e) {
22566
+ const code = e.code;
22567
+ if (code === "ENOENT") return "File or directory not found";
22568
+ if (code === "EACCES" || code === "EPERM") return "Permission denied";
22569
+ if (code === "EISDIR") return "Not a file";
22570
+ if (code === "ENOTDIR") return "Not a directory";
22571
+ }
22572
+ return "Local file operation failed";
22573
+ }
22574
+ function isCliLocalFileInterrupt(value) {
22575
+ if (typeof value !== "object" || value === null) return false;
22576
+ const v = value;
22577
+ return v.type === "cli_local_file" && typeof v.action === "string" && typeof v.path === "string";
22578
+ }
22579
+ var STORE_VERSION = 1;
22580
+ function storePath() {
22581
+ const base = process.env.XDG_CONFIG_HOME ?? (process.platform === "win32" ? path3.join(process.env.APPDATA ?? os.homedir(), "gravityrail") : path3.join(os.homedir(), ".config", "gravityrail"));
22582
+ return path3.join(base, "trusted-dirs.json");
22583
+ }
22584
+ function loadTrustedDirsStore() {
22585
+ const file = storePath();
22586
+ try {
22587
+ const raw = JSON.parse(fs.readFileSync(file, "utf8"));
22588
+ if (raw?.version === STORE_VERSION && Array.isArray(raw.always)) {
22589
+ return { version: STORE_VERSION, always: raw.always.map((p) => path3.resolve(p)) };
22590
+ }
22591
+ } catch {
22592
+ }
22593
+ return { version: STORE_VERSION, always: [] };
22594
+ }
22595
+ function saveTrustedDirsStore(store) {
22596
+ const file = storePath();
22597
+ const dir = path3.dirname(file);
22598
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
22599
+ fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
22600
+ }
22601
+ function isDirectoryTrusted(dirPath, opts) {
22602
+ if (opts?.trustCwdFlag) return true;
22603
+ const resolved = path3.resolve(dirPath);
22604
+ const store = loadTrustedDirsStore();
22605
+ return store.always.includes(resolved);
22606
+ }
22607
+ async function promptTrustInteractive(dirPath) {
22608
+ const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
22609
+ const question = (q) => new Promise((resolve2) => rl.question(q, resolve2));
22610
+ process.stderr.write(
22611
+ `
22612
+ Gravity Rail may read files in:
22613
+ ${dirPath}
22614
+
22615
+ File contents can influence the assistant. Do you trust the files in this directory?
22616
+ [1] Trust once (this session)
22617
+ [2] Trust always (remember for this path)
22618
+ [3] No \u2014 do not read local files
22619
+
22620
+ Choice [1/2/3] (default 3): `
22621
+ );
22622
+ const answer = (await question("")).trim() || "3";
22623
+ rl.close();
22624
+ if (answer === "1") return "once";
22625
+ if (answer === "2") return "always";
22626
+ return "denied";
22627
+ }
22628
+ var sessionTrusted = /* @__PURE__ */ new Set();
22629
+ async function resolveDirectoryTrust(opts) {
22630
+ const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
22631
+ if (isDirectoryTrusted(dir, { trustCwdFlag: opts.trustCwd })) return "trusted";
22632
+ if (sessionTrusted.has(dir)) return "trusted";
22633
+ if (opts.trustDir && path3.resolve(opts.trustDir) === dir) {
22634
+ sessionTrusted.add(dir);
22635
+ return "trusted";
22636
+ }
22637
+ if (opts.trustCwd) {
22638
+ sessionTrusted.add(dir);
22639
+ return "trusted";
22640
+ }
22641
+ if (!process.stdin.isTTY || opts.nonInteractive) {
22642
+ return "denied";
22643
+ }
22644
+ const decision = await promptTrustInteractive(dir);
22645
+ if (decision === "denied") {
22646
+ return "denied";
22647
+ }
22648
+ if (decision === "always") {
22649
+ const store = loadTrustedDirsStore();
22650
+ if (!store.always.includes(dir)) {
22651
+ store.always.push(dir);
22652
+ saveTrustedDirsStore(store);
22653
+ }
22654
+ }
22655
+ sessionTrusted.add(dir);
22656
+ return "trusted";
22657
+ }
22658
+
22659
+ // src/lib/cli-hitl.ts
22660
+ function extractHumanInputRequest(event) {
22661
+ const type = String(event.type ?? "").toUpperCase();
22662
+ if (type === "CUSTOM" && event.name === "human_input_request") {
22663
+ return event.value;
22664
+ }
22665
+ if (event.name === "human_input_request") {
22666
+ return event.value;
22667
+ }
22668
+ return null;
22669
+ }
22670
+ async function handleCliHitlInterrupt(value, opts) {
22671
+ if (!value || typeof value !== "object") return null;
22672
+ const payload = value;
22673
+ if (isCliLocalFileInterrupt(payload)) {
22674
+ const interrupt = payload;
22675
+ if (typeof payload.interrupt_id === "string") {
22676
+ interrupt.interrupt_id = payload.interrupt_id;
22677
+ }
22678
+ return handleCliLocalFile(interrupt, opts);
22679
+ }
22680
+ if (payload.type === "present_choice") {
22681
+ return handlePresentChoiceTerminal(payload);
22682
+ }
22683
+ return null;
22684
+ }
22685
+ async function handleCliLocalFile(interrupt, opts) {
22686
+ const trust = await resolveDirectoryTrust({
22687
+ cwd: process.cwd(),
22688
+ trustCwd: opts.trustCwd,
22689
+ trustDir: opts.trustDir,
22690
+ // Interactive sessions: prompt on first local-file use (review feedback).
22691
+ nonInteractive: opts.nonInteractive ?? process.stdin.isTTY !== true
22692
+ });
22693
+ if (trust !== "trusted") {
22694
+ return {
22695
+ resumeValue: JSON.stringify({
22696
+ ok: false,
22697
+ error: "Directory not trusted for local file access. Re-run with --trust-cwd or --trust-dir <path>, or approve trust interactively when prompted."
22698
+ }),
22699
+ resumeInterruptId: interrupt.interrupt_id
22700
+ };
22701
+ }
22702
+ process.stderr.write(`
22703
+ [local ${interrupt.action}] ${interrupt.path}
22704
+ `);
22705
+ const result2 = executeCliLocalFileAction(process.cwd(), interrupt, {
22706
+ allowWrite: opts.allowWrite
22707
+ });
22708
+ return {
22709
+ resumeValue: JSON.stringify(result2),
22710
+ resumeInterruptId: interrupt.interrupt_id
22711
+ };
22712
+ }
22713
+ async function handlePresentChoiceTerminal(payload) {
22714
+ const prompt = typeof payload.user_prompt === "string" && payload.user_prompt || typeof payload.description === "string" && payload.description || "Choose an option";
22715
+ const markup = typeof payload.openui_markup === "string" ? payload.openui_markup : "";
22716
+ process.stderr.write(`
22717
+ ${prompt}
22718
+ `);
22719
+ if (markup) {
22720
+ process.stderr.write(`${markup.slice(0, 2e3)}
22721
+ `);
22722
+ }
22723
+ if (!process.stdin.isTTY) {
22724
+ return {
22725
+ resumeValue: "",
22726
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
22727
+ };
22728
+ }
22729
+ const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
22730
+ const answer = await new Promise((resolve2) => {
22731
+ rl.question("Your choice (value): ", resolve2);
22732
+ });
22733
+ rl.close();
22734
+ return {
22735
+ resumeValue: answer.trim(),
22736
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
22737
+ };
22738
+ }
22739
+
22740
+ // src/lib/concierge-agui-loop.ts
22741
+ var MAX_HITL_ROUNDS = 25;
22742
+ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
22743
+ let streamText = "";
22744
+ let hasToolCalls = false;
22745
+ let resume;
22746
+ for (let round = 0; round < MAX_HITL_ROUNDS; round++) {
22747
+ let hitlPayload = null;
22748
+ await client.streamRequest(
22749
+ resume ? {
22750
+ messages: [],
22751
+ resumeValue: resume.resumeValue,
22752
+ resumeInterruptId: resume.resumeInterruptId
22753
+ } : { messages: initialMessages },
22754
+ (event) => {
22755
+ onEvent(event);
22756
+ const delta = getTextDelta(event);
22757
+ if (delta) streamText += delta;
22758
+ const type = event.type?.toLowerCase() ?? "";
22759
+ if (type === "tool_call_start") hasToolCalls = true;
22760
+ const errorMsg = getErrorMessage(event);
22761
+ if (errorMsg) process.stderr.write(`\x1B[31mError: ${errorMsg}\x1B[0m
22762
+ `);
22763
+ const hitl = extractHumanInputRequest(event);
22764
+ if (hitl) hitlPayload = hitl;
22765
+ },
22766
+ { autoApprove: opts.autoApprove }
22767
+ );
22768
+ if (!hitlPayload) {
22769
+ break;
22770
+ }
22771
+ const next = await handleCliHitlInterrupt(hitlPayload, {
22772
+ allowWrite: opts.allowWrite,
22773
+ autoApprove: opts.autoApprove,
22774
+ trustCwd: opts.trustCwd,
22775
+ trustDir: opts.trustDir,
22776
+ nonInteractive: opts.nonInteractive
22777
+ });
22778
+ if (!next) {
22779
+ process.stderr.write(
22780
+ " [HITL] Unsupported interrupt in CLI \u2014 open app.gravityrail.com/setup to continue.\n"
22781
+ );
22782
+ break;
22783
+ }
22784
+ resume = next;
22785
+ streamText = "";
22786
+ }
22787
+ return { streamText, hasToolCalls };
22788
+ }
22253
22789
  var RealtimeClient = class extends EventEmitter {
22254
22790
  ws = null;
22255
22791
  connected = false;
@@ -22269,7 +22805,7 @@ var RealtimeClient = class extends EventEmitter {
22269
22805
  new Error("WebSocket not available. Node.js 22+ is required for realtime mode.")
22270
22806
  );
22271
22807
  }
22272
- return new Promise((resolve, reject) => {
22808
+ return new Promise((resolve2, reject) => {
22273
22809
  const params = new URLSearchParams();
22274
22810
  if (this.options.vadMode) {
22275
22811
  params.append("vad", this.options.vadMode);
@@ -22324,7 +22860,7 @@ var RealtimeClient = class extends EventEmitter {
22324
22860
  }, 1e4);
22325
22861
  this.once("connected", () => {
22326
22862
  clearTimeout(timeout);
22327
- resolve();
22863
+ resolve2();
22328
22864
  });
22329
22865
  this.once("error", () => {
22330
22866
  clearTimeout(timeout);
@@ -23289,7 +23825,7 @@ async function listOutputDevices() {
23289
23825
  return listDevices("play");
23290
23826
  }
23291
23827
  function listDevices(command) {
23292
- return new Promise((resolve) => {
23828
+ return new Promise((resolve2) => {
23293
23829
  try {
23294
23830
  const proc = spawnSync(command, ["--list-devices"], {
23295
23831
  encoding: "utf-8",
@@ -23313,9 +23849,9 @@ function listDevices(command) {
23313
23849
  if (devices.length === 0) {
23314
23850
  devices.push({ id: "default", name: "System Default", isDefault: true });
23315
23851
  }
23316
- resolve(devices);
23852
+ resolve2(devices);
23317
23853
  } catch {
23318
- resolve([{ id: "default", name: "System Default", isDefault: true }]);
23854
+ resolve2([{ id: "default", name: "System Default", isDefault: true }]);
23319
23855
  }
23320
23856
  });
23321
23857
  }
@@ -23337,14 +23873,14 @@ async function selectDevice(devices, label) {
23337
23873
  process.stderr.write(` ${i + 1}. ${dev.name}${marker}
23338
23874
  `);
23339
23875
  }
23340
- return new Promise((resolve) => {
23876
+ return new Promise((resolve2) => {
23341
23877
  const defaultIdx = devices.findIndex((d) => d.isDefault);
23342
23878
  const defaultNum = defaultIdx >= 0 ? defaultIdx + 1 : 1;
23343
23879
  rl.question(` Select [${defaultNum}]: `, (answer) => {
23344
23880
  rl.close();
23345
23881
  const num = answer.trim() ? parseInt(answer.trim(), 10) : defaultNum;
23346
23882
  const idx = Math.max(0, Math.min(devices.length - 1, num - 1));
23347
- resolve(devices[idx]);
23883
+ resolve2(devices[idx]);
23348
23884
  });
23349
23885
  });
23350
23886
  }
@@ -23602,56 +24138,51 @@ async function runChatSession(session, args) {
23602
24138
  "Interactive mode requires a TTY. Use --message for non-interactive mode."
23603
24139
  );
23604
24140
  }
24141
+ const allowWrite = !!args["allow-write"] || !!session.allowWrite;
23605
24142
  if (message) {
23606
- await singleTurnChatSession(session, message, debug, autoApprove);
24143
+ await singleTurnChatSession(session, message, debug, autoApprove, allowWrite);
23607
24144
  } else if (isVoice) {
23608
24145
  await voiceChatSession(session, vadMode(args), args, debug);
23609
24146
  } else if (isRealtime) {
23610
24147
  await realtimeChatSession(session, vadMode(args), debug);
23611
24148
  } else {
23612
- await interactiveChatSession(session, debug);
24149
+ await interactiveChatSession(session, debug, autoApprove, allowWrite);
23613
24150
  }
23614
24151
  }
23615
24152
  function vadMode(args) {
23616
24153
  return typeof args.vad === "string" ? args.vad : "server";
23617
24154
  }
23618
- async function singleTurnChatSession(session, message, debug, autoApprove = false) {
24155
+ async function singleTurnChatSession(session, message, debug, autoApprove = false, allowWrite = false) {
23619
24156
  const aguiClient = new AGUIClient({
23620
24157
  baseUrl: session.ctx.envConfig.apiUrl,
24158
+ workspaceUuid: session.workspaceUuid,
24159
+ chatId: session.chatId,
23621
24160
  endpointUrl: session.aguiEndpointUrl,
23622
24161
  accessToken: session.ctx.accessToken,
23623
24162
  debug,
23624
- model: session.model
24163
+ model: session.model,
24164
+ appContext: session.appContext
23625
24165
  });
23626
- let streamText = "";
23627
- let hasToolCalls = false;
23628
- let eventCount = 0;
23629
- const logFile = process.env.GR_SSE_LOG;
23630
- const fs = logFile ? await import('fs') : null;
23631
- const logStream = logFile && fs ? fs.createWriteStream(logFile, { flags: "a" }) : null;
23632
- await aguiClient.sendMessage(
23633
- [{ role: "user", content: message }],
23634
- (event) => {
23635
- eventCount++;
23636
- if (logStream) logStream.write(JSON.stringify(event) + "\n");
23637
- const delta = getTextDelta(event);
23638
- if (delta) streamText += delta;
23639
- const type = event.type?.toLowerCase() ?? "";
23640
- if (type === "tool_call_start") {
23641
- hasToolCalls = true;
23642
- const name = event.name ?? "";
23643
- process.stderr.write(` [${name}]
23644
- `);
23645
- }
23646
- const errorMsg = getErrorMessage(event);
23647
- if (errorMsg) process.stderr.write(`\x1B[31mError: ${errorMsg}\x1B[0m
24166
+ const onEvent = (event) => {
24167
+ const type = event.type?.toLowerCase() ?? "";
24168
+ if (type === "tool_call_start") {
24169
+ const name = event.toolCallName ?? event.name ?? "";
24170
+ process.stderr.write(` [${name}]
23648
24171
  `);
23649
- },
23650
- { autoApprove }
24172
+ }
24173
+ };
24174
+ const { streamText, hasToolCalls } = await runConciergeAguiLoop(
24175
+ aguiClient,
24176
+ [{ role: "user", content: message }],
24177
+ onEvent,
24178
+ {
24179
+ autoApprove,
24180
+ allowWrite: allowWrite || !!session.allowWrite,
24181
+ trustCwd: session.trustCwd,
24182
+ trustDir: session.trustDir,
24183
+ nonInteractive: true
24184
+ }
23651
24185
  );
23652
- if (logStream) logStream.end();
23653
- if (debug) process.stderr.write(` (${eventCount} SSE events)
23654
- `);
23655
24186
  if (streamText) {
23656
24187
  process.stdout.write(`\x1B[32mAssistant:\x1B[0m ${streamText}
23657
24188
  `);
@@ -23685,13 +24216,16 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
23685
24216
  `);
23686
24217
  }
23687
24218
  }
23688
- async function interactiveChatSession(session, debug) {
24219
+ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
23689
24220
  const aguiClient = new AGUIClient({
23690
24221
  baseUrl: session.ctx.envConfig.apiUrl,
24222
+ workspaceUuid: session.workspaceUuid,
24223
+ chatId: session.chatId,
23691
24224
  endpointUrl: session.aguiEndpointUrl,
23692
24225
  accessToken: session.ctx.accessToken,
23693
24226
  debug,
23694
- model: session.model
24227
+ model: session.model,
24228
+ appContext: session.appContext
23695
24229
  });
23696
24230
  clearScreen();
23697
24231
  console.log("=".repeat(60));
@@ -23720,7 +24254,7 @@ async function interactiveChatSession(session, debug) {
23720
24254
  }
23721
24255
  }
23722
24256
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23723
- return new Promise((resolve) => {
24257
+ return new Promise((resolve2) => {
23724
24258
  const prompt = () => {
23725
24259
  rl.question("\x1B[34mYou:\x1B[0m ", async (input) => {
23726
24260
  const trimmed = input.trim();
@@ -23737,18 +24271,34 @@ async function interactiveChatSession(session, debug) {
23737
24271
  process.stdout.write("\n\x1B[32mAssistant:\x1B[0m ");
23738
24272
  let assistantContent = "";
23739
24273
  try {
23740
- await aguiClient.sendMessage(conversationMessages, (event) => {
23741
- const delta = getTextDelta(event);
23742
- if (delta) {
23743
- process.stdout.write(delta);
23744
- assistantContent += delta;
23745
- }
23746
- const errorMsg = getErrorMessage(event);
23747
- if (errorMsg) console.log(`
24274
+ const { streamText } = await runConciergeAguiLoop(
24275
+ aguiClient,
24276
+ conversationMessages,
24277
+ (event) => {
24278
+ const delta = getTextDelta(event);
24279
+ if (delta) {
24280
+ process.stdout.write(delta);
24281
+ assistantContent += delta;
24282
+ }
24283
+ const errorMsg = getErrorMessage(event);
24284
+ if (errorMsg) console.log(`
23748
24285
  \x1B[31mError: ${errorMsg}\x1B[0m`);
23749
- });
23750
- if (assistantContent)
24286
+ },
24287
+ {
24288
+ autoApprove,
24289
+ allowWrite: allowWrite || !!session.allowWrite,
24290
+ debug,
24291
+ trustCwd: session.trustCwd,
24292
+ trustDir: session.trustDir
24293
+ }
24294
+ );
24295
+ if (streamText && !assistantContent) {
24296
+ assistantContent = streamText;
24297
+ process.stdout.write(streamText);
24298
+ }
24299
+ if (assistantContent) {
23751
24300
  conversationMessages.push({ role: "assistant", content: assistantContent });
24301
+ }
23752
24302
  console.log("\n");
23753
24303
  } catch (error) {
23754
24304
  console.log(
@@ -23762,7 +24312,7 @@ async function interactiveChatSession(session, debug) {
23762
24312
  };
23763
24313
  rl.on("close", () => {
23764
24314
  console.log("\nGoodbye!");
23765
- resolve();
24315
+ resolve2();
23766
24316
  });
23767
24317
  prompt();
23768
24318
  });
@@ -23859,8 +24409,8 @@ async function realtimeChatSession(session, vad, debug) {
23859
24409
  });
23860
24410
  tui.loadHistory(history);
23861
24411
  tui.start();
23862
- await new Promise((resolve) => {
23863
- client.once("closed", resolve);
24412
+ await new Promise((resolve2) => {
24413
+ client.once("closed", resolve2);
23864
24414
  });
23865
24415
  }
23866
24416
  async function voiceChatSession(session, vad, args, debug) {
@@ -23977,8 +24527,8 @@ Connecting to ${session.label.toLowerCase()}...
23977
24527
  tui.loadHistory(history);
23978
24528
  tui.start();
23979
24529
  mic.start(inputDeviceId);
23980
- await new Promise((resolve) => {
23981
- client.once("closed", resolve);
24530
+ await new Promise((resolve2) => {
24531
+ client.once("closed", resolve2);
23982
24532
  });
23983
24533
  }
23984
24534
  async function listAudioDevices() {
@@ -24008,13 +24558,14 @@ async function conciergeCommand(hctx) {
24008
24558
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
24009
24559
  const message = typeof args.message === "string" ? args.message : void 0;
24010
24560
  const chat = await getOrCreateConciergeChat(ctx, chatIdArg, !!message);
24011
- const session = buildConciergeSession(ctx, chat);
24561
+ const session = buildConciergeSession(ctx, chat, args);
24012
24562
  await runChatSession(session, args);
24013
24563
  }
24014
- function buildConciergeSession(ctx, chat) {
24564
+ function buildConciergeSession(ctx, chat, args) {
24015
24565
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
24016
24566
  return {
24017
24567
  ctx,
24568
+ workspaceUuid: "",
24018
24569
  chatId: chat.id,
24019
24570
  chatUuid: chat.uuid,
24020
24571
  chatTitle: chat.title || chat.uuid,
@@ -24022,8 +24573,12 @@ function buildConciergeSession(ctx, chat) {
24022
24573
  wsUrl: `${wsBase}/api/v2/concierge/voice/stream-ws`,
24023
24574
  loadMessages: () => getConciergeChatMessages(ctx, chat.id).catch(() => []),
24024
24575
  label: "Concierge",
24025
- model: void 0
24576
+ model: void 0,
24026
24577
  // concierge doesn't send model field
24578
+ appContext: buildCliConciergeAppContext(),
24579
+ allowWrite: true,
24580
+ trustCwd: !!args["trust-cwd"],
24581
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
24027
24582
  };
24028
24583
  }
24029
24584
  async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
@@ -24058,6 +24613,95 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
24058
24613
  }
24059
24614
  return selectedChat;
24060
24615
  }
24616
+
24617
+ // src/commands/get-started.ts
24618
+ var WEB_SETUP = "https://app.gravityrail.com/setup";
24619
+ var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
24620
+ var HUMAN_GUIDE = `Gravity Rail \u2014 getting started
24621
+
24622
+ 1. Authenticate: gr login --env prod
24623
+ Confirm: gr whoami
24624
+
24625
+ 2. Recommended path \u2014 concierge (no workspace required):
24626
+ gr concierge chat --allow-write
24627
+ The concierge can create your org (auto-created on first login), list products,
24628
+ start Stripe checkout, and create your first workspace.
24629
+
24630
+ 3. Verify workspaces: gr workspaces list
24631
+
24632
+ 4. Web wizards (human-friendly): ${WEB_SETUP} and ${WEB_GET_STARTED}
24633
+
24634
+ Headless boundary: Stripe checkout needs a human in a browser \u2014 the concierge returns
24635
+ a checkout URL; an agent should hand that link to the user, not loop on payment.
24636
+
24637
+ Agent-readable steps: gr get-started --format llm
24638
+ `;
24639
+ var LLM_GUIDE = `# Gravity Rail CLI bootstrap (agent instructions)
24640
+
24641
+ ## Preconditions
24642
+ - Install: \`npm install -g @gravity-rail/cli\` or \`npx @gravity-rail/cli\`
24643
+ - Authenticate: \`gr login --env prod\` (interactive OAuth). Verify with \`gr whoami\`.
24644
+ - **Monorepo dev:** from gravity-team with Docker dev up, use \`yarn gr <cmd> --realm superadmin\` (reads \`dev-realms.json\`; no browser). Rebuild: \`yarn workspace @gravity-rail/gr-cli build\`.
24645
+
24646
+ ## Important facts
24647
+ - **Agents/CI:** do not leave stdin open on read-only commands; use \`GRAVITY_RAIL_API_KEY\` or redirect stdin from /dev/null. Commands only read stdin when \`--data\`/\`--file\` is passed.
24648
+ - Use \`--env dev|staging|prod\` (default prod) so credentials match the target; \`gr whoami\` shows the active env.
24649
+ - After login, an **organization is created automatically** \u2014 do not create a duplicate org unless \`gr organizations list\` is empty.
24650
+ - **Workspace is required** for workflow/agent/task commands (\`-w <workspace-uuid>\`).
24651
+ - **Concierge does not require \`-w\`** \u2014 use it for first-run setup.
24652
+
24653
+ ## Recommended path (concierge-driven)
24654
+ \`\`\`bash
24655
+ gr concierge chat --allow-write --message "Help me set up my first Gravity Rail workspace"
24656
+ \`\`\`
24657
+ Flags for unattended turns:
24658
+ - \`--message\` / \`-m\`: single-turn (non-interactive)
24659
+ - \`--auto-approve\`: skip HITL tool approval where policy allows
24660
+ - \`--allow-write\`: required for create/update tools
24661
+
24662
+ The concierge can: list/create orgs, list org/workspace products, create Stripe checkout sessions, create workspace specs, and \`create_workspace\`.
24663
+
24664
+ ## Manual CLI path (if not using concierge)
24665
+ 1. \`gr whoami\` \u2014 confirm account
24666
+ 2. \`gr organizations list\` \u2014 note org slug/UUID
24667
+ 3. \`gr concierge chat\` OR web: ${WEB_GET_STARTED}
24668
+ 4. Product purchase: concierge returns Stripe checkout URL \u2192 **human must complete in browser**
24669
+ 5. \`gr workspaces list\` \u2014 pick workspace UUID for \`-w\`
24670
+
24671
+ ## Support (account-level, no workspace)
24672
+ \`\`\`bash
24673
+ gr support create --type question --title "..." --description "..." --allow-write
24674
+ gr support list
24675
+ \`\`\`
24676
+
24677
+ ## Web fallbacks
24678
+ - Concierge / setup: ${WEB_SETUP}
24679
+ - Wizard: ${WEB_GET_STARTED}
24680
+
24681
+ ## Headless limits (do not retry forever)
24682
+ - Stripe payment / checkout: requires human browser
24683
+ - OAuth login: requires human browser
24684
+ - Directory trust for future local-file tools: use \`--trust-cwd\` only after user confirms files are safe
24685
+
24686
+ ## Local project files (CLI only)
24687
+ After directory trust + \`--allow-write\`, the concierge can use \`read_local_file\`, \`list_local_dir\`, \`write_local_file\` (client-executed via HITL).
24688
+
24689
+ ## Verify success
24690
+ \`gr workspaces list\` then \`gr workflows list -w <uuid>\` or \`gr agents list -w <uuid>\`
24691
+ `;
24692
+ async function getStartedCommand(hctx) {
24693
+ const format = hctx.args.format ?? hctx.args.agent;
24694
+ const isLlm = format === "llm" || format === "agent" || hctx.args["format"] === "llm" || !!hctx.args.agent;
24695
+ if (!isLlm) {
24696
+ try {
24697
+ requireAuth(hctx.ctx);
24698
+ } catch {
24699
+ }
24700
+ process.stdout.write(HUMAN_GUIDE);
24701
+ return;
24702
+ }
24703
+ process.stdout.write(LLM_GUIDE);
24704
+ }
24061
24705
  async function devImportLocalCommand(ctx, workspace) {
24062
24706
  stderr2(`Exporting workspace ${workspace}...`);
24063
24707
  const exportData = await ctx.apiClient.exportWorkspace(workspace);
@@ -24136,8 +24780,17 @@ async function manageCommand(hctx) {
24136
24780
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
24137
24781
  const message = typeof args.message === "string" ? args.message : void 0;
24138
24782
  const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
24139
- const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager");
24783
+ const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
24784
+ appContext: buildCliConciergeAppContext(),
24785
+ allowWrite: true,
24786
+ trustCwd: !!args["trust-cwd"],
24787
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
24788
+ });
24789
+ process.stderr.write(`Chat #${session.chatId}
24790
+ `);
24140
24791
  await runChatSession(session, args);
24792
+ process.stderr.write(`Chat #${session.chatId}
24793
+ `);
24141
24794
  }
24142
24795
  async function chatCommand(hctx) {
24143
24796
  const ctx = requireAuth(hctx.ctx);
@@ -24162,10 +24815,11 @@ async function chatCommand(hctx) {
24162
24815
  const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
24163
24816
  await runChatSession(session, args);
24164
24817
  }
24165
- function buildWorkspaceSession(ctx, workspace, chat, label) {
24818
+ function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
24166
24819
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
24167
24820
  return {
24168
24821
  ctx,
24822
+ workspaceUuid: workspace,
24169
24823
  chatId: chat.id,
24170
24824
  chatUuid: chat.uuid,
24171
24825
  chatTitle: chat.title || chat.uuid,
@@ -24173,7 +24827,8 @@ function buildWorkspaceSession(ctx, workspace, chat, label) {
24173
24827
  wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
24174
24828
  loadMessages: () => ctx.apiClient.getChatMessages(workspace, chat.id, 50).catch(() => []),
24175
24829
  label,
24176
- model: "manager"
24830
+ model: "manager",
24831
+ ...extra
24177
24832
  };
24178
24833
  }
24179
24834
  async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
@@ -24271,16 +24926,20 @@ var COLUMNS35 = [
24271
24926
  { header: "NAME", getValue: (a) => a.name || "", maxWidth: 30 },
24272
24927
  { header: "EMAIL", getValue: (a) => a.email || "", maxWidth: 40 },
24273
24928
  { header: "UUID", getValue: (a) => a.uuid },
24929
+ { header: "ENV", getValue: () => "" },
24274
24930
  { header: "SUPERUSER", getValue: (a) => a.isSuperuser ? "yes" : "no" }
24275
24931
  ];
24276
24932
  async function whoamiCommand(ctx) {
24277
24933
  const account = await ctx.apiClient.getUserAccount();
24278
- outputSingle(account, ctx, COLUMNS35);
24934
+ const columns = COLUMNS35.map(
24935
+ (col) => col.header === "ENV" ? { ...col, getValue: () => ctx.env } : col
24936
+ );
24937
+ outputSingle(account, ctx, columns);
24279
24938
  }
24280
24939
 
24281
24940
  // package.json
24282
24941
  var package_default = {
24283
- version: "0.5.0"};
24942
+ version: "0.6.0"};
24284
24943
 
24285
24944
  // src/gr.ts
24286
24945
  var VERSION = package_default.version;
@@ -24333,6 +24992,15 @@ function createRegistry() {
24333
24992
  name: "auto-approve",
24334
24993
  description: "Auto-approve all tool calls (skip HITL)",
24335
24994
  isFlag: true
24995
+ },
24996
+ {
24997
+ name: "trust-cwd",
24998
+ description: "Trust files in the current working directory (non-interactive)",
24999
+ isFlag: true
25000
+ },
25001
+ {
25002
+ name: "trust-dir",
25003
+ description: "Trust files in this directory (non-interactive; absolute path)"
24336
25004
  }
24337
25005
  ],
24338
25006
  handler: async (hctx) => {
@@ -24366,6 +25034,37 @@ function createRegistry() {
24366
25034
  await chatCommand(hctx);
24367
25035
  }
24368
25036
  });
25037
+ registry.registerCommand({
25038
+ path: ["get-started"],
25039
+ description: "Bootstrap instructions for new users and LLM agents",
25040
+ requiresAuth: false,
25041
+ args: [
25042
+ {
25043
+ name: "format",
25044
+ description: "Output format: llm (agent-readable) or default (human)"
25045
+ },
25046
+ {
25047
+ name: "agent",
25048
+ description: "Alias for --format llm",
25049
+ isFlag: true
25050
+ }
25051
+ ],
25052
+ handler: async (hctx) => {
25053
+ await getStartedCommand(hctx);
25054
+ }
25055
+ });
25056
+ registry.registerCommand({
25057
+ path: ["setup"],
25058
+ description: "Alias for get-started \u2014 bootstrap instructions",
25059
+ requiresAuth: false,
25060
+ args: [
25061
+ { name: "format", description: "Output format: llm or default" },
25062
+ { name: "agent", description: "Alias for --format llm", isFlag: true }
25063
+ ],
25064
+ handler: async (hctx) => {
25065
+ await getStartedCommand(hctx);
25066
+ }
25067
+ });
24369
25068
  registry.registerCommand({
24370
25069
  path: ["concierge", "chat"],
24371
25070
  description: "Interactive concierge chat (text, realtime, or voice)",
@@ -24385,6 +25084,15 @@ function createRegistry() {
24385
25084
  name: "auto-approve",
24386
25085
  description: "Auto-approve all tool calls (skip HITL)",
24387
25086
  isFlag: true
25087
+ },
25088
+ {
25089
+ name: "trust-cwd",
25090
+ description: "Trust files in the current working directory (non-interactive)",
25091
+ isFlag: true
25092
+ },
25093
+ {
25094
+ name: "trust-dir",
25095
+ description: "Trust files in this directory (non-interactive; absolute path)"
24388
25096
  }
24389
25097
  ],
24390
25098
  handler: async (hctx) => {
@@ -24423,6 +25131,7 @@ function createRegistry() {
24423
25131
  return registry;
24424
25132
  }
24425
25133
  function parseGlobalOptions(argv) {
25134
+ let env = "prod";
24426
25135
  let apiUrl;
24427
25136
  let outputFormat;
24428
25137
  let verbose = false;
@@ -24432,6 +25141,13 @@ function parseGlobalOptions(argv) {
24432
25141
  let collectingCommand = true;
24433
25142
  for (let i = 0; i < argv.length; i++) {
24434
25143
  const arg = argv[i];
25144
+ const eqIdx = arg.indexOf("=");
25145
+ const flagPart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
25146
+ const inlineValue = eqIdx !== -1 ? arg.slice(eqIdx + 1) : void 0;
25147
+ if (flagPart === "--env" && (inlineValue ?? argv[i + 1])) {
25148
+ env = inlineValue ?? argv[++i];
25149
+ continue;
25150
+ }
24435
25151
  if (arg === "--api-url" && argv[i + 1]) {
24436
25152
  apiUrl = argv[++i];
24437
25153
  continue;
@@ -24459,7 +25175,7 @@ function parseGlobalOptions(argv) {
24459
25175
  rest.unshift(commandWords.pop());
24460
25176
  }
24461
25177
  return {
24462
- opts: { apiUrl, outputFormat, verbose, allowWrite },
25178
+ opts: { env, apiUrl, outputFormat, verbose, allowWrite },
24463
25179
  commandWords,
24464
25180
  rest
24465
25181
  };
@@ -24508,7 +25224,8 @@ async function main() {
24508
25224
  process.stderr.write("Error: --workspace (-w) is required\n");
24509
25225
  process.exit(1);
24510
25226
  }
24511
- const data = await readJsonInput(parsedArgs);
25227
+ const acceptsJsonInput = cmd.args?.some((a) => a.name === "data" || a.name === "file");
25228
+ const data = acceptsJsonInput ? await readJsonInput(parsedArgs) : void 0;
24512
25229
  hctx = { ctx, args: parsedArgs, workspace, data };
24513
25230
  } else {
24514
25231
  const baseCtx = createPublicContext(opts);
@@ -24527,7 +25244,7 @@ async function main() {
24527
25244
  }
24528
25245
  await cmd.handler(hctx);
24529
25246
  if (process.stdout.writableNeedDrain) {
24530
- await new Promise((resolve) => process.stdout.once("drain", resolve));
25247
+ await new Promise((resolve2) => process.stdout.once("drain", resolve2));
24531
25248
  }
24532
25249
  process.exit(0);
24533
25250
  }