@tuttiai/cli 0.14.0 → 0.16.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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { config } from "dotenv";
5
- import { createLogger as createLogger15 } from "@tuttiai/core";
5
+ import { createLogger as createLogger16 } from "@tuttiai/core";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/init.ts
@@ -2603,8 +2603,8 @@ async function replayCommand(sessionId, opts = {}) {
2603
2603
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2604
2604
  try {
2605
2605
  while (true) {
2606
- const prompt3 = chalk17.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2607
- const raw = await rl.question(prompt3);
2606
+ const prompt5 = chalk17.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2607
+ const raw = await rl.question(prompt5);
2608
2608
  const input = raw.trim();
2609
2609
  if (!input) continue;
2610
2610
  const [cmd, ...args] = input.split(/\s+/);
@@ -3182,15 +3182,639 @@ function reviveSpanDates(span) {
3182
3182
  };
3183
3183
  }
3184
3184
 
3185
+ // src/commands/memory.ts
3186
+ import { writeFileSync as writeFileSync2 } from "fs";
3187
+ import chalk22 from "chalk";
3188
+ import Enquirer3 from "enquirer";
3189
+ import {
3190
+ MemoryUserMemoryStore,
3191
+ PostgresUserMemoryStore,
3192
+ SecretsManager as SecretsManager10,
3193
+ createLogger as createLogger15
3194
+ } from "@tuttiai/core";
3195
+
3196
+ // src/commands/memory-render.ts
3197
+ import chalk21 from "chalk";
3198
+ function visibleLen2(s) {
3199
+ return s.replace(/\u001b\[[0-9;]*m/g, "").length;
3200
+ }
3201
+ function pad5(s, len) {
3202
+ const v = visibleLen2(s);
3203
+ return v >= len ? s : s + " ".repeat(len - v);
3204
+ }
3205
+ function truncate(text, max) {
3206
+ const oneLine = text.replace(/\s+/g, " ").trim();
3207
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
3208
+ }
3209
+ function importanceStars(importance) {
3210
+ if (importance === 3) return "\u2605\u2605\u2605";
3211
+ if (importance === 2) return "\u2605\u2605\u2606";
3212
+ return "\u2605\u2606\u2606";
3213
+ }
3214
+ function formatDate(d) {
3215
+ const iso = d.toISOString();
3216
+ return iso.slice(0, 10) + " " + iso.slice(11, 16);
3217
+ }
3218
+ function sortMemoriesForList(memories) {
3219
+ return [...memories].sort((a, b) => {
3220
+ if (a.importance !== b.importance) return b.importance - a.importance;
3221
+ return b.created_at.getTime() - a.created_at.getTime();
3222
+ });
3223
+ }
3224
+ function renderTable(memories, emptyMessage) {
3225
+ if (memories.length === 0) return chalk21.dim(emptyMessage);
3226
+ const lines = [];
3227
+ lines.push("");
3228
+ lines.push(
3229
+ chalk21.dim(
3230
+ " " + pad5("ID", 10) + pad5("CONTENT", 62) + pad5("SOURCE", 12) + pad5("IMPORTANCE", 14) + "CREATED"
3231
+ )
3232
+ );
3233
+ lines.push(chalk21.dim(" " + "\u2500".repeat(110)));
3234
+ for (const m of memories) {
3235
+ const idShort = m.id.slice(0, 8);
3236
+ const content = truncate(m.content, 60);
3237
+ const sourceColored = m.source === "explicit" ? chalk21.green("explicit") : chalk21.yellow("inferred");
3238
+ const importance = importanceStars(m.importance);
3239
+ const created = formatDate(m.created_at);
3240
+ lines.push(
3241
+ " " + chalk21.bold(pad5(idShort, 10)) + pad5(content, 62) + pad5(sourceColored, 12) + pad5(importance, 14) + chalk21.dim(created)
3242
+ );
3243
+ }
3244
+ lines.push("");
3245
+ return lines.join("\n");
3246
+ }
3247
+ function renderMemoryList(memories, userId) {
3248
+ return renderTable(
3249
+ sortMemoriesForList(memories),
3250
+ 'No memories stored for user "' + userId + '".'
3251
+ );
3252
+ }
3253
+ function renderMemorySearch(memories, userId, query) {
3254
+ const header = chalk21.dim(
3255
+ "Search for " + chalk21.bold('"' + query + '"') + ' in user "' + userId + '" \u2014 ' + memories.length + " result" + (memories.length === 1 ? "" : "s")
3256
+ );
3257
+ const body = renderTable(
3258
+ memories,
3259
+ 'No memories matching "' + query + '" for user "' + userId + '".'
3260
+ );
3261
+ return header + body;
3262
+ }
3263
+ function renderMemoryAdded(memory) {
3264
+ return chalk21.green("\u2713") + " Stored memory " + chalk21.bold(memory.id.slice(0, 8)) + chalk21.dim(" (" + memory.source + ", " + importanceStars(memory.importance) + ")");
3265
+ }
3266
+ function renderMemoryDeleted(memoryId) {
3267
+ return chalk21.green("\u2713") + " Deleted memory " + chalk21.bold(memoryId.slice(0, 8));
3268
+ }
3269
+ function renderMemoryCleared(userId, count) {
3270
+ return chalk21.green("\u2713") + " Deleted " + chalk21.bold(String(count)) + " memor" + (count === 1 ? "y" : "ies") + ' for user "' + userId + '"';
3271
+ }
3272
+ function exportMemoriesJson(memories) {
3273
+ return JSON.stringify(memories, null, 2) + "\n";
3274
+ }
3275
+ function exportMemoriesCsv(memories) {
3276
+ const headers = [
3277
+ "id",
3278
+ "user_id",
3279
+ "content",
3280
+ "source",
3281
+ "importance",
3282
+ "tags",
3283
+ "created_at",
3284
+ "last_accessed_at",
3285
+ "expires_at"
3286
+ ];
3287
+ const rows = [headers.join(",")];
3288
+ for (const m of memories) {
3289
+ rows.push(
3290
+ [
3291
+ m.id,
3292
+ m.user_id,
3293
+ m.content,
3294
+ m.source,
3295
+ String(m.importance),
3296
+ m.tags?.join(";") ?? "",
3297
+ m.created_at.toISOString(),
3298
+ m.last_accessed_at?.toISOString() ?? "",
3299
+ m.expires_at?.toISOString() ?? ""
3300
+ ].map(csvEscape).join(",")
3301
+ );
3302
+ }
3303
+ return rows.join("\n") + "\n";
3304
+ }
3305
+ function csvEscape(field) {
3306
+ if (/[",\n\r]/.test(field)) {
3307
+ return '"' + field.replace(/"/g, '""') + '"';
3308
+ }
3309
+ return field;
3310
+ }
3311
+
3312
+ // src/commands/memory.ts
3313
+ var logger15 = createLogger15("tutti-cli");
3314
+ var { prompt: prompt3 } = Enquirer3;
3315
+ function resolveStore3() {
3316
+ const pgUrl = SecretsManager10.optional("TUTTI_PG_URL");
3317
+ if (pgUrl) {
3318
+ return new PostgresUserMemoryStore({ connection_string: pgUrl });
3319
+ }
3320
+ logger15.warn(
3321
+ "TUTTI_PG_URL not set \u2014 using in-memory store (memories are ephemeral; this command will appear to do nothing useful)"
3322
+ );
3323
+ return new MemoryUserMemoryStore();
3324
+ }
3325
+ async function closeStore2(store) {
3326
+ if (typeof store.close === "function") {
3327
+ await store.close();
3328
+ }
3329
+ }
3330
+ function requireUser(opts) {
3331
+ if (!opts.user || opts.user.trim() === "") {
3332
+ console.error(chalk22.red("--user <user-id> is required"));
3333
+ process.exit(1);
3334
+ }
3335
+ return opts.user.trim();
3336
+ }
3337
+ function parseImportance(raw) {
3338
+ if (raw === void 0) return 2;
3339
+ if (raw === "1") return 1;
3340
+ if (raw === "2") return 2;
3341
+ if (raw === "3") return 3;
3342
+ console.error(chalk22.red("--importance must be 1, 2, or 3"));
3343
+ process.exit(1);
3344
+ }
3345
+ async function memoryListCommand(opts) {
3346
+ const userId = requireUser(opts);
3347
+ const store = resolveStore3();
3348
+ try {
3349
+ const memories = await store.list(userId);
3350
+ console.log(renderMemoryList(memories, userId));
3351
+ } finally {
3352
+ await closeStore2(store);
3353
+ }
3354
+ }
3355
+ async function memorySearchCommand(query, opts) {
3356
+ const userId = requireUser(opts);
3357
+ if (query.trim() === "") {
3358
+ console.error(chalk22.red("Search query is required"));
3359
+ process.exit(1);
3360
+ }
3361
+ const store = resolveStore3();
3362
+ try {
3363
+ const memories = await store.search(userId, query);
3364
+ console.log(renderMemorySearch(memories, userId, query));
3365
+ } finally {
3366
+ await closeStore2(store);
3367
+ }
3368
+ }
3369
+ async function memoryAddCommand(content, opts) {
3370
+ const userId = requireUser(opts);
3371
+ if (content.trim() === "") {
3372
+ console.error(chalk22.red("Memory content is required"));
3373
+ process.exit(1);
3374
+ }
3375
+ const importance = parseImportance(opts.importance);
3376
+ const store = resolveStore3();
3377
+ try {
3378
+ const stored = await store.store(userId, content.trim(), {
3379
+ source: "explicit",
3380
+ importance
3381
+ });
3382
+ console.log(renderMemoryAdded(stored));
3383
+ } finally {
3384
+ await closeStore2(store);
3385
+ }
3386
+ }
3387
+ async function memoryDeleteCommand(memoryId, opts) {
3388
+ requireUser(opts);
3389
+ const store = resolveStore3();
3390
+ try {
3391
+ await store.delete(memoryId);
3392
+ console.log(renderMemoryDeleted(memoryId));
3393
+ } finally {
3394
+ await closeStore2(store);
3395
+ }
3396
+ }
3397
+ async function memoryClearCommand(opts) {
3398
+ const userId = requireUser(opts);
3399
+ const store = resolveStore3();
3400
+ try {
3401
+ const memories = await store.list(userId);
3402
+ if (memories.length === 0) {
3403
+ console.log(chalk22.dim('No memories stored for user "' + userId + '".'));
3404
+ return;
3405
+ }
3406
+ const { confirm } = await prompt3({
3407
+ type: "confirm",
3408
+ name: "confirm",
3409
+ message: "Delete all " + memories.length + ' memories for user "' + userId + '"?',
3410
+ initial: false
3411
+ });
3412
+ if (!confirm) {
3413
+ console.log(chalk22.dim(" Cancelled."));
3414
+ return;
3415
+ }
3416
+ await store.deleteAll(userId);
3417
+ console.log(renderMemoryCleared(userId, memories.length));
3418
+ } finally {
3419
+ await closeStore2(store);
3420
+ }
3421
+ }
3422
+ async function memoryExportCommand(opts) {
3423
+ const userId = requireUser(opts);
3424
+ const format = (opts.format ?? "json").toLowerCase();
3425
+ if (format !== "json" && format !== "csv") {
3426
+ console.error(chalk22.red("--format must be 'json' or 'csv'"));
3427
+ process.exit(1);
3428
+ }
3429
+ const store = resolveStore3();
3430
+ try {
3431
+ const memories = await store.list(userId);
3432
+ const body = format === "json" ? exportMemoriesJson(memories) : exportMemoriesCsv(memories);
3433
+ if (opts.out) {
3434
+ writeFileSync2(opts.out, body, "utf8");
3435
+ console.log(
3436
+ chalk22.green("\u2713") + " Wrote " + chalk22.bold(String(memories.length)) + " memor" + (memories.length === 1 ? "y" : "ies") + " to " + chalk22.bold(opts.out)
3437
+ );
3438
+ } else {
3439
+ process.stdout.write(body);
3440
+ }
3441
+ } finally {
3442
+ await closeStore2(store);
3443
+ }
3444
+ }
3445
+
3446
+ // src/commands/interrupts.ts
3447
+ import chalk24 from "chalk";
3448
+ import Enquirer4 from "enquirer";
3449
+ import { SecretsManager as SecretsManager11 } from "@tuttiai/core";
3450
+
3451
+ // src/commands/interrupts-render.ts
3452
+ import chalk23 from "chalk";
3453
+ function visibleLen3(s) {
3454
+ return s.replace(/\u001b\[[0-9;]*m/g, "").length;
3455
+ }
3456
+ function pad6(s, len) {
3457
+ const v = visibleLen3(s);
3458
+ return v >= len ? s : s + " ".repeat(len - v);
3459
+ }
3460
+ function truncate2(text, max) {
3461
+ const oneLine = text.replace(/\s+/g, " ").trim();
3462
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
3463
+ }
3464
+ function formatIsoShort(d) {
3465
+ const iso = d.toISOString();
3466
+ return iso.slice(0, 10) + " " + iso.slice(11, 19);
3467
+ }
3468
+ function formatRelativeTime(requested_at, now = /* @__PURE__ */ new Date()) {
3469
+ const diffMs = now.getTime() - requested_at.getTime();
3470
+ if (diffMs < 0) return "now";
3471
+ const s = Math.floor(diffMs / 1e3);
3472
+ if (s < 60) return s + "s ago";
3473
+ const m = Math.floor(s / 60);
3474
+ if (m < 60) return m + "m ago";
3475
+ const h = Math.floor(m / 60);
3476
+ if (h < 24) return h + "h ago";
3477
+ const d = Math.floor(h / 24);
3478
+ return d + "d ago";
3479
+ }
3480
+ function truncateArgs(tool_args, max = 80) {
3481
+ let json;
3482
+ try {
3483
+ json = JSON.stringify(tool_args);
3484
+ } catch {
3485
+ json = String(tool_args);
3486
+ }
3487
+ if (json === void 0) return "";
3488
+ return truncate2(json, max);
3489
+ }
3490
+ function renderInterruptsList(interrupts, now = /* @__PURE__ */ new Date()) {
3491
+ if (interrupts.length === 0) {
3492
+ return chalk23.dim("No pending interrupts.");
3493
+ }
3494
+ const lines = [];
3495
+ lines.push("");
3496
+ lines.push(
3497
+ chalk23.dim(
3498
+ " " + pad6("ID", 10) + pad6("SESSION", 14) + pad6("TOOL", 22) + pad6("ARGS", 52) + "AGE"
3499
+ )
3500
+ );
3501
+ lines.push(chalk23.dim(" " + "\u2500".repeat(110)));
3502
+ for (const r of interrupts) {
3503
+ const idShort = r.interrupt_id.slice(0, 8);
3504
+ const sessionShort = r.session_id.slice(0, 12);
3505
+ const toolName = truncate2(r.tool_name, 20);
3506
+ const argsPreview = truncateArgs(r.tool_args, 50);
3507
+ const age = formatRelativeTime(r.requested_at, now);
3508
+ lines.push(
3509
+ " " + chalk23.bold(pad6(idShort, 10)) + pad6(sessionShort, 14) + pad6(chalk23.cyan(toolName), 22) + pad6(chalk23.dim(argsPreview), 52) + chalk23.dim(age)
3510
+ );
3511
+ }
3512
+ lines.push("");
3513
+ return lines.join("\n");
3514
+ }
3515
+ function renderInterruptDetail(interrupt, now = /* @__PURE__ */ new Date()) {
3516
+ const lines = [];
3517
+ lines.push("");
3518
+ lines.push(chalk23.bold("Interrupt ") + chalk23.dim(interrupt.interrupt_id));
3519
+ lines.push(chalk23.dim("\u2500".repeat(60)));
3520
+ lines.push(chalk23.dim("Session: ") + interrupt.session_id);
3521
+ lines.push(chalk23.dim("Tool: ") + chalk23.cyan(interrupt.tool_name));
3522
+ lines.push(
3523
+ chalk23.dim("Requested: ") + formatIsoShort(interrupt.requested_at) + chalk23.dim(" (" + formatRelativeTime(interrupt.requested_at, now) + ")")
3524
+ );
3525
+ lines.push(chalk23.dim("Status: ") + colorStatus2(interrupt.status));
3526
+ if (interrupt.resolved_at) {
3527
+ lines.push(chalk23.dim("Resolved: ") + formatIsoShort(interrupt.resolved_at));
3528
+ }
3529
+ if (interrupt.resolved_by) {
3530
+ lines.push(chalk23.dim("Resolved by: ") + interrupt.resolved_by);
3531
+ }
3532
+ if (interrupt.denial_reason) {
3533
+ lines.push(chalk23.dim("Reason: ") + interrupt.denial_reason);
3534
+ }
3535
+ lines.push("");
3536
+ lines.push(chalk23.dim("Arguments:"));
3537
+ lines.push(prettyJson(interrupt.tool_args));
3538
+ lines.push("");
3539
+ return lines.join("\n");
3540
+ }
3541
+ function prettyJson(value) {
3542
+ try {
3543
+ return JSON.stringify(value, null, 2);
3544
+ } catch {
3545
+ return String(value);
3546
+ }
3547
+ }
3548
+ function colorStatus2(status) {
3549
+ if (status === "approved") return chalk23.green("approved");
3550
+ if (status === "denied") return chalk23.red("denied");
3551
+ return chalk23.yellow("pending");
3552
+ }
3553
+ function renderApproved(interrupt) {
3554
+ return chalk23.green("\u2713") + " Approved " + chalk23.bold(interrupt.interrupt_id.slice(0, 8)) + chalk23.dim(" (" + interrupt.tool_name + ")") + (interrupt.resolved_by ? chalk23.dim(" by " + interrupt.resolved_by) : "");
3555
+ }
3556
+ function renderDenied(interrupt) {
3557
+ return chalk23.red("\u2717") + " Denied " + chalk23.bold(interrupt.interrupt_id.slice(0, 8)) + chalk23.dim(" (" + interrupt.tool_name + ")") + (interrupt.denial_reason ? chalk23.dim(' \u2014 "' + interrupt.denial_reason + '"') : "");
3558
+ }
3559
+
3560
+ // src/commands/interrupts.ts
3561
+ var { prompt: prompt4 } = Enquirer4;
3562
+ var DEFAULT_SERVER_URL2 = "http://127.0.0.1:3847";
3563
+ var POLL_INTERVAL_MS = 2e3;
3564
+ function resolveUrl2(opts) {
3565
+ return opts.url ?? SecretsManager11.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL2;
3566
+ }
3567
+ function resolveAuthHeader2(opts) {
3568
+ const key = opts.apiKey ?? SecretsManager11.optional("TUTTI_API_KEY");
3569
+ return key ? { Authorization: "Bearer " + key } : {};
3570
+ }
3571
+ function explainConnectionError2(err, baseUrl) {
3572
+ const msg = err instanceof Error ? err.message : String(err);
3573
+ console.error(chalk24.red("Failed to reach Tutti server at " + baseUrl));
3574
+ console.error(chalk24.dim(" " + msg));
3575
+ console.error(
3576
+ chalk24.dim(" Is `tutti-ai serve` running? Set --url or TUTTI_SERVER_URL to override.")
3577
+ );
3578
+ process.exit(1);
3579
+ }
3580
+ function reviveInterrupt(wire) {
3581
+ const req = {
3582
+ interrupt_id: wire["interrupt_id"],
3583
+ session_id: wire["session_id"],
3584
+ tool_name: wire["tool_name"],
3585
+ tool_args: wire["tool_args"],
3586
+ requested_at: new Date(wire["requested_at"]),
3587
+ status: wire["status"]
3588
+ };
3589
+ if (typeof wire["resolved_at"] === "string") {
3590
+ req.resolved_at = new Date(wire["resolved_at"]);
3591
+ }
3592
+ if (typeof wire["resolved_by"] === "string") {
3593
+ req.resolved_by = wire["resolved_by"];
3594
+ }
3595
+ if (typeof wire["denial_reason"] === "string") {
3596
+ req.denial_reason = wire["denial_reason"];
3597
+ }
3598
+ return req;
3599
+ }
3600
+ async function httpJson(opts, method, path, body) {
3601
+ const baseUrl = resolveUrl2(opts);
3602
+ const url = baseUrl.replace(/\/$/, "") + path;
3603
+ let res;
3604
+ try {
3605
+ res = await fetch(url, {
3606
+ method,
3607
+ headers: {
3608
+ "Content-Type": "application/json",
3609
+ ...resolveAuthHeader2(opts)
3610
+ },
3611
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3612
+ });
3613
+ } catch (err) {
3614
+ explainConnectionError2(err, baseUrl);
3615
+ }
3616
+ const text = await res.text();
3617
+ const parsed = text === "" ? null : JSON.parse(text);
3618
+ return { status: res.status, body: parsed };
3619
+ }
3620
+ async function fetchPending(opts) {
3621
+ const { status, body } = await httpJson(
3622
+ opts,
3623
+ "GET",
3624
+ "/interrupts/pending"
3625
+ );
3626
+ if (status === 401) {
3627
+ console.error(chalk24.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3628
+ process.exit(1);
3629
+ }
3630
+ if (status === 503) {
3631
+ console.error(
3632
+ chalk24.red(
3633
+ "The server has no InterruptStore configured. Start `tutti-ai serve` with an interrupt store attached."
3634
+ )
3635
+ );
3636
+ process.exit(1);
3637
+ }
3638
+ if (status < 200 || status >= 300 || !("interrupts" in body)) {
3639
+ console.error(chalk24.red("Unexpected server response: " + status));
3640
+ process.exit(1);
3641
+ }
3642
+ return body.interrupts.map(reviveInterrupt);
3643
+ }
3644
+ async function postResolve(opts, interruptId, action, payload) {
3645
+ const { status, body } = await httpJson(
3646
+ opts,
3647
+ "POST",
3648
+ "/interrupts/" + encodeURIComponent(interruptId) + "/" + action,
3649
+ payload
3650
+ );
3651
+ if (status === 401) {
3652
+ console.error(chalk24.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3653
+ process.exit(1);
3654
+ }
3655
+ if (status === 404) {
3656
+ console.error(chalk24.red('Interrupt "' + interruptId + '" not found.'));
3657
+ process.exit(1);
3658
+ }
3659
+ if (status === 409) {
3660
+ const current = body.current;
3661
+ const currentStatus = current && typeof current["status"] === "string" ? current["status"] : "resolved";
3662
+ console.error(
3663
+ chalk24.red("Interrupt already " + currentStatus + " \u2014 refusing to override.")
3664
+ );
3665
+ process.exit(1);
3666
+ }
3667
+ if (status < 200 || status >= 300) {
3668
+ console.error(chalk24.red("Unexpected server response: " + status));
3669
+ process.exit(1);
3670
+ }
3671
+ return reviveInterrupt(body);
3672
+ }
3673
+ async function interruptsListCommand(opts) {
3674
+ const pending = await fetchPending(opts);
3675
+ console.log(renderInterruptsList(pending));
3676
+ }
3677
+ async function interruptsApproveCommand(interruptId, opts) {
3678
+ const resolved = await postResolve(opts, interruptId, "approve", {
3679
+ ...opts.resolvedBy !== void 0 ? { resolved_by: opts.resolvedBy } : {}
3680
+ });
3681
+ console.log(renderApproved(resolved));
3682
+ }
3683
+ async function interruptsDenyCommand(interruptId, opts) {
3684
+ const resolved = await postResolve(opts, interruptId, "deny", {
3685
+ ...opts.reason !== void 0 ? { reason: opts.reason } : {},
3686
+ ...opts.resolvedBy !== void 0 ? { resolved_by: opts.resolvedBy } : {}
3687
+ });
3688
+ console.log(renderDenied(resolved));
3689
+ }
3690
+ function clearScreen() {
3691
+ process.stdout.write("\x1B[2J\x1B[H");
3692
+ }
3693
+ function readKey() {
3694
+ const input = process.stdin;
3695
+ if (!input.isTTY) return Promise.resolve(null);
3696
+ return new Promise((resolve18) => {
3697
+ input.setRawMode(true);
3698
+ input.resume();
3699
+ input.setEncoding("utf8");
3700
+ const onData = (data) => {
3701
+ input.removeListener("data", onData);
3702
+ input.setRawMode(false);
3703
+ input.pause();
3704
+ resolve18(data);
3705
+ };
3706
+ input.on("data", onData);
3707
+ });
3708
+ }
3709
+ async function readKeyOrTimeout(ms) {
3710
+ let timer;
3711
+ const timeout = new Promise((resolve18) => {
3712
+ timer = setTimeout(() => resolve18(null), ms);
3713
+ });
3714
+ try {
3715
+ const winner = await Promise.race([readKey(), timeout]);
3716
+ return winner;
3717
+ } finally {
3718
+ if (timer) clearTimeout(timer);
3719
+ }
3720
+ }
3721
+ async function interruptsTUICommand(opts) {
3722
+ if (!process.stdin.isTTY) {
3723
+ await interruptsListCommand(opts);
3724
+ return;
3725
+ }
3726
+ const sigint = () => {
3727
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
3728
+ process.stdout.write("\n");
3729
+ process.exit(0);
3730
+ };
3731
+ process.on("SIGINT", sigint);
3732
+ try {
3733
+ for (; ; ) {
3734
+ const pending = await fetchPending(opts);
3735
+ clearScreen();
3736
+ console.log(
3737
+ chalk24.bold("Tutti \u2014 pending interrupts") + chalk24.dim(" (auto-refresh every " + POLL_INTERVAL_MS / 1e3 + "s)")
3738
+ );
3739
+ console.log(renderInterruptsList(pending));
3740
+ if (pending.length > 0) {
3741
+ console.log(
3742
+ chalk24.dim(
3743
+ "Press a number to inspect, 'r' to refresh, 'q' to quit."
3744
+ )
3745
+ );
3746
+ } else {
3747
+ console.log(chalk24.dim("Press 'r' to refresh, 'q' to quit."));
3748
+ }
3749
+ const indexed = pending.slice(0, 9);
3750
+ const key = await readKeyOrTimeout(POLL_INTERVAL_MS);
3751
+ if (key === null) continue;
3752
+ if (key === "q" || key === "") return;
3753
+ if (key === "r") continue;
3754
+ const digit = parseInt(key, 10);
3755
+ if (!Number.isNaN(digit) && digit >= 1 && digit <= indexed.length) {
3756
+ const chosen = indexed[digit - 1];
3757
+ const shouldContinue = await runDetailView(opts, chosen);
3758
+ if (!shouldContinue) return;
3759
+ }
3760
+ }
3761
+ } finally {
3762
+ process.off("SIGINT", sigint);
3763
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
3764
+ }
3765
+ }
3766
+ async function runDetailView(opts, interrupt) {
3767
+ clearScreen();
3768
+ console.log(renderInterruptDetail(interrupt));
3769
+ console.log(
3770
+ chalk24.dim(
3771
+ "Press 'a' to approve, 'd' to deny, 'q' to go back to the list."
3772
+ )
3773
+ );
3774
+ const key = await readKey();
3775
+ if (key === null || key === "q" || key === "") return true;
3776
+ if (key === "a") {
3777
+ const resolved = await postResolve(opts, interrupt.interrupt_id, "approve", {});
3778
+ clearScreen();
3779
+ console.log(renderApproved(resolved));
3780
+ await pause();
3781
+ return true;
3782
+ }
3783
+ if (key === "d") {
3784
+ const { reason } = await prompt4({
3785
+ type: "input",
3786
+ name: "reason",
3787
+ message: "Reason (optional):"
3788
+ });
3789
+ const payload = {};
3790
+ if (reason && reason.trim() !== "") payload["reason"] = reason.trim();
3791
+ const resolved = await postResolve(
3792
+ opts,
3793
+ interrupt.interrupt_id,
3794
+ "deny",
3795
+ payload
3796
+ );
3797
+ clearScreen();
3798
+ console.log(renderDenied(resolved));
3799
+ await pause();
3800
+ return true;
3801
+ }
3802
+ return true;
3803
+ }
3804
+ async function pause() {
3805
+ console.log(chalk24.dim("\nPress any key to continue..."));
3806
+ await readKey();
3807
+ }
3808
+
3185
3809
  // src/index.ts
3186
3810
  config();
3187
- var logger15 = createLogger15("tutti-cli");
3811
+ var logger16 = createLogger16("tutti-cli");
3188
3812
  process.on("unhandledRejection", (reason) => {
3189
- logger15.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
3813
+ logger16.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
3190
3814
  process.exit(1);
3191
3815
  });
3192
3816
  process.on("uncaughtException", (err) => {
3193
- logger15.error({ error: err.message }, "Fatal error");
3817
+ logger16.error({ error: err.message }, "Fatal error");
3194
3818
  process.exit(1);
3195
3819
  });
3196
3820
  var program = new Command();
@@ -3306,5 +3930,60 @@ tracesCmd.command("tail").description("Live-tail spans as they are emitted (Ctrl
3306
3930
  };
3307
3931
  await tracesTailCommand(resolved);
3308
3932
  });
3933
+ var memoryCmd = program.command("memory").description("Manage per-user memories (uses TUTTI_PG_URL like the runtime)");
3934
+ memoryCmd.command("list").description("Show every memory for a user, sorted by importance + recency").requiredOption("--user <user-id>", "End-user identifier").action(async (opts) => {
3935
+ await memoryListCommand({ user: opts.user });
3936
+ });
3937
+ memoryCmd.command("search <query>").description("Search a user's memories by relevance").requiredOption("--user <user-id>", "End-user identifier").action(async (query, opts) => {
3938
+ await memorySearchCommand(query, { user: opts.user });
3939
+ });
3940
+ memoryCmd.command("add <content>").description("Manually store a memory for a user").requiredOption("--user <user-id>", "End-user identifier").option("--importance <n>", "Importance: 1 (low), 2 (normal), 3 (high)", "2").action(async (content, opts) => {
3941
+ await memoryAddCommand(content, {
3942
+ user: opts.user,
3943
+ ...opts.importance !== void 0 ? { importance: opts.importance } : {}
3944
+ });
3945
+ });
3946
+ memoryCmd.command("delete <memory-id>").description("Delete a specific memory by id").requiredOption("--user <user-id>", "End-user identifier").action(async (memoryId, opts) => {
3947
+ await memoryDeleteCommand(memoryId, { user: opts.user });
3948
+ });
3949
+ memoryCmd.command("clear").description("Delete every memory for a user (prompts for confirmation)").requiredOption("--user <user-id>", "End-user identifier").action(async (opts) => {
3950
+ await memoryClearCommand({ user: opts.user });
3951
+ });
3952
+ memoryCmd.command("export").description("Export every memory for a user as JSON or CSV").requiredOption("--user <user-id>", "End-user identifier").option("--format <fmt>", "Output format: json | csv", "json").option("-o, --out <path>", "Write to file instead of stdout").action(async (opts) => {
3953
+ await memoryExportCommand({
3954
+ user: opts.user,
3955
+ ...opts.format !== void 0 ? { format: opts.format } : {},
3956
+ ...opts.out !== void 0 ? { out: opts.out } : {}
3957
+ });
3958
+ });
3959
+ function toInterruptsOptions(raw) {
3960
+ return {
3961
+ ...raw.url !== void 0 ? { url: raw.url } : {},
3962
+ ...raw.apiKey !== void 0 ? { apiKey: raw.apiKey } : {}
3963
+ };
3964
+ }
3965
+ var interruptsCmd = program.command("interrupts").description("Review and resolve approval-gated tool calls");
3966
+ interruptsCmd.option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (opts) => {
3967
+ await interruptsTUICommand(toInterruptsOptions(opts));
3968
+ });
3969
+ interruptsCmd.command("list").description("Print pending interrupts as a table and exit (script-friendly)").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (opts) => {
3970
+ await interruptsListCommand(toInterruptsOptions(opts));
3971
+ });
3972
+ interruptsCmd.command("approve <interrupt-id>").description("Approve an interrupt directly").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").option("--by <name>", "Reviewer identifier (resolved_by field)").action(async (id, opts) => {
3973
+ await interruptsApproveCommand(id, {
3974
+ ...toInterruptsOptions(opts),
3975
+ ...opts.by !== void 0 ? { resolvedBy: opts.by } : {}
3976
+ });
3977
+ });
3978
+ interruptsCmd.command("deny <interrupt-id>").description("Deny an interrupt directly").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").option("--reason <text>", "Free-text denial reason").option("--by <name>", "Reviewer identifier (resolved_by field)").action(async (id, opts) => {
3979
+ await interruptsDenyCommand(id, {
3980
+ ...toInterruptsOptions(opts),
3981
+ ...opts.reason !== void 0 ? { reason: opts.reason } : {},
3982
+ ...opts.by !== void 0 ? { resolvedBy: opts.by } : {}
3983
+ });
3984
+ });
3985
+ program.command("approve").description("Alias for `tutti-ai interrupts` \u2014 interactive approval TUI").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (opts) => {
3986
+ await interruptsTUICommand(toInterruptsOptions(opts));
3987
+ });
3309
3988
  program.parse();
3310
3989
  //# sourceMappingURL=index.js.map