@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/lib.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  AGUIClient,
3
3
  CredentialsManager,
4
- __export,
5
- __reExport,
6
4
  clearScreen,
7
5
  formatTimestamp,
8
6
  getEnvironmentConfig,
@@ -13,50 +11,16 @@ import {
13
11
  printTable,
14
12
  refreshAccessToken,
15
13
  selectFromList
16
- } from "./chunk-C6D4FO6A.js";
17
-
18
- // src/lib/errors.ts
19
- var CLIError = class extends Error {
20
- constructor(message, exitCode = 1) {
21
- super(message);
22
- this.exitCode = exitCode;
23
- this.name = "CLIError";
24
- }
25
- };
26
- var AuthRequiredError = class extends CLIError {
27
- constructor(message = "Not authenticated. Run `gr login` or set GRAVITY_RAIL_API_KEY.") {
28
- super(message, 2);
29
- this.name = "AuthRequiredError";
30
- }
31
- };
32
- function handleError(err) {
33
- if (err instanceof CLIError) {
34
- process.stderr.write(`Error: ${err.message}
35
- `);
36
- process.exit(err.exitCode);
37
- }
38
- if (err instanceof Error) {
39
- process.stderr.write(`Error: ${err.message}
40
- `);
41
- if ("errors" in err && Array.isArray(err.errors)) {
42
- for (const e of err.errors) {
43
- process.stderr.write(` - ${e}
44
- `);
45
- }
46
- }
47
- if ("statusCode" in err && err.statusCode === 401) {
48
- process.stderr.write(
49
- `
50
- Hint: Run "gr login" to authenticate, or set GRAVITY_RAIL_API_KEY for non-interactive use.
51
- `
52
- );
53
- }
54
- process.exit(1);
55
- }
56
- process.stderr.write(`Error: ${String(err)}
57
- `);
58
- process.exit(1);
59
- }
14
+ } from "./chunk-H62EC2CF.js";
15
+ import {
16
+ AuthRequiredError,
17
+ CLIError,
18
+ handleError
19
+ } from "./chunk-OPWCTOHP.js";
20
+ import {
21
+ __export,
22
+ __reExport
23
+ } from "./chunk-424PT5DM.js";
60
24
 
61
25
  // src/lib/pagination.ts
