@rooaak/cli 0.1.0-beta.1 → 0.1.0-beta.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/init/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/init/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAgTzD,wBAAsB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8WtF"}
|
|
@@ -41,7 +41,7 @@ function noTtyUsageError(message, details = {}) {
|
|
|
41
41
|
details,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
async function
|
|
44
|
+
async function requireInitTransport(ctx, r, argvApiKey) {
|
|
45
45
|
const store = createProfileStore({ configPath: ctx.profileConfigPath });
|
|
46
46
|
const cfgRes = store.readConfig();
|
|
47
47
|
if (cfgRes.kind === "corrupt") {
|
|
@@ -55,19 +55,27 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
55
55
|
env: ctx.env.ROOAAK_BASE_URL,
|
|
56
56
|
profile: profile?.baseUrl,
|
|
57
57
|
});
|
|
58
|
-
async function
|
|
58
|
+
async function validateInitKey(candidate) {
|
|
59
59
|
const transport = createTransport({ baseUrl, apiKey: candidate, fetchFn: ctx.fetchFn });
|
|
60
|
-
const res = await transport.getJson("/api/v1/
|
|
60
|
+
const res = await transport.getJson("/api/v1/agents?limit=1");
|
|
61
61
|
if (!res.ok) {
|
|
62
|
-
return { ok: false,
|
|
62
|
+
return { ok: false, status: res.status, requestId: res.requestId, err: res.error };
|
|
63
63
|
}
|
|
64
64
|
return { ok: true, transport, meta: { status: res.status, requestId: res.requestId } };
|
|
65
65
|
}
|
|
66
66
|
if (typeof argvApiKey === "string" && argvApiKey.length > 0) {
|
|
67
|
-
const v = await
|
|
68
|
-
if (v.ok)
|
|
69
|
-
return {
|
|
70
|
-
|
|
67
|
+
const v = await validateInitKey(argvApiKey);
|
|
68
|
+
if (v.ok) {
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
transport: v.transport,
|
|
72
|
+
baseUrl,
|
|
73
|
+
apiKey: argvApiKey,
|
|
74
|
+
keySource: "argv",
|
|
75
|
+
requestMeta: v.meta,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const status = v.status;
|
|
71
79
|
if (status === 401) {
|
|
72
80
|
return {
|
|
73
81
|
ok: false,
|
|
@@ -75,7 +83,7 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
75
83
|
err: normalizeInitError({
|
|
76
84
|
code: "auth.invalid_api_key",
|
|
77
85
|
message: "Invalid API key",
|
|
78
|
-
meta: { status: v.
|
|
86
|
+
meta: { status: v.status, requestId: v.requestId },
|
|
79
87
|
}),
|
|
80
88
|
};
|
|
81
89
|
}
|
|
@@ -84,21 +92,30 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
84
92
|
ok: false,
|
|
85
93
|
exit: ExitCode.Permission,
|
|
86
94
|
err: normalizeInitError({
|
|
87
|
-
code: "permission.
|
|
88
|
-
message: "
|
|
89
|
-
details: { next: "Use
|
|
90
|
-
meta: { status: v.
|
|
95
|
+
code: "permission.forbidden",
|
|
96
|
+
message: "API key lacks required scope: project:agent",
|
|
97
|
+
details: { next: "Use a project API key with scope project:agent." },
|
|
98
|
+
meta: { status: v.status, requestId: v.requestId },
|
|
91
99
|
}),
|
|
92
100
|
};
|
|
93
101
|
}
|
|
94
|
-
return { ok: false, exit: exitCodeForError(v.
|
|
102
|
+
return { ok: false, exit: exitCodeForError(v.err), err: v.err };
|
|
95
103
|
}
|
|
96
104
|
if (profile?.apiKey) {
|
|
97
|
-
const v = await
|
|
98
|
-
if (v.ok)
|
|
99
|
-
return {
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
const v = await validateInitKey(profile.apiKey);
|
|
106
|
+
if (v.ok) {
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
transport: v.transport,
|
|
110
|
+
baseUrl,
|
|
111
|
+
apiKey: profile.apiKey,
|
|
112
|
+
keySource: "profile",
|
|
113
|
+
sourceProfileName: config?.currentProfile,
|
|
114
|
+
requestMeta: v.meta,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const status = v.status;
|
|
118
|
+
const err = v.err;
|
|
102
119
|
if (ctx.flags.nonInteractive) {
|
|
103
120
|
if (status === 401) {
|
|
104
121
|
return {
|
|
@@ -107,7 +124,7 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
107
124
|
err: normalizeInitError({
|
|
108
125
|
code: "auth.invalid_api_key",
|
|
109
126
|
message: "Invalid API key",
|
|
110
|
-
meta: { status: v.
|
|
127
|
+
meta: { status: v.status, requestId: v.requestId },
|
|
111
128
|
}),
|
|
112
129
|
};
|
|
113
130
|
}
|
|
@@ -116,23 +133,21 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
116
133
|
ok: false,
|
|
117
134
|
exit: ExitCode.Permission,
|
|
118
135
|
err: normalizeInitError({
|
|
119
|
-
code: "permission.
|
|
120
|
-
message:
|
|
121
|
-
details: {
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
meta: { status: v.http?.status, requestId: v.http?.requestId },
|
|
136
|
+
code: "permission.forbidden",
|
|
137
|
+
message: "API key lacks required scope: project:agent",
|
|
138
|
+
details: { next: "Use a project API key with scope project:agent." },
|
|
139
|
+
meta: { status: v.status, requestId: v.requestId },
|
|
125
140
|
}),
|
|
126
141
|
};
|
|
127
142
|
}
|
|
128
143
|
return { ok: false, exit: exitCodeForError(err), err };
|
|
129
144
|
}
|
|
130
|
-
// Interactive: allow recovery by prompting for
|
|
145
|
+
// Interactive: allow recovery by prompting for another key on auth/permission failures.
|
|
131
146
|
if (status === 401) {
|
|
132
|
-
r.warn("Current profile API key is invalid. You'll be prompted for an
|
|
147
|
+
r.warn("Current profile API key is invalid. You'll be prompted for an API key.");
|
|
133
148
|
}
|
|
134
149
|
else if (status === 403) {
|
|
135
|
-
r.warn("Current profile API key cannot
|
|
150
|
+
r.warn("Current profile API key cannot run init. You'll be prompted for a key with project:agent scope.");
|
|
136
151
|
}
|
|
137
152
|
else {
|
|
138
153
|
// Network/transient failures won't be fixed by prompting.
|
|
@@ -156,107 +171,32 @@ async function requireAdminTransport(ctx, r, argvApiKey) {
|
|
|
156
171
|
err: noTtyUsageError("Cannot prompt for API key because stdin is not a TTY. Re-run with --non-interactive and provide --api-key, or run `rooaak auth login --api-key ...` from an interactive terminal."),
|
|
157
172
|
};
|
|
158
173
|
}
|
|
159
|
-
// Interactive: prompt until
|
|
174
|
+
// Interactive: prompt until a key that can run init is provided.
|
|
160
175
|
while (true) {
|
|
161
|
-
const entered = await promptSecret({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, "
|
|
162
|
-
const v = await
|
|
163
|
-
if (v.ok)
|
|
164
|
-
return {
|
|
165
|
-
|
|
176
|
+
const entered = await promptSecret({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, "API key", { allowEmpty: false });
|
|
177
|
+
const v = await validateInitKey(entered);
|
|
178
|
+
if (v.ok) {
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
transport: v.transport,
|
|
182
|
+
baseUrl,
|
|
183
|
+
apiKey: entered,
|
|
184
|
+
keySource: "prompt",
|
|
185
|
+
requestMeta: v.meta,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const status = v.status;
|
|
166
189
|
if (status === 401) {
|
|
167
190
|
r.error("Invalid API key. Try again.");
|
|
168
191
|
continue;
|
|
169
192
|
}
|
|
170
193
|
if (status === 403) {
|
|
171
|
-
r.error("That key
|
|
194
|
+
r.error("That key lacks required scope for init. Use a key with project:agent.");
|
|
172
195
|
continue;
|
|
173
196
|
}
|
|
174
|
-
r.error(v.
|
|
197
|
+
r.error(v.err.message ?? "Request failed. Try again.");
|
|
175
198
|
}
|
|
176
199
|
}
|
|
177
|
-
async function findProjectsByExactName(adminTransport, projectName) {
|
|
178
|
-
const matches = [];
|
|
179
|
-
let cursor = null;
|
|
180
|
-
let meta = null;
|
|
181
|
-
while (true) {
|
|
182
|
-
const qs = new URLSearchParams();
|
|
183
|
-
qs.set("limit", "500");
|
|
184
|
-
if (cursor)
|
|
185
|
-
qs.set("cursor", cursor);
|
|
186
|
-
const httpRes = await adminTransport.getJson(`/api/v1/projects?${qs.toString()}`);
|
|
187
|
-
if (!httpRes.ok)
|
|
188
|
-
return { ok: false, err: httpRes.error };
|
|
189
|
-
meta = { status: httpRes.status, requestId: httpRes.requestId };
|
|
190
|
-
const items = Array.isArray(httpRes.data.items) ? httpRes.data.items : [];
|
|
191
|
-
for (const p of items) {
|
|
192
|
-
if (p.name === projectName)
|
|
193
|
-
matches.push(p);
|
|
194
|
-
if (matches.length >= 2) {
|
|
195
|
-
return { ok: true, matches, meta };
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const next = httpRes.data.nextCursor ?? null;
|
|
199
|
-
if (!next)
|
|
200
|
-
return { ok: true, matches, meta };
|
|
201
|
-
cursor = next;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
async function ensureProjectByName(input) {
|
|
205
|
-
const findRes = await findProjectsByExactName(input.adminTransport, input.projectName);
|
|
206
|
-
if (!findRes.ok)
|
|
207
|
-
return { ok: false, err: findRes.err };
|
|
208
|
-
const matches = findRes.matches;
|
|
209
|
-
if (matches.length === 1)
|
|
210
|
-
return { ok: true, project: matches[0], created: false, meta: findRes.meta };
|
|
211
|
-
if (matches.length > 1) {
|
|
212
|
-
return {
|
|
213
|
-
ok: false,
|
|
214
|
-
err: normalizeInitError({
|
|
215
|
-
code: "conflict.duplicate_project_name",
|
|
216
|
-
message: `Multiple projects named '${input.projectName}' exist.`,
|
|
217
|
-
details: { next: "Run `rooaak init` in interactive mode to select a project." },
|
|
218
|
-
meta: findRes.meta,
|
|
219
|
-
}),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
const createRes = await input.adminTransport.requestJson({
|
|
223
|
-
method: "POST",
|
|
224
|
-
path: "/api/v1/projects",
|
|
225
|
-
body: { name: input.projectName },
|
|
226
|
-
});
|
|
227
|
-
if (createRes.ok) {
|
|
228
|
-
return { ok: true, project: { id: createRes.data.id, name: createRes.data.name }, created: true, meta: { status: createRes.status, requestId: createRes.requestId } };
|
|
229
|
-
}
|
|
230
|
-
// Best-effort recovery: if the create failed but the project exists now (race, duplicate name), select it.
|
|
231
|
-
const findAgain = await findProjectsByExactName(input.adminTransport, input.projectName);
|
|
232
|
-
if (findAgain.ok) {
|
|
233
|
-
const againMatches = findAgain.matches;
|
|
234
|
-
if (againMatches.length === 1)
|
|
235
|
-
return { ok: true, project: againMatches[0], created: false, meta: findAgain.meta };
|
|
236
|
-
}
|
|
237
|
-
return { ok: false, err: createRes.error };
|
|
238
|
-
}
|
|
239
|
-
async function createProjectApiKey(input) {
|
|
240
|
-
const httpRes = await input.adminTransport.requestJson({
|
|
241
|
-
method: "POST",
|
|
242
|
-
path: `/api/v1/projects/${input.projectId}/api-keys`,
|
|
243
|
-
body: { name: input.name },
|
|
244
|
-
});
|
|
245
|
-
if (!httpRes.ok)
|
|
246
|
-
return { ok: false, err: httpRes.error };
|
|
247
|
-
const key = isPlainObject(httpRes.data) && typeof httpRes.data.key === "string" ? httpRes.data.key : null;
|
|
248
|
-
if (!key) {
|
|
249
|
-
return {
|
|
250
|
-
ok: false,
|
|
251
|
-
err: normalizeInitError({
|
|
252
|
-
code: "server.invalid_response",
|
|
253
|
-
message: "Server returned an unexpected API key response.",
|
|
254
|
-
meta: { status: httpRes.status, requestId: httpRes.requestId },
|
|
255
|
-
}),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
return { ok: true, apiKey: key, meta: { status: httpRes.status, requestId: httpRes.requestId } };
|
|
259
|
-
}
|
|
260
200
|
function writeProjectProfile(input) {
|
|
261
201
|
const store = createProfileStore({ configPath: input.ctx.profileConfigPath });
|
|
262
202
|
const existingRes = store.readConfig();
|
|
@@ -307,7 +247,7 @@ export async function initCommand(ctx, argv) {
|
|
|
307
247
|
const r = createRenderer(ctx);
|
|
308
248
|
const parsed = parseOptions(argv, [
|
|
309
249
|
{ name: "--help", takesValue: false },
|
|
310
|
-
{ name: "--api-key", takesValue: true }, //
|
|
250
|
+
{ name: "--api-key", takesValue: true }, // must include project:agent
|
|
311
251
|
{ name: "--project-name", takesValue: true },
|
|
312
252
|
{ name: "--agent-name", takesValue: true },
|
|
313
253
|
{ name: "--test-message", takesValue: true },
|
|
@@ -347,14 +287,14 @@ export async function initCommand(ctx, argv) {
|
|
|
347
287
|
command: "init",
|
|
348
288
|
usage: "rooaak init [--api-key <key>] [--project-name <name>] [--agent-name <name>] [--test-message <text>] [--wait-timeout-ms <ms>] [--profile <name>] [--start|--no-start] [--json] [--non-interactive]",
|
|
349
289
|
flags: [
|
|
350
|
-
{ name: "--api-key <key>", description: "
|
|
351
|
-
{ name: "--project-name <name>", description: "
|
|
290
|
+
{ name: "--api-key <key>", description: "Project API key (must include project:agent scope)" },
|
|
291
|
+
{ name: "--project-name <name>", description: "Optional project label for local profile naming/output" },
|
|
352
292
|
{ name: "--agent-name <name>", description: "Agent name to create" },
|
|
353
293
|
{ name: "--test-message <text>", description: "Message to send after creation" },
|
|
354
294
|
{ name: "--wait-timeout-ms <ms>", description: "How long to wait for the first response (default 60000ms; overrides ROOAAK_SYNC_MESSAGE_TIMEOUT_MS)" },
|
|
355
295
|
{ name: "--start", description: "Start the agent (default)" },
|
|
356
296
|
{ name: "--no-start", description: "Skip starting the agent" },
|
|
357
|
-
{ name: "--profile <name>", description: "Profile name to save the
|
|
297
|
+
{ name: "--profile <name>", description: "Profile name to save the API key under" },
|
|
358
298
|
],
|
|
359
299
|
}));
|
|
360
300
|
return ExitCode.Success;
|
|
@@ -364,14 +304,14 @@ export async function initCommand(ctx, argv) {
|
|
|
364
304
|
" rooaak init [flags]",
|
|
365
305
|
"",
|
|
366
306
|
"Flags:",
|
|
367
|
-
" --api-key <key>
|
|
368
|
-
" --project-name <name>
|
|
307
|
+
" --api-key <key> Project API key (must include project:agent scope)",
|
|
308
|
+
" --project-name <name> Optional project label for local profile naming/output",
|
|
369
309
|
" --agent-name <name> Agent name to create",
|
|
370
310
|
" --test-message <text> Message to send after creation",
|
|
371
311
|
" --wait-timeout-ms <ms> How long to wait for the first response (default 60000ms; overrides ROOAAK_SYNC_MESSAGE_TIMEOUT_MS)",
|
|
372
312
|
" --start Start the agent (default)",
|
|
373
313
|
" --no-start Skip starting the agent",
|
|
374
|
-
" --profile <name> Profile name to save
|
|
314
|
+
" --profile <name> Profile name to save API key under",
|
|
375
315
|
"",
|
|
376
316
|
"Global flags:",
|
|
377
317
|
" --json Machine output (single JSON object on stdout)",
|
|
@@ -411,24 +351,7 @@ export async function initCommand(ctx, argv) {
|
|
|
411
351
|
else
|
|
412
352
|
startAgent = await promptConfirm({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, "Start the agent now?", { defaultYes: true });
|
|
413
353
|
if (!projectName) {
|
|
414
|
-
|
|
415
|
-
const err = normalizeLocalUsageError("Missing required flag: --project-name");
|
|
416
|
-
if (ctx.flags.json)
|
|
417
|
-
r.json(toJsonErrorEnvelope(err));
|
|
418
|
-
else
|
|
419
|
-
r.error(err.message);
|
|
420
|
-
return ExitCode.Usage;
|
|
421
|
-
}
|
|
422
|
-
if (ctx.stdinIsTTY !== true) {
|
|
423
|
-
const err = noTtyUsageError("Cannot prompt for --project-name because stdin is not a TTY. Re-run with --non-interactive and provide --project-name.");
|
|
424
|
-
if (ctx.flags.json)
|
|
425
|
-
r.json(toJsonErrorEnvelope(err));
|
|
426
|
-
else
|
|
427
|
-
r.error(err.message);
|
|
428
|
-
return ExitCode.Usage;
|
|
429
|
-
}
|
|
430
|
-
projectName = await promptText({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, "Project name", { defaultValue: safeBasename(ctx.cwd) });
|
|
431
|
-
projectName = projectName.trim();
|
|
354
|
+
projectName = safeBasename(ctx.cwd);
|
|
432
355
|
}
|
|
433
356
|
if (!agentName) {
|
|
434
357
|
if (!interactive) {
|
|
@@ -492,110 +415,42 @@ export async function initCommand(ctx, argv) {
|
|
|
492
415
|
}
|
|
493
416
|
waitTimeoutMs = Math.floor(n);
|
|
494
417
|
}
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
if (!
|
|
418
|
+
const inputApiKey = typeof parsed.values["--api-key"] === "string" ? parsed.values["--api-key"] : undefined;
|
|
419
|
+
const initAuthRes = await requireInitTransport(ctx, r, inputApiKey);
|
|
420
|
+
if (!initAuthRes.ok) {
|
|
498
421
|
if (ctx.flags.json)
|
|
499
|
-
r.json(toJsonErrorEnvelope(
|
|
422
|
+
r.json(toJsonErrorEnvelope(initAuthRes.err));
|
|
500
423
|
else
|
|
501
|
-
r.error(
|
|
502
|
-
return
|
|
503
|
-
}
|
|
504
|
-
const { transport: adminTransport, baseUrl } = adminRes;
|
|
505
|
-
if (interactive) {
|
|
506
|
-
r.warn("This will create/select a project, generate a project API key, create an agent, and send a first message.");
|
|
424
|
+
r.error(initAuthRes.err.message);
|
|
425
|
+
return initAuthRes.exit;
|
|
507
426
|
}
|
|
508
|
-
|
|
509
|
-
|
|
427
|
+
const { baseUrl } = initAuthRes;
|
|
428
|
+
const project = { id: null, name: projectName, created: false };
|
|
429
|
+
let projectApiKey = initAuthRes.apiKey;
|
|
430
|
+
let profileName = initAuthRes.sourceProfileName ?? "default";
|
|
510
431
|
if (interactive) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
432
|
+
r.warn("Using your project API key. This will create an agent and send a first message.");
|
|
433
|
+
}
|
|
434
|
+
const shouldPersistProfile = initAuthRes.keySource !== "profile" || typeof flagProfileName === "string";
|
|
435
|
+
if (shouldPersistProfile) {
|
|
436
|
+
const writeRes = writeProjectProfile({
|
|
437
|
+
ctx,
|
|
438
|
+
r,
|
|
439
|
+
baseUrl,
|
|
440
|
+
apiKey: projectApiKey,
|
|
441
|
+
projectName: project.name,
|
|
442
|
+
profileName: typeof flagProfileName === "string" ? flagProfileName : undefined,
|
|
443
|
+
});
|
|
444
|
+
if (!writeRes.ok) {
|
|
445
|
+
const err = writeRes.err;
|
|
514
446
|
if (ctx.flags.json)
|
|
515
447
|
r.json(toJsonErrorEnvelope(err));
|
|
516
448
|
else
|
|
517
449
|
r.error(err.message);
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
if (findRes.matches.length === 1) {
|
|
521
|
-
const shouldPrompt = ctx.stdinIsTTY === true;
|
|
522
|
-
const useExisting = shouldPrompt
|
|
523
|
-
? await promptConfirm({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, `Project '${projectName}' exists. Use it?`, { defaultYes: true })
|
|
524
|
-
: true;
|
|
525
|
-
if (useExisting) {
|
|
526
|
-
projectRes = { ok: true, project: findRes.matches[0], created: false, meta: findRes.meta };
|
|
527
|
-
}
|
|
528
|
-
else {
|
|
529
|
-
projectName = await promptText({ stderr: ctx.stderr, stdin: ctx.stdin, stdinIsTTY: ctx.stdinIsTTY }, "Project name", { defaultValue: `${projectName}-2` });
|
|
530
|
-
projectName = projectName.trim();
|
|
531
|
-
projectRes = await ensureProjectByName({ adminTransport, projectName });
|
|
532
|
-
}
|
|
450
|
+
return writeRes.exit;
|
|
533
451
|
}
|
|
534
|
-
|
|
535
|
-
projectRes = {
|
|
536
|
-
ok: false,
|
|
537
|
-
err: normalizeInitError({
|
|
538
|
-
code: "conflict.duplicate_project_name",
|
|
539
|
-
message: `Multiple projects named '${projectName}' exist.`,
|
|
540
|
-
details: { next: "Specify a unique --project-name or clean up duplicates." },
|
|
541
|
-
meta: findRes.meta,
|
|
542
|
-
}),
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
projectRes = await ensureProjectByName({ adminTransport, projectName });
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
projectRes = await ensureProjectByName({ adminTransport, projectName });
|
|
452
|
+
profileName = writeRes.profileName;
|
|
551
453
|
}
|
|
552
|
-
if (!projectRes.ok) {
|
|
553
|
-
const err = projectRes.err;
|
|
554
|
-
if (ctx.flags.json)
|
|
555
|
-
r.json(toJsonErrorEnvelope(err));
|
|
556
|
-
else {
|
|
557
|
-
r.error(err.message);
|
|
558
|
-
if (err.code === "permission.forbidden" || err.code.startsWith("permission.")) {
|
|
559
|
-
r.warn("Project management requires an admin API key with scope admin:project.");
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return exitCodeForError(err);
|
|
563
|
-
}
|
|
564
|
-
const project = projectRes.project;
|
|
565
|
-
// Create a project-scoped API key and store it locally.
|
|
566
|
-
const apiKeyRes = await createProjectApiKey({
|
|
567
|
-
adminTransport,
|
|
568
|
-
projectId: project.id,
|
|
569
|
-
name: "rooaak-cli-init",
|
|
570
|
-
});
|
|
571
|
-
if (!apiKeyRes.ok) {
|
|
572
|
-
const err = apiKeyRes.err;
|
|
573
|
-
if (ctx.flags.json)
|
|
574
|
-
r.json(toJsonErrorEnvelope(err));
|
|
575
|
-
else {
|
|
576
|
-
r.error(err.message);
|
|
577
|
-
r.warn("Creating project API keys requires admin:project (admin key) or project:api-keys (project key).");
|
|
578
|
-
}
|
|
579
|
-
return exitCodeForError(err);
|
|
580
|
-
}
|
|
581
|
-
const projectApiKey = apiKeyRes.apiKey;
|
|
582
|
-
const writeRes = writeProjectProfile({
|
|
583
|
-
ctx,
|
|
584
|
-
r,
|
|
585
|
-
baseUrl,
|
|
586
|
-
apiKey: projectApiKey,
|
|
587
|
-
projectName: project.name,
|
|
588
|
-
profileName: typeof flagProfileName === "string" ? flagProfileName : undefined,
|
|
589
|
-
});
|
|
590
|
-
if (!writeRes.ok) {
|
|
591
|
-
const err = writeRes.err;
|
|
592
|
-
if (ctx.flags.json)
|
|
593
|
-
r.json(toJsonErrorEnvelope(err));
|
|
594
|
-
else
|
|
595
|
-
r.error(err.message);
|
|
596
|
-
return writeRes.exit;
|
|
597
|
-
}
|
|
598
|
-
const profileName = writeRes.profileName;
|
|
599
454
|
const projectTransport = createTransport({ baseUrl, apiKey: projectApiKey, fetchFn: ctx.fetchFn });
|
|
600
455
|
// Create agent.
|
|
601
456
|
const agentRes = await projectTransport.requestJson({
|
|
@@ -710,7 +565,7 @@ export async function initCommand(ctx, argv) {
|
|
|
710
565
|
const output = {
|
|
711
566
|
baseUrl,
|
|
712
567
|
profile: profileName,
|
|
713
|
-
project: { id: project.id, name: project.name, created:
|
|
568
|
+
project: { id: project.id, name: project.name, created: project.created },
|
|
714
569
|
agent: { id: agent.id, name: agent.name, started: startAgent },
|
|
715
570
|
message: {
|
|
716
571
|
id: messageId,
|
|
@@ -727,7 +582,10 @@ export async function initCommand(ctx, argv) {
|
|
|
727
582
|
r.json(toJsonOkEnvelope(output, meta));
|
|
728
583
|
return ExitCode.Success;
|
|
729
584
|
}
|
|
730
|
-
|
|
585
|
+
if (project.id)
|
|
586
|
+
r.text(`Project: ${project.name} (${project.id})`);
|
|
587
|
+
else
|
|
588
|
+
r.text(`Project: ${project.name}`);
|
|
731
589
|
r.text(`Profile saved: '${profileName}'`);
|
|
732
590
|
r.text(`Agent: ${agent.name} (${agent.id})`);
|
|
733
591
|
if (messageStatus === "responded") {
|