@geostack/arc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,890 @@
1
+ #!/usr/bin/env node
2
+ import { randomUUID } from "node:crypto";
3
+ import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { dirname, extname, join, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { ArcAgentClient, ArcDeveloperClient, ArcHttpError, createActionSyncPayload, createArcAgentRuntime, createSlug } from "./index.js";
8
+ const defaultApiUrl = "http://127.0.0.1:4000";
9
+ const defaultDevEmail = "dev@example.test";
10
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
+ export async function main(argv = process.argv.slice(2), env = process.env, io = process) {
12
+ const [scope, action, ...rest] = argv;
13
+ try {
14
+ if (!scope || scope === "help" || scope === "--help" || scope === "-h") {
15
+ printHelp(io);
16
+ return 0;
17
+ }
18
+ if (scope === "config" && action === "set") {
19
+ await configSet(parseArgs(rest), env, io);
20
+ return 0;
21
+ }
22
+ if (scope === "config" && action === "get") {
23
+ await configGet(env, io);
24
+ return 0;
25
+ }
26
+ if (scope === "app" && action === "create") {
27
+ await appCreate(parseArgs(rest), env, io);
28
+ return 0;
29
+ }
30
+ if (scope === "actions" && action === "sync") {
31
+ await actionsSync(parseArgs(rest), env, io);
32
+ return 0;
33
+ }
34
+ if (scope === "agent" && action === "dev-token") {
35
+ await agentDevToken(parseArgs(rest), env, io);
36
+ return 0;
37
+ }
38
+ if (scope === "agent" && action === "whoami") {
39
+ await agentWhoami(parseArgs(rest), env, io);
40
+ return 0;
41
+ }
42
+ if (scope === "agent" && action === "apps") {
43
+ await agentApps(parseArgs(rest), env, io);
44
+ return 0;
45
+ }
46
+ if (scope === "agent" && action === "actions") {
47
+ await agentActions(parseArgs(rest), env, io);
48
+ return 0;
49
+ }
50
+ if (scope === "agent" && action === "invoke") {
51
+ await agentInvoke(parseArgs(rest), env, io);
52
+ return 0;
53
+ }
54
+ if (scope === "invoke") {
55
+ await invokeAction(parseArgs([action ?? "", ...rest].filter(Boolean)), env, io);
56
+ return 0;
57
+ }
58
+ if (scope === "delegations" && action === "list") {
59
+ await delegationsList(parseArgs(rest), env, io);
60
+ return 0;
61
+ }
62
+ if (scope === "delegations" && action === "show") {
63
+ await delegationsShow(parseArgs(rest), env, io);
64
+ return 0;
65
+ }
66
+ if (scope === "delegations" && action === "create") {
67
+ await delegationsCreate(parseArgs(rest), env, io);
68
+ return 0;
69
+ }
70
+ if (scope === "delegations" && action === "revoke") {
71
+ await delegationsRevoke(parseArgs(rest), env, io);
72
+ return 0;
73
+ }
74
+ if (scope === "orgs" && action === "list") {
75
+ await orgsList(parseArgs(rest), env, io);
76
+ return 0;
77
+ }
78
+ if (scope === "orgs" && action === "current") {
79
+ await orgsCurrent(parseArgs(rest), env, io);
80
+ return 0;
81
+ }
82
+ if (scope === "orgs" && action === "members") {
83
+ await orgsMembers(parseArgs(rest), env, io);
84
+ return 0;
85
+ }
86
+ if (scope === "orgs" && action === "invite") {
87
+ await orgsInvite(parseArgs(rest), env, io);
88
+ return 0;
89
+ }
90
+ if (scope === "approvals" && action === "list") {
91
+ await approvalsList(parseArgs(rest), env, io);
92
+ return 0;
93
+ }
94
+ if (scope === "approvals" && action === "approve") {
95
+ await approvalsResolve("approve", parseArgs(rest), env, io);
96
+ return 0;
97
+ }
98
+ if (scope === "approvals" && action === "deny") {
99
+ await approvalsResolve("deny", parseArgs(rest), env, io);
100
+ return 0;
101
+ }
102
+ if (scope === "audit" && action === "tail") {
103
+ await auditTail(parseArgs(rest), env, io);
104
+ return 0;
105
+ }
106
+ if (scope === "audit" && action === "export") {
107
+ await auditExport(parseArgs(rest), env, io);
108
+ return 0;
109
+ }
110
+ if (scope === "audit" && action === "verify") {
111
+ await auditVerify(parseArgs(rest), env, io);
112
+ return 0;
113
+ }
114
+ if (scope === "audit" && action === "timeline") {
115
+ await auditTimeline(parseArgs(rest), env, io);
116
+ return 0;
117
+ }
118
+ if (scope === "dev" && action === "smoke") {
119
+ await devSmoke(parseArgs(rest), env, io);
120
+ return 0;
121
+ }
122
+ throw new Error(`Unknown arc command: ${argv.join(" ")}`);
123
+ }
124
+ catch (error) {
125
+ printError(error, io);
126
+ return 1;
127
+ }
128
+ }
129
+ async function configSet(args, env, io) {
130
+ const store = await loadConfig(env);
131
+ const apiUrl = optionalFlag(args, "api-url");
132
+ const devEmail = optionalFlag(args, "dev-email");
133
+ const orgId = optionalFlag(args, "org");
134
+ if (!apiUrl && !devEmail && !orgId) {
135
+ throw new Error("config set requires --api-url, --dev-email, or --org.");
136
+ }
137
+ if (apiUrl) {
138
+ store.config.apiUrl = trimTrailingSlash(apiUrl);
139
+ }
140
+ if (devEmail) {
141
+ store.config.devEmail = devEmail;
142
+ }
143
+ if (orgId) {
144
+ store.config.currentOrgId = orgId;
145
+ }
146
+ await store.save();
147
+ io.stdout.write(`Arc config saved at ${store.path}\n`);
148
+ }
149
+ async function configGet(env, io) {
150
+ const store = await loadConfig(env);
151
+ io.stdout.write(`${JSON.stringify(sanitizeConfig(store.config), null, 2)}\n`);
152
+ }
153
+ async function appCreate(args, env, io) {
154
+ const name = requiredPositional(args, 0, "app name");
155
+ const executeUrl = requiredFlag(args, "execute-url");
156
+ const slug = optionalFlag(args, "slug") ?? createSlug(name);
157
+ const appUserId = optionalFlag(args, "app-user-id") ?? "local-dev-user";
158
+ const store = await loadConfig(env);
159
+ const client = await ensureDeveloperClient(store, io);
160
+ const response = await client.createApp({
161
+ execute_url: executeUrl,
162
+ name,
163
+ slug
164
+ });
165
+ const app = readRecord(readRecord(response, "data"), "app");
166
+ store.config.currentAppId = readString(app, "id");
167
+ store.config.currentAppSlug = readString(app, "slug");
168
+ try {
169
+ await client.createAppConnection({
170
+ app_id: store.config.currentAppId,
171
+ app_user_id: appUserId
172
+ });
173
+ }
174
+ catch (error) {
175
+ if (!(error instanceof ArcHttpError && error.status === 409)) {
176
+ throw error;
177
+ }
178
+ }
179
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
180
+ await store.save();
181
+ io.stdout.write(`Created app ${readString(app, "name")} (${store.config.currentAppSlug})\n` +
182
+ `App id: ${store.config.currentAppId}\n` +
183
+ `Linked local app user: ${appUserId}\n`);
184
+ }
185
+ async function actionsSync(args, env, io) {
186
+ const file = requiredPositional(args, 0, "actions file");
187
+ const actions = await loadActionsFile(file);
188
+ const payload = createActionSyncPayload(actions);
189
+ if (args.flags.has("dry-run")) {
190
+ io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
191
+ return;
192
+ }
193
+ const store = await loadConfig(env);
194
+ const client = await ensureDeveloperClient(store, io);
195
+ const appId = await resolveAppId(client, store.config, optionalFlag(args, "app"));
196
+ const response = await client.syncActions(appId, payload.actions);
197
+ const synced = readArray(readRecord(response, "data").actions);
198
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
199
+ await store.save();
200
+ io.stdout.write(`Synced ${synced.length} action${synced.length === 1 ? "" : "s"} to app ${appId}\n`);
201
+ }
202
+ async function agentDevToken(args, env, io) {
203
+ const store = await loadConfig(env);
204
+ const client = await ensureDeveloperClient(store, io);
205
+ const response = await client.createDevAgentToken({
206
+ agent_type: optionalFlag(args, "agent-type") ?? "local-cli",
207
+ display_name: optionalFlag(args, "display-name") ?? "Arc CLI"
208
+ });
209
+ const token = readRecord(readRecord(readRecord(response, "data"), "agent_token"), "agent_connection");
210
+ const tokenResponse = readRecord(readRecord(response, "data"), "agent_token");
211
+ const plainToken = readString(tokenResponse, "token");
212
+ if (!args.flags.has("no-store")) {
213
+ store.config.agentToken = plainToken;
214
+ }
215
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
216
+ await store.save();
217
+ io.stdout.write(`Created dev agent token for ${readString(token, "id")}\n` +
218
+ `Token: ${plainToken}\n` +
219
+ (args.flags.has("no-store")
220
+ ? ""
221
+ : "Stored local dev token in Arc CLI config. Use only for development.\n"));
222
+ }
223
+ async function agentWhoami(args, env, io) {
224
+ const runtime = await createRuntimeClient(args, env);
225
+ const agent = await runtime.me();
226
+ io.stdout.write(`Agent ${agent.name} (${agent.id})\n`);
227
+ io.stdout.write(`Type: ${agent.type}\n`);
228
+ io.stdout.write(`Environment: ${agent.environment}\n`);
229
+ io.stdout.write(`Status: ${agent.status}\n`);
230
+ }
231
+ async function agentApps(args, env, io) {
232
+ const runtime = await createRuntimeClient(args, env);
233
+ const apps = await runtime.listApps();
234
+ if (apps.length === 0) {
235
+ io.stdout.write("No granted apps.\n");
236
+ return;
237
+ }
238
+ for (const app of apps) {
239
+ io.stdout.write(`${app.id} ${app.slug} ${app.name} actions:${app.granted_action_count} verification:${app.arc_verification_status}\n`);
240
+ }
241
+ }
242
+ async function agentActions(args, env, io) {
243
+ const app = requiredFlag(args, "app");
244
+ const runtime = await createRuntimeClient(args, env);
245
+ const appId = await resolveRuntimeAppId(runtime, app);
246
+ const actions = await runtime.listActions(appId);
247
+ if (actions.length === 0) {
248
+ io.stdout.write("No granted actions.\n");
249
+ return;
250
+ }
251
+ for (const action of actions) {
252
+ io.stdout.write(`${action.key} decision:${action.decision} risk:${action.risk_level} default:${action.default_decision}\n`);
253
+ }
254
+ }
255
+ async function agentInvoke(args, env, io) {
256
+ const app = requiredFlag(args, "app");
257
+ const actionKey = requiredFlag(args, "action");
258
+ const input = parseJsonFlag(optionalFlag(args, "input") ?? "{}", "input");
259
+ const runtime = await createRuntimeClient(args, env);
260
+ const appId = await resolveRuntimeAppId(runtime, app);
261
+ const result = await runtime.invoke(appId, actionKey, input, {
262
+ idempotencyKey: optionalFlag(args, "idempotency-key") ?? randomUUID()
263
+ });
264
+ if (result.status === "error") {
265
+ throw new Error(`Arc runtime error ${result.error.code}: ${result.error.message}`);
266
+ }
267
+ io.stdout.write(`Invocation ${result.invocation_id ?? "unknown"} status: ${result.status} decision: ${result.decision}\n`);
268
+ if (result.status === "pending_approval" && result.approval_id) {
269
+ io.stdout.write(`Approval required: ${result.approval_id}\n`);
270
+ }
271
+ }
272
+ async function invokeAction(args, env, io) {
273
+ const actionKey = requiredPositional(args, 0, "action key");
274
+ const store = await loadConfig(env);
275
+ const agentToken = optionalFlag(args, "agent-token") ?? store.config.agentToken;
276
+ const app = optionalFlag(args, "app") ?? store.config.currentAppSlug ?? store.config.currentAppId;
277
+ if (!agentToken) {
278
+ throw new Error("No agent token configured. Run arc agent dev-token first.");
279
+ }
280
+ if (!app) {
281
+ throw new Error("No app configured. Pass --app or run arc app create first.");
282
+ }
283
+ const input = parseJsonFlag(optionalFlag(args, "input") ?? "{}", "input");
284
+ const client = new ArcAgentClient({
285
+ agentToken,
286
+ baseUrl: store.config.apiUrl ?? defaultApiUrl
287
+ });
288
+ const response = await client.invoke({
289
+ action_key: actionKey,
290
+ app_id: uuidPattern.test(app) ? app : undefined,
291
+ app_slug: uuidPattern.test(app) ? undefined : app,
292
+ idempotency_key: optionalFlag(args, "idempotency-key") ?? randomUUID(),
293
+ input
294
+ });
295
+ const data = readRecord(response, "data");
296
+ const invocation = readRecord(data, "invocation");
297
+ const approval = maybeRecord(data.approval);
298
+ io.stdout.write(`Invocation ${readString(invocation, "id")} status: ${readString(data, "status")}\n`);
299
+ if (approval) {
300
+ io.stdout.write(`Approval required: ${readString(approval, "id")}\n`);
301
+ }
302
+ }
303
+ async function delegationsList(args, env, io) {
304
+ const store = await loadConfig(env);
305
+ const client = await ensureDeveloperClient(store, io);
306
+ const response = await client.listDelegations();
307
+ const delegations = readArray(readRecord(response, "data").delegations);
308
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
309
+ await store.save();
310
+ if (delegations.length === 0) {
311
+ io.stdout.write("No delegations.\n");
312
+ return;
313
+ }
314
+ for (const item of delegations) {
315
+ const delegation = readRecordValue(item);
316
+ const agent = readRecord(delegation, "agent");
317
+ const app = readRecord(delegation, "app");
318
+ io.stdout.write(`${readString(delegation, "id")} ${readString(delegation, "status")} ` +
319
+ `${readString(agent, "name")} -> ${readString(app, "slug")}\n`);
320
+ }
321
+ }
322
+ async function delegationsShow(args, env, io) {
323
+ const delegationId = requiredPositional(args, 0, "delegation id");
324
+ const store = await loadConfig(env);
325
+ const client = await ensureDeveloperClient(store, io);
326
+ const response = await client.getDelegation(delegationId);
327
+ const delegation = readRecord(readRecord(response, "data"), "delegation");
328
+ const agent = readRecord(delegation, "agent");
329
+ const app = readRecord(delegation, "app");
330
+ const matrix = readRecord(delegation, "permission_matrix");
331
+ const actions = readArray(matrix.actions);
332
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
333
+ await store.save();
334
+ io.stdout.write(`Delegation ${readString(delegation, "id")}\n`);
335
+ io.stdout.write(`Status: ${readString(delegation, "status")}\n`);
336
+ io.stdout.write(`Agent: ${readString(agent, "name")}\n`);
337
+ io.stdout.write(`App: ${readString(app, "slug")}\n`);
338
+ for (const item of actions) {
339
+ const action = readRecordValue(item);
340
+ const decision = typeof action.decision === "string" ? action.decision : "block";
341
+ io.stdout.write(`${readString(action, "key")} decision:${decision} risk:${readString(action, "risk_level")}\n`);
342
+ }
343
+ }
344
+ async function delegationsCreate(args, env, io) {
345
+ const agentId = requiredFlag(args, "agent");
346
+ const appId = requiredFlag(args, "app");
347
+ const actions = parseJsonFlag(requiredFlag(args, "actions"), "actions");
348
+ const store = await loadConfig(env);
349
+ const client = await ensureDeveloperClient(store, io);
350
+ const response = await client.createDelegation({
351
+ actions: actions,
352
+ agent_id: agentId,
353
+ app_id: appId,
354
+ expires_at: optionalFlag(args, "expires-at") ?? undefined,
355
+ label: optionalFlag(args, "label") ?? undefined,
356
+ name: optionalFlag(args, "name") ?? undefined
357
+ });
358
+ const delegation = readRecord(readRecord(response, "data"), "delegation");
359
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
360
+ await store.save();
361
+ io.stdout.write(`Created delegation ${readString(delegation, "id")}\n`);
362
+ io.stdout.write(`Status: ${readString(delegation, "status")}\n`);
363
+ }
364
+ async function delegationsRevoke(args, env, io) {
365
+ const delegationId = requiredPositional(args, 0, "delegation id");
366
+ const store = await loadConfig(env);
367
+ const client = await ensureDeveloperClient(store, io);
368
+ const response = await client.revokeDelegation(delegationId, {
369
+ reason: optionalFlag(args, "reason") ?? undefined
370
+ });
371
+ const delegation = readRecord(readRecord(response, "data"), "delegation");
372
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
373
+ await store.save();
374
+ io.stdout.write(`Revoked delegation ${readString(delegation, "id")}\n`);
375
+ io.stdout.write(`Status: ${readString(delegation, "status")}\n`);
376
+ }
377
+ async function orgsList(args, env, io) {
378
+ void args;
379
+ const store = await loadConfig(env);
380
+ const client = await ensureDeveloperClient(store, io);
381
+ const response = await client.listOrgs();
382
+ const data = readRecord(response, "data");
383
+ const current = readRecord(data, "current_org");
384
+ const orgs = readArray(data.orgs);
385
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
386
+ store.config.currentOrgId = readString(current, "id");
387
+ await store.save();
388
+ for (const item of orgs) {
389
+ const org = readRecordValue(item);
390
+ const marker = readString(org, "org_id") === store.config.currentOrgId ? "*" : " ";
391
+ io.stdout.write(`${marker} ${readString(org, "org_id")} ${readString(org, "org_slug")} ` +
392
+ `${readString(org, "role")} ${readString(org, "status")}\n`);
393
+ }
394
+ }
395
+ async function orgsCurrent(args, env, io) {
396
+ void args;
397
+ const store = await loadConfig(env);
398
+ const client = await ensureDeveloperClient(store, io);
399
+ const response = await client.getCurrentOrg();
400
+ const data = readRecord(response, "data");
401
+ const current = readRecord(data, "current_org");
402
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
403
+ store.config.currentOrgId = readString(current, "id");
404
+ await store.save();
405
+ io.stdout.write(`${readString(current, "name")} (${readString(current, "id")})\n`);
406
+ io.stdout.write(`Slug: ${readString(current, "slug")}\n`);
407
+ io.stdout.write(`Role: ${readString(data, "role")}\n`);
408
+ }
409
+ async function orgsMembers(args, env, io) {
410
+ void args;
411
+ const store = await loadConfig(env);
412
+ const client = await ensureDeveloperClient(store, io);
413
+ const response = await client.listOrgMembers();
414
+ const members = readArray(readRecord(response, "data").members);
415
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
416
+ await store.save();
417
+ for (const item of members) {
418
+ const member = readRecordValue(item);
419
+ io.stdout.write(`${readString(member, "user_id")} ${readString(member, "email")} ` +
420
+ `${readString(member, "role")} ${readString(member, "status")}\n`);
421
+ }
422
+ }
423
+ async function orgsInvite(args, env, io) {
424
+ const email = requiredFlag(args, "email");
425
+ const role = requiredFlag(args, "role");
426
+ const name = optionalFlag(args, "name");
427
+ const store = await loadConfig(env);
428
+ const client = await ensureDeveloperClient(store, io);
429
+ const response = await client.inviteOrgMember({ email, name, role });
430
+ const user = readRecord(readRecord(response, "data"), "user");
431
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
432
+ await store.save();
433
+ io.stdout.write(`Invited ${readString(user, "email")} as ${readString(user, "role")}\n`);
434
+ }
435
+ async function createRuntimeClient(args, env) {
436
+ const store = await loadConfig(env);
437
+ const agentToken = optionalFlag(args, "agent-token") ?? env.ARC_AGENT_TOKEN ?? store.config.agentToken;
438
+ if (!agentToken) {
439
+ throw new Error("ARC_AGENT_TOKEN is required for agent runtime commands.");
440
+ }
441
+ return createArcAgentRuntime({
442
+ agentToken,
443
+ apiUrl: trimTrailingSlash(optionalFlag(args, "api-url") ?? env.ARC_API_URL ?? store.config.apiUrl ?? defaultApiUrl)
444
+ });
445
+ }
446
+ async function resolveRuntimeAppId(runtime, requestedApp) {
447
+ if (uuidPattern.test(requestedApp)) {
448
+ return requestedApp;
449
+ }
450
+ const apps = await runtime.listApps();
451
+ const match = apps.find((app) => app.slug === requestedApp || app.name === requestedApp || app.id === requestedApp);
452
+ if (!match) {
453
+ throw new Error(`Granted app not found: ${requestedApp}`);
454
+ }
455
+ return match.id;
456
+ }
457
+ async function approvalsList(args, env, io) {
458
+ const limit = Number(optionalFlag(args, "limit") ?? "20");
459
+ const store = await loadConfig(env);
460
+ const client = await ensureDeveloperClient(store, io);
461
+ const response = await client.listApprovals();
462
+ const approvals = readArray(readRecord(response, "data").approvals).slice(0, limit);
463
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
464
+ await store.save();
465
+ if (approvals.length === 0) {
466
+ io.stdout.write("No approvals.\n");
467
+ return;
468
+ }
469
+ for (const approval of approvals) {
470
+ const row = readRecordValue(approval);
471
+ const summary = maybeRecord(row.summary);
472
+ io.stdout.write(`${readString(row, "id")} ${readString(row, "status")} ` +
473
+ `${summary ? readString(summary, "text") : ""}\n`);
474
+ }
475
+ }
476
+ async function approvalsResolve(resolution, args, env, io) {
477
+ const approvalId = requiredPositional(args, 0, "approval id");
478
+ const store = await loadConfig(env);
479
+ const client = await ensureDeveloperClient(store, io);
480
+ const response = resolution === "approve"
481
+ ? await client.approveApproval(approvalId)
482
+ : await client.denyApproval(approvalId);
483
+ const approval = readRecord(readRecord(response, "data"), "approval");
484
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
485
+ await store.save();
486
+ io.stdout.write(`${resolution === "approve" ? "Approved" : "Denied"} approval ${readString(approval, "id")}\n`);
487
+ io.stdout.write(`Status: ${readString(approval, "status")}\n`);
488
+ }
489
+ async function auditTail(args, env, io) {
490
+ const store = await loadConfig(env);
491
+ const client = await ensureDeveloperClient(store, io);
492
+ const response = await client.tailAudit({
493
+ limit: Number(optionalFlag(args, "limit") ?? "20")
494
+ });
495
+ const events = readArray(readRecord(response, "data").events);
496
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
497
+ await store.save();
498
+ if (events.length === 0) {
499
+ io.stdout.write("No audit events.\n");
500
+ return;
501
+ }
502
+ for (const event of events) {
503
+ const row = readRecordValue(event);
504
+ const createdAt = readString(row, "created_at");
505
+ const eventType = readString(row, "event_type");
506
+ const actorType = readString(row, "actor_type");
507
+ const reason = typeof row.reason === "string" ? row.reason : "";
508
+ io.stdout.write(`${createdAt} ${eventType} ${actorType}${reason ? ` ${reason}` : ""}\n`);
509
+ }
510
+ }
511
+ async function auditExport(args, env, io) {
512
+ const store = await loadConfig(env);
513
+ const client = await ensureDeveloperClient(store, io);
514
+ const input = auditInputFromArgs(args);
515
+ const response = await client.exportAudit(input);
516
+ const data = readRecord(response, "data");
517
+ const content = readString(data, "content");
518
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
519
+ await store.save();
520
+ io.stdout.write(`${content}${content.endsWith("\n") ? "" : "\n"}`);
521
+ }
522
+ async function auditVerify(args, env, io) {
523
+ const store = await loadConfig(env);
524
+ const client = await ensureDeveloperClient(store, io);
525
+ const response = await client.verifyAudit(auditInputFromArgs(args));
526
+ const verification = readRecord(readRecord(response, "data"), "verification");
527
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
528
+ await store.save();
529
+ io.stdout.write(`Audit verification: ${readString(verification, "status")}\n`);
530
+ io.stdout.write(`Continuity: ${readString(verification, "continuity")}\n`);
531
+ io.stdout.write(`Events: ${String(verification.event_count ?? 0)}\n`);
532
+ const mismatches = Array.isArray(verification.mismatches) ? verification.mismatches.length : 0;
533
+ const gaps = Array.isArray(verification.gaps) ? verification.gaps.length : 0;
534
+ io.stdout.write(`Mismatches: ${mismatches}\n`);
535
+ io.stdout.write(`Scoped gaps: ${gaps}\n`);
536
+ io.stdout.write("Evidence: tamper-evident inside Arc DB unless externally anchored.\n");
537
+ }
538
+ async function auditTimeline(args, env, io) {
539
+ const store = await loadConfig(env);
540
+ const client = await ensureDeveloperClient(store, io);
541
+ const response = await client.getAuditTimeline(auditInputFromArgs(args));
542
+ const timeline = readRecord(readRecord(response, "data"), "timeline");
543
+ const events = readArray(timeline.events);
544
+ store.config.sessionCookie = client.cookie ?? store.config.sessionCookie;
545
+ await store.save();
546
+ if (events.length === 0) {
547
+ io.stdout.write("No timeline events.\n");
548
+ return;
549
+ }
550
+ for (const item of events) {
551
+ const event = readRecordValue(item);
552
+ io.stdout.write(`${readString(event, "created_at")} ${readString(event, "event_type")} ` +
553
+ `${String(event.actor_type ?? "")} ${String(event.reason ?? "")}\n`);
554
+ }
555
+ }
556
+ async function devSmoke(args, env, io) {
557
+ const store = await loadConfig(env);
558
+ const apiUrl = trimTrailingSlash(optionalFlag(args, "api-url") ?? store.config.apiUrl ?? defaultApiUrl);
559
+ const response = await fetch(`${apiUrl}/healthz`);
560
+ if (!response.ok) {
561
+ throw new Error(`Arc API health check failed with HTTP ${response.status}.`);
562
+ }
563
+ io.stdout.write(`Arc API healthy at ${apiUrl}\n`);
564
+ }
565
+ async function ensureDeveloperClient(store, io, env = process.env) {
566
+ const client = new ArcDeveloperClient({
567
+ baseUrl: store.config.apiUrl ?? defaultApiUrl,
568
+ orgId: env.ARC_ORG_ID ?? store.config.currentOrgId,
569
+ sessionCookie: store.config.sessionCookie
570
+ });
571
+ if (store.config.sessionCookie) {
572
+ try {
573
+ await client.me();
574
+ return client;
575
+ }
576
+ catch (error) {
577
+ if (!(error instanceof ArcHttpError && error.status === 401)) {
578
+ throw error;
579
+ }
580
+ }
581
+ }
582
+ const email = store.config.devEmail ?? defaultDevEmail;
583
+ await client.devLogin({
584
+ email,
585
+ name: "Arc CLI Dev"
586
+ });
587
+ store.config.sessionCookie = client.cookie ?? undefined;
588
+ await store.save();
589
+ io.stderr.write(`Using Arc dev login as ${email}\n`);
590
+ return client;
591
+ }
592
+ async function resolveAppId(client, config, requestedApp) {
593
+ if (!requestedApp && config.currentAppId) {
594
+ return config.currentAppId;
595
+ }
596
+ const app = requestedApp ?? config.currentAppSlug;
597
+ if (!app) {
598
+ throw new Error("No app configured. Pass --app or run arc app create first.");
599
+ }
600
+ if (uuidPattern.test(app)) {
601
+ return app;
602
+ }
603
+ const response = await client.listApps();
604
+ const apps = readArray(readRecord(readRecord(response, "data"), "apps"));
605
+ const match = apps
606
+ .map(readRecordValue)
607
+ .find((item) => item.slug === app || item.name === app || item.id === app);
608
+ if (!match) {
609
+ throw new Error(`App not found: ${app}`);
610
+ }
611
+ return readString(match, "id");
612
+ }
613
+ async function loadActionsFile(file) {
614
+ const absolute = resolve(file);
615
+ const extension = extname(absolute);
616
+ let imported;
617
+ if (extension === ".ts" || extension === ".tsx") {
618
+ imported = await importTranspiledTypescript(absolute);
619
+ }
620
+ else {
621
+ imported = await import(`${pathToFileURL(absolute).href}?v=${Date.now()}`);
622
+ }
623
+ const module = readRecordValue(imported);
624
+ const actions = module.actions ?? module.default;
625
+ if (!actions || typeof actions !== "object") {
626
+ throw new Error("Actions file must export actions or a default action object.");
627
+ }
628
+ return actions;
629
+ }
630
+ async function importTranspiledTypescript(absolute) {
631
+ const typescript = await import("typescript");
632
+ const source = await readFile(absolute, "utf8");
633
+ const output = typescript.transpileModule(source, {
634
+ compilerOptions: {
635
+ esModuleInterop: true,
636
+ module: typescript.ModuleKind.ES2022,
637
+ moduleResolution: typescript.ModuleResolutionKind.NodeNext,
638
+ target: typescript.ScriptTarget.ES2022
639
+ },
640
+ fileName: absolute
641
+ }).outputText;
642
+ const tempFile = join(dirname(absolute), `.arc-actions-${process.pid}-${Date.now()}.mjs`);
643
+ await writeFile(tempFile, output, "utf8");
644
+ try {
645
+ return await import(`${pathToFileURL(tempFile).href}?v=${Date.now()}`);
646
+ }
647
+ finally {
648
+ await unlink(tempFile).catch(() => undefined);
649
+ }
650
+ }
651
+ function auditInputFromArgs(args) {
652
+ return {
653
+ action_id: optionalFlag(args, "action-id"),
654
+ action_key: optionalFlag(args, "action") ?? optionalFlag(args, "action-key"),
655
+ actor_id: optionalFlag(args, "actor") ?? optionalFlag(args, "actor-id"),
656
+ agent_id: optionalFlag(args, "agent") ?? optionalFlag(args, "agent-id"),
657
+ app_id: optionalFlag(args, "app") ?? optionalFlag(args, "app-id"),
658
+ approval_id: optionalFlag(args, "approval") ?? optionalFlag(args, "approval-id"),
659
+ decision: parseAuditDecisionFlag(optionalFlag(args, "decision")),
660
+ delegation_id: optionalFlag(args, "delegation") ?? optionalFlag(args, "delegation-id"),
661
+ event_type: optionalFlag(args, "event-type"),
662
+ format: parseAuditFormatFlag(optionalFlag(args, "format")),
663
+ from: optionalFlag(args, "from"),
664
+ invocation_id: optionalFlag(args, "invocation") ?? optionalFlag(args, "invocation-id"),
665
+ limit: optionalNumberFlag(args, "limit"),
666
+ offset: optionalNumberFlag(args, "offset"),
667
+ status: optionalFlag(args, "status"),
668
+ to: optionalFlag(args, "to"),
669
+ user_id: optionalFlag(args, "user") ?? optionalFlag(args, "user-id")
670
+ };
671
+ }
672
+ function parseAuditFormatFlag(value) {
673
+ if (!value) {
674
+ return undefined;
675
+ }
676
+ if (value === "jsonl" || value === "csv" || value === "bundle") {
677
+ return value;
678
+ }
679
+ throw new Error("--format must be jsonl, csv, or bundle.");
680
+ }
681
+ function parseAuditDecisionFlag(value) {
682
+ if (!value) {
683
+ return undefined;
684
+ }
685
+ if (value === "allow" || value === "ask" || value === "block") {
686
+ return value;
687
+ }
688
+ throw new Error("--decision must be allow, ask, or block.");
689
+ }
690
+ function optionalNumberFlag(args, name) {
691
+ const value = optionalFlag(args, name);
692
+ if (value === undefined) {
693
+ return undefined;
694
+ }
695
+ const parsed = Number(value);
696
+ if (!Number.isInteger(parsed) || parsed < 0) {
697
+ throw new Error(`--${name} must be a non-negative integer.`);
698
+ }
699
+ return parsed;
700
+ }
701
+ async function loadConfig(env) {
702
+ const configDir = env.ARC_CONFIG_HOME ? resolve(env.ARC_CONFIG_HOME) : join(homedir(), ".arc");
703
+ const configPath = join(configDir, "config.json");
704
+ let config = {};
705
+ try {
706
+ config = JSON.parse(await readFile(configPath, "utf8"));
707
+ }
708
+ catch (error) {
709
+ if (error.code !== "ENOENT") {
710
+ throw error;
711
+ }
712
+ }
713
+ return {
714
+ config,
715
+ path: configPath,
716
+ save: async () => {
717
+ await mkdir(configDir, { mode: 0o700, recursive: true });
718
+ await chmod(configDir, 0o700).catch(() => undefined);
719
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, {
720
+ encoding: "utf8",
721
+ mode: 0o600
722
+ });
723
+ await chmod(configPath, 0o600).catch(() => undefined);
724
+ }
725
+ };
726
+ }
727
+ function parseArgs(argv) {
728
+ const flags = new Map();
729
+ const positionals = [];
730
+ for (let index = 0; index < argv.length; index += 1) {
731
+ const item = argv[index];
732
+ if (!item) {
733
+ continue;
734
+ }
735
+ if (item.startsWith("--")) {
736
+ const [rawName, inlineValue] = item.slice(2).split("=", 2);
737
+ const name = rawName ?? "";
738
+ if (!name) {
739
+ continue;
740
+ }
741
+ if (inlineValue !== undefined) {
742
+ flags.set(name, inlineValue);
743
+ continue;
744
+ }
745
+ const next = argv[index + 1];
746
+ if (next && !next.startsWith("--")) {
747
+ flags.set(name, next);
748
+ index += 1;
749
+ }
750
+ else {
751
+ flags.set(name, true);
752
+ }
753
+ continue;
754
+ }
755
+ positionals.push(item);
756
+ }
757
+ return { flags, positionals };
758
+ }
759
+ function requiredFlag(args, name) {
760
+ const value = optionalFlag(args, name);
761
+ if (!value) {
762
+ throw new Error(`Missing required --${name}.`);
763
+ }
764
+ return value;
765
+ }
766
+ function optionalFlag(args, name) {
767
+ const value = args.flags.get(name);
768
+ return typeof value === "string" ? value : undefined;
769
+ }
770
+ function requiredPositional(args, index, label) {
771
+ const value = args.positionals[index];
772
+ if (!value) {
773
+ throw new Error(`Missing ${label}.`);
774
+ }
775
+ return value;
776
+ }
777
+ function parseJsonFlag(value, label) {
778
+ try {
779
+ const parsed = JSON.parse(value);
780
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
781
+ throw new Error("not object");
782
+ }
783
+ return parsed;
784
+ }
785
+ catch {
786
+ throw new Error(`--${label} must be a JSON object.`);
787
+ }
788
+ }
789
+ function sanitizeConfig(config) {
790
+ return {
791
+ ...config,
792
+ agentToken: config.agentToken ? maskSecret(config.agentToken, "stored dev token") : undefined,
793
+ sessionCookie: config.sessionCookie ? maskSecret(config.sessionCookie, "stored session cookie") : undefined
794
+ };
795
+ }
796
+ function maskSecret(value, label) {
797
+ if (value.length < 24) {
798
+ return `[${label}: hidden]`;
799
+ }
800
+ return `[${label}: ${value.slice(0, 6)}...${value.slice(-4)}]`;
801
+ }
802
+ function readRecord(input, key) {
803
+ const parent = readRecordValue(input);
804
+ return readRecordValue(parent[key]);
805
+ }
806
+ function maybeRecord(input) {
807
+ return input && typeof input === "object" && !Array.isArray(input)
808
+ ? input
809
+ : null;
810
+ }
811
+ function readRecordValue(input) {
812
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
813
+ throw new Error("Arc response was missing an expected object.");
814
+ }
815
+ return input;
816
+ }
817
+ function readArray(input) {
818
+ if (!Array.isArray(input)) {
819
+ throw new Error("Arc response was missing an expected array.");
820
+ }
821
+ return input;
822
+ }
823
+ function readString(input, key) {
824
+ const value = input[key];
825
+ if (typeof value !== "string") {
826
+ throw new Error(`Arc response was missing ${key}.`);
827
+ }
828
+ return value;
829
+ }
830
+ function printHelp(io) {
831
+ io.stdout.write(`Arc CLI
832
+
833
+ Trust layer tooling for high-risk AI actions.
834
+ From this monorepo after npm run build, run:
835
+ node packages/arc-sdk/dist/cli.js <command>
836
+ Installed package usage:
837
+ arc <command>
838
+
839
+ Commands:
840
+ arc config set --api-url <url> [--dev-email <email>] [--org <id>] Save local Arc API config
841
+ arc config get Show config with secrets masked
842
+ arc app create <name> --execute-url <url> [--slug <s>] Register an app and local app connection
843
+ arc actions sync <file> [--app <id-or-slug>] Sync arc.defineActions output
844
+ arc actions sync <file> --dry-run Print the sync payload only
845
+ arc agent dev-token [--agent-type <type>] Create a local dev agent token
846
+ arc agent whoami Show agent runtime identity
847
+ arc agent apps List apps granted to ARC_AGENT_TOKEN
848
+ arc agent actions --app <id-or-slug> List granted app actions
849
+ arc agent invoke --app <id-or-slug> --action <key> Request an action through Arc
850
+ arc invoke <action-key> --app <id-or-slug> --input '{}' Invoke through Arc
851
+ arc delegations list List Agentic SSO delegations
852
+ arc delegations show <delegation-id> Show delegated authority
853
+ arc delegations create --agent <id> --app <id> --actions '{"read":"allow"}'
854
+ arc delegations revoke <delegation-id> Revoke delegated authority
855
+ arc orgs list List available workspaces
856
+ arc orgs current Show current workspace context
857
+ arc orgs members List current workspace members
858
+ arc orgs invite --email <email> --role <role> Invite a member to the workspace
859
+ arc approvals list List pending/resolved approvals
860
+ arc approvals approve <approval-id> Approve and queue execution
861
+ arc approvals deny <approval-id> Deny without execution
862
+ arc audit tail [--limit <n>] Show recent redacted audit events
863
+ arc audit export --format jsonl|csv [--agent <id>] Export redacted audit evidence
864
+ arc audit verify [--agent <id>] Verify audit hashes for a scope
865
+ arc audit timeline --invocation <id> Show an incident timeline
866
+ arc dev smoke Check Arc API health
867
+
868
+ Local proof:
869
+ ARC_API_URL=http://127.0.0.1:<dev-port> npm --workspace @geostack/ai-support-ops-reference run smoke:dev
870
+ `);
871
+ }
872
+ function printError(error, io) {
873
+ if (error instanceof ArcHttpError) {
874
+ io.stderr.write(`Arc API error ${error.status} ${error.code}: ${error.message}\n`);
875
+ return;
876
+ }
877
+ if (error instanceof Error) {
878
+ io.stderr.write(`${error.message}\n`);
879
+ return;
880
+ }
881
+ io.stderr.write("Unknown Arc CLI error.\n");
882
+ }
883
+ function trimTrailingSlash(value) {
884
+ return value.replace(/\/+$/u, "");
885
+ }
886
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
887
+ const code = await main();
888
+ process.exitCode = code;
889
+ }
890
+ //# sourceMappingURL=cli.js.map