62
26
  function isListResult(data) {
@@ -206,7 +170,7 @@ var COMMON_ARGS = [
206
170
  { name: "uuid", description: "Entity UUID" }
207
171
  ];
208
172
  function parseArgs(defs, argv) {
209
- const allDefs = [...COMMON_ARGS, ...defs];
173
+ const allDefs = [...defs, ...COMMON_ARGS];
210
174
  const result = {};
211
175
  for (const def of allDefs) {
212
176
  if (def.default !== void 0) {
@@ -233,8 +197,7 @@ function parseArgs(defs, argv) {
233
197
  const def = findDef(allDefs, flagPart);
234
198
  if (!def) {
235
199
  if ((flagPart.startsWith("--") || flagPart.startsWith("-")) && !KNOWN_GLOBAL_FLAGS.has(flagPart)) {
236
- process.stderr.write(`Warning: unknown flag "${flagPart}" (ignored)
237
- `);
200
+ throw new CLIError(`Unknown flag "${flagPart}"`);
238
201
  }
239
202
  continue;
240
203
  }
@@ -249,9 +212,11 @@ function parseArgs(defs, argv) {
249
212
  }
250
213
  }
251
214
  for (const def of allDefs) {
252
- if (def.required && result[def.name] === void 0) {
253
- throw new CLIError(`Missing required argument: --${def.name}`);
254
- }
215
+ if (!def.required) continue;
216
+ if (result[def.name] !== void 0) continue;
217
+ const aliasNames = [def.alias, ...def.aliases ?? []].filter(Boolean);
218
+ if (aliasNames.some((a) => result[a] !== void 0)) continue;
219
+ throw new CLIError(`Missing required argument: --${def.name}`);
255
220
  }
256
221
  return result;
257
222
  }
@@ -2203,12 +2168,14 @@ function requireAuth(ctx) {
2203
2168
  return ctx;
2204
2169
  }
2205
2170
  function createPublicContext(opts) {
2206
- const apiUrl = opts.apiUrl || process.env.GRAVITY_RAIL_API_URL || "https://api.gravityrail.com";
2207
- const frontendUrl = opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com";
2208
- const envConfig = { apiUrl, frontendUrl };
2209
- const credentialsManager = new CredentialsManager(homedir(), apiUrl);
2171
+ const env = opts.env ?? "prod";
2172
+ const envConfig = opts.apiUrl ? {
2173
+ apiUrl: opts.apiUrl,
2174
+ frontendUrl: opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com"
2175
+ } : getEnvironmentConfig(env);
2176
+ const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);
2210
2177
  return {
2211
- env: "prod",
2178
+ env,
2212
2179
  envConfig,
2213
2180
  outputFormat: detectOutputFormat(opts.outputFormat),
2214
2181
  verbose: opts.verbose ?? false,
@@ -2260,32 +2227,48 @@ async function readJsonInput(args) {
2260
2227
  throw new CLIError(`Cannot read file: ${filePath}`);
2261
2228
  }
2262
2229
  }
2263
- if (!process.stdin.isTTY) {
2264
- return readStdin();
2230
+ if (process.stdin.isTTY === true) {
2231
+ return void 0;
2265
2232
  }
2266
- return void 0;
2233
+ return readStdinIfPiped();
2267
2234
  }
2268
- function readStdin() {
2235
+ function readStdinIfPiped() {
2269
2236
  return new Promise((resolve, reject) => {
2270
2237
  let data = "";
2271
- process.stdin.setEncoding("utf-8");
2272
- process.stdin.on("data", (chunk) => {
2273
- data += chunk;
2274
- });
2275
- process.stdin.on("end", () => {
2238
+ let settled = false;
2239
+ const finish = (value) => {
2240
+ if (settled) return;
2241
+ settled = true;
2242
+ resolve(value);
2243
+ };
2244
+ const flush = () => {
2276
2245
  if (!data.trim()) {
2277
- resolve(void 0);
2246
+ finish(void 0);
2278
2247
  return;
2279
2248
  }
2280
2249
  try {
2281
- resolve(JSON.parse(data));
2250
+ finish(JSON.parse(data));
2282
2251
  } catch {
2283
2252
  reject(new CLIError("Invalid JSON from stdin"));
2284
2253
  }
2254
+ };
2255
+ process.stdin.setEncoding("utf-8");
2256
+ process.stdin.on("data", (chunk) => {
2257
+ data += chunk;
2285
2258
  });
2259
+ process.stdin.on("end", flush);
2286
2260
  process.stdin.on("error", (err) => {
2287
2261
  reject(new CLIError(`Error reading stdin: ${err.message}`));
2288
2262
  });
2263
+ process.stdin.resume();
2264
+ setImmediate(() => {
2265
+ if (data) return;
2266
+ if (process.stdin.readableEnded) {
2267
+ flush();
2268
+ return;
2269
+ }
2270
+ finish(void 0);
2271
+ });
2289
2272
  });
2290
2273
  }
2291
2274
 
@@ -2881,6 +2864,17 @@ function requireNumber(args, key) {
2881
2864
  if (isNaN(num)) throw new Error(`Argument --${key} must be a number`);
2882
2865
  return num;
2883
2866
  }
2867
+ function requireNumberFrom(args, ...keys) {
2868
+ for (const key of keys) {
2869
+ const val = args[key];
2870
+ if (typeof val === "string") {
2871
+ const num = Number(val);
2872
+ if (!isNaN(num)) return num;
2873
+ }
2874
+ }
2875
+ const hint = keys.map((k) => `--${k}`).join(" or ");
2876
+ throw new Error(`Missing required argument: ${hint}`);
2877
+ }
2884
2878
 
2885
2879
  // src/domains/agent-tasks.ts
2886
2880
  var LIST_COLUMNS = [
@@ -3041,6 +3035,7 @@ var agentsDomain = {
3041
3035
  var COLUMNS2 = [
3042
3036
  COL.id(),
3043
3037
  COL.uuid(),
3038
+ { header: "TYPE", getValue: (item) => item.chatType ?? item.chat_type ?? "" },
3044
3039
  { header: "MEMBER", getValue: (item) => item.member_name ?? String(item.member_id ?? "") },
3045
3040
  COL.status(),
3046
3041
  { header: "CHANNEL", getValue: (item) => item.channel ?? "" },
@@ -3092,7 +3087,11 @@ var chatsDomain = {
3092
3087
  isFlag: true
3093
3088
  },
3094
3089
  { name: "is-test", description: "Show only test chats", isFlag: true },
3095
- { name: "member-id", description: "Filter by member ID" }
3090
+ { name: "member-id", description: "Filter by member ID" },
3091
+ {
3092
+ name: "chat-type",
3093
+ description: "Filter by chat type (assignment, manager, developer, member, contributor). Default: assignment"
3094
+ }
3096
3095
  ]
3097
3096
  }),
3098
3097
  // Messages
@@ -3101,7 +3100,7 @@ var chatsDomain = {
3101
3100
  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.",
3102
3101
  requiresWorkspace: true,
3103
3102
  args: [
3104
- { name: "chat-id", description: "Chat ID", required: true },
3103
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
3105
3104
  {
3106
3105
  name: "limit",
3107
3106
  description: "Max messages to return (1-100, default 100)"
@@ -3124,7 +3123,7 @@ var chatsDomain = {
3124
3123
  }
3125
3124
  const result = await requireAuth(ctx).apiClient.getChatMessages(
3126
3125
  workspace,
3127
- requireNumber(args, "chat-id"),
3126
+ requireNumberFrom(args, "chat-id", "id"),
3128
3127
  limit,
3129
3128
  offset
3130
3129
  );
@@ -3137,7 +3136,7 @@ var chatsDomain = {
3137
3136
  mutates: true,
3138
3137
  requiresWorkspace: true,
3139
3138
  args: [
3140
- { name: "chat-id", description: "Chat ID", required: true },
3139
+ { name: "chat-id", aliases: ["id"], description: "Chat ID", required: true },
3141
3140
  { name: "stream", description: "Stream the AI response via AG-UI", isFlag: true }
3142
3141
  ],
3143
3142
  handler: async ({ ctx, workspace, args, data }) => {
@@ -3146,7 +3145,7 @@ var chatsDomain = {
3146
3145
  throw new Error(
3147
3146
  `Message content is required (pass --data '"your message"' or --data '{"content":"your message"}')`
3148
3147
  );
3149
- const chatId = requireNumber(args, "chat-id");
3148
+ const chatId = requireNumberFrom(args, "chat-id", "id");
3150
3149
  if (args.stream) {
3151
3150
  const aguiClient = new AGUIClient({
3152
3151
  baseUrl: ctx.envConfig.apiUrl,
@@ -3179,11 +3178,11 @@ var chatsDomain = {
3179
3178
  description: "Send an assistant message to a chat (does not trigger AI inference)",
3180
3179
  mutates: true,
3181
3180
  requiresWorkspace: true,
3182
- args: [{ name: "chat-id", description: "Chat ID", required: true }],
3181
+ args: [{ name: "chat-id", aliases: ["id"], description: "Chat ID", required: true }],
3183
3182
  handler: async ({ ctx, workspace, args, data }) => {
3184
3183
  const result = await requireAuth(ctx).apiClient.sendAssistantMessage(
3185
3184
  workspace,
3186
- requireNumber(args, "chat-id"),
3185
+ requireNumberFrom(args, "chat-id", "id"),
3187
3186
  data
3188
3187
  );
3189
3188
  outputSingle(result, ctx, MESSAGE_COLUMNS2);
@@ -5246,10 +5245,20 @@ var eventRulesCrudNoList = crudCommands({
5246
5245
  ' "eventType":"journey_step:execute","actionType":"member:label",',
5247
5246
  ` "actionParams":{"addLabelIds":["<label-uuid>"]},"journeyStepId":<step-id>}'`,
5248
5247
  "",
5248
+ "Scheduled outbound (routine \u2192 chat \u2192 voice/SMS):",
5249
+ `gr events rules create -w $WID --allow-write --data '{"name":"AM voice check-in",`,
5250
+ ' "objectType":"routine","eventType":"routine:triggered","actionType":"chat:create",',
5251
+ ' "routineId":<routine-id>,',
5252
+ ' "actionParams":{"workflowId":<workflow-id>,"channel":"phone-voice",',
5253
+ ` "phoneNumberId":<workspace-phone-id>}}'`,
5254
+ "",
5255
+ ' PM SMS variant: use "channel":"phone-sms" and a second rule on the same routineId.',
5256
+ " Prereq: org phone assigned + gr phone-numbers link; workflow published with start task.",
5257
+ "",
5249
5258
  "Required: objectType, eventType, actionType",
5250
- "Optional: name, actionParams, conditions (CEL), journeyStepId, delay (sec), order",
5259
+ "Optional: name, actionParams, conditions (CEL), journeyStepId, routineId, delay (sec), order",
5251
5260
  "",
5252
- "Common eventTypes: journey_step:execute, journey:enrolled, journey:completed,",
5261
+ "Common eventTypes: journey_step:execute, journey:enrolled, routine:triggered,",
5253
5262
  " task:created, chat:created, data_record:created, member:created",
5254
5263
  "",
5255
5264
  "Common actionTypes: member:label, member:notify, webhook:send, email:send,",
@@ -7533,7 +7542,7 @@ var conciergeDomain = {
7533
7542
  path: ["concierge", "messages"],
7534
7543
  description: "Show messages for a concierge chat",
7535
7544
  requiresWorkspace: false,
7536
- args: [{ name: "id", description: "Chat ID", required: true }],
7545
+ args: [{ name: "id", aliases: ["chat-id"], description: "Chat ID", required: true }],
7537
7546
  handler: async ({ ctx, args }) => {
7538
7547
  const auth = requireAuth(ctx);
7539
7548
  const chatId = Number(args.id);
@@ -8166,15 +8175,15 @@ var DOMAIN_UUID_ARG = {
8166
8175
  function pluralize(count, singular, plural = `${singular}s`) {
8167
8176
  return count === 1 ? singular : plural;
8168
8177
  }
8169
- function emailVerifyCommand(path, description) {
8178
+ function emailVerifyCommand(path4, description) {
8170
8179
  return {
8171
- path,
8180
+ path: path4,
8172
8181
  description,
8173
8182
  mutates: true,
8174
8183
  requiresWorkspace: false,
8175
8184
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
8176
8185
  examples: [
8177
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
8186
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
8178
8187
  ],
8179
8188
  handler: async (hctx) => {
8180
8189
  const result = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainEmail(
@@ -8185,15 +8194,15 @@ function emailVerifyCommand(path, description) {
8185
8194
  }
8186
8195
  };
8187
8196
  }
8188
- function siteVerifyCommand(path, description) {
8197
+ function siteVerifyCommand(path4, description) {
8189
8198
  return {
8190
- path,
8199
+ path: path4,
8191
8200
  description,
8192
8201
  mutates: true,
8193
8202
  requiresWorkspace: false,
8194
8203
  args: [ORG_UUID_ARG, DOMAIN_UUID_ARG],
8195
8204
  examples: [
8196
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
8205
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write`
8197
8206
  ],
8198
8207
  handler: async (hctx) => {
8199
8208
  const result = await requireAuth(hctx.ctx).apiClient.verifyOrgDomainSite(
@@ -8204,9 +8213,9 @@ function siteVerifyCommand(path, description) {
8204
8213
  }
8205
8214
  };
8206
8215
  }
8207
- function registerDomainCommand(path) {
8216
+ function registerDomainCommand(path4) {
8208
8217
  return {
8209
- path,
8218
+ path: path4,
8210
8219
  description: "Register an organization domain and return ownership TXT instructions",
8211
8220
  mutates: true,
8212
8221
  requiresWorkspace: false,
@@ -8215,7 +8224,7 @@ function registerDomainCommand(path) {
8215
8224
  { name: "domain", description: "Domain to register, e.g. example.com", required: true }
8216
8225
  ],
8217
8226
  examples: [
8218
- `yarn gr ${path.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
8227
+ `yarn gr ${path4.join(" ")} --org-uuid $ORG_UUID --domain example.com --allow-write`
8219
8228
  ],
8220
8229
  handler: async (hctx) => {
8221
8230
  const result = await requireAuth(hctx.ctx).apiClient.registerOrgDomain(
@@ -8570,6 +8579,12 @@ var commands2 = [
8570
8579
  workspaceUuid
8571
8580
  );
8572
8581
  outputSingle(result, hctx.ctx, PROVIDER_PHONE_COLUMNS);
8582
+ process.stdout.write(
8583
+ `
8584
+ Create a workspace phone linked to this provider number (sets Twilio webhooks):
8585
+ gr phone-numbers create -w ${workspaceUuid} --allow-write --data '{"name":"\u2026","providerPhoneUuid":"${providerPhoneUuid}"}'
8586
+ `
8587
+ );
8573
8588
  }
8574
8589
  },
8575
8590
  {
@@ -8673,6 +8688,31 @@ var phoneNumbersDomain = {
8673
8688
  outputData(result, ctx, COLUMNS25);
8674
8689
  }
8675
8690
  },
8691
+ {
8692
+ path: ["phone-numbers", "link"],
8693
+ description: "Link a workspace phone number to a provider (org) phone",
8694
+ mutates: true,
8695
+ requiresWorkspace: true,
8696
+ args: [
8697
+ {
8698
+ name: "phone-number-id",
8699
+ aliases: ["id"],
8700
+ description: "Workspace phone number ID",
8701
+ required: true
8702
+ },
8703
+ { name: "provider-phone-uuid", description: "Provider phone UUID", required: true }
8704
+ ],
8705
+ examples: [
8706
+ "gr phone-numbers link -w $WID --id 1 --provider-phone-uuid <uuid> --allow-write"
8707
+ ],
8708
+ handler: async ({ ctx, workspace, args }) => {
8709
+ const auth = requireAuth(ctx);
8710
+ const id = requireNumberFrom(args, "phone-number-id", "id");
8711
+ const providerPhoneUuid = requireString(args, "provider-phone-uuid");
8712
+ const result = await auth.apiClient.linkPhoneNumber(workspace, id, providerPhoneUuid);
8713
+ outputData([result], auth, COLUMNS25);
8714
+ }
8715
+ },
8676
8716
  {
8677
8717
  path: ["phone-numbers", "request"],
8678
8718
  description: "Request a phone number",
@@ -8691,7 +8731,12 @@ var phoneNumbersDomain = {
8691
8731
  description: "List calls for a phone number",
8692
8732
  requiresWorkspace: true,
8693
8733
  args: [
8694
- { name: "phone-number-id", description: "Phone number ID", required: true },
8734
+ {
8735
+ name: "phone-number-id",
8736
+ aliases: ["id"],
8737
+ description: "Phone number ID",
8738
+ required: true
8739
+ },
8695
8740
  { name: "page", description: "Page number" },
8696
8741
  { name: "page-size", description: "Page size" },
8697
8742
  { name: "status", description: "Filter by status" }
@@ -8700,7 +8745,7 @@ var phoneNumbersDomain = {
8700
8745
  const status = args.status;
8701
8746
  const result = await requireAuth(ctx).apiClient.getPhoneNumberCalls(
8702
8747
  workspace,
8703
- requireNumber(args, "phone-number-id"),
8748
+ requireNumberFrom(args, "phone-number-id", "id"),
8704
8749
  args.page ? Number(args.page) : void 0,
8705
8750
  args["page-size"] ? Number(args["page-size"]) : void 0,
8706
8751
  status ? { status } : void 0
@@ -8713,14 +8758,19 @@ var phoneNumbersDomain = {
8713
8758
  description: "List messages for a phone number",
8714
8759
  requiresWorkspace: true,
8715
8760
  args: [
8716
- { name: "phone-number-id", description: "Phone number ID", required: true },
8761
+ {
8762
+ name: "phone-number-id",
8763
+ aliases: ["id"],
8764
+ description: "Phone number ID",
8765
+ required: true
8766
+ },
8717
8767
  { name: "page", description: "Page number" },
8718
8768
  { name: "page-size", description: "Page size" }
8719
8769
  ],
8720
8770
  handler: async ({ ctx, workspace, args }) => {
8721
8771
  const result = await requireAuth(ctx).apiClient.getPhoneNumberMessages(
8722
8772
  workspace,
8723
- requireNumber(args, "phone-number-id"),
8773
+ requireNumberFrom(args, "phone-number-id", "id"),
8724
8774
  args.page ? Number(args.page) : void 0,
8725
8775
  args["page-size"] ? Number(args["page-size"]) : void 0
8726
8776
  );
@@ -8730,36 +8780,85 @@ var phoneNumbersDomain = {
8730
8780
  ]
8731
8781
  };
8732
8782
 
8783
+ // src/lib/resolve-org.ts
8784
+ async function resolveOrgUuidFromWorkspace(apiClient, workspaceUuid) {
8785
+ const workspace = await apiClient.getWorkspace(workspaceUuid);
8786
+ const orgUuid = workspace.organizationUuid;
8787
+ if (!orgUuid) {
8788
+ throw new CLIError(
8789
+ "Workspace has no organization; pass --org-uuid for organization-scoped commands"
8790
+ );
8791
+ }
8792
+ return orgUuid;
8793
+ }
8794
+ function normalizePronunciationCreatePayload(data) {
8795
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
8796
+ throw new CLIError("JSON body required with term and respelling (--data or --file)");
8797
+ }
8798
+ const raw = data;
8799
+ const term = raw.term ?? raw.word;
8800
+ const respelling = raw.respelling ?? raw.pronunciation;
8801
+ if (typeof term !== "string" || !term) {
8802
+ throw new CLIError("Payload requires term (alias: word)");
8803
+ }
8804
+ if (typeof respelling !== "string" || !respelling) {
8805
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
8806
+ }
8807
+ const payload = { term, respelling };
8808
+ if (raw.accountId !== void 0 && raw.accountId !== null) {
8809
+ payload.accountId = Number(raw.accountId);
8810
+ }
8811
+ return payload;
8812
+ }
8813
+ function normalizePronunciationUpdatePayload(data) {
8814
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
8815
+ throw new CLIError("JSON body required (--data or --file)");
8816
+ }
8817
+ const raw = data;
8818
+ const respelling = raw.respelling ?? raw.pronunciation;
8819
+ if (typeof respelling !== "string" || !respelling) {
8820
+ throw new CLIError("Payload requires respelling (alias: pronunciation)");
8821
+ }
8822
+ return { respelling };
8823
+ }
8824
+
8733
8825
  // src/domains/pronunciations.ts
8734
8826
  var COLUMNS26 = [
8735
8827
  COL.id(),
8736
- { header: "WORD", getValue: (item) => item.word ?? "" },
8737
- { header: "PRONUNCIATION", getValue: (item) => item.pronunciation ?? "" },
8738
- { header: "TYPE", getValue: (item) => item.type ?? "" }
8828
+ { header: "TERM", getValue: (item) => item.term ?? "" },
8829
+ { header: "RESPELLING", getValue: (item) => item.respelling ?? "" },
8830
+ { header: "SCOPE", getValue: (item) => item.scope ?? "" }
8831
+ ];
8832
+ var CREATE_EXAMPLES = [
8833
+ `gr pronunciations create -w $WID --data '{"term":"Jeeves","respelling":"JEEVZ"}' --allow-write`,
8834
+ " (aliases: word \u2192 term, pronunciation \u2192 respelling)"
8739
8835
  ];
8740
8836
  var pronunciationsDomain = {
8741
8837
  name: "pronunciations",
8742
- description: "Manage pronunciation entries",
8838
+ description: "Manage pronunciation entries (org-scoped; -w resolves organization)",
8743
8839
  commands: [
8744
8840
  {
8745
8841
  path: ["pronunciations", "list"],
8746
- description: "List pronunciation entries",
8842
+ description: "List pronunciation entries for the workspace organization",
8747
8843
  requiresWorkspace: true,
8748
8844
  handler: async ({ ctx, workspace }) => {
8749
- const result = await requireAuth(ctx).apiClient.getPronunciationEntries(workspace);
8845
+ const auth = requireAuth(ctx);
8846
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
8847
+ const result = await auth.apiClient.getPronunciationEntries(orgUuid);
8750
8848
  outputData(result, ctx, COLUMNS26);
8751
8849
  }
8752
8850
  },
8753
8851
  {
8754
8852
  path: ["pronunciations", "create"],
8755
- description: "Create a pronunciation entry",
8853
+ description: "Create a pronunciation entry (term + respelling)",
8756
8854
  mutates: true,
8757
8855
  requiresWorkspace: true,
8856
+ examples: CREATE_EXAMPLES,
8758
8857
  handler: async ({ ctx, workspace, data }) => {
8759
- const result = await requireAuth(ctx).apiClient.createPronunciationEntry(
8760
- workspace,
8761
- data
8762
- );
8858
+ const auth = requireAuth(ctx);
8859
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
8860
+ const payload = normalizePronunciationCreatePayload(data);
8861
+ const result = await auth.apiClient.createPronunciationEntry(orgUuid, payload);
8763
8862
  outputSingle(result, ctx, COLUMNS26);
8764
8863
  }
8765
8864
  },
@@ -8770,10 +8869,13 @@ var pronunciationsDomain = {
8770
8869
  requiresWorkspace: true,
8771
8870
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
8772
8871
  handler: async ({ ctx, workspace, args, data }) => {
8773
- const result = await requireAuth(ctx).apiClient.updatePronunciationEntry(
8774
- workspace,
8872
+ const auth = requireAuth(ctx);
8873
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
8874
+ const payload = normalizePronunciationUpdatePayload(data);
8875
+ const result = await auth.apiClient.updatePronunciationEntry(
8876
+ orgUuid,
8775
8877
  requireNumber(args, "id"),
8776
- data
8878
+ payload
8777
8879
  );
8778
8880
  outputSingle(result, ctx, COLUMNS26);
8779
8881
  }
@@ -8785,8 +8887,10 @@ var pronunciationsDomain = {
8785
8887
  requiresWorkspace: true,
8786
8888
  args: [{ name: "id", description: "Pronunciation entry ID", required: true }],
8787
8889
  handler: async ({ ctx, workspace, args }) => {
8890
+ const auth = requireAuth(ctx);
8891
+ const orgUuid = await resolveOrgUuidFromWorkspace(auth.apiClient, workspace);
8788
8892
  const id = requireNumber(args, "id");
8789
- await requireAuth(ctx).apiClient.deletePronunciationEntry(workspace, id);
8893
+ await auth.apiClient.deletePronunciationEntry(orgUuid, id);
8790
8894
  process.stdout.write(`Deleted pronunciation entry ${id}
8791
8895
  `);
8792
8896
  }
@@ -8806,7 +8910,8 @@ var pronunciationsDomain = {
8806
8910
  mutates: true,
8807
8911
  requiresWorkspace: true,
8808
8912
  handler: async ({ ctx, data }) => {
8809
- const result = await requireAuth(ctx).apiClient.createMyPronunciation(data);
8913
+ const payload = normalizePronunciationCreatePayload(data);
8914
+ const result = await requireAuth(ctx).apiClient.createMyPronunciation(payload);
8810
8915
  outputSingle(result, ctx, COLUMNS26);
8811
8916
  }
8812
8917
  },
@@ -8814,11 +8919,17 @@ var pronunciationsDomain = {
8814
8919
  path: ["pronunciations", "suggest"],
8815
8920
  description: "Get pronunciation suggestions for a word",
8816
8921
  requiresWorkspace: true,
8817
- args: [{ name: "word", description: "Word to get suggestions for", required: true }],
8922
+ args: [
8923
+ {
8924
+ name: "word",
8925
+ aliases: ["term"],
8926
+ description: "Word to get suggestions for",
8927
+ required: true
8928
+ }
8929
+ ],
8818
8930
  handler: async ({ ctx, args }) => {
8819
- const result = await requireAuth(ctx).apiClient.suggestPronunciations(
8820
- requireString(args, "word")
8821
- );
8931
+ const word = typeof args.word === "string" ? args.word : typeof args.term === "string" ? args.term : requireString(args, "word");
8932
+ const result = await requireAuth(ctx).apiClient.suggestPronunciations(word);
8822
8933
  outputData(result, ctx);
8823
8934
  }
8824
8935
  },
@@ -10779,8 +10890,356 @@ function openUrl(url) {
10779
10890
  });
10780
10891
  }
10781
10892
 
10893
+ // src/lib/cli-client-context.ts
10894
+ import path from "path";
10895
+ function buildCliConciergeAppContext() {
10896
+ const shell = process.env.SHELL ? path.basename(process.env.SHELL) : void 0;
10897
+ return {
10898
+ clientSurface: "cli",
10899
+ channel: "text",
10900
+ cliContext: {
10901
+ shell,
10902
+ os: process.platform,
10903
+ cwdBasename: path.basename(process.cwd())
10904
+ }
10905
+ };
10906
+ }
10907
+
10908
+ // src/lib/cli-hitl.ts
10909
+ import readline2 from "readline";
10910
+
10911
+ // src/lib/local-files.ts
10912
+ import fs from "fs";
10913
+ import path2 from "path";
10914
+ var DEFAULT_MAX_READ_BYTES = 5e4;
10915
+ var ABSOLUTE_MAX_READ_BYTES = 1e6;
10916
+ function resolveSafePath(root, relativePath) {
10917
+ const rootResolved = path2.resolve(root);
10918
+ const target = path2.resolve(rootResolved, relativePath || ".");
10919
+ if (target !== rootResolved && !target.startsWith(rootResolved + path2.sep)) {
10920
+ return null;
10921
+ }
10922
+ let cursor = rootResolved;
10923
+ const rest = path2.relative(rootResolved, target);
10924
+ if (rest && rest !== ".") {
10925
+ for (const segment of rest.split(path2.sep)) {
10926
+ cursor = path2.join(cursor, segment);
10927
+ try {
10928
+ const lstat = fs.lstatSync(cursor);
10929
+ if (lstat.isSymbolicLink()) {
10930
+ return null;
10931
+ }
10932
+ } catch {
10933
+ break;
10934
+ }
10935
+ }
10936
+ }
10937
+ return target;
10938
+ }
10939
+ function executeCliLocalFileAction(root, interrupt, opts) {
10940
+ const safe = resolveSafePath(root, interrupt.path);
10941
+ if (!safe) {
10942
+ return { ok: false, error: "Path escapes trusted directory" };
10943
+ }
10944
+ try {
10945
+ switch (interrupt.action) {
10946
+ case "read_file": {
10947
+ if (!fs.existsSync(safe)) {
10948
+ return { ok: false, error: "File not found" };
10949
+ }
10950
+ const stat = fs.lstatSync(safe);
10951
+ if (stat.isSymbolicLink()) {
10952
+ return { ok: false, error: "Path escapes trusted directory" };
10953
+ }
10954
+ if (!stat.isFile()) {
10955
+ return { ok: false, error: "Not a file" };
10956
+ }
10957
+ const requested = Number.isFinite(interrupt.max_bytes) ? Math.max(1, Math.min(Number(interrupt.max_bytes), ABSOLUTE_MAX_READ_BYTES)) : DEFAULT_MAX_READ_BYTES;
10958
+ if (stat.size > requested) {
10959
+ const buf = fs.readFileSync(safe, { encoding: "utf8" });
10960
+ return {
10961
+ ok: true,
10962
+ content: buf.slice(0, requested) + `
10963
+
10964
+ [truncated at ${requested} bytes]`
10965
+ };
10966
+ }
10967
+ return { ok: true, content: fs.readFileSync(safe, "utf8") };
10968
+ }
10969
+ case "list_dir": {
10970
+ if (!fs.existsSync(safe)) {
10971
+ return { ok: false, error: "Directory not found" };
10972
+ }
10973
+ const entries = fs.readdirSync(safe, { withFileTypes: true });
10974
+ const lines = entries.slice(0, 200).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
10975
+ if (entries.length > 200) {
10976
+ lines.push(`... and ${entries.length - 200} more`);
10977
+ }
10978
+ return { ok: true, content: lines.join("\n") };
10979
+ }
10980
+ case "write_file": {
10981
+ if (!opts.allowWrite) {
10982
+ return { ok: false, error: "Write requires --allow-write" };
10983
+ }
10984
+ if (interrupt.content === void 0) {
10985
+ return { ok: false, error: "Missing content for write_file" };
10986
+ }
10987
+ fs.mkdirSync(path2.dirname(safe), { recursive: true });
10988
+ fs.writeFileSync(safe, interrupt.content, "utf8");
10989
+ return { ok: true, content: "written" };
10990
+ }
10991
+ default:
10992
+ return { ok: false, error: `Unknown action: ${interrupt.action}` };
10993
+ }
10994
+ } catch (e) {
10995
+ return { ok: false, error: sanitizeLocalFileError(e) };
10996
+ }
10997
+ }
10998
+ function sanitizeLocalFileError(e) {
10999
+ if (e && typeof e === "object" && "code" in e) {
11000
+ const code = e.code;
11001
+ if (code === "ENOENT") return "File or directory not found";
11002
+ if (code === "EACCES" || code === "EPERM") return "Permission denied";
11003
+ if (code === "EISDIR") return "Not a file";
11004
+ if (code === "ENOTDIR") return "Not a directory";
11005
+ }
11006
+ return "Local file operation failed";
11007
+ }
11008
+ function isCliLocalFileInterrupt(value) {
11009
+ if (typeof value !== "object" || value === null) return false;
11010
+ const v = value;
11011
+ return v.type === "cli_local_file" && typeof v.action === "string" && typeof v.path === "string";
11012
+ }
11013
+
11014
+ // src/lib/trusted-dirs.ts
11015
+ import fs2 from "fs";
11016
+ import os from "os";
11017
+ import path3 from "path";
11018
+ import readline from "readline";
11019
+ var STORE_VERSION = 1;
11020
+ function storePath() {
11021
+ const base = process.env.XDG_CONFIG_HOME ?? (process.platform === "win32" ? path3.join(process.env.APPDATA ?? os.homedir(), "gravityrail") : path3.join(os.homedir(), ".config", "gravityrail"));
11022
+ return path3.join(base, "trusted-dirs.json");
11023
+ }
11024
+ function loadTrustedDirsStore() {
11025
+ const file = storePath();
11026
+ try {
11027
+ const raw = JSON.parse(fs2.readFileSync(file, "utf8"));
11028
+ if (raw?.version === STORE_VERSION && Array.isArray(raw.always)) {
11029
+ return { version: STORE_VERSION, always: raw.always.map((p) => path3.resolve(p)) };
11030
+ }
11031
+ } catch {
11032
+ }
11033
+ return { version: STORE_VERSION, always: [] };
11034
+ }
11035
+ function saveTrustedDirsStore(store) {
11036
+ const file = storePath();
11037
+ const dir = path3.dirname(file);
11038
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
11039
+ fs2.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
11040
+ }
11041
+ function isDirectoryTrusted(dirPath, opts) {
11042
+ if (opts?.trustCwdFlag) return true;
11043
+ const resolved = path3.resolve(dirPath);
11044
+ const store = loadTrustedDirsStore();
11045
+ return store.always.includes(resolved);
11046
+ }
11047
+ async function promptTrustInteractive(dirPath) {
11048
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
11049
+ const question = (q) => new Promise((resolve) => rl.question(q, resolve));
11050
+ process.stderr.write(
11051
+ `
11052
+ Gravity Rail may read files in:
11053
+ ${dirPath}
11054
+
11055
+ File contents can influence the assistant. Do you trust the files in this directory?
11056
+ [1] Trust once (this session)
11057
+ [2] Trust always (remember for this path)
11058
+ [3] No \u2014 do not read local files
11059
+
11060
+ Choice [1/2/3] (default 3): `
11061
+ );
11062
+ const answer = (await question("")).trim() || "3";
11063
+ rl.close();
11064
+ if (answer === "1") return "once";
11065
+ if (answer === "2") return "always";
11066
+ return "denied";
11067
+ }
11068
+ var sessionTrusted = /* @__PURE__ */ new Set();
11069
+ async function resolveDirectoryTrust(opts) {
11070
+ const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
11071
+ if (isDirectoryTrusted(dir, { trustCwdFlag: opts.trustCwd })) return "trusted";
11072
+ if (sessionTrusted.has(dir)) return "trusted";
11073
+ if (opts.trustDir && path3.resolve(opts.trustDir) === dir) {
11074
+ sessionTrusted.add(dir);
11075
+ return "trusted";
11076
+ }
11077
+ if (opts.trustCwd) {
11078
+ sessionTrusted.add(dir);
11079
+ return "trusted";
11080
+ }
11081
+ if (!process.stdin.isTTY || opts.nonInteractive) {
11082
+ return "denied";
11083
+ }
11084
+ const decision = await promptTrustInteractive(dir);
11085
+ if (decision === "denied") {
11086
+ return "denied";
11087
+ }
11088
+ if (decision === "always") {
11089
+ const store = loadTrustedDirsStore();
11090
+ if (!store.always.includes(dir)) {
11091
+ store.always.push(dir);
11092
+ saveTrustedDirsStore(store);
11093
+ }
11094
+ }
11095
+ sessionTrusted.add(dir);
11096
+ return "trusted";
11097
+ }
11098
+ async function requireDirectoryTrust(opts) {
11099
+ const { CLIError: CLIError2 } = await import("./errors-4MAJGD3C.js");
11100
+ const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
11101
+ const status = await resolveDirectoryTrust(opts);
11102
+ if (status === "trusted") return;
11103
+ throw new CLIError2(
11104
+ `Directory not trusted: ${dir}
11105
+
11106
+ Re-run with --trust-cwd (or --trust-dir <path>) after reviewing files in that directory, or run interactively to approve trust when a local file tool is first used.`
11107
+ );
11108
+ }
11109
+
11110
+ // src/lib/cli-hitl.ts
11111
+ function extractHumanInputRequest(event) {
11112
+ const type = String(event.type ?? "").toUpperCase();
11113
+ if (type === "CUSTOM" && event.name === "human_input_request") {
11114
+ return event.value;
11115
+ }
11116
+ if (event.name === "human_input_request") {
11117
+ return event.value;
11118
+ }
11119
+ return null;
11120
+ }
11121
+ async function handleCliHitlInterrupt(value, opts) {
11122
+ if (!value || typeof value !== "object") return null;
11123
+ const payload = value;
11124
+ if (isCliLocalFileInterrupt(payload)) {
11125
+ const interrupt = payload;
11126
+ if (typeof payload.interrupt_id === "string") {
11127
+ interrupt.interrupt_id = payload.interrupt_id;
11128
+ }
11129
+ return handleCliLocalFile(interrupt, opts);
11130
+ }
11131
+ if (payload.type === "present_choice") {
11132
+ return handlePresentChoiceTerminal(payload);
11133
+ }
11134
+ return null;
11135
+ }
11136
+ async function handleCliLocalFile(interrupt, opts) {
11137
+ const trust = await resolveDirectoryTrust({
11138
+ cwd: process.cwd(),
11139
+ trustCwd: opts.trustCwd,
11140
+ trustDir: opts.trustDir,
11141
+ // Interactive sessions: prompt on first local-file use (review feedback).
11142
+ nonInteractive: opts.nonInteractive ?? process.stdin.isTTY !== true
11143
+ });
11144
+ if (trust !== "trusted") {
11145
+ return {
11146
+ resumeValue: JSON.stringify({
11147
+ ok: false,
11148
+ error: "Directory not trusted for local file access. Re-run with --trust-cwd or --trust-dir <path>, or approve trust interactively when prompted."
11149
+ }),
11150
+ resumeInterruptId: interrupt.interrupt_id
11151
+ };
11152
+ }
11153
+ process.stderr.write(`
11154
+ [local ${interrupt.action}] ${interrupt.path}
11155
+ `);
11156
+ const result = executeCliLocalFileAction(process.cwd(), interrupt, {
11157
+ allowWrite: opts.allowWrite
11158
+ });
11159
+ return {
11160
+ resumeValue: JSON.stringify(result),
11161
+ resumeInterruptId: interrupt.interrupt_id
11162
+ };
11163
+ }
11164
+ async function handlePresentChoiceTerminal(payload) {
11165
+ const prompt = typeof payload.user_prompt === "string" && payload.user_prompt || typeof payload.description === "string" && payload.description || "Choose an option";
11166
+ const markup = typeof payload.openui_markup === "string" ? payload.openui_markup : "";
11167
+ process.stderr.write(`
11168
+ ${prompt}
11169
+ `);
11170
+ if (markup) {
11171
+ process.stderr.write(`${markup.slice(0, 2e3)}
11172
+ `);
11173
+ }
11174
+ if (!process.stdin.isTTY) {
11175
+ return {
11176
+ resumeValue: "",
11177
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
11178
+ };
11179
+ }
11180
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
11181
+ const answer = await new Promise((resolve) => {
11182
+ rl.question("Your choice (value): ", resolve);
11183
+ });
11184
+ rl.close();
11185
+ return {
11186
+ resumeValue: answer.trim(),
11187
+ resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
11188
+ };
11189
+ }
11190
+
11191
+ // src/lib/concierge-agui-loop.ts
11192
+ var MAX_HITL_ROUNDS = 25;
11193
+ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
11194
+ let streamText = "";
11195
+ let hasToolCalls = false;
11196
+ let resume;
11197
+ for (let round = 0; round < MAX_HITL_ROUNDS; round++) {
11198
+ let hitlPayload = null;
11199
+ await client.streamRequest(
11200
+ resume ? {
11201
+ messages: [],
11202
+ resumeValue: resume.resumeValue,
11203
+ resumeInterruptId: resume.resumeInterruptId
11204
+ } : { messages: initialMessages },
11205
+ (event) => {
11206
+ onEvent(event);
11207
+ const delta = getTextDelta(event);
11208
+ if (delta) streamText += delta;
11209
+ const type = event.type?.toLowerCase() ?? "";
11210
+ if (type === "tool_call_start") hasToolCalls = true;
11211
+ const errorMsg = getErrorMessage(event);
11212
+ if (errorMsg) process.stderr.write(`\x1B[31mError: ${errorMsg}\x1B[0m
11213
+ `);
11214
+ const hitl = extractHumanInputRequest(event);
11215
+ if (hitl) hitlPayload = hitl;
11216
+ },
11217
+ { autoApprove: opts.autoApprove }
11218
+ );
11219
+ if (!hitlPayload) {
11220
+ break;
11221
+ }
11222
+ const next = await handleCliHitlInterrupt(hitlPayload, {
11223
+ allowWrite: opts.allowWrite,
11224
+ autoApprove: opts.autoApprove,
11225
+ trustCwd: opts.trustCwd,
11226
+ trustDir: opts.trustDir,
11227
+ nonInteractive: opts.nonInteractive
11228
+ });
11229
+ if (!next) {
11230
+ process.stderr.write(
11231
+ " [HITL] Unsupported interrupt in CLI \u2014 open app.gravityrail.com/setup to continue.\n"
11232
+ );
11233
+ break;
11234
+ }
11235
+ resume = next;
11236
+ streamText = "";
11237
+ }
11238
+ return { streamText, hasToolCalls };
11239
+ }
11240
+
10782
11241
  // src/lib/chat-session.ts
10783
- import * as readline2 from "readline";
11242
+ import * as readline4 from "readline";
10784
11243
 
10785
11244
  // src/lib/realtime-client.ts
10786
11245
  import { EventEmitter } from "events";
@@ -11813,7 +12272,7 @@ function buildDebugSummary(_direction, msg) {
11813
12272
  // src/lib/audio.ts
11814
12273
  import { spawn, execSync as execSync2, spawnSync } from "child_process";
11815
12274
  import { EventEmitter as EventEmitter2 } from "events";
11816
- import * as readline from "readline";
12275
+ import * as readline3 from "readline";
11817
12276
  var SAMPLE_RATE = 24e3;
11818
12277
  var MIC_CHUNK_BYTES = 4800;
11819
12278
  function checkSoxAvailable() {
@@ -11865,7 +12324,7 @@ async function selectDevice(devices, label) {
11865
12324
  if (devices.length <= 1) {
11866
12325
  return devices[0];
11867
12326
  }
11868
- const rl = readline.createInterface({
12327
+ const rl = readline3.createInterface({
11869
12328
  input: process.stdin,
11870
12329
  output: process.stderr,
11871
12330
  terminal: true
@@ -12144,56 +12603,52 @@ async function runChatSession(session, args) {
12144
12603
  "Interactive mode requires a TTY. Use --message for non-interactive mode."
12145
12604
  );
12146
12605
  }
12606
+ const allowWrite = !!args["allow-write"] || !!session.allowWrite;
12147
12607
  if (message) {
12148
- await singleTurnChatSession(session, message, debug, autoApprove);
12608
+ await singleTurnChatSession(session, message, debug, autoApprove, allowWrite);
12149
12609
  } else if (isVoice) {
12150
12610
  await voiceChatSession(session, vadMode(args), args, debug);
12151
12611
  } else if (isRealtime) {
12152
12612
  await realtimeChatSession(session, vadMode(args), debug);
12153
12613
  } else {
12154
- await interactiveChatSession(session, debug);
12614
+ await interactiveChatSession(session, debug, autoApprove, allowWrite);
12155
12615
  }
12156
12616
  }
12157
12617
  function vadMode(args) {
12158
12618
  return typeof args.vad === "string" ? args.vad : "server";
12159
12619
  }
12160
- async function singleTurnChatSession(session, message, debug, autoApprove = false) {
12620
+ async function singleTurnChatSession(session, message, debug, autoApprove = false, allowWrite = false) {
12161
12621
  const aguiClient = new AGUIClient({
12162
12622
  baseUrl: session.ctx.envConfig.apiUrl,
12623
+ workspaceUuid: session.workspaceUuid,
12624
+ chatId: session.chatId,
12163
12625
  endpointUrl: session.aguiEndpointUrl,
12164
12626
  accessToken: session.ctx.accessToken,
12165
12627
  debug,
12166
- model: session.model
12628
+ model: session.model,
12629
+ appContext: session.appContext
12167
12630
  });
12168
- let streamText = "";
12169
- let hasToolCalls = false;
12170
- let eventCount = 0;
12171
- const logFile = process.env.GR_SSE_LOG;
12172
- const fs = logFile ? await import("fs") : null;
12173
- const logStream = logFile && fs ? fs.createWriteStream(logFile, { flags: "a" }) : null;
12174
- await aguiClient.sendMessage(
12175
- [{ role: "user", content: message }],
12176
- (event) => {
12177
- eventCount++;
12178
- if (logStream) logStream.write(JSON.stringify(event) + "\n");
12179
- const delta = getTextDelta(event);
12180
- if (delta) streamText += delta;
12181
- const type = event.type?.toLowerCase() ?? "";
12182
- if (type === "tool_call_start") {
12183
- hasToolCalls = true;
12184
- const name = event.name ?? "";
12185
- process.stderr.write(` [${name}]
12186
- `);
12187
- }
12188
- const errorMsg = getErrorMessage(event);
12189
- if (errorMsg) process.stderr.write(`\x1B[31mError: ${errorMsg}\x1B[0m
12631
+ const onEvent = (event) => {
12632
+ const type = event.type?.toLowerCase() ?? "";
12633
+ if (type === "tool_call_start") {
12634
+ const name = event.toolCallName ?? event.name ?? "";
12635
+ process.stderr.write(` [${name}]
12190
12636
  `);
12191
- },
12192
- { autoApprove }
12637
+ }
12638
+ };
12639
+ const { streamText, hasToolCalls } = await runConciergeAguiLoop(
12640
+ aguiClient,
12641
+ [{ role: "user", content: message }],
12642
+ onEvent,
12643
+ {
12644
+ autoApprove,
12645
+ allowWrite: allowWrite || !!session.allowWrite,
12646
+ debug,
12647
+ trustCwd: session.trustCwd,
12648
+ trustDir: session.trustDir,
12649
+ nonInteractive: true
12650
+ }
12193
12651
  );
12194
- if (logStream) logStream.end();
12195
- if (debug) process.stderr.write(` (${eventCount} SSE events)
12196
- `);
12197
12652
  if (streamText) {
12198
12653
  process.stdout.write(`\x1B[32mAssistant:\x1B[0m ${streamText}
12199
12654
  `);
@@ -12227,13 +12682,16 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
12227
12682
  `);
12228
12683
  }
12229
12684
  }
12230
- async function interactiveChatSession(session, debug) {
12685
+ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
12231
12686
  const aguiClient = new AGUIClient({
12232
12687
  baseUrl: session.ctx.envConfig.apiUrl,
12688
+ workspaceUuid: session.workspaceUuid,
12689
+ chatId: session.chatId,
12233
12690
  endpointUrl: session.aguiEndpointUrl,
12234
12691
  accessToken: session.ctx.accessToken,
12235
12692
  debug,
12236
- model: session.model
12693
+ model: session.model,
12694
+ appContext: session.appContext
12237
12695
  });
12238
12696
  clearScreen();
12239
12697
  console.log("=".repeat(60));
@@ -12261,7 +12719,7 @@ async function interactiveChatSession(session, debug) {
12261
12719
  conversationMessages.push({ role: msg.role, content: text });
12262
12720
  }
12263
12721
  }
12264
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
12722
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
12265
12723
  return new Promise((resolve) => {
12266
12724
  const prompt = () => {
12267
12725
  rl.question("\x1B[34mYou:\x1B[0m ", async (input) => {
@@ -12279,18 +12737,34 @@ async function interactiveChatSession(session, debug) {
12279
12737
  process.stdout.write("\n\x1B[32mAssistant:\x1B[0m ");
12280
12738
  let assistantContent = "";
12281
12739
  try {
12282
- await aguiClient.sendMessage(conversationMessages, (event) => {
12283
- const delta = getTextDelta(event);
12284
- if (delta) {
12285
- process.stdout.write(delta);
12286
- assistantContent += delta;
12287
- }
12288
- const errorMsg = getErrorMessage(event);
12289
- if (errorMsg) console.log(`
12740
+ const { streamText } = await runConciergeAguiLoop(
12741
+ aguiClient,
12742
+ conversationMessages,
12743
+ (event) => {
12744
+ const delta = getTextDelta(event);
12745
+ if (delta) {
12746
+ process.stdout.write(delta);
12747
+ assistantContent += delta;
12748
+ }
12749
+ const errorMsg = getErrorMessage(event);
12750
+ if (errorMsg) console.log(`
12290
12751
  \x1B[31mError: ${errorMsg}\x1B[0m`);
12291
- });
12292
- if (assistantContent)
12752
+ },
12753
+ {
12754
+ autoApprove,
12755
+ allowWrite: allowWrite || !!session.allowWrite,
12756
+ debug,
12757
+ trustCwd: session.trustCwd,
12758
+ trustDir: session.trustDir
12759
+ }
12760
+ );
12761
+ if (streamText && !assistantContent) {
12762
+ assistantContent = streamText;
12763
+ process.stdout.write(streamText);
12764
+ }
12765
+ if (assistantContent) {
12293
12766
  conversationMessages.push({ role: "assistant", content: assistantContent });
12767
+ }
12294
12768
  console.log("\n");
12295
12769
  } catch (error) {
12296
12770
  console.log(
@@ -12550,13 +13024,14 @@ async function conciergeCommand(hctx) {
12550
13024
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
12551
13025
  const message = typeof args.message === "string" ? args.message : void 0;
12552
13026
  const chat = await getOrCreateConciergeChat(ctx, chatIdArg, !!message);
12553
- const session = buildConciergeSession(ctx, chat);
13027
+ const session = buildConciergeSession(ctx, chat, args);
12554
13028
  await runChatSession(session, args);
12555
13029
  }
12556
- function buildConciergeSession(ctx, chat) {
13030
+ function buildConciergeSession(ctx, chat, args) {
12557
13031
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
12558
13032
  return {
12559
13033
  ctx,
13034
+ workspaceUuid: "",
12560
13035
  chatId: chat.id,
12561
13036
  chatUuid: chat.uuid,
12562
13037
  chatTitle: chat.title || chat.uuid,
@@ -12564,8 +13039,12 @@ function buildConciergeSession(ctx, chat) {
12564
13039
  wsUrl: `${wsBase}/api/v2/concierge/voice/stream-ws`,
12565
13040
  loadMessages: () => getConciergeChatMessages(ctx, chat.id).catch(() => []),
12566
13041
  label: "Concierge",
12567
- model: void 0
13042
+ model: void 0,
12568
13043
  // concierge doesn't send model field
13044
+ appContext: buildCliConciergeAppContext(),
13045
+ allowWrite: true,
13046
+ trustCwd: !!args["trust-cwd"],
13047
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
12569
13048
  };
12570
13049
  }
12571
13050
  async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
@@ -12601,6 +13080,95 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
12601
13080
  return selectedChat;
12602
13081
  }
12603
13082
 
13083
+ // src/commands/get-started.ts
13084
+ var WEB_SETUP = "https://app.gravityrail.com/setup";
13085
+ var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
13086
+ var HUMAN_GUIDE = `Gravity Rail \u2014 getting started
13087
+
13088
+ 1. Authenticate: gr login --env prod
13089
+ Confirm: gr whoami
13090
+
13091
+ 2. Recommended path \u2014 concierge (no workspace required):
13092
+ gr concierge chat --allow-write
13093
+ The concierge can create your org (auto-created on first login), list products,
13094
+ start Stripe checkout, and create your first workspace.
13095
+
13096
+ 3. Verify workspaces: gr workspaces list
13097
+
13098
+ 4. Web wizards (human-friendly): ${WEB_SETUP} and ${WEB_GET_STARTED}
13099
+
13100
+ Headless boundary: Stripe checkout needs a human in a browser \u2014 the concierge returns
13101
+ a checkout URL; an agent should hand that link to the user, not loop on payment.
13102
+
13103
+ Agent-readable steps: gr get-started --format llm
13104
+ `;
13105
+ var LLM_GUIDE = `# Gravity Rail CLI bootstrap (agent instructions)
13106
+
13107
+ ## Preconditions
13108
+ - Install: \`npm install -g @gravity-rail/cli\` or \`npx @gravity-rail/cli\`
13109
+ - Authenticate: \`gr login --env prod\` (interactive OAuth). Verify with \`gr whoami\`.
13110
+ - **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\`.
13111
+
13112
+ ## Important facts
13113
+ - **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.
13114
+ - Use \`--env dev|staging|prod\` (default prod) so credentials match the target; \`gr whoami\` shows the active env.
13115
+ - After login, an **organization is created automatically** \u2014 do not create a duplicate org unless \`gr organizations list\` is empty.
13116
+ - **Workspace is required** for workflow/agent/task commands (\`-w <workspace-uuid>\`).
13117
+ - **Concierge does not require \`-w\`** \u2014 use it for first-run setup.
13118
+
13119
+ ## Recommended path (concierge-driven)
13120
+ \`\`\`bash
13121
+ gr concierge chat --allow-write --message "Help me set up my first Gravity Rail workspace"
13122
+ \`\`\`
13123
+ Flags for unattended turns:
13124
+ - \`--message\` / \`-m\`: single-turn (non-interactive)
13125
+ - \`--auto-approve\`: skip HITL tool approval where policy allows
13126
+ - \`--allow-write\`: required for create/update tools
13127
+
13128
+ The concierge can: list/create orgs, list org/workspace products, create Stripe checkout sessions, create workspace specs, and \`create_workspace\`.
13129
+
13130
+ ## Manual CLI path (if not using concierge)
13131
+ 1. \`gr whoami\` \u2014 confirm account
13132
+ 2. \`gr organizations list\` \u2014 note org slug/UUID
13133
+ 3. \`gr concierge chat\` OR web: ${WEB_GET_STARTED}
13134
+ 4. Product purchase: concierge returns Stripe checkout URL \u2192 **human must complete in browser**
13135
+ 5. \`gr workspaces list\` \u2014 pick workspace UUID for \`-w\`
13136
+
13137
+ ## Support (account-level, no workspace)
13138
+ \`\`\`bash
13139
+ gr support create --type question --title "..." --description "..." --allow-write
13140
+ gr support list
13141
+ \`\`\`
13142
+
13143
+ ## Web fallbacks
13144
+ - Concierge / setup: ${WEB_SETUP}
13145
+ - Wizard: ${WEB_GET_STARTED}
13146
+
13147
+ ## Headless limits (do not retry forever)
13148
+ - Stripe payment / checkout: requires human browser
13149
+ - OAuth login: requires human browser
13150
+ - Directory trust for future local-file tools: use \`--trust-cwd\` only after user confirms files are safe
13151
+
13152
+ ## Local project files (CLI only)
13153
+ After directory trust + \`--allow-write\`, the concierge can use \`read_local_file\`, \`list_local_dir\`, \`write_local_file\` (client-executed via HITL).
13154
+
13155
+ ## Verify success
13156
+ \`gr workspaces list\` then \`gr workflows list -w <uuid>\` or \`gr agents list -w <uuid>\`
13157
+ `;
13158
+ async function getStartedCommand(hctx) {
13159
+ const format = hctx.args.format ?? hctx.args.agent;
13160
+ const isLlm = format === "llm" || format === "agent" || hctx.args["format"] === "llm" || !!hctx.args.agent;
13161
+ if (!isLlm) {
13162
+ try {
13163
+ requireAuth(hctx.ctx);
13164
+ } catch {
13165
+ }
13166
+ process.stdout.write(HUMAN_GUIDE);
13167
+ return;
13168
+ }
13169
+ process.stdout.write(LLM_GUIDE);
13170
+ }
13171
+
12604
13172
  // src/commands/dev.ts
12605
13173
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
12606
13174
  import { execSync as execSync3 } from "child_process";
@@ -12684,8 +13252,17 @@ async function manageCommand(hctx) {
12684
13252
  const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
12685
13253
  const message = typeof args.message === "string" ? args.message : void 0;
12686
13254
  const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
12687
- const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager");
13255
+ const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
13256
+ appContext: buildCliConciergeAppContext(),
13257
+ allowWrite: true,
13258
+ trustCwd: !!args["trust-cwd"],
13259
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
13260
+ });
13261
+ process.stderr.write(`Chat #${session.chatId}
13262
+ `);
12688
13263
  await runChatSession(session, args);
13264
+ process.stderr.write(`Chat #${session.chatId}
13265
+ `);
12689
13266
  }
12690
13267
  async function chatCommand(hctx) {
12691
13268
  const ctx = requireAuth(hctx.ctx);
@@ -12710,10 +13287,11 @@ async function chatCommand(hctx) {
12710
13287
  const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
12711
13288
  await runChatSession(session, args);
12712
13289
  }
12713
- function buildWorkspaceSession(ctx, workspace, chat, label) {
13290
+ function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
12714
13291
  const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
12715
13292
  return {
12716
13293
  ctx,
13294
+ workspaceUuid: workspace,
12717
13295
  chatId: chat.id,
12718
13296
  chatUuid: chat.uuid,
12719
13297
  chatTitle: chat.title || chat.uuid,
@@ -12721,7 +13299,8 @@ function buildWorkspaceSession(ctx, workspace, chat, label) {
12721
13299
  wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
12722
13300
  loadMessages: () => ctx.apiClient.getChatMessages(workspace, chat.id, 50).catch(() => []),
12723
13301
  label,
12724
- model: "manager"
13302
+ model: "manager",
13303
+ ...extra
12725
13304
  };
12726
13305
  }
12727
13306
  async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
@@ -12819,11 +13398,15 @@ var COLUMNS35 = [
12819
13398
  { header: "NAME", getValue: (a) => a.name || "", maxWidth: 30 },
12820
13399
  { header: "EMAIL", getValue: (a) => a.email || "", maxWidth: 40 },
12821
13400
  { header: "UUID", getValue: (a) => a.uuid },
13401
+ { header: "ENV", getValue: () => "" },
12822
13402
  { header: "SUPERUSER", getValue: (a) => a.isSuperuser ? "yes" : "no" }
12823
13403
  ];
12824
13404
  async function whoamiCommand(ctx) {
12825
13405
  const account = await ctx.apiClient.getUserAccount();
12826
- outputSingle(account, ctx, COLUMNS35);
13406
+ const columns = COLUMNS35.map(
13407
+ (col) => col.header === "ENV" ? { ...col, getValue: () => ctx.env } : col
13408
+ );
13409
+ outputSingle(account, ctx, columns);
12827
13410
  }
12828
13411
  export {
12829
13412
  AuthRequiredError,
@@ -12833,6 +13416,7 @@ export {
12833
13416
  LIST_PAGINATION_ARGS,
12834
13417
  PAGINATION_ARGS,
12835
13418
  authBrowserCommand,
13419
+ buildCliConciergeAppContext,
12836
13420
  chatCommand,
12837
13421
  conciergeCommand,
12838
13422
  createAuthenticatedContext,
@@ -12849,8 +13433,10 @@ export {
12849
13433
  formatPaginationWarning,
12850
13434
  getConciergeChat,
12851
13435
  getConciergeChatMessages,
13436
+ getStartedCommand,
12852
13437
  handleError,
12853
13438
  isCursorPaginatedResponse,
13439
+ isDirectoryTrusted,
12854
13440
  isListResult,
12855
13441
  isPagePaginatedResponse,
12856
13442
  isPaginatedResponse,
@@ -12867,6 +13453,7 @@ export {
12867
13453
  readJsonInput,
12868
13454
  registerPublicDomains,
12869
13455
  requireAuth,
13456
+ requireDirectoryTrust,
12870
13457
  requireNumber,
12871
13458
  requireString,
12872
13459
  simpleCrudCommands,