@gravity-rail/cli 0.5.0 → 0.5.1

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-XRKRVY46.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" },
@@ -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 {
@@ -12990,11 +12950,87 @@ var ApiClient = class extends GravityRailClient {
12990
12950
  // ignore X-Org-Token entirely, so propagating an org token to adminApi is
12991
12951
  // dead code that would only obscure auth flow if admin route logic changes.
12992
12952
  };
12993
-
12994
- // src/cli-utils/config.ts
12953
+ var ENVIRONMENTS = {
12954
+ dev: {
12955
+ apiUrl: "https://api.gravityrail.test:5054",
12956
+ frontendUrl: "https://app.gravityrail.test:3000"
12957
+ },
12958
+ prod: {
12959
+ apiUrl: "https://api.gravityrail.com",
12960
+ frontendUrl: "https://app.gravityrail.com"
12961
+ },
12962
+ staging: {
12963
+ apiUrl: "https://api.gr-staging.com",
12964
+ frontendUrl: "https://app.gr-staging.com"
12965
+ }
12966
+ };
12995
12967
  var CALLBACK_PORT = 31337;
12996
12968
  var CALLBACK_PATH = "/oauth/callback";
12997
12969
  var REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
12970
+ function detectWorktreePorts() {
12971
+ let dir = resolve(process.cwd());
12972
+ let overridePath;
12973
+ while (true) {
12974
+ const candidate = join(dir, "docker-compose.override.yml");
12975
+ if (existsSync(candidate)) {
12976
+ overridePath = candidate;
12977
+ break;
12978
+ }
12979
+ const parent = dirname(dir);
12980
+ if (parent === dir) break;
12981
+ dir = parent;
12982
+ }
12983
+ if (!overridePath) return {};
12984
+ let content;
12985
+ try {
12986
+ content = readFileSync(overridePath, "utf-8");
12987
+ } catch {
12988
+ return {};
12989
+ }
12990
+ if (!content.includes("worktree-setup.sh")) return {};
12991
+ let apiPort;
12992
+ let frontendPort;
12993
+ for (const match of content.matchAll(/-\s*'(\d+):(\d+)'/g)) {
12994
+ const host = parseInt(match[1], 10);
12995
+ const container = parseInt(match[2], 10);
12996
+ if (container === 5050) apiPort = host;
12997
+ if (container === 3e3) frontendPort = host;
12998
+ }
12999
+ return { apiPort, frontendPort };
13000
+ }
13001
+ function getEnvironmentConfig(env) {
13002
+ if (env.startsWith("dev-cloud:")) {
13003
+ const namespace = env.slice("dev-cloud:".length);
13004
+ if (!namespace) {
13005
+ throw new Error("dev-cloud environment requires a namespace: --env dev-cloud:NAMESPACE");
13006
+ }
13007
+ return {
13008
+ apiUrl: `https://${namespace}-api.gravityrail-dev.com`,
13009
+ frontendUrl: `https://${namespace}-app.gravityrail-dev.com`
13010
+ };
13011
+ }
13012
+ const config = ENVIRONMENTS[env];
13013
+ if (!config) {
13014
+ throw new Error(
13015
+ `Invalid environment: ${env}. Use 'dev', 'staging', 'prod', or 'dev-cloud:NAMESPACE'.`
13016
+ );
13017
+ }
13018
+ const apiUrl = process.env.GRAVITY_RAIL_API_URL || config.apiUrl;
13019
+ const frontendUrl = process.env.GRAVITY_RAIL_FRONTEND_URL || config.frontendUrl;
13020
+ if (apiUrl !== config.apiUrl || frontendUrl !== config.frontendUrl) {
13021
+ return { apiUrl, frontendUrl };
13022
+ }
13023
+ if (env === "dev") {
13024
+ const ports = detectWorktreePorts();
13025
+ if (ports.apiPort || ports.frontendPort) {
13026
+ return {
13027
+ apiUrl: ports.apiPort ? `https://api.gravityrail.test:${ports.apiPort}` : config.apiUrl,
13028
+ frontendUrl: ports.frontendPort ? `https://app.gravityrail.test:${ports.frontendPort}` : config.frontendUrl
13029
+ };
13030
+ }
13031
+ }
13032
+ return config;
13033
+ }
12998
13034
  var CredentialsManager = class {
12999
13035
  cacheDir;
13000
13036
  apiUrl;
@@ -13124,7 +13160,7 @@ function openBrowser(url) {
13124
13160
  });
13125
13161
  }
