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