@orangecollective/oc 0.1.1 → 0.2.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 +19 -4
- package/dist/oc.js +707 -102
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,18 +52,28 @@ In the chat:
|
|
|
52
52
|
|
|
53
53
|
| | |
|
|
54
54
|
|---|---|
|
|
55
|
-
| `@name` |
|
|
55
|
+
| `@name` | mention a company, founder, batch or skill — it stays in your text, highlighted, and goes to the agent as context |
|
|
56
56
|
| `/` | open the command list (tab to complete) |
|
|
57
57
|
| `/model [name]` | switch model (no argument lists them) |
|
|
58
58
|
| `/web` | toggle web search |
|
|
59
59
|
| `/team [slug]` · `/batch [W26]` | change scope |
|
|
60
60
|
| `/digest [-r]` | re-run the digest (`-r` regenerates it server-side) |
|
|
61
|
+
| `/activity [n]` | the latest batch activity (default 10) |
|
|
62
|
+
| `/activity on` · `off` | the live ticker — new batch activity prints as it happens (on by default, polled once a minute) |
|
|
63
|
+
| `/fav <company> [in <folder>]` | add a company to a favorites folder (`/fav @Lyon` works too; a folder that doesn't exist is created) |
|
|
64
|
+
| `/unfav <company>` | remove your favorite |
|
|
65
|
+
| `/favs [folder]` | list your team's folders, or one folder's companies |
|
|
61
66
|
| `/new` · `/clear` · `/quit` | |
|
|
62
67
|
| `?` | shortcuts (on an empty prompt) |
|
|
63
68
|
| `↑ ↓` | history, or move through a list |
|
|
64
|
-
| `esc` | interrupt · close a list
|
|
69
|
+
| `esc` | interrupt · close a list |
|
|
65
70
|
| `ctrl-c` ×2 | exit |
|
|
66
71
|
|
|
72
|
+
Activity rows led by an orange `◆` are things a teammate did (favorited a
|
|
73
|
+
company, made a folder, joined) — the same events the web app's Activity tab
|
|
74
|
+
shows. Rows led by `·` are the sync pipeline noticing a change (a new launch, an
|
|
75
|
+
edited one-liner).
|
|
76
|
+
|
|
67
77
|
Config lives in `~/.oc/` — `credentials.json` (session) and `state.json` (model,
|
|
68
78
|
team, batch, thread), both `0600`. Set `OC_CONFIG_DIR` to relocate them.
|
|
69
79
|
|
|
@@ -98,6 +108,11 @@ Notes for anyone editing this:
|
|
|
98
108
|
- `build.mjs` resolves the app's `@/` alias so a small allowlist of **pure**
|
|
99
109
|
modules can be shared (the digest wire format). Never import anything that
|
|
100
110
|
touches `next/headers`, a Supabase server client, or React components.
|
|
111
|
+
- An accepted `@`-mention is ordinary text (`@Label`) plus a record attached
|
|
112
|
+
behind it. The record lives exactly as long as its `@Label` is in the prompt
|
|
113
|
+
— delete the text and the context goes with it — and `findMentionSpans` in
|
|
114
|
+
`App.tsx` is what both highlights it and stops the picker reopening inside it.
|
|
101
115
|
- Server-side pieces this depends on: `lib/supabase/request.ts` (bearer auth),
|
|
102
|
-
`app/api/cli
|
|
103
|
-
the `oc` CLI section in the
|
|
116
|
+
`app/api/cli/*` (bootstrap, activity, favorites), `app/auth/cli/[port]/[code]/page.tsx`,
|
|
117
|
+
`lib/cli-auth.ts`, `lib/favorites-core.ts`. See the `oc` CLI section in the
|
|
118
|
+
repo's `CLAUDE.md`.
|
package/dist/oc.js
CHANGED
|
@@ -13,7 +13,7 @@ import open from "open";
|
|
|
13
13
|
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
|
-
var VERSION = true ? "0.
|
|
16
|
+
var VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
17
17
|
function isOlderThan(version, floor) {
|
|
18
18
|
const parse = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
19
19
|
const [a, b] = [parse(version), parse(floor)];
|
|
@@ -323,6 +323,16 @@ var OcClient = class {
|
|
|
323
323
|
}
|
|
324
324
|
host;
|
|
325
325
|
getToken;
|
|
326
|
+
/** Read a JSON error body into a thrown Error, falling back to the status. */
|
|
327
|
+
async fail(res, fallback) {
|
|
328
|
+
let detail = null;
|
|
329
|
+
try {
|
|
330
|
+
const json = await res.json();
|
|
331
|
+
if (typeof json.error === "string") detail = json.error;
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
throw new Error(detail ?? `${fallback} (HTTP ${res.status})`);
|
|
335
|
+
}
|
|
326
336
|
async authHeaders() {
|
|
327
337
|
return {
|
|
328
338
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
@@ -403,6 +413,65 @@ var OcClient = class {
|
|
|
403
413
|
if (!res.ok) return [];
|
|
404
414
|
return flattenContextResults(await res.json());
|
|
405
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* The batch's activity feed (`GET /api/cli/activity`) — with `after`, only
|
|
418
|
+
* events newer than that timestamp, which is how the ticker polls cheaply.
|
|
419
|
+
*/
|
|
420
|
+
async activity(args) {
|
|
421
|
+
const params = new URLSearchParams({ teamId: String(args.teamId), batch: args.batch });
|
|
422
|
+
if (args.after) params.set("after", args.after);
|
|
423
|
+
if (args.limit) params.set("limit", String(args.limit));
|
|
424
|
+
const res = await this.request(`/api/cli/activity?${params}`, { signal: args.signal });
|
|
425
|
+
if (!res.ok) return this.fail(res, "Couldn't load activity");
|
|
426
|
+
const json = await res.json();
|
|
427
|
+
return json.items ?? [];
|
|
428
|
+
}
|
|
429
|
+
/** Company-only search, for resolving the name given to `/fav` and `/unfav`. */
|
|
430
|
+
async searchCompanies(query, limit = 8) {
|
|
431
|
+
const params = new URLSearchParams({
|
|
432
|
+
q: query,
|
|
433
|
+
companies: String(limit),
|
|
434
|
+
founders: "0",
|
|
435
|
+
skills: "0",
|
|
436
|
+
sections: "companies"
|
|
437
|
+
});
|
|
438
|
+
const res = await this.request(`/api/search/context?${params}`);
|
|
439
|
+
if (!res.ok) return [];
|
|
440
|
+
const data = await res.json();
|
|
441
|
+
return (data.companies ?? []).map((c) => ({ id: c.id, name: c.name, batch: c.batch }));
|
|
442
|
+
}
|
|
443
|
+
async favoriteFolders(teamId) {
|
|
444
|
+
const res = await this.request(`/api/cli/favorites?teamId=${teamId}`);
|
|
445
|
+
if (!res.ok) return this.fail(res, "Couldn't load your folders");
|
|
446
|
+
const json = await res.json();
|
|
447
|
+
return json.folders ?? [];
|
|
448
|
+
}
|
|
449
|
+
async addFavorite(folderId, companyId) {
|
|
450
|
+
const res = await this.request("/api/cli/favorites", {
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers: { "Content-Type": "application/json" },
|
|
453
|
+
body: JSON.stringify({ folderId, companyId })
|
|
454
|
+
});
|
|
455
|
+
if (!res.ok) return this.fail(res, "Couldn't add the favorite");
|
|
456
|
+
}
|
|
457
|
+
async removeFavorite(folderId, companyId) {
|
|
458
|
+
const res = await this.request("/api/cli/favorites", {
|
|
459
|
+
method: "DELETE",
|
|
460
|
+
headers: { "Content-Type": "application/json" },
|
|
461
|
+
body: JSON.stringify({ folderId, companyId })
|
|
462
|
+
});
|
|
463
|
+
if (!res.ok) return this.fail(res, "Couldn't remove the favorite");
|
|
464
|
+
}
|
|
465
|
+
async createFavoriteFolder(teamId, name) {
|
|
466
|
+
const res = await this.request("/api/cli/favorites/folders", {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json" },
|
|
469
|
+
body: JSON.stringify({ teamId, name })
|
|
470
|
+
});
|
|
471
|
+
if (!res.ok) return this.fail(res, "Couldn't create the folder");
|
|
472
|
+
const json = await res.json();
|
|
473
|
+
return json.id;
|
|
474
|
+
}
|
|
406
475
|
/**
|
|
407
476
|
* Transport for POST /api/chat. Sends exactly the body the web chat panel's
|
|
408
477
|
* transport sends, so the route needs no CLI-specific branch.
|
|
@@ -462,7 +531,7 @@ function buildContextPrefix(items) {
|
|
|
462
531
|
|
|
463
532
|
// src/ui/App.tsx
|
|
464
533
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
465
|
-
import { Box as
|
|
534
|
+
import { Box as Box6, Static, Text as Text6, useApp, useInput } from "ink";
|
|
466
535
|
import { generateId, readUIMessageStream } from "ai";
|
|
467
536
|
|
|
468
537
|
// src/markdown.ts
|
|
@@ -485,9 +554,118 @@ function renderMarkdown(text) {
|
|
|
485
554
|
}
|
|
486
555
|
}
|
|
487
556
|
|
|
488
|
-
// src/ui/
|
|
557
|
+
// src/ui/Activity.tsx
|
|
489
558
|
import { Box, Text } from "ink";
|
|
490
559
|
|
|
560
|
+
// ../lib/format-relative-date.ts
|
|
561
|
+
function formatRelativeDate(iso) {
|
|
562
|
+
if (!iso) return "";
|
|
563
|
+
const then = new Date(iso).getTime();
|
|
564
|
+
if (Number.isNaN(then)) return "";
|
|
565
|
+
const diffMs = Date.now() - then;
|
|
566
|
+
const diffSec = Math.floor(diffMs / 1e3);
|
|
567
|
+
if (diffSec < 60) return "just now";
|
|
568
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
569
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
570
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
571
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
572
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
573
|
+
if (diffDay < 7) return `${diffDay}d ago`;
|
|
574
|
+
const d = new Date(then);
|
|
575
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/activity.ts
|
|
579
|
+
var FIELD_LABELS = {
|
|
580
|
+
one_liner: "one-liner",
|
|
581
|
+
founder_dynamics: "founder dynamics",
|
|
582
|
+
group_partner: "group partner",
|
|
583
|
+
tldr: "TLDR"
|
|
584
|
+
};
|
|
585
|
+
var humanize = (field) => FIELD_LABELS[field] ?? field.replace(/_/g, " ");
|
|
586
|
+
function joinFields(fields) {
|
|
587
|
+
const labels = fields.map(humanize);
|
|
588
|
+
if (labels.length <= 1) return labels[0] ?? "";
|
|
589
|
+
if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
|
|
590
|
+
return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
|
|
591
|
+
}
|
|
592
|
+
function money(n) {
|
|
593
|
+
if (n == null) return null;
|
|
594
|
+
if (n >= 1e6) {
|
|
595
|
+
const v = n / 1e6;
|
|
596
|
+
return `$${Number.isInteger(v) ? v : v.toFixed(1)}M`;
|
|
597
|
+
}
|
|
598
|
+
if (n >= 1e3) return `$${Math.round(n / 1e3)}K`;
|
|
599
|
+
return `$${n}`;
|
|
600
|
+
}
|
|
601
|
+
var strong = (text) => ({ text, tone: "strong" });
|
|
602
|
+
var dim = (text) => ({ text, tone: "dim" });
|
|
603
|
+
function isSocialActivity(item) {
|
|
604
|
+
return item.actor != null;
|
|
605
|
+
}
|
|
606
|
+
function describeActivity(item) {
|
|
607
|
+
const { company, actor, meta } = item;
|
|
608
|
+
const name = company?.name ?? "a company";
|
|
609
|
+
const batch = company?.batch ? dim(` (${company.batch})`) : null;
|
|
610
|
+
const who = strong(actor?.name ?? meta.memberName ?? "Someone");
|
|
611
|
+
switch (item.eventType) {
|
|
612
|
+
case "yc_company_added":
|
|
613
|
+
return [
|
|
614
|
+
strong(name),
|
|
615
|
+
dim(` was added${company?.batch ? ` to ${company.batch}` : ""}`),
|
|
616
|
+
...company?.oneLiner ? [dim(` \u2014 ${company.oneLiner}`)] : []
|
|
617
|
+
];
|
|
618
|
+
case "yc_company_launched":
|
|
619
|
+
return [strong(name), ...batch ? [batch] : [], dim(" published their Launch YC post")];
|
|
620
|
+
case "yc_company_updated": {
|
|
621
|
+
const fields = meta.changedFields ?? [];
|
|
622
|
+
const single = fields.length === 1 ? fields[0] : null;
|
|
623
|
+
const value = single ? meta.changedValues?.[single] : null;
|
|
624
|
+
const old = single ? meta.changedOldValues?.[single] : null;
|
|
625
|
+
let verb;
|
|
626
|
+
if (!fields.length) verb = "was updated";
|
|
627
|
+
else if (single && value && old) verb = `updated their ${humanize(single)}: ${old} \u2192 ${value}`;
|
|
628
|
+
else if (single && value) verb = `updated their ${humanize(single)} to ${value}`;
|
|
629
|
+
else verb = `updated their ${joinFields(fields)}`;
|
|
630
|
+
return [strong(name), ...batch ? [batch] : [], dim(` ${verb}`)];
|
|
631
|
+
}
|
|
632
|
+
case "yc_company_team_size_changed": {
|
|
633
|
+
const span = meta.oldTeamSize != null && meta.newTeamSize != null ? `updated their team size: ${meta.oldTeamSize} \u2192 ${meta.newTeamSize}` : "changed team size";
|
|
634
|
+
return [strong(name), ...batch ? [batch] : [], dim(` ${span}`)];
|
|
635
|
+
}
|
|
636
|
+
case "company_progress_updated":
|
|
637
|
+
return [
|
|
638
|
+
dim("New progress update \xB7 "),
|
|
639
|
+
strong(name),
|
|
640
|
+
...meta.headline ? [dim(`: ${meta.headline}`)] : []
|
|
641
|
+
];
|
|
642
|
+
case "investment_tranche_added": {
|
|
643
|
+
const detail = [money(meta.amount), meta.stage].filter(Boolean).join(" \xB7 ");
|
|
644
|
+
return [
|
|
645
|
+
dim(meta.trancheType === "follow_on" ? "Follow-on investment in " : "New investment in "),
|
|
646
|
+
strong(name),
|
|
647
|
+
...detail ? [dim(` \xB7 ${detail}`)] : []
|
|
648
|
+
];
|
|
649
|
+
}
|
|
650
|
+
case "shared_folder_favorite_added":
|
|
651
|
+
return [
|
|
652
|
+
who,
|
|
653
|
+
dim(" favorited "),
|
|
654
|
+
strong(name),
|
|
655
|
+
...meta.folderName ? [dim(" into "), strong(meta.folderName)] : []
|
|
656
|
+
];
|
|
657
|
+
case "shared_folder_created":
|
|
658
|
+
return [who, dim(" created a shared folder "), strong(meta.folderName ?? "Untitled")];
|
|
659
|
+
case "team_member_added":
|
|
660
|
+
return [who, dim(" joined the team")];
|
|
661
|
+
default:
|
|
662
|
+
return [dim("Activity for "), strong(name)];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function activityAge(item) {
|
|
666
|
+
return formatRelativeDate(item.createdAt);
|
|
667
|
+
}
|
|
668
|
+
|
|
491
669
|
// src/ui/theme.ts
|
|
492
670
|
var ORANGE = {
|
|
493
671
|
tint: "#ffe2c4",
|
|
@@ -507,40 +685,97 @@ var LOGO_RAMP = [
|
|
|
507
685
|
];
|
|
508
686
|
var ACCENT = ORANGE.base;
|
|
509
687
|
|
|
688
|
+
// src/ui/Activity.tsx
|
|
689
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
690
|
+
function Segments({ segments }) {
|
|
691
|
+
return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, index) => /* @__PURE__ */ jsx(Text, { bold: segment.tone === "strong", dimColor: segment.tone === "dim", children: segment.text }, index)) });
|
|
692
|
+
}
|
|
693
|
+
function ActivityBlock({
|
|
694
|
+
heading,
|
|
695
|
+
items
|
|
696
|
+
}) {
|
|
697
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
698
|
+
heading ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: heading }) : null,
|
|
699
|
+
items.map((item) => /* @__PURE__ */ jsxs(Box, { children: [
|
|
700
|
+
/* @__PURE__ */ jsx(Text, { color: isSocialActivity(item) ? ACCENT : void 0, dimColor: !isSocialActivity(item), children: isSocialActivity(item) ? "\u25C6 " : "\xB7 " }),
|
|
701
|
+
/* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
|
|
702
|
+
/* @__PURE__ */ jsx(Segments, { segments: describeActivity(item) }),
|
|
703
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${activityAge(item)}` })
|
|
704
|
+
] })
|
|
705
|
+
] }, item.id))
|
|
706
|
+
] });
|
|
707
|
+
}
|
|
708
|
+
|
|
510
709
|
// src/ui/Input.tsx
|
|
511
|
-
import {
|
|
710
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
711
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
512
712
|
var PLACEHOLDER = "ask anything \u2014 @ for context, / for commands";
|
|
713
|
+
function HighlightedText({
|
|
714
|
+
value,
|
|
715
|
+
cursor,
|
|
716
|
+
spans,
|
|
717
|
+
dimPlain = false
|
|
718
|
+
}) {
|
|
719
|
+
const points = /* @__PURE__ */ new Set([0, value.length]);
|
|
720
|
+
if (cursor != null) {
|
|
721
|
+
points.add(cursor);
|
|
722
|
+
points.add(Math.min(cursor + 1, value.length));
|
|
723
|
+
}
|
|
724
|
+
for (const span of spans) {
|
|
725
|
+
points.add(span.start);
|
|
726
|
+
points.add(span.end);
|
|
727
|
+
}
|
|
728
|
+
const edges = [...points].sort((a, b) => a - b);
|
|
729
|
+
const runs = [];
|
|
730
|
+
for (let i = 0; i < edges.length - 1; i++) {
|
|
731
|
+
const [start, end] = [edges[i], edges[i + 1]];
|
|
732
|
+
if (start === end) continue;
|
|
733
|
+
const mention = spans.some((s) => start >= s.start && end <= s.end);
|
|
734
|
+
const caret = cursor != null && start === cursor && end === cursor + 1;
|
|
735
|
+
runs.push(
|
|
736
|
+
/* @__PURE__ */ jsx2(
|
|
737
|
+
Text2,
|
|
738
|
+
{
|
|
739
|
+
color: mention ? ACCENT : void 0,
|
|
740
|
+
bold: mention,
|
|
741
|
+
inverse: caret,
|
|
742
|
+
dimColor: !mention && dimPlain,
|
|
743
|
+
children: value.slice(start, end)
|
|
744
|
+
},
|
|
745
|
+
start
|
|
746
|
+
)
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (cursor != null && cursor >= value.length) {
|
|
750
|
+
runs.push(
|
|
751
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }, "caret")
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: runs });
|
|
755
|
+
}
|
|
513
756
|
function Input({
|
|
514
757
|
value,
|
|
515
758
|
cursor,
|
|
516
|
-
|
|
759
|
+
spans,
|
|
517
760
|
busy,
|
|
518
761
|
status,
|
|
519
762
|
hint
|
|
520
763
|
}) {
|
|
521
|
-
return /* @__PURE__ */
|
|
522
|
-
|
|
523
|
-
/* @__PURE__ */
|
|
524
|
-
/* @__PURE__ */
|
|
525
|
-
] }) : null,
|
|
526
|
-
/* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: busy ? "gray" : ACCENT, paddingX: 1, children: [
|
|
527
|
-
/* @__PURE__ */ jsx(Text, { color: busy ? "gray" : ACCENT, children: "> " }),
|
|
528
|
-
value.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: PLACEHOLDER }) : /* @__PURE__ */ jsxs(Text, { children: [
|
|
529
|
-
value.slice(0, cursor),
|
|
530
|
-
/* @__PURE__ */ jsx(Text, { inverse: true, children: value[cursor] ?? " " }),
|
|
531
|
-
value.slice(cursor + 1)
|
|
532
|
-
] })
|
|
764
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
765
|
+
/* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? "gray" : ACCENT, paddingX: 1, children: [
|
|
766
|
+
/* @__PURE__ */ jsx2(Text2, { color: busy ? "gray" : ACCENT, children: "> " }),
|
|
767
|
+
value.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: PLACEHOLDER }) : /* @__PURE__ */ jsx2(Text2, { children: /* @__PURE__ */ jsx2(HighlightedText, { value, cursor, spans }) })
|
|
533
768
|
] }),
|
|
534
|
-
/* @__PURE__ */
|
|
535
|
-
/* @__PURE__ */
|
|
536
|
-
/* @__PURE__ */
|
|
769
|
+
/* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, children: [
|
|
770
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: status }),
|
|
771
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: hint ? ` ${hint}` : "" })
|
|
537
772
|
] })
|
|
538
773
|
] });
|
|
539
774
|
}
|
|
540
775
|
|
|
541
776
|
// src/ui/Logo.tsx
|
|
542
|
-
import { Box as
|
|
543
|
-
import { jsx as
|
|
777
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
778
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
544
779
|
var LOGO_ROWS = [
|
|
545
780
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
546
781
|
"\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
@@ -551,17 +786,17 @@ var LOGO_ROWS = [
|
|
|
551
786
|
];
|
|
552
787
|
var LOGO_WIDTH = 43;
|
|
553
788
|
function CompactLogo() {
|
|
554
|
-
return /* @__PURE__ */
|
|
789
|
+
return /* @__PURE__ */ jsx3(Text3, { bold: true, color: ORANGE.base, children: "oc-cli" });
|
|
555
790
|
}
|
|
556
791
|
function Logo() {
|
|
557
792
|
const columns = process.stdout.columns || 80;
|
|
558
|
-
if (columns < LOGO_WIDTH + 2) return /* @__PURE__ */
|
|
559
|
-
return /* @__PURE__ */
|
|
793
|
+
if (columns < LOGO_WIDTH + 2) return /* @__PURE__ */ jsx3(CompactLogo, {});
|
|
794
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: LOGO_ROWS.map((row, index) => /* @__PURE__ */ jsx3(Text3, { color: LOGO_RAMP[index], children: row }, row)) });
|
|
560
795
|
}
|
|
561
796
|
|
|
562
797
|
// src/ui/Picker.tsx
|
|
563
|
-
import { Box as
|
|
564
|
-
import { jsx as
|
|
798
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
799
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
565
800
|
var MAX_VISIBLE = 10;
|
|
566
801
|
var MAX_LABEL_PAD = 28;
|
|
567
802
|
function Picker({
|
|
@@ -576,22 +811,22 @@ function Picker({
|
|
|
576
811
|
visible.reduce((width, row) => Math.max(width, row.label.length), 0)
|
|
577
812
|
);
|
|
578
813
|
if (rows.length === 0) {
|
|
579
|
-
return status ? /* @__PURE__ */
|
|
814
|
+
return status ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: status }) }) : null;
|
|
580
815
|
}
|
|
581
|
-
return /* @__PURE__ */
|
|
816
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: visible.map((row) => {
|
|
582
817
|
const isSelected = rows[selected]?.key === row.key;
|
|
583
|
-
return /* @__PURE__ */
|
|
584
|
-
/* @__PURE__ */
|
|
585
|
-
/* @__PURE__ */
|
|
586
|
-
row.meta ? /* @__PURE__ */
|
|
818
|
+
return /* @__PURE__ */ jsxs3(Box4, { paddingLeft: 1, children: [
|
|
819
|
+
/* @__PURE__ */ jsx4(Text4, { color: isSelected ? ACCENT : void 0, children: isSelected ? "\u276F " : " " }),
|
|
820
|
+
/* @__PURE__ */ jsx4(Text4, { color: isSelected ? ACCENT : void 0, bold: isSelected, children: row.label }),
|
|
821
|
+
row.meta ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `${" ".repeat(Math.max(1, labelPad - row.label.length))} ${row.meta}` }) : null
|
|
587
822
|
] }, row.key);
|
|
588
823
|
}) });
|
|
589
824
|
}
|
|
590
825
|
|
|
591
826
|
// src/ui/Turn.tsx
|
|
592
827
|
import { useEffect, useState } from "react";
|
|
593
|
-
import { Box as
|
|
594
|
-
import { jsx as
|
|
828
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
829
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
595
830
|
var SPINNER = ["\u2722", "\u2733", "\u2736", "\u273B", "\u273D", "\u273B", "\u2736", "\u2733"];
|
|
596
831
|
function useSpinner(active) {
|
|
597
832
|
const [frame, setFrame] = useState(0);
|
|
@@ -604,15 +839,15 @@ function useSpinner(active) {
|
|
|
604
839
|
}
|
|
605
840
|
function ToolRows({ tools, live }) {
|
|
606
841
|
if (tools.length === 0) return null;
|
|
607
|
-
return /* @__PURE__ */
|
|
608
|
-
/* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
842
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: tools.map((tool) => /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
843
|
+
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
844
|
+
/* @__PURE__ */ jsxs4(Text5, { color: tool.state === "error" ? "red" : tool.state === "running" ? ACCENT : "gray", children: [
|
|
610
845
|
"\u25CF",
|
|
611
846
|
" "
|
|
612
847
|
] }),
|
|
613
|
-
/* @__PURE__ */
|
|
848
|
+
/* @__PURE__ */ jsx5(Text5, { bold: tool.state === "running" && live, children: tool.name })
|
|
614
849
|
] }),
|
|
615
|
-
/* @__PURE__ */
|
|
850
|
+
/* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
616
851
|
" \u23BF ",
|
|
617
852
|
tool.state === "running" ? "running\u2026" : tool.state === "error" ? "failed" : "done"
|
|
618
853
|
] })
|
|
@@ -620,7 +855,7 @@ function ToolRows({ tools, live }) {
|
|
|
620
855
|
}
|
|
621
856
|
function Sources({ sources }) {
|
|
622
857
|
if (sources.length === 0) return null;
|
|
623
|
-
return /* @__PURE__ */
|
|
858
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: sources.map((source, index) => /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
624
859
|
` ${index + 1}. `,
|
|
625
860
|
source.title ? `${source.title} \u2014 ` : "",
|
|
626
861
|
source.url
|
|
@@ -633,27 +868,27 @@ function AssistantTurn({
|
|
|
633
868
|
thoughtSeconds
|
|
634
869
|
}) {
|
|
635
870
|
const body = renderMarkdown(stripContextLeakage(text));
|
|
636
|
-
return /* @__PURE__ */
|
|
637
|
-
/* @__PURE__ */
|
|
638
|
-
thoughtSeconds != null ? /* @__PURE__ */
|
|
871
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
872
|
+
/* @__PURE__ */ jsx5(ToolRows, { tools, live: false }),
|
|
873
|
+
thoughtSeconds != null ? /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
639
874
|
"\u273B thought for ",
|
|
640
875
|
thoughtSeconds,
|
|
641
876
|
"s"
|
|
642
877
|
] }) : null,
|
|
643
|
-
body ? /* @__PURE__ */
|
|
644
|
-
/* @__PURE__ */
|
|
878
|
+
body ? /* @__PURE__ */ jsx5(Box5, { marginTop: tools.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx5(Text5, { children: body }) }) : null,
|
|
879
|
+
/* @__PURE__ */ jsx5(Sources, { sources })
|
|
645
880
|
] });
|
|
646
881
|
}
|
|
647
882
|
function StreamingTurn({ turn }) {
|
|
648
883
|
const body = stripContextLeakage(turn.text);
|
|
649
884
|
const reasoningTail = turn.reasoning.trim().split("\n").at(-1) ?? "";
|
|
650
|
-
return /* @__PURE__ */
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
!body && reasoningTail ? /* @__PURE__ */
|
|
885
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
886
|
+
/* @__PURE__ */ jsx5(ToolRows, { tools: turn.tools, live: true }),
|
|
887
|
+
!body && reasoningTail ? /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
653
888
|
" ",
|
|
654
889
|
truncate(reasoningTail, (process.stdout.columns || 80) - 4)
|
|
655
890
|
] }) : null,
|
|
656
|
-
body ? /* @__PURE__ */
|
|
891
|
+
body ? /* @__PURE__ */ jsx5(Text5, { children: body }) : null
|
|
657
892
|
] });
|
|
658
893
|
}
|
|
659
894
|
function StatusLine({ turn }) {
|
|
@@ -666,16 +901,16 @@ function StatusLine({ turn }) {
|
|
|
666
901
|
const elapsed = Math.max(0, Math.round((now - turn.startedAt) / 1e3));
|
|
667
902
|
const running = turn.tools.find((t) => t.state === "running");
|
|
668
903
|
const verb = running ? running.name : turn.text ? "Writing" : "Thinking";
|
|
669
|
-
return /* @__PURE__ */
|
|
670
|
-
/* @__PURE__ */
|
|
904
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
905
|
+
/* @__PURE__ */ jsxs4(Text5, { color: ACCENT, children: [
|
|
671
906
|
spinner,
|
|
672
907
|
" "
|
|
673
908
|
] }),
|
|
674
|
-
/* @__PURE__ */
|
|
909
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
675
910
|
verb,
|
|
676
911
|
"\u2026 "
|
|
677
912
|
] }),
|
|
678
|
-
/* @__PURE__ */
|
|
913
|
+
/* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
679
914
|
"(",
|
|
680
915
|
elapsed,
|
|
681
916
|
"s \xB7 esc to interrupt)"
|
|
@@ -697,29 +932,56 @@ var EMPTY_TURN = {
|
|
|
697
932
|
};
|
|
698
933
|
|
|
699
934
|
// src/ui/App.tsx
|
|
700
|
-
import { jsx as
|
|
935
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
701
936
|
var FLUSH_INTERVAL_MS = 60;
|
|
702
937
|
var MENTION_DEBOUNCE_MS = 200;
|
|
703
938
|
var EXIT_CONFIRM_MS = 3e3;
|
|
939
|
+
var ACTIVITY_POLL_MS = 6e4;
|
|
940
|
+
var ACTIVITY_LIST_DEFAULT = 10;
|
|
941
|
+
var ACTIVITY_LIST_MAX = 30;
|
|
704
942
|
var COMMANDS = [
|
|
705
943
|
{ name: "model", args: "[name]", blurb: "switch model" },
|
|
706
944
|
{ name: "web", blurb: "toggle web search" },
|
|
707
945
|
{ name: "team", args: "[slug]", blurb: "switch active team" },
|
|
708
946
|
{ name: "batch", args: "[W26]", blurb: "set the batch for the digest" },
|
|
709
947
|
{ name: "digest", args: "[-r]", blurb: "re-run the activity digest" },
|
|
948
|
+
{ name: "activity", args: "[n | on | off]", blurb: "latest batch activity, or toggle the ticker" },
|
|
949
|
+
{ name: "fav", args: "<company> [in folder]", blurb: "add a company to a favorites folder" },
|
|
950
|
+
{ name: "unfav", args: "<company>", blurb: "remove your favorite" },
|
|
951
|
+
{ name: "favs", args: "[folder]", blurb: "list folders, or a folder's companies" },
|
|
710
952
|
{ name: "new", blurb: "start a fresh thread" },
|
|
711
953
|
{ name: "clear", blurb: "clear the screen" },
|
|
712
954
|
{ name: "help", blurb: "show shortcuts" },
|
|
713
955
|
{ name: "quit", blurb: "exit" }
|
|
714
956
|
];
|
|
715
|
-
var SHORTCUTS = ` @name
|
|
957
|
+
var SHORTCUTS = ` @name mention a company, founder, batch or skill (stays in your text)
|
|
716
958
|
/ commands
|
|
717
959
|
\u2191 \u2193 history, or move through a list
|
|
718
960
|
tab accept a suggestion
|
|
719
|
-
esc interrupt \xB7 close a list
|
|
961
|
+
esc interrupt \xB7 close a list
|
|
720
962
|
ctrl-a / ctrl-e start / end of line
|
|
721
963
|
ctrl-w / ctrl-u delete word / line
|
|
722
964
|
ctrl-c \xD72 exit`;
|
|
965
|
+
function findMentionSpans(text, records) {
|
|
966
|
+
const spans = [];
|
|
967
|
+
for (const record of records) {
|
|
968
|
+
const needle = `@${record.label}`;
|
|
969
|
+
let from = 0;
|
|
970
|
+
for (; ; ) {
|
|
971
|
+
const at = text.indexOf(needle, from);
|
|
972
|
+
if (at < 0) break;
|
|
973
|
+
spans.push({ start: at, end: at + needle.length });
|
|
974
|
+
from = at + needle.length;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
978
|
+
return spans.filter((span, i) => !spans.slice(0, i).some((k) => span.start >= k.start && span.end <= k.end));
|
|
979
|
+
}
|
|
980
|
+
function parseFavArgument(argument) {
|
|
981
|
+
const match = /^(.*\S)\s+(?:in|into)\s+(\S.*)$/.exec(argument);
|
|
982
|
+
if (match) return { name: match[1].trim(), folder: match[2].trim() };
|
|
983
|
+
return { name: argument.trim(), folder: null };
|
|
984
|
+
}
|
|
723
985
|
function App({ client, bootstrap, host, state }) {
|
|
724
986
|
const { exit } = useApp();
|
|
725
987
|
const [transcript, setTranscript] = useState2([]);
|
|
@@ -738,6 +1000,10 @@ function App({ client, bootstrap, host, state }) {
|
|
|
738
1000
|
const [mentionLoading, setMentionLoading] = useState2(false);
|
|
739
1001
|
const [mentionSearchedFor, setMentionSearchedFor] = useState2(null);
|
|
740
1002
|
const [selected, setSelected] = useState2(0);
|
|
1003
|
+
const [modal, setModal] = useState2(null);
|
|
1004
|
+
const [activityOn, setActivityOn] = useState2(state.activityUpdates ?? true);
|
|
1005
|
+
const activityCursorRef = useRef(null);
|
|
1006
|
+
const pendingActivityRef = useRef([]);
|
|
741
1007
|
const [teamId, setTeamId] = useState2(
|
|
742
1008
|
state.activeTeamId ?? bootstrap.activeTeamId
|
|
743
1009
|
);
|
|
@@ -746,6 +1012,8 @@ function App({ client, bootstrap, host, state }) {
|
|
|
746
1012
|
const [webSearch, setWebSearch] = useState2(state.webSearchEnabled ?? false);
|
|
747
1013
|
const threadIdRef = useRef(state.threadId ?? generateId());
|
|
748
1014
|
const messagesRef = useRef([]);
|
|
1015
|
+
const busyRef = useRef(false);
|
|
1016
|
+
busyRef.current = busy;
|
|
749
1017
|
const turnAbortRef = useRef(null);
|
|
750
1018
|
const digestAbortRef = useRef(null);
|
|
751
1019
|
const team = useMemo(
|
|
@@ -866,9 +1134,280 @@ function App({ client, bootstrap, host, state }) {
|
|
|
866
1134
|
controller.abort();
|
|
867
1135
|
};
|
|
868
1136
|
}, [client, mentionQuery, teamId]);
|
|
1137
|
+
const flushPendingActivity = useCallback(() => {
|
|
1138
|
+
const items = pendingActivityRef.current;
|
|
1139
|
+
if (items.length === 0) return;
|
|
1140
|
+
pendingActivityRef.current = [];
|
|
1141
|
+
push({ kind: "activity", heading: null, items });
|
|
1142
|
+
}, [push]);
|
|
1143
|
+
useEffect2(() => {
|
|
1144
|
+
if (!activityOn || !batch || teamId == null) return;
|
|
1145
|
+
let cancelled = false;
|
|
1146
|
+
const controller = new AbortController();
|
|
1147
|
+
const prime = async () => {
|
|
1148
|
+
if (activityCursorRef.current) return;
|
|
1149
|
+
try {
|
|
1150
|
+
const [latest] = await client.activity({ teamId, batch, limit: 1, signal: controller.signal });
|
|
1151
|
+
activityCursorRef.current = latest?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1152
|
+
} catch {
|
|
1153
|
+
if (!cancelled) activityCursorRef.current = (/* @__PURE__ */ new Date()).toISOString();
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
const tick = async () => {
|
|
1157
|
+
if (cancelled || !activityCursorRef.current) return;
|
|
1158
|
+
try {
|
|
1159
|
+
const items = await client.activity({
|
|
1160
|
+
teamId,
|
|
1161
|
+
batch,
|
|
1162
|
+
after: activityCursorRef.current,
|
|
1163
|
+
limit: 20,
|
|
1164
|
+
signal: controller.signal
|
|
1165
|
+
});
|
|
1166
|
+
if (cancelled || items.length === 0) return;
|
|
1167
|
+
activityCursorRef.current = items[0].createdAt;
|
|
1168
|
+
const chronological = [...items].reverse();
|
|
1169
|
+
if (busyRef.current) pendingActivityRef.current.push(...chronological);
|
|
1170
|
+
else push({ kind: "activity", heading: null, items: chronological });
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
void prime();
|
|
1175
|
+
const timer = setInterval(() => void tick(), ACTIVITY_POLL_MS);
|
|
1176
|
+
return () => {
|
|
1177
|
+
cancelled = true;
|
|
1178
|
+
controller.abort();
|
|
1179
|
+
clearInterval(timer);
|
|
1180
|
+
};
|
|
1181
|
+
}, [activityOn, batch, client, push, teamId]);
|
|
1182
|
+
const showRecentActivity = useCallback(
|
|
1183
|
+
async (count) => {
|
|
1184
|
+
if (!batch || teamId == null) {
|
|
1185
|
+
notice("No current batch for this team.", "error");
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
const items = await client.activity({ teamId, batch, limit: count });
|
|
1190
|
+
if (items.length === 0) {
|
|
1191
|
+
notice(`No activity yet for ${batch}.`);
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (!activityCursorRef.current || items[0].createdAt > activityCursorRef.current) {
|
|
1195
|
+
activityCursorRef.current = items[0].createdAt;
|
|
1196
|
+
}
|
|
1197
|
+
push({
|
|
1198
|
+
kind: "activity",
|
|
1199
|
+
heading: `Latest activity \xB7 ${batch} \xB7 ${team?.name ?? "team"}`,
|
|
1200
|
+
items: [...items].reverse()
|
|
1201
|
+
});
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
notice(describeError(error), "error");
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
[batch, client, notice, push, team?.name, teamId]
|
|
1207
|
+
);
|
|
1208
|
+
const pickFrom = useCallback(
|
|
1209
|
+
(title, rows, defaultIndex = 0) => new Promise((resolve) => {
|
|
1210
|
+
setSelected(Math.max(0, Math.min(defaultIndex, rows.length - 1)));
|
|
1211
|
+
setModal({
|
|
1212
|
+
title,
|
|
1213
|
+
rows,
|
|
1214
|
+
resolve: (key) => {
|
|
1215
|
+
setModal(null);
|
|
1216
|
+
resolve(key);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}),
|
|
1220
|
+
[]
|
|
1221
|
+
);
|
|
1222
|
+
const resolveCompany = useCallback(
|
|
1223
|
+
async (name, attached) => {
|
|
1224
|
+
const mentioned = attached.find((c) => c.type === "company");
|
|
1225
|
+
if (mentioned) {
|
|
1226
|
+
return { id: Number(mentioned.rawId), name: mentioned.label, batch: mentioned.subtitle ?? null };
|
|
1227
|
+
}
|
|
1228
|
+
if (!name) {
|
|
1229
|
+
notice("Name a company \u2014 /fav Lyon, or /fav @Lyon.", "error");
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
const hits = await client.searchCompanies(name);
|
|
1233
|
+
if (hits.length === 0) {
|
|
1234
|
+
notice(`No company matches \u201C${name}\u201D.`, "error");
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
const exact = hits.filter((h) => h.name.toLowerCase() === name.toLowerCase());
|
|
1238
|
+
if (exact.length === 1) return exact[0];
|
|
1239
|
+
if (hits.length === 1) return hits[0];
|
|
1240
|
+
const key = await pickFrom(
|
|
1241
|
+
`Which \u201C${name}\u201D?`,
|
|
1242
|
+
hits.map((h) => ({ key: String(h.id), label: h.name, meta: h.batch ?? void 0 }))
|
|
1243
|
+
);
|
|
1244
|
+
return hits.find((h) => String(h.id) === key) ?? null;
|
|
1245
|
+
},
|
|
1246
|
+
[client, notice, pickFrom]
|
|
1247
|
+
);
|
|
1248
|
+
const runFav = useCallback(
|
|
1249
|
+
async (argument, attached) => {
|
|
1250
|
+
if (teamId == null) {
|
|
1251
|
+
notice("Pick a team first \u2014 /team.", "error");
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const { name, folder: folderName } = parseFavArgument(argument);
|
|
1255
|
+
const company = await resolveCompany(name, attached);
|
|
1256
|
+
if (!company) return;
|
|
1257
|
+
let folders;
|
|
1258
|
+
try {
|
|
1259
|
+
folders = await client.favoriteFolders(teamId);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
notice(describeError(error), "error");
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const me = bootstrap.user.id;
|
|
1265
|
+
const already = (f) => f.items.some((i) => i.companyId === company.id && i.addedBy === me);
|
|
1266
|
+
let folder = null;
|
|
1267
|
+
if (folderName) {
|
|
1268
|
+
const needle = folderName.toLowerCase();
|
|
1269
|
+
folder = folders.find((f) => f.name.toLowerCase() === needle) ?? folders.find((f) => f.name.toLowerCase().includes(needle)) ?? null;
|
|
1270
|
+
if (!folder) {
|
|
1271
|
+
try {
|
|
1272
|
+
const id = await client.createFavoriteFolder(teamId, folderName);
|
|
1273
|
+
folder = { id, name: folderName };
|
|
1274
|
+
notice(`Created folder \u201C${folderName}\u201D in ${team?.name ?? "this team"}.`);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
notice(describeError(error), "error");
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} else if (folders.length === 0) {
|
|
1281
|
+
notice(
|
|
1282
|
+
`No favorites folders in ${team?.name ?? "this team"} yet \u2014 /fav ${company.name} in ${company.batch ?? batch ?? "My"} favorites creates one.`,
|
|
1283
|
+
"error"
|
|
1284
|
+
);
|
|
1285
|
+
return;
|
|
1286
|
+
} else {
|
|
1287
|
+
const ordered = [...folders].sort((a, b) => Number(b.isOwner) - Number(a.isOwner));
|
|
1288
|
+
const batchKey = (company.batch ?? batch ?? "").toLowerCase();
|
|
1289
|
+
const defaultIndex = Math.max(
|
|
1290
|
+
0,
|
|
1291
|
+
ordered.findIndex((f) => batchKey && f.name.toLowerCase().startsWith(batchKey))
|
|
1292
|
+
);
|
|
1293
|
+
const key = await pickFrom(
|
|
1294
|
+
`Add ${company.name} to\u2026`,
|
|
1295
|
+
ordered.map((f) => ({
|
|
1296
|
+
key: String(f.id),
|
|
1297
|
+
label: f.name,
|
|
1298
|
+
meta: [
|
|
1299
|
+
f.isOwner ? "mine" : f.ownerName,
|
|
1300
|
+
`${f.items.length} ${f.items.length === 1 ? "company" : "companies"}`,
|
|
1301
|
+
already(f) ? "already added" : null
|
|
1302
|
+
].filter(Boolean).join(" \xB7 ")
|
|
1303
|
+
})),
|
|
1304
|
+
defaultIndex
|
|
1305
|
+
);
|
|
1306
|
+
if (!key) return;
|
|
1307
|
+
folder = ordered.find((f) => String(f.id) === key) ?? null;
|
|
1308
|
+
if (!folder) return;
|
|
1309
|
+
}
|
|
1310
|
+
if ("items" in folder && already(folder)) {
|
|
1311
|
+
notice(`${company.name} is already in ${folder.name}.`);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
try {
|
|
1315
|
+
await client.addFavorite(folder.id, company.id);
|
|
1316
|
+
notice(`\u2605 Added ${company.name} to ${folder.name}.`);
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
notice(describeError(error), "error");
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
[batch, bootstrap.user.id, client, notice, pickFrom, resolveCompany, team?.name, teamId]
|
|
1322
|
+
);
|
|
1323
|
+
const runUnfav = useCallback(
|
|
1324
|
+
async (argument, attached) => {
|
|
1325
|
+
if (teamId == null) {
|
|
1326
|
+
notice("Pick a team first \u2014 /team.", "error");
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const company = await resolveCompany(argument.trim(), attached);
|
|
1330
|
+
if (!company) return;
|
|
1331
|
+
let folders;
|
|
1332
|
+
try {
|
|
1333
|
+
folders = await client.favoriteFolders(teamId);
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
notice(describeError(error), "error");
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const me = bootstrap.user.id;
|
|
1339
|
+
const holding = folders.filter(
|
|
1340
|
+
(f) => f.items.some((i) => i.companyId === company.id && i.addedBy === me)
|
|
1341
|
+
);
|
|
1342
|
+
if (holding.length === 0) {
|
|
1343
|
+
notice(`${company.name} isn't in any of your folders in ${team?.name ?? "this team"}.`);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
let folder = holding[0];
|
|
1347
|
+
if (holding.length > 1) {
|
|
1348
|
+
const key = await pickFrom(
|
|
1349
|
+
`Remove ${company.name} from\u2026`,
|
|
1350
|
+
holding.map((f) => ({ key: String(f.id), label: f.name }))
|
|
1351
|
+
);
|
|
1352
|
+
if (!key) return;
|
|
1353
|
+
folder = holding.find((f) => String(f.id) === key) ?? folder;
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
await client.removeFavorite(folder.id, company.id);
|
|
1357
|
+
notice(`Removed ${company.name} from ${folder.name}.`);
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
notice(describeError(error), "error");
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
[bootstrap.user.id, client, notice, pickFrom, resolveCompany, team?.name, teamId]
|
|
1363
|
+
);
|
|
1364
|
+
const runFavs = useCallback(
|
|
1365
|
+
async (argument) => {
|
|
1366
|
+
if (teamId == null) {
|
|
1367
|
+
notice("Pick a team first \u2014 /team.", "error");
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
let folders;
|
|
1371
|
+
try {
|
|
1372
|
+
folders = await client.favoriteFolders(teamId);
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
notice(describeError(error), "error");
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (folders.length === 0) {
|
|
1378
|
+
notice(`No favorites folders in ${team?.name ?? "this team"} yet.`);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const needle = argument.trim().toLowerCase();
|
|
1382
|
+
if (!needle) {
|
|
1383
|
+
const ordered = [...folders].sort((a, b) => Number(b.isOwner) - Number(a.isOwner));
|
|
1384
|
+
const width = Math.max(...ordered.map((f) => f.name.length));
|
|
1385
|
+
notice(
|
|
1386
|
+
ordered.map(
|
|
1387
|
+
(f) => ` \u2605 ${f.name.padEnd(width)} ${f.items.length} ${f.items.length === 1 ? "company" : "companies"} \xB7 ${f.isOwner ? "mine" : f.ownerName}`
|
|
1388
|
+
).join("\n")
|
|
1389
|
+
);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const folder = folders.find((f) => f.name.toLowerCase() === needle) ?? folders.find((f) => f.name.toLowerCase().includes(needle));
|
|
1393
|
+
if (!folder) {
|
|
1394
|
+
notice(`No folder matches \u201C${argument.trim()}\u201D. /favs lists them.`, "error");
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (folder.items.length === 0) {
|
|
1398
|
+
notice(`${folder.name} is empty.`);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const me = bootstrap.user.id;
|
|
1402
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1403
|
+
const lines = folder.items.filter((i) => seen.has(i.companyId) ? false : (seen.add(i.companyId), true)).sort((a, b) => a.companyName.localeCompare(b.companyName)).map((i) => ` ${i.addedBy === me ? "\u2605" : "\u2606"} ${i.companyName}${i.batch ? ` \xB7 ${i.batch}` : ""}`);
|
|
1404
|
+
notice(`${folder.name}
|
|
1405
|
+
${lines.join("\n")}`);
|
|
1406
|
+
},
|
|
1407
|
+
[bootstrap.user.id, client, notice, team?.name, teamId]
|
|
1408
|
+
);
|
|
869
1409
|
const send = useCallback(
|
|
870
|
-
async (raw) => {
|
|
871
|
-
const attached = context;
|
|
1410
|
+
async (raw, attached) => {
|
|
872
1411
|
const userMessage = {
|
|
873
1412
|
id: generateId(),
|
|
874
1413
|
role: "user",
|
|
@@ -877,7 +1416,6 @@ function App({ client, bootstrap, host, state }) {
|
|
|
877
1416
|
const outgoing = [...messagesRef.current, userMessage];
|
|
878
1417
|
messagesRef.current = outgoing;
|
|
879
1418
|
push({ kind: "user", text: raw, context: attached });
|
|
880
|
-
setContext([]);
|
|
881
1419
|
setBusy(true);
|
|
882
1420
|
const controller = new AbortController();
|
|
883
1421
|
turnAbortRef.current = controller;
|
|
@@ -934,12 +1472,13 @@ function App({ client, bootstrap, host, state }) {
|
|
|
934
1472
|
setLive(null);
|
|
935
1473
|
setBusy(false);
|
|
936
1474
|
turnAbortRef.current = null;
|
|
1475
|
+
flushPendingActivity();
|
|
937
1476
|
}
|
|
938
1477
|
},
|
|
939
|
-
[
|
|
1478
|
+
[flushPendingActivity, notice, push, transport]
|
|
940
1479
|
);
|
|
941
1480
|
const runCommand = useCallback(
|
|
942
|
-
(raw) => {
|
|
1481
|
+
(raw, attached) => {
|
|
943
1482
|
const [command, ...rest] = raw.trim().slice(1).split(/\s+/);
|
|
944
1483
|
const argument = rest.join(" ").trim();
|
|
945
1484
|
switch (command) {
|
|
@@ -998,6 +1537,7 @@ function App({ client, bootstrap, host, state }) {
|
|
|
998
1537
|
return;
|
|
999
1538
|
}
|
|
1000
1539
|
setTeamId(match.teamId);
|
|
1540
|
+
activityCursorRef.current = null;
|
|
1001
1541
|
void patchState({ activeTeamId: match.teamId });
|
|
1002
1542
|
notice(`Team: ${match.name}`);
|
|
1003
1543
|
return;
|
|
@@ -1009,6 +1549,7 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1009
1549
|
}
|
|
1010
1550
|
const next = argument.toUpperCase();
|
|
1011
1551
|
setBatch(next);
|
|
1552
|
+
activityCursorRef.current = null;
|
|
1012
1553
|
void patchState({ batch: next });
|
|
1013
1554
|
notice(`Batch: ${next}`);
|
|
1014
1555
|
return;
|
|
@@ -1016,37 +1557,80 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1016
1557
|
case "digest":
|
|
1017
1558
|
void runDigest(/--refresh|-r\b/.test(argument));
|
|
1018
1559
|
return;
|
|
1560
|
+
case "activity": {
|
|
1561
|
+
const flag = argument.toLowerCase();
|
|
1562
|
+
if (flag === "on" || flag === "off") {
|
|
1563
|
+
const next = flag === "on";
|
|
1564
|
+
setActivityOn(next);
|
|
1565
|
+
void patchState({ activityUpdates: next });
|
|
1566
|
+
notice(next ? "Activity updates on \u2014 new batch activity shows up as it happens." : "Activity updates off.");
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const count = Number.parseInt(argument, 10);
|
|
1570
|
+
void showRecentActivity(
|
|
1571
|
+
Number.isInteger(count) && count > 0 ? Math.min(count, ACTIVITY_LIST_MAX) : ACTIVITY_LIST_DEFAULT
|
|
1572
|
+
);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
case "fav":
|
|
1576
|
+
void runFav(argument, attached);
|
|
1577
|
+
return;
|
|
1578
|
+
case "unfav":
|
|
1579
|
+
void runUnfav(argument, attached);
|
|
1580
|
+
return;
|
|
1581
|
+
case "favs":
|
|
1582
|
+
void runFavs(argument);
|
|
1583
|
+
return;
|
|
1019
1584
|
default:
|
|
1020
1585
|
notice(`Unknown command \u201C/${command}\u201D. Type / to see them.`, "error");
|
|
1021
1586
|
}
|
|
1022
1587
|
},
|
|
1023
|
-
[
|
|
1588
|
+
[
|
|
1589
|
+
batch,
|
|
1590
|
+
bootstrap.teams,
|
|
1591
|
+
exit,
|
|
1592
|
+
modelId,
|
|
1593
|
+
notice,
|
|
1594
|
+
runDigest,
|
|
1595
|
+
runFav,
|
|
1596
|
+
runFavs,
|
|
1597
|
+
runUnfav,
|
|
1598
|
+
showRecentActivity,
|
|
1599
|
+
teamId,
|
|
1600
|
+
webSearch
|
|
1601
|
+
]
|
|
1024
1602
|
);
|
|
1025
1603
|
const submit = useCallback(() => {
|
|
1026
1604
|
const raw = value.trim();
|
|
1605
|
+
const attached = context.filter((c) => raw.includes(`@${c.label}`));
|
|
1027
1606
|
setValue("");
|
|
1028
1607
|
setCursor(0);
|
|
1608
|
+
setContext([]);
|
|
1029
1609
|
setHistoryIndex(null);
|
|
1030
1610
|
setMentionQuery(null);
|
|
1031
1611
|
if (!raw) return;
|
|
1032
1612
|
setHistory((prev) => [...prev, raw]);
|
|
1033
1613
|
if (raw.startsWith("/")) {
|
|
1034
|
-
runCommand(raw);
|
|
1614
|
+
runCommand(raw, attached);
|
|
1035
1615
|
return;
|
|
1036
1616
|
}
|
|
1037
|
-
void send(raw);
|
|
1038
|
-
}, [runCommand, send, value]);
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1617
|
+
void send(raw, attached);
|
|
1618
|
+
}, [context, runCommand, send, value]);
|
|
1619
|
+
const mentionSpans = useMemo(() => findMentionSpans(value, context), [context, value]);
|
|
1620
|
+
const syncMention = useCallback((text, at, records) => {
|
|
1621
|
+
const inside = findMentionSpans(text, records).some((s) => at > s.start && at <= s.end);
|
|
1622
|
+
const match = inside ? null : /(?:^|\s)@([^\s@]*)$/.exec(text.slice(0, at));
|
|
1041
1623
|
setMentionQuery(match ? match[1] : null);
|
|
1042
1624
|
}, []);
|
|
1043
1625
|
const applyEdit = useCallback(
|
|
1044
1626
|
(text, at) => {
|
|
1627
|
+
const kept = context.filter((c) => text.includes(`@${c.label}`));
|
|
1628
|
+
if (kept.length !== context.length) setContext(kept);
|
|
1045
1629
|
setValue(text);
|
|
1046
1630
|
setCursor(at);
|
|
1047
|
-
syncMention(text, at);
|
|
1631
|
+
syncMention(text, at, kept);
|
|
1048
1632
|
},
|
|
1049
|
-
[syncMention]
|
|
1633
|
+
[context, syncMention]
|
|
1050
1634
|
);
|
|
1051
1635
|
const acceptPick = useCallback(() => {
|
|
1052
1636
|
if (slashQuery !== null) {
|
|
@@ -1059,7 +1643,7 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1059
1643
|
}
|
|
1060
1644
|
const record = mentionRecords[selected];
|
|
1061
1645
|
if (!record) return;
|
|
1062
|
-
const before = value.slice(0, cursor).replace(/@[^\s@]*$/,
|
|
1646
|
+
const before = value.slice(0, cursor).replace(/@[^\s@]*$/, `@${record.label} `);
|
|
1063
1647
|
setContext(
|
|
1064
1648
|
(prev) => prev.some((c) => c.type === record.type && c.rawId === record.rawId) ? prev : [...prev, record]
|
|
1065
1649
|
);
|
|
@@ -1082,6 +1666,18 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1082
1666
|
return;
|
|
1083
1667
|
}
|
|
1084
1668
|
if (exitArmed) setExitArmed(false);
|
|
1669
|
+
if (modal) {
|
|
1670
|
+
if (key.escape) {
|
|
1671
|
+
modal.resolve(null);
|
|
1672
|
+
} else if (key.upArrow) {
|
|
1673
|
+
setSelected((i) => (i - 1 + modal.rows.length) % modal.rows.length);
|
|
1674
|
+
} else if (key.downArrow) {
|
|
1675
|
+
setSelected((i) => (i + 1) % modal.rows.length);
|
|
1676
|
+
} else if (key.return || key.tab) {
|
|
1677
|
+
modal.resolve(modal.rows[selected]?.key ?? null);
|
|
1678
|
+
}
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1085
1681
|
if (key.escape) {
|
|
1086
1682
|
if (pickerOpen) {
|
|
1087
1683
|
setMentionQuery(null);
|
|
@@ -1100,7 +1696,6 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1100
1696
|
setShowShortcuts(false);
|
|
1101
1697
|
return;
|
|
1102
1698
|
}
|
|
1103
|
-
if (context.length > 0) setContext([]);
|
|
1104
1699
|
return;
|
|
1105
1700
|
}
|
|
1106
1701
|
if (pickerOpen && pickerRows.length > 0) {
|
|
@@ -1177,14 +1772,17 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1177
1772
|
team?.name ?? "no team",
|
|
1178
1773
|
batch ?? "no batch"
|
|
1179
1774
|
].join(" \xB7 ");
|
|
1180
|
-
const hint = exitArmed ? "press ctrl-c again to exit" : busy ? "" : showShortcuts ? "esc to close" : "? for shortcuts";
|
|
1181
|
-
return /* @__PURE__ */
|
|
1182
|
-
/* @__PURE__ */
|
|
1183
|
-
digestText !== null ? /* @__PURE__ */
|
|
1184
|
-
live ? /* @__PURE__ */
|
|
1185
|
-
live ? /* @__PURE__ */
|
|
1186
|
-
showShortcuts ? /* @__PURE__ */
|
|
1187
|
-
|
|
1775
|
+
const hint = exitArmed ? "press ctrl-c again to exit" : modal ? "enter to choose \xB7 esc to cancel" : busy ? "" : showShortcuts ? "esc to close" : "? for shortcuts";
|
|
1776
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
1777
|
+
/* @__PURE__ */ jsx6(Static, { items: transcript, children: (item) => /* @__PURE__ */ jsx6(TranscriptBlock, { item }, item.id) }),
|
|
1778
|
+
digestText !== null ? /* @__PURE__ */ jsx6(DigestStream, { text: digestText }) : null,
|
|
1779
|
+
live ? /* @__PURE__ */ jsx6(StreamingTurn, { turn: live }) : null,
|
|
1780
|
+
live ? /* @__PURE__ */ jsx6(StatusLine, { turn: live }) : null,
|
|
1781
|
+
showShortcuts ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: SHORTCUTS }) }) : null,
|
|
1782
|
+
modal ? /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
1783
|
+
/* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsx6(Text6, { color: ACCENT, children: modal.title }) }),
|
|
1784
|
+
/* @__PURE__ */ jsx6(Picker, { rows: modal.rows, selected })
|
|
1785
|
+
] }) : pickerOpen ? /* @__PURE__ */ jsx6(
|
|
1188
1786
|
Picker,
|
|
1189
1787
|
{
|
|
1190
1788
|
rows: pickerRows,
|
|
@@ -1192,12 +1790,12 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1192
1790
|
status: mentionQuery !== null ? mentionLoading || mentionSearchedFor !== mentionQuery ? "searching\u2026" : `no records match \u201C${mentionQuery}\u201D` : void 0
|
|
1193
1791
|
}
|
|
1194
1792
|
) : null,
|
|
1195
|
-
/* @__PURE__ */
|
|
1793
|
+
/* @__PURE__ */ jsx6(
|
|
1196
1794
|
Input,
|
|
1197
1795
|
{
|
|
1198
1796
|
value,
|
|
1199
1797
|
cursor,
|
|
1200
|
-
|
|
1798
|
+
spans: mentionSpans,
|
|
1201
1799
|
busy,
|
|
1202
1800
|
status,
|
|
1203
1801
|
hint
|
|
@@ -1208,45 +1806,52 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1208
1806
|
function DigestStream({ text }) {
|
|
1209
1807
|
const spinner = useSpinner(true);
|
|
1210
1808
|
if (!text.trim()) {
|
|
1211
|
-
return /* @__PURE__ */
|
|
1212
|
-
/* @__PURE__ */
|
|
1809
|
+
return /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
|
|
1810
|
+
/* @__PURE__ */ jsxs5(Text6, { color: ACCENT, children: [
|
|
1213
1811
|
spinner,
|
|
1214
1812
|
" "
|
|
1215
1813
|
] }),
|
|
1216
|
-
/* @__PURE__ */
|
|
1814
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Summarizing the latest activity\u2026" })
|
|
1217
1815
|
] });
|
|
1218
1816
|
}
|
|
1219
|
-
return /* @__PURE__ */
|
|
1817
|
+
return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { children: text }) });
|
|
1220
1818
|
}
|
|
1221
1819
|
function TranscriptBlock({ item }) {
|
|
1222
1820
|
switch (item.kind) {
|
|
1223
1821
|
case "banner":
|
|
1224
|
-
return /* @__PURE__ */
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
1227
|
-
/* @__PURE__ */
|
|
1822
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginBottom: 1, children: [
|
|
1823
|
+
/* @__PURE__ */ jsx6(Logo, {}),
|
|
1824
|
+
/* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1825
|
+
/* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
1228
1826
|
item.email,
|
|
1229
1827
|
item.email ? " \xB7 " : "",
|
|
1230
1828
|
item.host.replace(/^https?:\/\//, "")
|
|
1231
1829
|
] }),
|
|
1232
|
-
/* @__PURE__ */
|
|
1830
|
+
/* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
1233
1831
|
item.team,
|
|
1234
1832
|
item.batch ? ` \xB7 ${item.batch}` : ""
|
|
1235
1833
|
] })
|
|
1236
1834
|
] })
|
|
1237
1835
|
] });
|
|
1238
1836
|
case "digest":
|
|
1239
|
-
return /* @__PURE__ */
|
|
1837
|
+
return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { children: item.text }) });
|
|
1240
1838
|
case "user":
|
|
1241
|
-
return /* @__PURE__ */
|
|
1242
|
-
/* @__PURE__ */
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1839
|
+
return /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
|
|
1840
|
+
/* @__PURE__ */ jsx6(Text6, { color: ACCENT, children: "> " }),
|
|
1841
|
+
/* @__PURE__ */ jsx6(Text6, { children: /* @__PURE__ */ jsx6(
|
|
1842
|
+
HighlightedText,
|
|
1843
|
+
{
|
|
1844
|
+
value: item.text,
|
|
1845
|
+
cursor: null,
|
|
1846
|
+
spans: findMentionSpans(item.text, item.context),
|
|
1847
|
+
dimPlain: true
|
|
1848
|
+
}
|
|
1849
|
+
) })
|
|
1247
1850
|
] });
|
|
1851
|
+
case "activity":
|
|
1852
|
+
return /* @__PURE__ */ jsx6(ActivityBlock, { heading: item.heading, items: item.items });
|
|
1248
1853
|
case "assistant":
|
|
1249
|
-
return /* @__PURE__ */
|
|
1854
|
+
return /* @__PURE__ */ jsx6(
|
|
1250
1855
|
AssistantTurn,
|
|
1251
1856
|
{
|
|
1252
1857
|
text: item.text,
|
|
@@ -1256,7 +1861,7 @@ function TranscriptBlock({ item }) {
|
|
|
1256
1861
|
}
|
|
1257
1862
|
);
|
|
1258
1863
|
case "notice":
|
|
1259
|
-
return /* @__PURE__ */
|
|
1864
|
+
return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: item.tone === "error" ? "red" : void 0, dimColor: item.tone !== "error", children: item.text }) });
|
|
1260
1865
|
}
|
|
1261
1866
|
}
|
|
1262
1867
|
function readTurn(message) {
|
|
@@ -1319,7 +1924,7 @@ function truncateError(message) {
|
|
|
1319
1924
|
}
|
|
1320
1925
|
|
|
1321
1926
|
// src/oc.tsx
|
|
1322
|
-
import { jsx as
|
|
1927
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1323
1928
|
var USAGE = `oc \u2014 Orange Collective in your terminal
|
|
1324
1929
|
|
|
1325
1930
|
Usage
|
|
@@ -1404,7 +2009,7 @@ ${USAGE}`);
|
|
|
1404
2009
|
fail("Your account isn't on a team yet \u2014 ask an admin to add you.");
|
|
1405
2010
|
}
|
|
1406
2011
|
const instance = render(
|
|
1407
|
-
/* @__PURE__ */
|
|
2012
|
+
/* @__PURE__ */ jsx7(App, { client, bootstrap, host, state: state ?? {} }),
|
|
1408
2013
|
// Ctrl-C is handled in App (press twice), so Ink must not exit on the first.
|
|
1409
2014
|
{ exitOnCtrlC: false }
|
|
1410
2015
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecollective/oc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Orange Collective in your terminal — the YC research agent, batch digests, and company context",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://www.orangecollective.vc",
|