13126
13162
  function startCallbackServer() {
13127
- return new Promise((resolve, reject) => {
13163
+ return new Promise((resolve2, reject) => {
13128
13164
  let authCode = null;
13129
13165
  let error = null;
13130
13166
  let receivedState = null;
@@ -13166,7 +13202,7 @@ function startCallbackServer() {
13166
13202
  </html>
13167
13203
  `);
13168
13204
  server.close();
13169
- resolve({ code: authCode, state: receivedState });
13205
+ resolve2({ code: authCode, state: receivedState });
13170
13206
  } else {
13171
13207
  res.writeHead(400);
13172
13208
  res.end("Missing code parameter");
@@ -13358,7 +13394,7 @@ async function selectFromList(items, options = {}) {
13358
13394
  return { item: null, isNew: false };
13359
13395
  }
13360
13396
  const displayItems = allowNew ? [NEW_ITEM_MARKER, ...items] : items;
13361
- return new Promise((resolve) => {
13397
+ return new Promise((resolve2) => {
13362
13398
  let selectedIndex = allowNew ? 1 : 0;
13363
13399
  let filterText = "";
13364
13400
  const getFilteredItems = () => {
@@ -13441,16 +13477,16 @@ async function selectFromList(items, options = {}) {
13441
13477
  if (filtered.length > 0 && selectedIndex < filtered.length) {
13442
13478
  const selected = filtered[selectedIndex];
13443
13479
  if (selected === NEW_ITEM_MARKER) {
13444
- resolve({ item: null, isNew: true });
13480
+ resolve2({ item: null, isNew: true });
13445
13481
  } else {
13446
- resolve({ item: selected, isNew: false });
13482
+ resolve2({ item: selected, isNew: false });
13447
13483
  }
13448
13484
  } else {
13449
- resolve({ item: null, isNew: false });
13485
+ resolve2({ item: null, isNew: false });
13450
13486
  }
13451
13487
  } else if (key.name === "escape" || key.ctrl && key.name === "c") {
13452
13488
  cleanup();
13453
- resolve({ item: null, isNew: false });
13489
+ resolve2({ item: null, isNew: false });
13454
13490
  } else if (key.name === "backspace") {
13455
13491
  filterText = filterText.slice(0, -1);
13456
13492
  selectedIndex = allowNew ? 0 : 0;
@@ -13560,29 +13596,82 @@ function clearScreen() {
13560
13596
  // src/cli-utils/agui.ts
13561
13597
  var AGUIClient = class {
13562
13598
  endpointUrl;
13599
+ baseUrl;
13600
+ workspaceUuid;
13601
+ chatId;
13563
13602
  accessToken;
13564
13603
  debug;
13565
13604
  model;
13605
+ appContext;
13566
13606
  constructor(options) {
13607
+ this.baseUrl = options.baseUrl;
13608
+ this.workspaceUuid = options.workspaceUuid ?? "";
13609
+ this.chatId = options.chatId ?? 0;
13610
+ if (!options.endpointUrl && (!options.workspaceUuid || options.chatId === void 0)) {
13611
+ throw new Error("AGUIClient requires workspaceUuid and chatId, or endpointUrl");
13612
+ }
13567
13613
  this.endpointUrl = options.endpointUrl ?? `${options.baseUrl}/api/v2/w/${options.workspaceUuid}/web-chat/${options.chatId}/chat/agui`;
13568
13614
  this.accessToken = options.accessToken;
13569
13615
  this.debug = options.debug ?? false;
13570
13616
  this.model = "model" in options ? options.model : "manager";
13617
+ this.appContext = options.appContext;
13618
+ }
13619
+ inferenceStatusUrl() {
13620
+ if (!this.workspaceUuid || this.chatId <= 0) return null;
13621
+ return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;
13622
+ }
13623
+ async waitForInferenceInactive(maxWaitMs = 6e4) {
13624
+ const url = this.inferenceStatusUrl();
13625
+ if (!url) return false;
13626
+ const deadline = Date.now() + maxWaitMs;
13627
+ while (Date.now() < deadline) {
13628
+ try {
13629
+ const res = await fetch(url, {
13630
+ headers: { Authorization: `Bearer ${this.accessToken}` }
13631
+ });
13632
+ if (res.ok) {
13633
+ const body = await res.json();
13634
+ if (!body.active) return true;
13635
+ }
13636
+ } catch (err) {
13637
+ if (this.debug) {
13638
+ console.log(
13639
+ `[DEBUG] waitForInferenceInactive fetch failed: ${err instanceof Error ? err.message : String(err)}`
13640
+ );
13641
+ }
13642
+ }
13643
+ await new Promise((r) => setTimeout(r, 2e3));
13644
+ }
13645
+ return false;
13571
13646
  }
13572
13647
  /**
13573
13648
  * Send messages and stream the response
13574
13649
  */
13575
13650
  async sendMessage(messages, onEvent, options) {
13651
+ return this.streamRequest({ messages }, onEvent, options);
13652
+ }
13653
+ /**
13654
+ * Stream an AG-UI request (initial messages or HITL resume).
13655
+ */
13656
+ async streamRequest(request, onEvent, options) {
13576
13657
  const body = JSON.stringify({
13577
13658
  ...this.model ? { model: this.model } : {},
13578
- messages,
13659
+ messages: request.messages ?? [],
13660
+ ...this.appContext ? { appContext: this.appContext } : {},
13661
+ ...request.resumeValue !== void 0 ? { resumeValue: request.resumeValue } : {},
13662
+ ...request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {},
13579
13663
  ...options?.autoApprove ? { context: { autoApprove: true } } : {}
13580
13664
  });
13581
13665
  if (this.debug) {
13582
13666
  console.log(`
13583
13667
  [DEBUG] POST ${this.endpointUrl}`);
13584
- console.log(`[DEBUG] Body: ${messages.length} message(s)`);
13668
+ console.log(
13669
+ `[DEBUG] Body: ${request.resumeValue !== void 0 ? "resume" : `${(request.messages ?? []).length} message(s)`}`
13670
+ );
13585
13671
  }
13672
+ await this.postStream(body, onEvent, options);
13673
+ }
13674
+ async postStream(body, onEvent, options, conflictRetries = 0) {
13586
13675
  const response = await fetch(this.endpointUrl, {
13587
13676
  method: "POST",
13588
13677
  headers: {
@@ -13595,12 +13684,21 @@ var AGUIClient = class {
13595
13684
  if (this.debug) {
13596
13685
  console.log(`[DEBUG] Response status: ${response.status}`);
13597
13686
  }
13687
+ if (response.status === 409 && conflictRetries < 3) {
13688
+ const ready = await this.waitForInferenceInactive();
13689
+ if (ready) {
13690
+ await this.postStream(body, onEvent, options, conflictRetries + 1);
13691
+ return;
13692
+ }
13693
+ }
13598
13694
  if (!response.ok) {
13599
13695
  const errorText = await response.text().catch(() => "");
13600
13696
  if (this.debug) {
13601
13697
  console.log(`[DEBUG] Error response: ${errorText}`);
13602
13698
  }
13603
- throw new Error(`Chat request failed: ${response.status}`);
13699
+ throw new Error(
13700
+ `Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ""}`
13701
+ );
13604
13702
  }
13605
13703
  const reader = response.body.getReader();
13606
13704
  const decoder = new TextDecoder();
@@ -13873,12 +13971,14 @@ function requireAuth(ctx) {
13873
13971
  return ctx;
13874
13972
  }
13875
13973
  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);
13974
+ const env = opts.env ?? "prod";
13975
+ const envConfig = opts.apiUrl ? {
13976
+ apiUrl: opts.apiUrl,
13977
+ frontendUrl: opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com"
13978
+ } : getEnvironmentConfig(env);
13979
+ const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);
13880
13980
  return {
13881
- env: "prod",
13981
+ env,
13882
13982
  envConfig,
13883
13983
  outputFormat: detectOutputFormat(opts.outputFormat),
13884
13984
  verbose: opts.verbose ?? false,
@@ -13927,32 +14027,48 @@ async function readJsonInput(args) {
13927
14027
  throw new CLIError(`Cannot read file: ${filePath}`);
13928
14028
  }
13929
14029
  }
13930
- if (!process.stdin.isTTY) {
13931
- return readStdin();
14030
+ if (process.stdin.isTTY === true) {
14031
+ return void 0;
13932
14032
  }
13933
- return void 0;
14033
+ return readStdinIfPiped();
13934
14034
  }
13935
- function readStdin() {
13936
- return new Promise((resolve, reject) => {
14035
+ function readStdinIfPiped() {
14036
+ return new Promise((resolve2, reject) => {
13937
14037
  let data = "";
13938
- process.stdin.setEncoding("utf-8");
13939
- process.stdin.on("data", (chunk) => {
13940
- data += chunk;
13941
- });
13942
- process.stdin.on("end", () => {
14038
+ let settled = false;
14039
+ const finish = (value) => {
14040
+ if (settled) return;
14041
+ settled = true;
14042
+ resolve2(value);
14043
+ };
14044
+ const flush = () => {
13943
14045
  if (!data.trim()) {
13944
- resolve(void 0);
14046
+ finish(void 0);
13945
14047
  return;
13946
14048
  }
13947
14049
  try {
13948
- resolve(JSON.parse(data));
14050
+ finish(JSON.parse(data));
13949
14051
  } catch {
13950
14052
  reject(new CLIError("Invalid JSON from stdin"));
13951
14053
  }
14054
+ };
14055
+ process.stdin.setEncoding("utf-8");
14056
+ process.stdin.on("data", (chunk) => {
14057
+ data += chunk;
13952
14058
  });
14059
+ process.stdin.on("end", flush);
13953
14060
  process.stdin.on("error", (err) => {
13954
14061
  reject(new CLIError(`Error reading stdin: ${err.message}`));
13955
14062
  });
14063
+ process.stdin.resume();
14064
+ setImmediate(() => {
14065
+ if (data) return;
14066
+ if (process.stdin.readableEnded) {
14067
+ flush();
14068
+ return;
14069
+ }
14070
+ finish(void 0);
14071
+ });
13956
14072
  });
13957
14073
  }
13958
14074
 
@@ -14358,6 +14474,17 @@ function requireNumber(args, key) {
14358
14474
  if (isNaN(num)) throw new Error(`Argument --${key} must be a number`);
14359
14475
  return num;
14360
14476
  }
14477
+ function requireNumberFrom(args, ...keys) {
14478
+ for (const key of keys) {
14479
+ const val = args[key];
14480
+ if (typeof val === "string") {
14481
+ const num = Number(val);
14482
+ if (!isNaN(num)) return num;
14483
+ }
14484
+ }
14485
+ const hint = keys.map((k) => `--${k}`).join(" or ");
14486
+ throw new Error(`Missing required argument: ${hint}`);
14487
+ }
14361
14488
 
14362
14489
  // src/domains/agent-tasks.ts
14363
14490
  var LIST_COLUMNS = [
@@ -14518,6 +14645,7 @@ var agentsDomain = {
14518
14645
  var COLUMNS2 = [
14519
14646
  COL.id(),
14520
14647
  COL.uuid(),
14648
+ { header: "TYPE", getValue: (item) => item.chatType ?? item.chat_type ?? "" },
14521
14649
  { header: "MEMBER", getValue: (item) => item.member_name ?? String(item.member_id ?? "") },
14522
14650
  COL.status(),
14523
14651
  { header: "CHANNEL", getValue: (item) => item.channel ?? "" },
@@ -14569,7 +14697,11 @@ var chatsDomain = {
14569
14697
  isFlag: true
14570
14698
  },
14571
14699
  { name: "is-test", description: "Show only test chats", isFlag: true },
14572
- { name: "member-id", description: "Filter by member ID" }
14700
+ { name: "member-id", description: "Filter by member ID" },
14701
+ {
14702
+ name: "chat-type",
14703
+ description: "Filter by chat type (assignment, manager, developer, member, contributor). Default: assignment"
14704
+ }
14573
14705
  ]
14574
14706
  }),
14575
14707
  // Messages
@@ -14578,7 +14710,7 @@ var chatsDomain = {
14578
14710
  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
14711
  requiresWorkspace: true,
14580
14712
  args: [
14581
- { name: "chat-id", description: "Chat ID", required: true },
14713
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
14582
14714
  {
14583
14715
  name: "limit",
14584
14716
  description: "Max messages to return (1-100, default 100)"
@@ -14601,7 +14733,7 @@ var chatsDomain = {
14601
14733
  }
14602
14734
  const result2 = await requireAuth(ctx).apiClient.getChatMessages(
14603
14735
  workspace,
14604
- requireNumber(args, "chat-id"),
14736
+ requireNumberFrom(args, "chat-id", "id"),
14605
14737
  limit,
14606
14738
  offset
14607
14739
  );
@@ -14614,7 +14746,7 @@ var chatsDomain = {
14614
14746
  mutates: true,
14615
14747
  requiresWorkspace: true,
14616
14748
  args: [
14617
- { name: "chat-id", description: "Chat ID", required: true },
14749
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
14618
14750
  { name: "stream", description: "Stream the AI response via AG-UI", isFlag: true }
14619
14751
  ],
14620
14752
  handler: async ({ ctx, workspace, args, data }) => {
@@ -14623,7 +14755,7 @@ var chatsDomain = {
14623
14755
  throw new Error(
14624
14756
  `Message content is required (pass --data '"your message"' or --data '{"content":"your message"}')`
14625
14757
  );
14626
- const chatId = requireNumber(args, "chat-id");
14758
+ const chatId = requireNumberFrom(args, "chat-id", "id");
14627
14759
  if (args.stream) {
14628
14760
  const aguiClient = new AGUIClient({
14629
14761
  baseUrl: ctx.envConfig.apiUrl,
@@ -14656,11 +14788,11 @@ var chatsDomain = {
14656
14788
  description: "Send an assistant message to a chat (does not trigger AI inference)",
14657
14789
  mutates: true,
14658
14790
  requiresWorkspace: true,
14659
- args: [{ name: "chat-id", description: "Chat ID", required: true }],
14791
+ args: [{ name: "chat-id", aliases: ["id"], description: "Chat ID", required: true }],
14660
14792
  handler: async ({ ctx, workspace, args, data }) => {
14661
14793
  const result2 = await requireAuth(ctx).apiClient.sendAssistantMessage(
14662
14794
  workspace,
14663
- requireNumber(args, "chat-id"),
14795
+ requireNumberFrom(args, "chat-id", "id"),
14664
14796
  data
14665
14797
  );
14666
14798
  outputSingle(result2, ctx, MESSAGE_COLUMNS2);
@@ -16723,10 +16855,20 @@ var eventRulesCrudNoList = crudCommands({
16723
16855
  ' "eventType":"journey_step:execute","actionType":"member:label",',
16724
16856
  ` "actionParams":{"addLabelIds":["<label-uuid>"]},"journeyStepId":<step-id>}'`,
16725
16857
  "",
16858
+ "Scheduled outbound (routine \u2192 chat \u2192 voice/SMS):",
16859
+ `gr events rules create -w $WID --allow-write --data '{"name":"AM voice check-in",`,
16860
+ ' "objectType":"routine","eventType":"routine:triggered","actionType":"chat:create",',
16861
+ ' "routineId":<routine-id>,',
16862
+ ' "actionParams":{"workflowId":<workflow-id>,"channel":"phone-voice",',
16863
+ ` "phoneNumberId":<workspace-phone-id>}}'`,
16864
+ "",
16865
+ ' PM SMS variant: use "channel":"phone-sms" and a second rule on the same routineId.',
16866
+ " Prereq: org phone assigned + gr phone-numbers link; workflow published with start task.",
16867
+ "",
16726
16868
  "Required: objectType, eventType, actionType",
16727
- "Optional: name, actionParams, conditions (CEL), journeyStepId, delay (sec), order",
16869
+ "Optional: name, actionParams, conditions (CEL), journeyStepId, routineId, delay (sec), order",
16728
16870
  "",
16729
- "Common eventTypes: journey_step:execute, journey:enrolled, journey:completed,",
16871
+ "Common eventTypes: journey_step:execute, journey:enrolled, routine:triggered,",
16730
16872
  " task:created, chat:created, data_record:created, member:created",
16731
16873
  "",
16732
16874
  "Common actionTypes: member:label, member:notify, webhook:send, email:send,",
@@ -19009,7 +19151,7 @@ var conciergeDomain = {
19009
19151
  path: ["concierge", "messages"],
19010
19152
  description: "Show messages for a concierge chat",
19011
19153
  requiresWorkspace: false,
19012
- args: [{ name: "id", description: "Chat ID", required: true }],
19154
+ args: [{ name: "id", aliases: ["chat-id"], description: "Chat ID", required: true }],
19013
19155
  handler: async ({ ctx, args }) => {
19014
19156
  const auth = requireAuth(ctx);
19015
19157
  const chatId = Number(args.id);
@@ -19642,15 +19784,15 @@ var DOMAIN_UUID_ARG = {
19642
19784
  function pluralize(count, singular, plural = `${singular}s`) {
19643
19785
  return count === 1 ? singular : plural;
19644
19786
  }
19645
- function emailVerifyCommand(path, description) {
19787
+ function emailVerifyCommand(path4, description) {
19646
19788
  return {
19647
- path,
19789
+ path: path4,
19648
19790
  description,
19649
19791
  mutates: true,
19650
19792
  requiresWorkspace: false,
19651
19793
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
19652
19794
  examples: [
19653
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19795
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19654
19796
  ],
19655
19797
  handler: async (hctx) => {
19656
19798
  const result2 = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainEmail(
@@ -19661,15 +19803,15 @@ function emailVerifyCommand(path, description) {
19661
19803
  }
19662
19804
  };
19663
19805
  }
19664
- function siteVerifyCommand(path, description) {
19806
+ function siteVerifyCommand(path4, description) {
19665
19807
  return {
19666
- path,
19808
+ path: path4,
19667
19809
  description,
19668
19810
  mutates: true,
19669
19811
  requiresWorkspace: false,
19670
19812
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
19671
19813
  examples: [
19672
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19814
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
19673
19815
  ],
19674
19816
  handler: async (hctx) => {
19675
19817
  const result2 = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainSite(
@@ -19680,9 +19822,9 @@ function siteVerifyCommand(path, description) {
19680
19822
  }
19681
19823
  };
19682
19824
  }
19683
- function registerDomainCommand(path) {
19825
+ function registerDomainCommand(path4) {
19684
19826
  return {
19685
- path,
19827
+ path: path4,
19686
19828
  description: "Register an organization domain and return ownership TXT instructions",
19687
19829
  mutates: true,
19688
19830
  requiresWorkspace: false,
@@ -19691,7 +19833,7 @@ function registerDomainCommand(path) {
19691
19833
  { name: "domain", description: "Domain to register, e.g. example.com", required: true }
19692
19834
  ],
19693
19835
  examples: [
19694
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
19836
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
19695
19837
  ],
19696
19838
  handler: async (hctx) => {
19697
19839
  const result2 = await requireAuth(hctx.ctx).apiClient.registerOrgDomain(
@@ -20045,6 +20187,12 @@ var commands2 = [
20045
20187
  workspaceUuid
20046
20188
  );
20047
20189
  outputSingle(result2, hctx.ctx, PROVIDER_PHONE_COLUMNS);
20190
+ process.stdout.write(
20191
+ `
20192
+ If workspace phone list is still empty, create a workspace phone record then:
20193
+ gr phone-numbers link -w ${workspaceUuid} --id <workspace-phone-id> --provider-phone-uuid ${providerPhoneUuid} --allow-write
20194
+ `
20195
+ );
20048
20196
  }
20049
20197
  },
20050
20198
  {
@@ -20148,6 +20296,31 @@ var phoneNumbersDomain = {
20148
20296
  outputData(result2, ctx, COLUMNS25);
20149
20297
  }
20150
20298
  },
20299
+ {
20300
+ path: ["phone-numbers", "link"],
20301
+ description: "Link a workspace phone number to a provider (org) phone",
20302
+ mutates: true,
20303
+ requiresWorkspace: true,
20304
+ args: [
20305
+ {
20306
+ name: "phone-number-id",
20307
+ aliases: ["id"],
20308
+ description: "Workspace phone number ID",
20309
+ required: true
20310
+ },
20311
+ { name: "provider-phone-uuid", description: "Provider phone UUID", required: true }
20312
+ ],
20313
+ examples: [
20314
+ "gr phone-numbers link -w $WID --id 1 --provider-phone-uuid <uuid> --allow-write"
20315
+ ],
20316
+ handler: async ({ ctx, workspace, args }) => {
20317
+ const auth = requireAuth(ctx);
20318
+ const id = requireNumberFrom(args, "phone-number-id", "id");
20319
+ const providerPhoneUuid = requireString(args, "provider-phone-uuid");
20320
+ const result2 = await auth.apiClient.linkPhoneNumber(workspace, id, providerPhoneUuid);
20321
+ outputData([result2], auth, COLUMNS25);
20322
+ }
20323
+ },
20151
20324
  {
20152
20325
  path: ["phone-numbers", "request"],
20153
20326
  description: "Request a phone number",
@@ -20166,7 +20339,12 @@ var phoneNumbersDomain = {
20166
20339
  description: "List calls for a phone number",
20167
20340
  requiresWorkspace: true,
20168
20341
  args: [
20169
- { name: "phone-number-id", description: "Phone number ID", required: true },
20342
+ {
20343
+ name: "phone-number-id",
20344
+ aliases: ["id"],
20345
+ description: "Phone number ID",
20346
+ required: true
20347
+ },
20170
20348
  { name: "page", description: "Page number" },
20171
20349
  { name: "page-size", description: "Page size" },
20172
20350
  { name: "status", description: "Filter by status" }
@@ -20175,7 +20353,7 @@ var phoneNumbersDomain = {
20175
20353
  const status = args.status;
20176
20354
  const result2 = await requireAuth(ctx).apiClient.getPhoneNumberCalls(
20177
20355
  workspace,
20178
- requireNumber(args, "phone-number-id"),
20356
+ requireNumberFrom(args, "phone-number-id", "id"),
20179
20357
  args.page ? Number(args.page) : void 0,
20180
20358
  args["page-size"] ? Number(args["page-size"]) : void 0,
20181
20359
  status ? { status } : void 0
@@ -20188,14 +20366,19 @@ var phoneNumbersDomain = {
20188
20366
  description: "List messages for a phone number",
20189
20367
  requiresWorkspace: true,
20190
20368
  args: [
20191
- { name: "phone-number-id", description: "Phone number ID", required: true },
20369
+ {
20370
+ name: "phone-number-id",
20371
+ aliases: ["id"],
20372
+ description: "Phone number ID",
20373
+ required: true
20374
+ },
20192
20375
  { name: "page", description: "Page number" },
20193
20376
  { name: "page-size", description: "Page size" }
20194
20377
  ],
20195
20378
  handler: async ({ ctx, workspace, args }) => {
20196
20379
  const result2 = await requireAuth(ctx).apiClient.getPhoneNumberMessages(
20197
20380
  workspace,
20198
- requireNumber(args, "phone-number-id"),
20381
+ requireNumberFrom(args, "phone-number-id", "id"),
20199
20382
  args.page ? Number(args.page) : void 0,
20200
20383
  args["page-size"] ? Number(args["page-size"]) : void 0
20201
20384
  );
@@ -20205,36 +20388,85 @@ var phoneNumbersDomain = {
20205
20388
  ]
20206
20389
  };
20207
20390
 
20391
+ // src/lib/resolve-org.ts
20392
+ async function resolveOrgUuidFromWorkspace(apiClient, workspaceUuid) {
20393
+ const workspace = await apiClient.getWorkspace(workspaceUuid);
20394
+ const orgUuid = workspace.organizationUuid;
20395
+ if (!orgUuid) {
20396
+ throw new CLIError(
20397
+ "Workspace has no organization; pass --org-uuid for organization-scoped commands"
20398
+ );
20399
+ }
20400
+ return orgUuid;
20401
+ }
20402
+ function normalizePronunciationCreatePayload(data) {
20403
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
20404
+ throw new CLIError("JSON body required with term and respelling (--data or --file)");
20405
+ }
20406
+ const raw = data;
20407
+ const term = raw.term ?? raw.word;
20408
+ const respelling = raw.respelling ?? raw.pronunciation;
20409
+ if (typeof term !== "string" || !term) {
20410
+ throw new CLIError("Payload requires term (alias: word)");
20411
+ }
20412
+ if (typeof respelling !== "string" || !respelling) {
20413
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
20414
+ }
20415
+ const payload = { term, respelling };
20416
+ if (raw.accountId !== void 0 && raw.accountId !== null) {
20417
+ payload.accountId = Number(raw.accountId);
20418
+ }
20419
+ return payload;
20420
+ }
20421
+ function normalizePronunciationUpdatePayload(data) {
20422
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
20423
+ throw new CLIError("JSON body required (--data or --file)");
20424
+ }
20425
+ const raw = data;
20426
+ const respelling = raw.respelling ?? raw.pronunciation;
20427
+ if (typeof respelling !== "string" || !respelling) {
20428
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
20429
+ }
20430
+ return { respelling };
20431
+ }
20432
+
20208
20433
  // src/domains/pronunciations.ts
20209
20434
  var COLUMNS26 = [
20210
20435
  COL.id(),
20211
- { header: "WORD", getValue: (item) => item.word ?? "" },
20212
- { header: "PRONUNCIATION", getValue: (item) => item.pronunciation ?? "" },
20213
- { header: "TYPE", getValue: (item) => item.type ?? "" }
20436
+ { header: "TERM", getValue: (item) => item.term ?? "" },
20437
+ { header: "RESPELLING", getValue: (item) => item.respelling ?? "" },
20438
+ { header: "SCOPE", getValue: (item) => item.scope ?? "" }
20439
+ ];
20440
+ var CREATE_EXAMPLES = [
20441
+ `gr pronunciations create -w $WID --data '{"term":"Jeeves","respelling":"JEEVZ"}' --allow-write`,
20442
+ " (aliases: word \u2192 term, pronunciation \u2192 respelling)"
20214
20443
  ];
20215
20444
  var pronunciationsDomain = {
20216
20445
  name: "pronunciations",
20217
- description: "Manage pronunciation entries",
20446
+ description: "Manage pronunciation entries (org-scoped; -w resolves organization)",
20218
20447
  commands: [
20219
20448
  {
20220
20449
  path: ["pronunciations", "list"],
20221
- description: "List pronunciation entries",
20450
+ description: "List pronunciation entries for the workspace organization",
20222
20451
  requiresWorkspace: true,
20223
20452
  handler: async ({ ctx, workspace }) => {
20224
- const result2 = await requireAuth(ctx).apiClient.getPronunciationEntries(workspace);
20453
+ const auth = requireAuth(ctx);
20454
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20455
+ const result2 = await auth.apiClient.getPronunciationEntries(orgUuid);
20225
20456
  outputData(result2, ctx, COLUMNS26);
20226
20457
  }
20227
20458
  },
20228
20459
  {
20229
20460
  path: ["pronunciations", "create"],
20230
- description: "Create a pronunciation entry",
20461
+ description: "Create a pronunciation entry (term + respelling)",
20231
20462
  mutates: true,
20232
20463
  requiresWorkspace: true,
20464
+ examples: CREATE_EXAMPLES,
20233
20465
  handler: async ({ ctx, workspace, data }) => {
20234
- const result2 = await requireAuth(ctx).apiClient.createPronunciationEntry(
20235
- workspace,
20236
- data
20237
- );
20466
+ const auth = requireAuth(ctx);
20467
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20468
+ const payload = normalizePronunciationCreatePayload(data);
20469
+ const result2 = await auth.apiClient.createPronunciationEntry(orgUuid, payload);
20238
20470
  outputSingle(result2, ctx, COLUMNS26);
20239
20471
  }
20240
20472
  },
@@ -20245,10 +20477,13 @@ var pronunciationsDomain = {
20245
20477
  requiresWorkspace: true,
20246
20478
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
20247
20479
  handler: async ({ ctx, workspace, args, data }) => {
20248
- const result2 = await requireAuth(ctx).apiClient.updatePronunciationEntry(
20249
- workspace,
20480
+ const auth = requireAuth(ctx);
20481
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20482
+ const payload = normalizePronunciationUpdatePayload(data);
20483
+ const result2 = await auth.apiClient.updatePronunciationEntry(
20484
+ orgUuid,
20250
20485
  requireNumber(args, "id"),
20251
- data
20486
+ payload
20252
20487
  );
20253
20488
  outputSingle(result2, ctx, COLUMNS26);
20254
20489
  }
@@ -20260,8 +20495,10 @@ var pronunciationsDomain = {
20260
20495
  requiresWorkspace: true,
20261
20496
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
20262
20497
  handler: async ({ ctx, workspace, args }) => {
20498
+ const auth = requireAuth(ctx);
20499
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
20263
20500
  const id = requireNumber(args, "id");
20264
- await requireAuth(ctx).apiClient.deletePronunciationEntry(workspace, id);
20501
+ await auth.apiClient.deletePronunciationEntry(orgUuid, id);
20265
20502
  process.stdout.write(`Deleted pronunciation entry ${id}
20266
20503
  `);
20267
20504
  }
@@ -20281,7 +20518,8 @@ var pronunciationsDomain = {
20281
20518
  mutates: true,
20282
20519
  requiresWorkspace: true,
20283
20520
  handler: async ({ ctx, data }) => {
20284
- const result2 = await requireAuth(ctx).apiClient.createMyPronunciation(data);
20521
+ const payload = normalizePronunciationCreatePayload(data);
20522
+ const result2 = await requireAuth(ctx).apiClient.createMyPronunciation(payload);
20285
20523
  outputSingle(result2, ctx, COLUMNS26);
20286
20524
  }
20287
20525
  },
@@ -20289,11 +20527,17 @@ var pronunciationsDomain = {
20289
20527
  path: ["pronunciations", "suggest"],
20290
20528
  description: "Get pronunciation suggestions for a word",
20291
20529
  requiresWorkspace: true,
20292
- args: [{ name: "word", description: "Word to get suggestions for", required: true }],
20530
+ args: [
20531
+ {
20532
+ name: "word",
20533
+ aliases: ["term"],
20534
+ description: "Word to get suggestions for",
20535
+ required: true
20536
+ }
20537
+ ],
20293
20538
  handler: async ({ ctx, args }) => {
20294
- const result2 = await requireAuth(ctx).apiClient.suggestPronunciations(
20295
- requireString(args, "word")
20296
- );
20539
+ const word = typeof args.word === "string" ? args.word : typeof args.term === "string" ? args.term : requireString(args, "word");
20540
+ const result2 = await requireAuth(ctx).apiClient.suggestPronunciations(word);
20297
20541
  outputData(result2, ctx);
20298
20542
  }
20299
20543
  },
@@ -22250,6 +22494,327 @@ function openUrl(url) {
22250
22494
  }
22251
22495
  });
22252
22496
  }
22497
+ function buildCliConciergeAppContext() {
22498
+ const shell = process.env.SHELL ? path3.basename(process.env.SHELL) : void 0;
22499
+ return {
22500
+ clientSurface: "cli",
22501
+ channel: "text",
22502
+ cliContext: {
22503
+ shell,
22504
+ os: process.platform,
22505
+ cwdBasename: path3.basename(process.cwd())
22506
+ }
22507
+ };
22508
+ }
22509
+ var DEFAULT_MAX_READ_BYTES = 5e4;
22510
+ var ABSOLUTE_MAX_READ_BYTES = 1e6;
22511
+ function resolveSafePath(root, relativePath) {
22512
+ const rootResolved = path3.resolve(root);
22513
+ const target = path3.resolve(rootResolved, relativePath || ".");
22514
+ if (target !== rootResolved && !target.startsWith(rootResolved + path3.sep)) {
22515
+ return null;
22516
+ }
22517
+ let cursor = rootResolved;
22518
+ const rest = path3.relative(rootResolved, target);
22519
+ if (rest && rest !== ".") {
22520
+ for (const segment of rest.split(path3.sep)) {
22521
+ cursor = path3.join(cursor, segment);
22522
+ try {
22523
+ const lstat = fs.lstatSync(cursor);
22524
+ if (lstat.isSymbolicLink()) {
22525
+ return null;
22526
+ }
22527
+ } catch {
22528
+ break;
22529
+ }
22530
+ }
22531
+ }
22532
+ return target;
22533
+ }
22534
+ function executeCliLocalFileAction(root, interrupt, opts) {
22535
+ const safe = resolveSafePath(root, interrupt.path);
22536
+ if (!safe) {
22537
+ return { ok: false, error: "Path escapes trusted directory" };
22538
+ }
22539
+ try {
22540
+ switch (interrupt.action) {
22541
+ case "read_file": {
22542
+ if (!fs.existsSync(safe)) {
22543
+ return { ok: false, error: "File not found" };
22544
+ }
22545
+ const stat = fs.lstatSync(safe);
22546
+ if (stat.isSymbolicLink()) {
22547
+ return { ok: false, error: "Path escapes trusted directory" };
22548
+ }
22549
+ if (!stat.isFile()) {
22550
+ return { ok: false, error: "Not a file" };
22551
+ }
22552
+ const requested = Number.isFinite(interrupt.max_bytes) ? Math.max(1, Math.min(Number(interrupt.max_bytes), ABSOLUTE_MAX_READ_BYTES)) : DEFAULT_MAX_READ_BYTES;
22553
+ if (stat.size > requested) {
22554
+ const buf = fs.readFileSync(safe, { encoding: "utf8" });
22555
+ return {
22556
+ ok: true,
22557
+ content: buf.slice(0, requested) + `
22558
+
22559
+ [truncated at ${requested} bytes]`
22560
+ };
22561
+ }
22562
+ return { ok: true, content: fs.readFileSync(safe, "utf8") };
22563
+ }
22564
+ case "list_dir": {
22565
+ if (!fs.existsSync(safe)) {
22566
+ return { ok: false, error: "Directory not found" };
22567
+ }
22568
+ const entries = fs.readdirSync(safe, { withFileTypes: true });
22569
+ const lines = entries.slice(0, 200).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
22570
+ if (entries.length > 200) {
22571
+ lines.push(`... and ${entries.length - 200} more`);
22572
+ }
22573
+ return { ok: true, content: lines.join("\n") };
22574
+ }
22575
+ case "write_file": {
22576
+ if (!opts.allowWrite) {
22577
+ return { ok: false, error: "Write requires --allow-write" };
22578
+ }
22579
+ if (interrupt.content === void 0) {
22580
+ return { ok: false, error: "Missing content for write_file" };
22581
+ }
22582
+ fs.mkdirSync(path3.dirname(safe), { recursive: true });
22583
+ fs.writeFileSync(safe, interrupt.content, "utf8");
22584
+ return { ok: true, content: "written" };
22585
+ }
22586
+ default:
22587
+ return { ok: false, error: `Unknown action: ${interrupt.action}` };
22588
+ }
22589
+ } catch (e) {
22590
+ return { ok: false, error: sanitizeLocalFileError(e) };
22591
+ }
22592
+ }
22593
+ function sanitizeLocalFileError(e) {
22594
+ if (e && typeof e === "object" && "code" in e) {
22595
+ const code = e.code;
22596
+ if (code === "ENOENT") return "File or directory not found";
22597
+ if (code === "EACCES" || code === "EPERM") return "Permission denied";
22598
+ if (code === "EISDIR") return "Not a file";
22599
+ if (code === "ENOTDIR") return "Not a directory";
22600
+ }
22601
+ return "Local file operation failed";
22602
+ }
22603
+ function isCliLocalFileInterrupt(value) {
22604
+ if (typeof value !== "object" || value === null) return false;
22605
+ const v = value;
22606
+ return v.type === "cli_local_file" && typeof v.action === "string" && typeof v.path === "string";
22607
+ }
22608
+ var STORE_VERSION = 1;
22609
+ function storePath() {
22610
+ const base = process.env.XDG_CONFIG_HOME ?? (process.platform === "win32" ? path3.join(process.env.APPDATA ?? os.homedir(), "gravityrail") : path3.join(os.homedir(), ".config", "gravityrail"));
22611
+ return path3.join(base, "trusted-dirs.json");
22612
+ }
22613
+ function loadTrustedDirsStore() {
22614
+ const file = storePath();
22615
+ try {
22616
+ const raw = JSON.parse(fs.readFileSync(file, "utf8"));
22617
+ if (raw?.version === STORE_VERSION && Array.isArray(raw.always)) {
22618
+ return { version: STORE_VERSION, always: raw.always.map((p) => path3.resolve(p)) };
22619
+ }
22620
+ } catch {
22621
+ }
22622
+ return { version: STORE_VERSION, always: [] };
22623
+ }
22624
+ function saveTrustedDirsStore(store) {
22625
+ const file = storePath();
22626
+ const dir = path3.dirname(file);
22627
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
22628
+ fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
22629
+ }
22630
+ function isDirectoryTrusted(dirPath, opts) {
22631
+ if (opts?.trustCwdFlag) return true;
22632
+ const resolved = path3.resolve(dirPath);
22633
+ const store = loadTrustedDirsStore();
22634
+ return store.always.includes(resolved);
22635
+ }
22636
+ async function promptTrustInteractive(dirPath) {
22637
+ const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
22638
+ const question = (q) => new Promise((resolve2) => rl.question(q, resolve2));
22639
+ process.stderr.write(
22640
+ `
22641
+ Gravity Rail may read files in:
22642
+ ${dirPath}
22643
+
22644
+ File contents can influence the assistant. Do you trust the files in this directory?
22645
+ [1] Trust once (this session)
22646
+ [2] Trust always (remember for this path)
22647
+ [3] No \u2014 do not read local files
22648
+
22649
+ Choice [1/2/3] (default 3): `
22650
+ );
22651
+ const answer = (await question("")).trim() || "3";
22652
+ rl.close();
22653
+ if (answer === "1") return "once";
22654
+ if (answer === "2") return "always";
22655
+ return "denied";
22656
+ }
22657
+ var sessionTrusted = /* @__PURE__ */ new Set();
22658
+ async function resolveDirectoryTrust(opts) {
22659
+ const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
22660
+ if (isDirectoryTrusted(dir, { trustCwdFlag: opts.trustCwd })) return "trusted";
22661
+ if (sessionTrusted.has(dir)) return "trusted";
22662
+ if (opts.trustDir && path3.resolve(opts.trustDir) === dir) {
22663
+ sessionTrusted.add(dir);
22664
+ return "trusted";
22665
+ }
22666
+ if (opts.trustCwd) {
22667
+ sessionTrusted.add(dir);
22668
+ return "trusted";
22669
+ }
22670
+ if (!process.stdin.isTTY || opts.nonInteractive) {
22671
+ return "denied";
22672
+ }
22673
+ const decision = await promptTrustInteractive(dir);
22674
+ if (decision === "denied") {
22675
+ return "denied";
22676
+ }
22677
+ if (decision === "always") {
22678
+ const store = loadTrustedDirsStore();
22679
+ if (!store.always.includes(dir)) {
22680
+ store.always.push(dir);
22681
+ saveTrustedDirsStore(store);
22682
+ }
22683
+ }
22684
+ sessionTrusted.add(dir);
22685
+ return "trusted";
22686
+ }
22687
+
22688
+ // src/lib/cli-hitl.ts
22689
+ function extractHumanInputRequest(event) {
22690
+ const type = String(event.type ?? "").toUpperCase();
22691
+ if (type === "CUSTOM" && event.name === "human_input_request") {
22692
+ return event.value;
22693
+ }
22694
+ if (event.name === "human_input_request") {
22695
+ return event.value;
22696
+ }
22697
+ return null;
22698
+ }
22699
+ async function handleCliHitlInterrupt(value, opts) {
22700
+ if (!value || typeof value !== "object") return null;
22701
+ const payload = value;
22702
+ if (isCliLocalFileInterrupt(payload)) {
22703
+ const interrupt = payload;
22704
+ if (typeof payload.interrupt_id === "string") {
22705
+ interrupt.interrupt_id = payload.interrupt_id;
22706
+ }
22707
+ return handleCliLocalFile(interrupt, opts);
22708
+ }
22709
+ if (payload.type === "present_choice") {
22710
+ return handlePresentChoiceTerminal(payload);
22711
+ }
22712
+ return null;
22713
+ }
22714
+ async function handleCliLocalFile(interrupt, opts) {
22715
+ const trust = await resolveDirectoryTrust({
22716
+ cwd: process.cwd(),
22717
+ trustCwd: opts.trustCwd,
22718
+ trustDir: opts.trustDir,
22719
+ // Interactive sessions: prompt on first local-file use (review feedback).
22720
+ nonInteractive: opts.nonInteractive ?? process.stdin.isTTY !== true
22721
+ });
22722
+ if (trust !== "trusted") {
22723
+ return {
22724
+ resumeValue: JSON.stringify({
22725
+ ok: false,
22726
+ error: "Directory not trusted for local file access. Re-run with --trust-cwd or --trust-dir <path>, or approve trust interactively when prompted."
22727
+ }),
22728
+ resumeInterruptId: interrupt.interrupt_id
22729
+ };
22730
+ }
22731
+ process.stderr.write(`
22732
+ [local ${interrupt.action}] ${interrupt.path}
22733
+ `);
22734
+ const result2 = executeCliLocalFileAction(process.cwd(), interrupt, {
22735
+ allowWrite: opts.allowWrite
22736
+ });
22737
+ return {
22738
+ resumeValue: JSON.stringify(result2),
22739
+ resumeInterruptId: interrupt.interrupt_id
22740
+ };
22741
+ }
22742
+ async function handlePresentChoiceTerminal(payload) {
22743
+ const prompt = typeof payload.user_prompt === "string" && payload.user_prompt || typeof payload.description === "string" && payload.description || "Choose an option";
22744
+ const markup = typeof payload.openui_markup === "string" ? payload.openui_markup : "";
22745
+ process.stderr.write(`
22746
+ ${prompt}
22747
+ `);
22748
+ if (markup) {
22749
+ process.stderr.write(`${markup.slice(0, 2e3)}
22750
+ `);
22751
+ }
22752
+ if (!process.stdin.isTTY) {
22753
+ return {
22754
+ resumeValue: "",
22755
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
22756
+ };
22757
+ }
22758
+ const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
22759
+ const answer = await new Promise((resolve2) => {
22760
+ rl.question("Your choice (value): ", resolve2);
22761
+ });
22762
+ rl.close();
22763
+ return {
22764
+ resumeValue: answer.trim(),
22765
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
22766
+ };
22767
+ }
22768
+
22769
+ // src/lib/concierge-agui-loop.ts
22770
+ var MAX_HITL_ROUNDS = 25;
22771
+ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
22772
+ let streamText = "";
22773
+ let hasToolCalls = false;
22774
+ let resume;
22775
+ for (let round = 0; round < MAX_HITL_ROUNDS; round++) {
22776
+ let hitlPayload = null;
22777
+ await client.streamRequest(
22778
+ resume ? {
22779
+ messages: [],
22780
+ resumeValue: resume.resumeValue,
22781
+ resumeInterruptId: resume.resumeInterruptId
22782
+ } : { messages: initialMessages },
22783
+ (event) => {
22784
+ onEvent(event);
22785
+ const delta = getTextDelta(event);
22786
+ if (delta) streamText += delta;
22787
+ const type = event.type?.toLowerCase() ?? "";
22788
+ if (type === "tool_call_start") hasToolCalls = true;
22789
+ const errorMsg = getErrorMessage(event);
22790
+ if (errorMsg) process.stderr.write(`\x1B[31mError: ${errorMsg}\x1B[0m
22791
+ `);
22792
+ const hitl = extractHumanInputRequest(event);
22793
+ if (hitl) hitlPayload = hitl;
22794
+ },
22795
+ { autoApprove: opts.autoApprove }
22796
+ );
22797
+ if (!hitlPayload) {
22798
+ break;
22799
+ }
22800
+ const next = await handleCliHitlInterrupt(hitlPayload, {
22801
+ allowWrite: opts.allowWrite,
22802
+ autoApprove: opts.autoApprove,
22803
+ trustCwd: opts.trustCwd,
22804
+ trustDir: opts.trustDir,
22805
+ nonInteractive: opts.nonInteractive
22806
+ });
22807
+ if (!next) {
22808
+ process.stderr.write(
22809
+ " [HITL] Unsupported interrupt in CLI \u2014 open app.gravityrail.com/setup to continue.\n"
22810
+ );
22811
+ break;
22812
+ }
22813
+ resume = next;
22814
+ streamText = "";
22815
+ }
22816
+ return { streamText, hasToolCalls };
22817
+ }
22253
22818
  var RealtimeClient = class extends EventEmitter {
22254
22819
  ws = null;
22255
22820
  connected = false;
@@ -22269,7 +22834,7 @@ var RealtimeClient = class extends EventEmitter {
22269
22834
  new Error("WebSocket not available. Node.js 22+ is required for realtime mode.")
22270
22835
  );
22271
22836
  }
22272
- return new Promise((resolve, reject) => {
22837
+ return new Promise((resolve2, reject) => {
22273
22838
  const params = new URLSearchParams();
22274
22839
  if (this.options.vadMode) {
22275
22840
  params.append("vad", this.options.vadMode);
@@ -22324,7 +22889,7 @@ var RealtimeClient = class extends EventEmitter {
22324
22889
  }, 1e4);
22325
22890
  this.once("connected", () => {
22326
22891
  clearTimeout(timeout);
22327
- resolve();
22892
+ resolve2();
22328
22893
  });
22329
22894
  this.once("error", () => {
22330
22895
  clearTimeout(timeout);
@@ -23289,7 +23854,7 @@ async function listOutputDevices() {
23289
23854
  return listDevices("play");
23290
23855
  }
23291
23856
  function listDevices(command) {
23292
- return new Promise((resolve) => {
23857
+ return new Promise((resolve2) => {
23293
23858
  try {
23294
23859
  const proc = spawnSync(command, ["--list-devices"], {
23295
23860
  encoding: "utf-8",
@@ -23313,9 +23878,9 @@ function listDevices(command) {
23313
23878
  if (devices.length === 0) {
23314
23879
  devices.push({ id: "default", name: "System Default", isDefault: true });
23315
23880
  }
23316
- resolve(devices);
23881
+ resolve2(devices);
23317
23882
  } catch {
23318
- resolve([{ id: "default", name: "System Default", isDefault: true }]);
23883
+ resolve2([{ id: "default", name: "System Default", isDefault: true }]);
23319
23884
  }
23320
23885
  });
23321
23886
  }
@@ -23337,14 +23902,14 @@ async function selectDevice(devices, label) {
23337
23902
  process.stderr.write(` ${i + 1}. ${dev.name}${marker}
23338
23903
  `);
23339
23904
  }
23340
- return new Promise((resolve) => {
23905
+ return new Promise((resolve2) => {
23341
23906
  const defaultIdx = devices.findIndex((d) => d.isDefault);
23342
23907
  const defaultNum = defaultIdx >= 0 ? defaultIdx + 1 : 1;
23343
23908
  rl.question(` Select [${defaultNum}]: `, (answer) => {
23344
23909
  rl.close();
23345
23910
  const num = answer.trim() ? parseInt(answer.trim(), 10) : defaultNum;
23346
23911
  const idx = Math.max(0, Math.min(devices.length - 1, num - 1));
23347
- resolve(devices[idx]);
23912
+ resolve2(devices[idx]);
23348
23913
  });
23349
23914
  });
23350
23915
  }
@@ -23602,56 +24167,51 @@ async function runChatSession(session, args) {
23602
24167
  "Interactive mode requires a TTY. Use --message for non-interactive mode."
23603
24168
  );
23604
24169
  }
24170
+ const allowWrite = !!args["allow-write"] || !!session.allowWrite;
23605
24171
  if (message) {
23606
- await singleTurnChatSession(session, message, debug, autoApprove);
24172
+ await singleTurnChatSession(session, message, debug, autoApprove, allowWrite);
23607
24173
  } else if (isVoice) {
23608
24174
  await voiceChatSession(session, vadMode(args), args, debug);
23609
24175
  } else if (isRealtime) {
23610
24176
  await realtimeChatSession(session, vadMode(args), debug);
23611
24177
  } else {
23612
- await interactiveChatSession(session, debug);
24178
+ await interactiveChatSession(session, debug, autoApprove, allowWrite);
23613
24179
  }
23614
24180
  }
23615
24181
  function vadMode(args) {
23616
24182
  return typeof args.vad === "string" ? args.vad : "server";
23617
24183
  }
23618
- async function singleTurnChatSession(session, message, debug, autoApprove = false) {
24184
+ async function singleTurnChatSession(session, message, debug, autoApprove = false, allowWrite = false) {
23619
24185
  const aguiClient = new AGUIClient({
23620
24186
  baseUrl: session.ctx.envConfig.apiUrl,
24187
+ workspaceUuid: session.workspaceUuid,
24188
+ chatId: session.chatId,
23621
24189
  endpointUrl: session.aguiEndpointUrl,
23622
24190
  accessToken: session.ctx.accessToken,
23623
24191
  debug,
23624
- model: session.model
24192
+ model: session.model,
24193
+ appContext: session.appContext
23625
24194
  });
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
24195
+ const onEvent = (event) => {
24196
+ const type = event.type?.toLowerCase() ?? "";
24197
+ if (type === "tool_call_start") {
24198
+ const name = event.toolCallName ?? event.name ?? "";
24199
+ process.stderr.write(` [${name}]
23648
24200
  `);
23649
- },
23650
- { autoApprove }
24201
+ }
24202
+ };
24203
+ const { streamText, hasToolCalls } = await runConciergeAguiLoop(
24204
+ aguiClient,
24205
+ [{ role: "user", content: message }],
24206
+ onEvent,
24207
+ {
24208
+ autoApprove,
24209
+ allowWrite: allowWrite || !!session.allowWrite,
24210
+ trustCwd: session.trustCwd,
24211
+ trustDir: session.trustDir,
24212
+ nonInteractive: true
24213
+ }
23651
24214
  );
23652
- if (logStream) logStream.end();
23653
- if (debug) process.stderr.write(` (${eventCount} SSE events)
23654
- `);
23655
24215
  if (streamText) {
23656
24216
  process.stdout.write(`\x1B[32mAssistant:\x1B[0m ${streamText}
23657
24217
  `);
@@ -23685,13 +24245,16 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
23685
24245
  `);
23686
24246
  }
23687
24247
  }
23688
- async function interactiveChatSession(session, debug) {
24248
+ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
23689
24249
  const aguiClient = new AGUIClient({
23690
24250
  baseUrl: session.ctx.envConfig.apiUrl,
24251
+ workspaceUuid: session.workspaceUuid,
24252
+ chatId: session.chatId,
23691
24253
  endpointUrl: session.aguiEndpointUrl,
23692
24254
  accessToken: session.ctx.accessToken,
23693
24255
  debug,
23694
- model: session.model
24256
+ model: session.model,
24257
+ appContext: session.appContext
23695
24258
  });
23696
24259
  clearScreen();
23697
24260
  console.log("=".repeat(60));
@@ -23720,7 +24283,7 @@ async function interactiveChatSession(session, debug) {
23720
24283
  }
23721
24284
  }
23722
24285
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23723
- return new Promise((resolve) => {
24286
+ return new Promise((resolve2) => {
23724
24287
  const prompt = () => {
23725
24288
  rl.question("\x1B[34mYou:\x1B[0m ", async (input) => {
23726
24289
  const trimmed = input.trim();
@@ -23737,18 +24300,34 @@ async function interactiveChatSession(session, debug) {
23737
24300
  process.stdout.write("\n\x1B[32mAssistant:\x1B[0m ");
23738
24301
  let assistantContent = "";
23739
24302
  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(`
24303
+ const { streamText } = await runConciergeAguiLoop(
24304
+ aguiClient,
24305
+ conversationMessages,
24306
+ (event) => {
24307
+ const delta = getTextDelta(event);
24308
+ if (delta) {
24309
+ process.stdout.write(delta);
24310
+ assistantContent += delta;
24311
+ }
24312
+ const errorMsg = getErrorMessage(event);
24313
+ if (errorMsg) console.log(`
23748
24314
  \x1B[31mError: ${errorMsg}\x1B[0m`);
23749
- });
23750
- if (assistantContent)
24315
+ },
24316
+ {
24317
+ autoApprove,
24318
+ allowWrite: allowWrite || !!session.allowWrite,
24319
+ debug,
24320
+ trustCwd: session.trustCwd,
24321
+ trustDir: session.trustDir
24322
+ }
24323
+ );
24324
+ if (streamText && !assistantContent) {
24325
+ assistantContent = streamText;
24326
+ process.stdout.write(streamText);
24327
+ }
24328
+ if (assistantContent) {
23751
24329
  conversationMessages.push({ role: "assistant", content: assistantContent });
24330
+ }
23752
24331
  console.log("\n");
23753
24332
  } catch (error) {
23754
24333
  console.log(
@@ -23762,7 +24341,7 @@ async function interactiveChatSession(session, debug) {
23762
24341
  };
23763
24342
  rl.on("close", () => {
23764
24343
  console.log("\nGoodbye!");
23765
- resolve();
24344
+ resolve2();
23766
24345
  });
23767
24346
  prompt();
23768
24347
  });
@@ -23859,8 +24438,8 @@ async function realtimeChatSession(session, vad, debug) {
23859
24438
  });
23860
24439
  tui.loadHistory(history);
23861
24440
  tui.start();
23862
- await new Promise((resolve) => {
23863
- client.once("closed", resolve);
24441
+ await new Promise((resolve2) => {
24442
+ client.once("closed", resolve2);
23864
24443
  });
23865
24444
  }
23866
24445
  async function voiceChatSession(session, vad, args, debug) {
@@ -23977,8 +24556,8 @@ Connecting to ${session.label.toLowerCase()}...
23977
24556
  tui.loadHistory(history);
23978
24557
  tui.start();
23979
24558
  mic.start(inputDeviceId);
23980
- await new Promise((resolve) => {
23981
- client.once("closed", resolve);
24559
+ await new Promise((resolve2) => {
24560
+ client.once("closed", resolve2);
23982
24561
  });
23983
24562
  }
23984
24563
  async function listAudioDevices() {
@@ -24008,13 +24587,14 @@ async function conciergeCommand(hctx) {
24008
24587
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
24009
24588
  const message = typeof args.message === "string" ? args.message : void 0;
24010
24589
  const chat = await getOrCreateConciergeChat(ctx, chatIdArg, !!message);
24011
- const session = buildConciergeSession(ctx, chat);
24590
+ const session = buildConciergeSession(ctx, chat, args);
24012
24591
  await runChatSession(session, args);
24013
24592
  }
24014
- function buildConciergeSession(ctx, chat) {
24593
+ function buildConciergeSession(ctx, chat, args) {
24015
24594
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
24016
24595
  return {
24017
24596
  ctx,
24597
+ workspaceUuid: "",
24018
24598
  chatId: chat.id,
24019
24599
  chatUuid: chat.uuid,
24020
24600
  chatTitle: chat.title || chat.uuid,
@@ -24022,8 +24602,12 @@ function buildConciergeSession(ctx, chat) {
24022
24602
  wsUrl: `${wsBase}/api/v2/concierge/voice/stream-ws`,
24023
24603
  loadMessages: () => getConciergeChatMessages(ctx, chat.id).catch(() => []),
24024
24604
  label: "Concierge",
24025
- model: void 0
24605
+ model: void 0,
24026
24606
  // concierge doesn't send model field
24607
+ appContext: buildCliConciergeAppContext(),
24608
+ allowWrite: true,
24609
+ trustCwd: !!args["trust-cwd"],
24610
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
24027
24611
  };
24028
24612
  }
24029
24613
  async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
@@ -24058,6 +24642,95 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
24058
24642
  }
24059
24643
  return selectedChat;
24060
24644
  }
24645
+
24646
+ // src/commands/get-started.ts
24647
+ var WEB_SETUP = "https://app.gravityrail.com/setup";
24648
+ var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
24649
+ var HUMAN_GUIDE = `Gravity Rail \u2014 getting started
24650
+
24651
+ 1. Authenticate: gr login --env prod
24652
+ Confirm: gr whoami
24653
+
24654
+ 2. Recommended path \u2014 concierge (no workspace required):
24655
+ gr concierge chat --allow-write
24656
+ The concierge can create your org (auto-created on first login), list products,
24657
+ start Stripe checkout, and create your first workspace.
24658
+
24659
+ 3. Verify workspaces: gr workspaces list
24660
+
24661
+ 4. Web wizards (human-friendly): ${WEB_SETUP} and ${WEB_GET_STARTED}
24662
+
24663
+ Headless boundary: Stripe checkout needs a human in a browser \u2014 the concierge returns
24664
+ a checkout URL; an agent should hand that link to the user, not loop on payment.
24665
+
24666
+ Agent-readable steps: gr get-started --format llm
24667
+ `;
24668
+ var LLM_GUIDE = `# Gravity Rail CLI bootstrap (agent instructions)
24669
+
24670
+ ## Preconditions
24671
+ - Install: \`npm install -g @gravity-rail/cli\` or \`npx @gravity-rail/cli\`
24672
+ - Authenticate: \`gr login --env prod\` (interactive OAuth). Verify with \`gr whoami\`.
24673
+ - **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\`.
24674
+
24675
+ ## Important facts
24676
+ - **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.
24677
+ - Use \`--env dev|staging|prod\` (default prod) so credentials match the target; \`gr whoami\` shows the active env.
24678
+ - After login, an **organization is created automatically** \u2014 do not create a duplicate org unless \`gr organizations list\` is empty.
24679
+ - **Workspace is required** for workflow/agent/task commands (\`-w <workspace-uuid>\`).
24680
+ - **Concierge does not require \`-w\`** \u2014 use it for first-run setup.
24681
+
24682
+ ## Recommended path (concierge-driven)
24683
+ \`\`\`bash
24684
+ gr concierge chat --allow-write --message "Help me set up my first Gravity Rail workspace"
24685
+ \`\`\`
24686
+ Flags for unattended turns:
24687
+ - \`--message\` / \`-m\`: single-turn (non-interactive)
24688
+ - \`--auto-approve\`: skip HITL tool approval where policy allows
24689
+ - \`--allow-write\`: required for create/update tools
24690
+
24691
+ The concierge can: list/create orgs, list org/workspace products, create Stripe checkout sessions, create workspace specs, and \`create_workspace\`.
24692
+
24693
+ ## Manual CLI path (if not using concierge)
24694
+ 1. \`gr whoami\` \u2014 confirm account
24695
+ 2. \`gr organizations list\` \u2014 note org slug/UUID
24696
+ 3. \`gr concierge chat\` OR web: ${WEB_GET_STARTED}
24697
+ 4. Product purchase: concierge returns Stripe checkout URL \u2192 **human must complete in browser**
24698
+ 5. \`gr workspaces list\` \u2014 pick workspace UUID for \`-w\`
24699
+
24700
+ ## Support (account-level, no workspace)
24701
+ \`\`\`bash
24702
+ gr support create --type question --title "..." --description "..." --allow-write
24703
+ gr support list
24704
+ \`\`\`
24705
+
24706
+ ## Web fallbacks
24707
+ - Concierge / setup: ${WEB_SETUP}
24708
+ - Wizard: ${WEB_GET_STARTED}
24709
+
24710
+ ## Headless limits (do not retry forever)
24711
+ - Stripe payment / checkout: requires human browser
24712
+ - OAuth login: requires human browser
24713
+ - Directory trust for future local-file tools: use \`--trust-cwd\` only after user confirms files are safe
24714
+
24715
+ ## Local project files (CLI only)
24716
+ After directory trust + \`--allow-write\`, the concierge can use \`read_local_file\`, \`list_local_dir\`, \`write_local_file\` (client-executed via HITL).
24717
+
24718
+ ## Verify success
24719
+ \`gr workspaces list\` then \`gr workflows list -w <uuid>\` or \`gr agents list -w <uuid>\`
24720
+ `;
24721
+ async function getStartedCommand(hctx) {
24722
+ const format = hctx.args.format ?? hctx.args.agent;
24723
+ const isLlm = format === "llm" || format === "agent" || hctx.args["format"] === "llm" || !!hctx.args.agent;
24724
+ if (!isLlm) {
24725
+ try {
24726
+ requireAuth(hctx.ctx);
24727
+ } catch {
24728
+ }
24729
+ process.stdout.write(HUMAN_GUIDE);
24730
+ return;
24731
+ }
24732
+ process.stdout.write(LLM_GUIDE);
24733
+ }
24061
24734
  async function devImportLocalCommand(ctx, workspace) {
24062
24735
  stderr2(`Exporting workspace ${workspace}...`);
24063
24736
  const exportData = await ctx.apiClient.exportWorkspace(workspace);
@@ -24136,8 +24809,17 @@ async function manageCommand(hctx) {
24136
24809
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
24137
24810
  const message = typeof args.message === "string" ? args.message : void 0;
24138
24811
  const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
24139
- const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager");
24812
+ const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
24813
+ appContext: buildCliConciergeAppContext(),
24814
+ allowWrite: true,
24815
+ trustCwd: !!args["trust-cwd"],
24816
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
24817
+ });
24818
+ process.stderr.write(`Chat #${session.chatId}
24819
+ `);
24140
24820
  await runChatSession(session, args);
24821
+ process.stderr.write(`Chat #${session.chatId}
24822
+ `);
24141
24823
  }
24142
24824
  async function chatCommand(hctx) {
24143
24825
  const ctx = requireAuth(hctx.ctx);
@@ -24162,10 +24844,11 @@ async function chatCommand(hctx) {
24162
24844
  const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
24163
24845
  await runChatSession(session, args);
24164
24846
  }
24165
- function buildWorkspaceSession(ctx, workspace, chat, label) {
24847
+ function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
24166
24848
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
24167
24849
  return {
24168
24850
  ctx,
24851
+ workspaceUuid: workspace,
24169
24852
  chatId: chat.id,
24170
24853
  chatUuid: chat.uuid,
24171
24854
  chatTitle: chat.title || chat.uuid,
@@ -24173,7 +24856,8 @@ function buildWorkspaceSession(ctx, workspace, chat, label) {
24173
24856
  wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
24174
24857
  loadMessages: () => ctx.apiClient.getChatMessages(workspace, chat.id, 50).catch(() => []),
24175
24858
  label,
24176
- model: "manager"
24859
+ model: "manager",
24860
+ ...extra
24177
24861
  };
24178
24862
  }
24179
24863
  async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
@@ -24271,16 +24955,20 @@ var COLUMNS35 = [
24271
24955
  { header: "NAME", getValue: (a) => a.name || "", maxWidth: 30 },
24272
24956
  { header: "EMAIL", getValue: (a) => a.email || "", maxWidth: 40 },
24273
24957
  { header: "UUID", getValue: (a) => a.uuid },
24958
+ { header: "ENV", getValue: () => "" },
24274
24959
  { header: "SUPERUSER", getValue: (a) => a.isSuperuser ? "yes" : "no" }
24275
24960
  ];
24276
24961
  async function whoamiCommand(ctx) {
24277
24962
  const account = await ctx.apiClient.getUserAccount();
24278
- outputSingle(account, ctx, COLUMNS35);
24963
+ const columns = COLUMNS35.map(
24964
+ (col) => col.header === "ENV" ? { ...col, getValue: () => ctx.env } : col
24965
+ );
24966
+ outputSingle(account, ctx, columns);
24279
24967
  }
24280
24968
 
24281
24969
  // package.json
24282
24970
  var package_default = {
24283
- version: "0.5.0"};
24971
+ version: "0.5.1"};
24284
24972
 
24285
24973
  // src/gr.ts
24286
24974
  var VERSION = package_default.version;
@@ -24333,6 +25021,15 @@ function createRegistry() {
24333
25021
  name: "auto-approve",
24334
25022
  description: "Auto-approve all tool calls (skip HITL)",
24335
25023
  isFlag: true
25024
+ },
25025
+ {
25026
+ name: "trust-cwd",
25027
+ description: "Trust files in the current working directory (non-interactive)",
25028
+ isFlag: true
25029
+ },
25030
+ {
25031
+ name: "trust-dir",
25032
+ description: "Trust files in this directory (non-interactive; absolute path)"
24336
25033
  }
24337
25034
  ],
24338
25035
  handler: async (hctx) => {
@@ -24366,6 +25063,37 @@ function createRegistry() {
24366
25063
  await chatCommand(hctx);
24367
25064
  }
24368
25065
  });
25066
+ registry.registerCommand({
25067
+ path: ["get-started"],
25068
+ description: "Bootstrap instructions for new users and LLM agents",
25069
+ requiresAuth: false,
25070
+ args: [
25071
+ {
25072
+ name: "format",
25073
+ description: "Output format: llm (agent-readable) or default (human)"
25074
+ },
25075
+ {
25076
+ name: "agent",
25077
+ description: "Alias for --format llm",
25078
+ isFlag: true
25079
+ }
25080
+ ],
25081
+ handler: async (hctx) => {
25082
+ await getStartedCommand(hctx);
25083
+ }
25084
+ });
25085
+ registry.registerCommand({
25086
+ path: ["setup"],
25087
+ description: "Alias for get-started \u2014 bootstrap instructions",
25088
+ requiresAuth: false,
25089
+ args: [
25090
+ { name: "format", description: "Output format: llm or default" },
25091
+ { name: "agent", description: "Alias for --format llm", isFlag: true }
25092
+ ],
25093
+ handler: async (hctx) => {
25094
+ await getStartedCommand(hctx);
25095
+ }
25096
+ });
24369
25097
  registry.registerCommand({
24370
25098
  path: ["concierge", "chat"],
24371
25099
  description: "Interactive concierge chat (text, realtime, or voice)",
@@ -24385,6 +25113,15 @@ function createRegistry() {
24385
25113
  name: "auto-approve",
24386
25114
  description: "Auto-approve all tool calls (skip HITL)",
24387
25115
  isFlag: true
25116
+ },
25117
+ {
25118
+ name: "trust-cwd",
25119
+ description: "Trust files in the current working directory (non-interactive)",
25120
+ isFlag: true
25121
+ },
25122
+ {
25123
+ name: "trust-dir",
25124
+ description: "Trust files in this directory (non-interactive; absolute path)"
24388
25125
  }
24389
25126
  ],
24390
25127
  handler: async (hctx) => {
@@ -24423,6 +25160,7 @@ function createRegistry() {
24423
25160
  return registry;
24424
25161
  }
24425
25162
  function parseGlobalOptions(argv) {
25163
+ let env = "prod";
24426
25164
  let apiUrl;
24427
25165
  let outputFormat;
24428
25166
  let verbose = false;
@@ -24432,6 +25170,13 @@ function parseGlobalOptions(argv) {
24432
25170
  let collectingCommand = true;
24433
25171
  for (let i = 0; i < argv.length; i++) {
24434
25172
  const arg = argv[i];
25173
+ const eqIdx = arg.indexOf("=");
25174
+ const flagPart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
25175
+ const inlineValue = eqIdx !== -1 ? arg.slice(eqIdx + 1) : void 0;
25176
+ if (flagPart === "--env" && (inlineValue ?? argv[i + 1])) {
25177
+ env = inlineValue ?? argv[++i];
25178
+ continue;
25179
+ }
24435
25180
  if (arg === "--api-url" && argv[i + 1]) {
24436
25181
  apiUrl = argv[++i];
24437
25182
  continue;
@@ -24459,7 +25204,7 @@ function parseGlobalOptions(argv) {
24459
25204
  rest.unshift(commandWords.pop());
24460
25205
  }
24461
25206
  return {
24462
- opts: { apiUrl, outputFormat, verbose, allowWrite },
25207
+ opts: { env, apiUrl, outputFormat, verbose, allowWrite },
24463
25208
  commandWords,
24464
25209
  rest
24465
25210
  };
@@ -24508,7 +25253,8 @@ async function main() {
24508
25253
  process.stderr.write("Error: --workspace (-w) is required\n");
24509
25254
  process.exit(1);
24510
25255
  }
24511
- const data = await readJsonInput(parsedArgs);
25256
+ const acceptsJsonInput = cmd.args?.some((a) => a.name === "data" || a.name === "file");
25257
+ const data = acceptsJsonInput ? await readJsonInput(parsedArgs) : void 0;
24512
25258
  hctx = { ctx, args: parsedArgs, workspace, data };
24513
25259
  } else {
24514
25260
  const baseCtx = createPublicContext(opts);
@@ -24527,7 +25273,7 @@ async function main() {
24527
25273
  }
24528
25274
  await cmd.handler(hctx);
24529
25275
  if (process.stdout.writableNeedDrain) {
24530
- await new Promise((resolve) => process.stdout.once("drain", resolve));
25276
+ await new Promise((resolve2) => process.stdout.once("drain", resolve2));
24531
25277
  }
24532
25278
  process.exit(0);
24533
25279
  }