@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/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-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 = [...
|
|
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
|
-
|
|
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
|
|
253
|
-
|
|
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
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
|
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 (
|
|
2264
|
-
return
|
|
2230
|
+
if (process.stdin.isTTY === true) {
|
|
2231
|
+
return void 0;
|
|
2265
2232
|
}
|
|
2266
|
-
return
|
|
2233
|
+
return readStdinIfPiped();
|
|
2267
2234
|
}
|
|
2268
|
-
function
|
|
2235
|
+
function readStdinIfPiped() {
|
|
2269
2236
|
return new Promise((resolve, reject) => {
|
|
2270
2237
|
let data = "";
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
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
|
-
|
|
2246
|
+
finish(void 0);
|
|
2278
2247
|
return;
|
|
2279
2248
|
}
|
|
2280
2249
|
try {
|
|
2281
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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: "
|
|
8737
|
-
{ header: "
|
|
8738
|
-
{ header: "
|
|
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
|
|
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
|
|
8760
|
-
|
|
8761
|
-
|
|
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
|
|
8774
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
8820
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|