@orangecollective/oc 0.2.0 → 0.3.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/oc.js +350 -62
- package/package.json +1 -1
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.3.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)];
|
|
@@ -24,6 +24,7 @@ function isOlderThan(version, floor) {
|
|
|
24
24
|
}
|
|
25
25
|
var DEFAULT_HOST = "https://www.orangecollective.vc";
|
|
26
26
|
var MODELS = [
|
|
27
|
+
{ provider: "anthropic", id: "claude-haiku-4-5", label: "Claude Haiku 4.5", short: "Haiku 4.5" },
|
|
27
28
|
{ provider: "anthropic", id: "claude-fable-5-1", label: "Claude Fable 5.1", short: "Fable 5.1" },
|
|
28
29
|
{ provider: "anthropic", id: "claude-opus-5-5", label: "Claude Opus 5.5", short: "Opus 5.5" },
|
|
29
30
|
{ provider: "anthropic", id: "claude-sonnet-5", label: "Claude Sonnet 5", short: "Sonnet 5" },
|
|
@@ -31,7 +32,7 @@ var MODELS = [
|
|
|
31
32
|
{ provider: "openai", id: "gpt-6-sol", label: "GPT-6 Sol", short: "GPT-6 Sol" },
|
|
32
33
|
{ provider: "openai", id: "gpt-6-luna", label: "GPT-6 Luna", short: "GPT-6 Luna" }
|
|
33
34
|
];
|
|
34
|
-
var DEFAULT_MODEL_ID = "
|
|
35
|
+
var DEFAULT_MODEL_ID = "claude-haiku-4-5";
|
|
35
36
|
var configDir = () => process.env.OC_CONFIG_DIR ?? path.join(homedir(), ".oc");
|
|
36
37
|
var credentialsPath = () => path.join(configDir(), "credentials.json");
|
|
37
38
|
var statePath = () => path.join(configDir(), "state.json");
|
|
@@ -406,7 +407,7 @@ var OcClient = class {
|
|
|
406
407
|
companies: "6",
|
|
407
408
|
founders: "6",
|
|
408
409
|
skills: "4",
|
|
409
|
-
sections: "batches,companies,founders,skills"
|
|
410
|
+
sections: "batches,companies,founders,members,skills"
|
|
410
411
|
});
|
|
411
412
|
if (args.teamId != null) params.set("teamId", String(args.teamId));
|
|
412
413
|
const res = await this.request(`/api/search/context?${params}`, { signal: args.signal });
|
|
@@ -472,6 +473,32 @@ var OcClient = class {
|
|
|
472
473
|
const json = await res.json();
|
|
473
474
|
return json.id;
|
|
474
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* The presence tick: report that you're here and what you're on, and get back
|
|
478
|
+
* who else is around. One request does both, so the ticker costs one round-trip
|
|
479
|
+
* a minute rather than two.
|
|
480
|
+
*/
|
|
481
|
+
async presence(args) {
|
|
482
|
+
const res = await this.request("/api/presence", {
|
|
483
|
+
method: "POST",
|
|
484
|
+
headers: { "Content-Type": "application/json" },
|
|
485
|
+
signal: args.signal,
|
|
486
|
+
body: JSON.stringify({ surface: "cli", label: args.label ?? null, path: args.path ?? null })
|
|
487
|
+
});
|
|
488
|
+
if (!res.ok) return this.fail(res, "Couldn't load presence");
|
|
489
|
+
const json = await res.json();
|
|
490
|
+
return json.members ?? [];
|
|
491
|
+
}
|
|
492
|
+
/** Companies the org has been opening, asking about and starring. */
|
|
493
|
+
async trending(args) {
|
|
494
|
+
const params = new URLSearchParams();
|
|
495
|
+
if (args.batch) params.set("batch", args.batch);
|
|
496
|
+
if (args.limit) params.set("limit", String(args.limit));
|
|
497
|
+
const res = await this.request(`/api/trending?${params}`, { signal: args.signal });
|
|
498
|
+
if (!res.ok) return this.fail(res, "Couldn't load trending");
|
|
499
|
+
const json = await res.json();
|
|
500
|
+
return { companies: json.companies ?? [], days: json.days ?? 7 };
|
|
501
|
+
}
|
|
475
502
|
/**
|
|
476
503
|
* Transport for POST /api/chat. Sends exactly the body the web chat panel's
|
|
477
504
|
* transport sends, so the route needs no CLI-specific branch.
|
|
@@ -514,6 +541,13 @@ function flattenContextResults(raw) {
|
|
|
514
541
|
label: f.name,
|
|
515
542
|
iconUrl: f.avatarUrl ?? void 0
|
|
516
543
|
})),
|
|
544
|
+
...(data.members ?? []).map((m) => ({
|
|
545
|
+
type: "member",
|
|
546
|
+
rawId: m.userId,
|
|
547
|
+
label: m.name,
|
|
548
|
+
subtitle: "teammate",
|
|
549
|
+
iconUrl: m.avatarUrl ?? m.authAvatarUrl ?? void 0
|
|
550
|
+
})),
|
|
517
551
|
...(data.skills ?? []).map((s) => ({
|
|
518
552
|
type: "skill",
|
|
519
553
|
rawId: s.slug,
|
|
@@ -531,9 +565,18 @@ function buildContextPrefix(items) {
|
|
|
531
565
|
|
|
532
566
|
// src/ui/App.tsx
|
|
533
567
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
534
|
-
import { Box as
|
|
568
|
+
import { Box as Box7, Static, Text as Text7, useApp, useInput } from "ink";
|
|
535
569
|
import { generateId, readUIMessageStream } from "ai";
|
|
536
570
|
|
|
571
|
+
// ../lib/presence-core.ts
|
|
572
|
+
var PRESENCE_TICK_MS = 6e4;
|
|
573
|
+
var PRESENCE_ONLINE_MS = 15e4;
|
|
574
|
+
function isOnline(member, now = Date.now()) {
|
|
575
|
+
const seen = Date.parse(member.lastSeenAt);
|
|
576
|
+
if (Number.isNaN(seen)) return false;
|
|
577
|
+
return now - seen < PRESENCE_ONLINE_MS;
|
|
578
|
+
}
|
|
579
|
+
|
|
537
580
|
// src/markdown.ts
|
|
538
581
|
import { Marked } from "marked";
|
|
539
582
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -759,7 +802,8 @@ function Input({
|
|
|
759
802
|
spans,
|
|
760
803
|
busy,
|
|
761
804
|
status,
|
|
762
|
-
hint
|
|
805
|
+
hint,
|
|
806
|
+
right
|
|
763
807
|
}) {
|
|
764
808
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
765
809
|
/* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? "gray" : ACCENT, paddingX: 1, children: [
|
|
@@ -767,8 +811,11 @@ function Input({
|
|
|
767
811
|
value.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: PLACEHOLDER }) : /* @__PURE__ */ jsx2(Text2, { children: /* @__PURE__ */ jsx2(HighlightedText, { value, cursor, spans }) })
|
|
768
812
|
] }),
|
|
769
813
|
/* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, children: [
|
|
770
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children:
|
|
771
|
-
|
|
814
|
+
/* @__PURE__ */ jsx2(Box2, { flexGrow: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate-end", children: [
|
|
815
|
+
status,
|
|
816
|
+
hint ? ` ${hint}` : ""
|
|
817
|
+
] }) }),
|
|
818
|
+
right ? /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: right }) : null
|
|
772
819
|
] })
|
|
773
820
|
] });
|
|
774
821
|
}
|
|
@@ -823,10 +870,118 @@ function Picker({
|
|
|
823
870
|
}) });
|
|
824
871
|
}
|
|
825
872
|
|
|
826
|
-
// src/ui/
|
|
827
|
-
import { useEffect, useState } from "react";
|
|
873
|
+
// src/ui/Social.tsx
|
|
828
874
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
829
875
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
876
|
+
var STRIP_MAX = 3;
|
|
877
|
+
function stripParts(members) {
|
|
878
|
+
const others = members.filter((m) => !m.isSelf);
|
|
879
|
+
const shown = others.slice(0, STRIP_MAX);
|
|
880
|
+
return { others, shown, extra: others.length - shown.length };
|
|
881
|
+
}
|
|
882
|
+
function presenceStripWidth(members) {
|
|
883
|
+
const { others, shown, extra } = stripParts(members);
|
|
884
|
+
if (others.length === 0) return 0;
|
|
885
|
+
const names = shown.reduce((total, m) => total + 2 + m.shortName.length + 2, 0);
|
|
886
|
+
return names + (extra > 0 ? `+${extra} `.length : 0) + `${others.length + 1} online`.length;
|
|
887
|
+
}
|
|
888
|
+
function PresenceStrip({ members }) {
|
|
889
|
+
const { others, shown, extra } = stripParts(members);
|
|
890
|
+
if (others.length === 0) return null;
|
|
891
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
892
|
+
shown.map((member) => {
|
|
893
|
+
const online = isOnline(member);
|
|
894
|
+
return /* @__PURE__ */ jsxs4(Text5, { color: online ? ACCENT : void 0, dimColor: !online, children: [
|
|
895
|
+
online ? "\u25CF " : "\u25CB ",
|
|
896
|
+
member.shortName.toLowerCase(),
|
|
897
|
+
" "
|
|
898
|
+
] }, member.userId);
|
|
899
|
+
}),
|
|
900
|
+
extra > 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `+${extra} ` }) : null,
|
|
901
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `${others.length + 1} online` })
|
|
902
|
+
] });
|
|
903
|
+
}
|
|
904
|
+
function PresenceBlock({ members }) {
|
|
905
|
+
if (members.length === 0) {
|
|
906
|
+
return /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Nobody else is around right now." }) });
|
|
907
|
+
}
|
|
908
|
+
const width = Math.max(
|
|
909
|
+
12,
|
|
910
|
+
...members.map((m) => m.name.length + (m.isSelf ? 6 : 0))
|
|
911
|
+
);
|
|
912
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
913
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "ONLINE" }),
|
|
914
|
+
members.map((member) => {
|
|
915
|
+
const online = isOnline(member);
|
|
916
|
+
const name = `${member.name}${member.isSelf ? " (you)" : ""}`;
|
|
917
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
918
|
+
/* @__PURE__ */ jsx5(Text5, { color: online ? ACCENT : void 0, dimColor: !online, children: online ? "\u25CF " : "\u25CB " }),
|
|
919
|
+
/* @__PURE__ */ jsx5(Text5, { bold: online, children: name.padEnd(width + 2) }),
|
|
920
|
+
/* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
921
|
+
member.viewingLabel ?? "\u2014",
|
|
922
|
+
member.surface === "cli" ? " \xB7 cli" : ""
|
|
923
|
+
] })
|
|
924
|
+
] }, member.userId);
|
|
925
|
+
})
|
|
926
|
+
] });
|
|
927
|
+
}
|
|
928
|
+
function SocialBlock({ lines }) {
|
|
929
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginBottom: 1, children: lines.map((line) => /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
930
|
+
/* @__PURE__ */ jsx5(Text5, { color: ACCENT, children: "\u25C6 " }),
|
|
931
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: line })
|
|
932
|
+
] }, line)) });
|
|
933
|
+
}
|
|
934
|
+
var BAR_CELLS = 8;
|
|
935
|
+
function HeatBar({ heat }) {
|
|
936
|
+
const filled = Math.max(1, Math.min(BAR_CELLS, Math.round(heat / 100 * BAR_CELLS)));
|
|
937
|
+
return /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
938
|
+
/* @__PURE__ */ jsx5(Text5, { color: heat >= 66 ? ORANGE.base : heat >= 33 ? ORANGE.deep : ORANGE.shadow, children: "\u2588".repeat(filled) }),
|
|
939
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2591".repeat(BAR_CELLS - filled) })
|
|
940
|
+
] });
|
|
941
|
+
}
|
|
942
|
+
function TrendingBlock({
|
|
943
|
+
companies,
|
|
944
|
+
batch,
|
|
945
|
+
days
|
|
946
|
+
}) {
|
|
947
|
+
if (companies.length === 0) {
|
|
948
|
+
return /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
|
|
949
|
+
"Nothing has moved",
|
|
950
|
+
batch ? ` in ${batch}` : "",
|
|
951
|
+
" in the last ",
|
|
952
|
+
days,
|
|
953
|
+
" days."
|
|
954
|
+
] }) });
|
|
955
|
+
}
|
|
956
|
+
const nameWidth = Math.min(22, Math.max(...companies.map((c) => c.name.length)));
|
|
957
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
958
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `TRENDING${batch ? ` \xB7 ${batch}` : ""} \xB7 LAST ${days} DAYS` }),
|
|
959
|
+
companies.map((company, index) => {
|
|
960
|
+
const climb = company.prevRank != null ? company.prevRank - company.rank : null;
|
|
961
|
+
const detail = [
|
|
962
|
+
company.views > 0 && `${company.views} opened`,
|
|
963
|
+
company.chats > 0 && `${company.chats} asked`,
|
|
964
|
+
company.favorites > 0 && `${company.favorites} starred`
|
|
965
|
+
].filter(Boolean).join(" \xB7 ");
|
|
966
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
967
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `${String(index + 1).padStart(2)} ` }),
|
|
968
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: truncate(company.name, nameWidth).padEnd(nameWidth + 2) }),
|
|
969
|
+
/* @__PURE__ */ jsx5(HeatBar, { heat: company.heat }),
|
|
970
|
+
/* @__PURE__ */ jsx5(Text5, { children: String(company.heat).padStart(5) }),
|
|
971
|
+
/* @__PURE__ */ jsx5(Text5, { color: climb != null && climb > 0 ? ACCENT : void 0, dimColor: climb == null || climb <= 0, children: climb == null || climb === 0 ? " \u2014" : `${climb > 0 ? " \u25B2" : " \u25BC"}${String(Math.abs(climb)).padStart(2)}` }),
|
|
972
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${detail}` })
|
|
973
|
+
] }, company.companyId);
|
|
974
|
+
})
|
|
975
|
+
] });
|
|
976
|
+
}
|
|
977
|
+
function truncate(value, width) {
|
|
978
|
+
return value.length <= width ? value : `${value.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/ui/Turn.tsx
|
|
982
|
+
import { useEffect, useState } from "react";
|
|
983
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
984
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
830
985
|
var SPINNER = ["\u2722", "\u2733", "\u2736", "\u273B", "\u273D", "\u273B", "\u2736", "\u2733"];
|
|
831
986
|
function useSpinner(active) {
|
|
832
987
|
const [frame, setFrame] = useState(0);
|
|
@@ -839,15 +994,15 @@ function useSpinner(active) {
|
|
|
839
994
|
}
|
|
840
995
|
function ToolRows({ tools, live }) {
|
|
841
996
|
if (tools.length === 0) return null;
|
|
842
|
-
return /* @__PURE__ */
|
|
843
|
-
/* @__PURE__ */
|
|
844
|
-
/* @__PURE__ */
|
|
997
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: tools.map((tool) => /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
998
|
+
/* @__PURE__ */ jsxs5(Box6, { children: [
|
|
999
|
+
/* @__PURE__ */ jsxs5(Text6, { color: tool.state === "error" ? "red" : tool.state === "running" ? ACCENT : "gray", children: [
|
|
845
1000
|
"\u25CF",
|
|
846
1001
|
" "
|
|
847
1002
|
] }),
|
|
848
|
-
/* @__PURE__ */
|
|
1003
|
+
/* @__PURE__ */ jsx6(Text6, { bold: tool.state === "running" && live, children: tool.name })
|
|
849
1004
|
] }),
|
|
850
|
-
/* @__PURE__ */
|
|
1005
|
+
/* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
851
1006
|
" \u23BF ",
|
|
852
1007
|
tool.state === "running" ? "running\u2026" : tool.state === "error" ? "failed" : "done"
|
|
853
1008
|
] })
|
|
@@ -855,7 +1010,7 @@ function ToolRows({ tools, live }) {
|
|
|
855
1010
|
}
|
|
856
1011
|
function Sources({ sources }) {
|
|
857
1012
|
if (sources.length === 0) return null;
|
|
858
|
-
return /* @__PURE__ */
|
|
1013
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: sources.map((source, index) => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
859
1014
|
` ${index + 1}. `,
|
|
860
1015
|
source.title ? `${source.title} \u2014 ` : "",
|
|
861
1016
|
source.url
|
|
@@ -868,27 +1023,27 @@ function AssistantTurn({
|
|
|
868
1023
|
thoughtSeconds
|
|
869
1024
|
}) {
|
|
870
1025
|
const body = renderMarkdown(stripContextLeakage(text));
|
|
871
|
-
return /* @__PURE__ */
|
|
872
|
-
/* @__PURE__ */
|
|
873
|
-
thoughtSeconds != null ? /* @__PURE__ */
|
|
1026
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginBottom: 1, children: [
|
|
1027
|
+
/* @__PURE__ */ jsx6(ToolRows, { tools, live: false }),
|
|
1028
|
+
thoughtSeconds != null ? /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
874
1029
|
"\u273B thought for ",
|
|
875
1030
|
thoughtSeconds,
|
|
876
1031
|
"s"
|
|
877
1032
|
] }) : null,
|
|
878
|
-
body ? /* @__PURE__ */
|
|
879
|
-
/* @__PURE__ */
|
|
1033
|
+
body ? /* @__PURE__ */ jsx6(Box6, { marginTop: tools.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx6(Text6, { children: body }) }) : null,
|
|
1034
|
+
/* @__PURE__ */ jsx6(Sources, { sources })
|
|
880
1035
|
] });
|
|
881
1036
|
}
|
|
882
1037
|
function StreamingTurn({ turn }) {
|
|
883
1038
|
const body = stripContextLeakage(turn.text);
|
|
884
1039
|
const reasoningTail = turn.reasoning.trim().split("\n").at(-1) ?? "";
|
|
885
|
-
return /* @__PURE__ */
|
|
886
|
-
/* @__PURE__ */
|
|
887
|
-
!body && reasoningTail ? /* @__PURE__ */
|
|
1040
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginBottom: 1, children: [
|
|
1041
|
+
/* @__PURE__ */ jsx6(ToolRows, { tools: turn.tools, live: true }),
|
|
1042
|
+
!body && reasoningTail ? /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
888
1043
|
" ",
|
|
889
|
-
|
|
1044
|
+
truncate2(reasoningTail, (process.stdout.columns || 80) - 4)
|
|
890
1045
|
] }) : null,
|
|
891
|
-
body ? /* @__PURE__ */
|
|
1046
|
+
body ? /* @__PURE__ */ jsx6(Text6, { children: body }) : null
|
|
892
1047
|
] });
|
|
893
1048
|
}
|
|
894
1049
|
function StatusLine({ turn }) {
|
|
@@ -901,23 +1056,23 @@ function StatusLine({ turn }) {
|
|
|
901
1056
|
const elapsed = Math.max(0, Math.round((now - turn.startedAt) / 1e3));
|
|
902
1057
|
const running = turn.tools.find((t) => t.state === "running");
|
|
903
1058
|
const verb = running ? running.name : turn.text ? "Writing" : "Thinking";
|
|
904
|
-
return /* @__PURE__ */
|
|
905
|
-
/* @__PURE__ */
|
|
1059
|
+
return /* @__PURE__ */ jsxs5(Box6, { children: [
|
|
1060
|
+
/* @__PURE__ */ jsxs5(Text6, { color: ACCENT, children: [
|
|
906
1061
|
spinner,
|
|
907
1062
|
" "
|
|
908
1063
|
] }),
|
|
909
|
-
/* @__PURE__ */
|
|
1064
|
+
/* @__PURE__ */ jsxs5(Text6, { children: [
|
|
910
1065
|
verb,
|
|
911
1066
|
"\u2026 "
|
|
912
1067
|
] }),
|
|
913
|
-
/* @__PURE__ */
|
|
1068
|
+
/* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
914
1069
|
"(",
|
|
915
1070
|
elapsed,
|
|
916
1071
|
"s \xB7 esc to interrupt)"
|
|
917
1072
|
] })
|
|
918
1073
|
] });
|
|
919
1074
|
}
|
|
920
|
-
function
|
|
1075
|
+
function truncate2(value, width) {
|
|
921
1076
|
if (width <= 1 || value.length <= width) return value;
|
|
922
1077
|
return `${value.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
923
1078
|
}
|
|
@@ -932,13 +1087,16 @@ var EMPTY_TURN = {
|
|
|
932
1087
|
};
|
|
933
1088
|
|
|
934
1089
|
// src/ui/App.tsx
|
|
935
|
-
import { jsx as
|
|
1090
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
936
1091
|
var FLUSH_INTERVAL_MS = 60;
|
|
937
1092
|
var MENTION_DEBOUNCE_MS = 200;
|
|
938
1093
|
var EXIT_CONFIRM_MS = 3e3;
|
|
939
1094
|
var ACTIVITY_POLL_MS = 6e4;
|
|
940
1095
|
var ACTIVITY_LIST_DEFAULT = 10;
|
|
941
1096
|
var ACTIVITY_LIST_MAX = 30;
|
|
1097
|
+
var SHORTCUT_TIP = "? for shortcuts";
|
|
1098
|
+
var TRENDING_LIST_DEFAULT = 8;
|
|
1099
|
+
var TRENDING_LIST_MAX = 25;
|
|
942
1100
|
var COMMANDS = [
|
|
943
1101
|
{ name: "model", args: "[name]", blurb: "switch model" },
|
|
944
1102
|
{ name: "web", blurb: "toggle web search" },
|
|
@@ -946,6 +1104,8 @@ var COMMANDS = [
|
|
|
946
1104
|
{ name: "batch", args: "[W26]", blurb: "set the batch for the digest" },
|
|
947
1105
|
{ name: "digest", args: "[-r]", blurb: "re-run the activity digest" },
|
|
948
1106
|
{ name: "activity", args: "[n | on | off]", blurb: "latest batch activity, or toggle the ticker" },
|
|
1107
|
+
{ name: "who", args: "[on | off]", blurb: "who's online, or toggle presence" },
|
|
1108
|
+
{ name: "trending", args: "[n]", blurb: "what the team has been looking at" },
|
|
949
1109
|
{ name: "fav", args: "<company> [in folder]", blurb: "add a company to a favorites folder" },
|
|
950
1110
|
{ name: "unfav", args: "<company>", blurb: "remove your favorite" },
|
|
951
1111
|
{ name: "favs", args: "[folder]", blurb: "list folders, or a folder's companies" },
|
|
@@ -1004,6 +1164,12 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1004
1164
|
const [activityOn, setActivityOn] = useState2(state.activityUpdates ?? true);
|
|
1005
1165
|
const activityCursorRef = useRef(null);
|
|
1006
1166
|
const pendingActivityRef = useRef([]);
|
|
1167
|
+
const [presenceOn, setPresenceOn] = useState2(state.presence ?? true);
|
|
1168
|
+
const [presence, setPresence] = useState2([]);
|
|
1169
|
+
const presenceRef = useRef([]);
|
|
1170
|
+
const seenOnlineRef = useRef(null);
|
|
1171
|
+
const pendingSocialRef = useRef([]);
|
|
1172
|
+
const viewingRef = useRef(null);
|
|
1007
1173
|
const [teamId, setTeamId] = useState2(
|
|
1008
1174
|
state.activeTeamId ?? bootstrap.activeTeamId
|
|
1009
1175
|
);
|
|
@@ -1135,6 +1301,11 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1135
1301
|
};
|
|
1136
1302
|
}, [client, mentionQuery, teamId]);
|
|
1137
1303
|
const flushPendingActivity = useCallback(() => {
|
|
1304
|
+
const lines = pendingSocialRef.current;
|
|
1305
|
+
if (lines.length > 0) {
|
|
1306
|
+
pendingSocialRef.current = [];
|
|
1307
|
+
push({ kind: "social", lines });
|
|
1308
|
+
}
|
|
1138
1309
|
const items = pendingActivityRef.current;
|
|
1139
1310
|
if (items.length === 0) return;
|
|
1140
1311
|
pendingActivityRef.current = [];
|
|
@@ -1179,6 +1350,80 @@ function App({ client, bootstrap, host, state }) {
|
|
|
1179
1350
|
clearInterval(timer);
|
|
1180
1351
|
};
|
|
1181
1352
|
}, [activityOn, batch, client, push, teamId]);
|
|
1353
|
+
useEffect2(() => {
|
|
1354
|
+
if (!presenceOn) {
|
|
1355
|
+
setPresence([]);
|
|
1356
|
+
presenceRef.current = [];
|
|
1357
|
+
seenOnlineRef.current = null;
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
let cancelled = false;
|
|
1361
|
+
const controller = new AbortController();
|
|
1362
|
+
const tick = async () => {
|
|
1363
|
+
if (cancelled) return;
|
|
1364
|
+
try {
|
|
1365
|
+
const members = await client.presence({
|
|
1366
|
+
label: viewingRef.current ?? batch,
|
|
1367
|
+
signal: controller.signal
|
|
1368
|
+
});
|
|
1369
|
+
if (cancelled) return;
|
|
1370
|
+
const previous = presenceRef.current;
|
|
1371
|
+
presenceRef.current = members;
|
|
1372
|
+
setPresence(members);
|
|
1373
|
+
const online = new Set(members.filter((m) => !m.isSelf).map((m) => m.userId));
|
|
1374
|
+
const seen = seenOnlineRef.current;
|
|
1375
|
+
if (seen == null) {
|
|
1376
|
+
seenOnlineRef.current = online;
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const lines = [];
|
|
1380
|
+
for (const member of members) {
|
|
1381
|
+
if (member.isSelf || seen.has(member.userId)) continue;
|
|
1382
|
+
const where = member.viewingLabel ? ` \xB7 ${member.viewingLabel}` : "";
|
|
1383
|
+
lines.push(`${member.name} came online${where}`);
|
|
1384
|
+
}
|
|
1385
|
+
for (const userId of seen) {
|
|
1386
|
+
if (online.has(userId)) continue;
|
|
1387
|
+
const gone = previous.find((m) => m.userId === userId);
|
|
1388
|
+
lines.push(`${gone?.name ?? "A teammate"} went offline`);
|
|
1389
|
+
}
|
|
1390
|
+
seenOnlineRef.current = online;
|
|
1391
|
+
if (lines.length === 0) return;
|
|
1392
|
+
if (busyRef.current) pendingSocialRef.current.push(...lines);
|
|
1393
|
+
else push({ kind: "social", lines });
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
void tick();
|
|
1398
|
+
const timer = setInterval(() => void tick(), PRESENCE_TICK_MS);
|
|
1399
|
+
return () => {
|
|
1400
|
+
cancelled = true;
|
|
1401
|
+
controller.abort();
|
|
1402
|
+
clearInterval(timer);
|
|
1403
|
+
};
|
|
1404
|
+
}, [batch, client, presenceOn, push]);
|
|
1405
|
+
const showPresence = useCallback(async () => {
|
|
1406
|
+
try {
|
|
1407
|
+
const members = await client.presence({ label: viewingRef.current ?? batch });
|
|
1408
|
+
presenceRef.current = members;
|
|
1409
|
+
setPresence(members);
|
|
1410
|
+
seenOnlineRef.current = new Set(members.filter((m) => !m.isSelf).map((m) => m.userId));
|
|
1411
|
+
push({ kind: "presence", members });
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
notice(describeError(error), "error");
|
|
1414
|
+
}
|
|
1415
|
+
}, [batch, client, notice, push]);
|
|
1416
|
+
const showTrending = useCallback(
|
|
1417
|
+
async (count) => {
|
|
1418
|
+
try {
|
|
1419
|
+
const { companies, days } = await client.trending({ batch, limit: count });
|
|
1420
|
+
push({ kind: "trending", batch, days, companies });
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
notice(describeError(error), "error");
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
[batch, client, notice, push]
|
|
1426
|
+
);
|
|
1182
1427
|
const showRecentActivity = useCallback(
|
|
1183
1428
|
async (count) => {
|
|
1184
1429
|
if (!batch || teamId == null) {
|
|
@@ -1572,6 +1817,31 @@ ${lines.join("\n")}`);
|
|
|
1572
1817
|
);
|
|
1573
1818
|
return;
|
|
1574
1819
|
}
|
|
1820
|
+
case "who": {
|
|
1821
|
+
const flag = argument.toLowerCase();
|
|
1822
|
+
if (flag === "on" || flag === "off") {
|
|
1823
|
+
const next = flag === "on";
|
|
1824
|
+
setPresenceOn(next);
|
|
1825
|
+
void patchState({ presence: next });
|
|
1826
|
+
notice(
|
|
1827
|
+
next ? "Presence on \u2014 teammates can see you're here and what you're on." : "Presence off \u2014 you're hidden, and the roster is hidden from you."
|
|
1828
|
+
);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (!presenceOn) {
|
|
1832
|
+
notice("Presence is off. `/who on` to turn it back on.", "error");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
void showPresence();
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
case "trending": {
|
|
1839
|
+
const count = Number.parseInt(argument, 10);
|
|
1840
|
+
void showTrending(
|
|
1841
|
+
Number.isInteger(count) && count > 0 ? Math.min(count, TRENDING_LIST_MAX) : TRENDING_LIST_DEFAULT
|
|
1842
|
+
);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1575
1845
|
case "fav":
|
|
1576
1846
|
void runFav(argument, attached);
|
|
1577
1847
|
return;
|
|
@@ -1595,7 +1865,10 @@ ${lines.join("\n")}`);
|
|
|
1595
1865
|
runFav,
|
|
1596
1866
|
runFavs,
|
|
1597
1867
|
runUnfav,
|
|
1868
|
+
showPresence,
|
|
1598
1869
|
showRecentActivity,
|
|
1870
|
+
showTrending,
|
|
1871
|
+
presenceOn,
|
|
1599
1872
|
teamId,
|
|
1600
1873
|
webSearch
|
|
1601
1874
|
]
|
|
@@ -1603,6 +1876,8 @@ ${lines.join("\n")}`);
|
|
|
1603
1876
|
const submit = useCallback(() => {
|
|
1604
1877
|
const raw = value.trim();
|
|
1605
1878
|
const attached = context.filter((c) => raw.includes(`@${c.label}`));
|
|
1879
|
+
const company = attached.filter((c) => c.type === "company").at(-1);
|
|
1880
|
+
if (company) viewingRef.current = company.label;
|
|
1606
1881
|
setValue("");
|
|
1607
1882
|
setCursor(0);
|
|
1608
1883
|
setContext([]);
|
|
@@ -1772,17 +2047,23 @@ ${lines.join("\n")}`);
|
|
|
1772
2047
|
team?.name ?? "no team",
|
|
1773
2048
|
batch ?? "no batch"
|
|
1774
2049
|
].join(" \xB7 ");
|
|
1775
|
-
const hint = exitArmed ? "press ctrl-c again to exit" : modal ? "enter to choose \xB7 esc to cancel" : busy ? "" : showShortcuts ? "esc to close" :
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
2050
|
+
const hint = exitArmed ? "press ctrl-c again to exit" : modal ? "enter to choose \xB7 esc to cancel" : busy ? "" : showShortcuts ? "esc to close" : SHORTCUT_TIP;
|
|
2051
|
+
const stripWidth = presenceOn ? presenceStripWidth(presence) : 0;
|
|
2052
|
+
const budget = (process.stdout.columns || 80) - 4;
|
|
2053
|
+
const overflows = (h) => status.length + (h ? h.length + 3 : 0) + stripWidth > budget;
|
|
2054
|
+
const dropTip = hint === SHORTCUT_TIP && overflows(hint);
|
|
2055
|
+
const visibleHint = dropTip ? "" : hint;
|
|
2056
|
+
const presenceVisible = stripWidth > 0 && !overflows(visibleHint);
|
|
2057
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
2058
|
+
/* @__PURE__ */ jsx7(Static, { items: transcript, children: (item) => /* @__PURE__ */ jsx7(TranscriptBlock, { item }, item.id) }),
|
|
2059
|
+
digestText !== null ? /* @__PURE__ */ jsx7(DigestStream, { text: digestText }) : null,
|
|
2060
|
+
live ? /* @__PURE__ */ jsx7(StreamingTurn, { turn: live }) : null,
|
|
2061
|
+
live ? /* @__PURE__ */ jsx7(StatusLine, { turn: live }) : null,
|
|
2062
|
+
showShortcuts ? /* @__PURE__ */ jsx7(Box7, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: SHORTCUTS }) }) : null,
|
|
2063
|
+
modal ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
2064
|
+
/* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: ACCENT, children: modal.title }) }),
|
|
2065
|
+
/* @__PURE__ */ jsx7(Picker, { rows: modal.rows, selected })
|
|
2066
|
+
] }) : pickerOpen ? /* @__PURE__ */ jsx7(
|
|
1786
2067
|
Picker,
|
|
1787
2068
|
{
|
|
1788
2069
|
rows: pickerRows,
|
|
@@ -1790,7 +2071,7 @@ ${lines.join("\n")}`);
|
|
|
1790
2071
|
status: mentionQuery !== null ? mentionLoading || mentionSearchedFor !== mentionQuery ? "searching\u2026" : `no records match \u201C${mentionQuery}\u201D` : void 0
|
|
1791
2072
|
}
|
|
1792
2073
|
) : null,
|
|
1793
|
-
/* @__PURE__ */
|
|
2074
|
+
/* @__PURE__ */ jsx7(
|
|
1794
2075
|
Input,
|
|
1795
2076
|
{
|
|
1796
2077
|
value,
|
|
@@ -1798,7 +2079,8 @@ ${lines.join("\n")}`);
|
|
|
1798
2079
|
spans: mentionSpans,
|
|
1799
2080
|
busy,
|
|
1800
2081
|
status,
|
|
1801
|
-
hint
|
|
2082
|
+
hint: visibleHint,
|
|
2083
|
+
right: presenceVisible ? /* @__PURE__ */ jsx7(PresenceStrip, { members: presence }) : null
|
|
1802
2084
|
}
|
|
1803
2085
|
)
|
|
1804
2086
|
] });
|
|
@@ -1806,39 +2088,39 @@ ${lines.join("\n")}`);
|
|
|
1806
2088
|
function DigestStream({ text }) {
|
|
1807
2089
|
const spinner = useSpinner(true);
|
|
1808
2090
|
if (!text.trim()) {
|
|
1809
|
-
return /* @__PURE__ */
|
|
1810
|
-
/* @__PURE__ */
|
|
2091
|
+
return /* @__PURE__ */ jsxs6(Box7, { marginBottom: 1, children: [
|
|
2092
|
+
/* @__PURE__ */ jsxs6(Text7, { color: ACCENT, children: [
|
|
1811
2093
|
spinner,
|
|
1812
2094
|
" "
|
|
1813
2095
|
] }),
|
|
1814
|
-
/* @__PURE__ */
|
|
2096
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Summarizing the latest activity\u2026" })
|
|
1815
2097
|
] });
|
|
1816
2098
|
}
|
|
1817
|
-
return /* @__PURE__ */
|
|
2099
|
+
return /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { children: text }) });
|
|
1818
2100
|
}
|
|
1819
2101
|
function TranscriptBlock({ item }) {
|
|
1820
2102
|
switch (item.kind) {
|
|
1821
2103
|
case "banner":
|
|
1822
|
-
return /* @__PURE__ */
|
|
1823
|
-
/* @__PURE__ */
|
|
1824
|
-
/* @__PURE__ */
|
|
1825
|
-
/* @__PURE__ */
|
|
2104
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
2105
|
+
/* @__PURE__ */ jsx7(Logo, {}),
|
|
2106
|
+
/* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2107
|
+
/* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
|
|
1826
2108
|
item.email,
|
|
1827
2109
|
item.email ? " \xB7 " : "",
|
|
1828
2110
|
item.host.replace(/^https?:\/\//, "")
|
|
1829
2111
|
] }),
|
|
1830
|
-
/* @__PURE__ */
|
|
2112
|
+
/* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
|
|
1831
2113
|
item.team,
|
|
1832
2114
|
item.batch ? ` \xB7 ${item.batch}` : ""
|
|
1833
2115
|
] })
|
|
1834
2116
|
] })
|
|
1835
2117
|
] });
|
|
1836
2118
|
case "digest":
|
|
1837
|
-
return /* @__PURE__ */
|
|
2119
|
+
return /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { children: item.text }) });
|
|
1838
2120
|
case "user":
|
|
1839
|
-
return /* @__PURE__ */
|
|
1840
|
-
/* @__PURE__ */
|
|
1841
|
-
/* @__PURE__ */
|
|
2121
|
+
return /* @__PURE__ */ jsxs6(Box7, { marginBottom: 1, children: [
|
|
2122
|
+
/* @__PURE__ */ jsx7(Text7, { color: ACCENT, children: "> " }),
|
|
2123
|
+
/* @__PURE__ */ jsx7(Text7, { children: /* @__PURE__ */ jsx7(
|
|
1842
2124
|
HighlightedText,
|
|
1843
2125
|
{
|
|
1844
2126
|
value: item.text,
|
|
@@ -1849,9 +2131,15 @@ function TranscriptBlock({ item }) {
|
|
|
1849
2131
|
) })
|
|
1850
2132
|
] });
|
|
1851
2133
|
case "activity":
|
|
1852
|
-
return /* @__PURE__ */
|
|
2134
|
+
return /* @__PURE__ */ jsx7(ActivityBlock, { heading: item.heading, items: item.items });
|
|
2135
|
+
case "presence":
|
|
2136
|
+
return /* @__PURE__ */ jsx7(PresenceBlock, { members: item.members });
|
|
2137
|
+
case "social":
|
|
2138
|
+
return /* @__PURE__ */ jsx7(SocialBlock, { lines: item.lines });
|
|
2139
|
+
case "trending":
|
|
2140
|
+
return /* @__PURE__ */ jsx7(TrendingBlock, { companies: item.companies, batch: item.batch, days: item.days });
|
|
1853
2141
|
case "assistant":
|
|
1854
|
-
return /* @__PURE__ */
|
|
2142
|
+
return /* @__PURE__ */ jsx7(
|
|
1855
2143
|
AssistantTurn,
|
|
1856
2144
|
{
|
|
1857
2145
|
text: item.text,
|
|
@@ -1861,7 +2149,7 @@ function TranscriptBlock({ item }) {
|
|
|
1861
2149
|
}
|
|
1862
2150
|
);
|
|
1863
2151
|
case "notice":
|
|
1864
|
-
return /* @__PURE__ */
|
|
2152
|
+
return /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { color: item.tone === "error" ? "red" : void 0, dimColor: item.tone !== "error", children: item.text }) });
|
|
1865
2153
|
}
|
|
1866
2154
|
}
|
|
1867
2155
|
function readTurn(message) {
|
|
@@ -1924,7 +2212,7 @@ function truncateError(message) {
|
|
|
1924
2212
|
}
|
|
1925
2213
|
|
|
1926
2214
|
// src/oc.tsx
|
|
1927
|
-
import { jsx as
|
|
2215
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1928
2216
|
var USAGE = `oc \u2014 Orange Collective in your terminal
|
|
1929
2217
|
|
|
1930
2218
|
Usage
|
|
@@ -2009,7 +2297,7 @@ ${USAGE}`);
|
|
|
2009
2297
|
fail("Your account isn't on a team yet \u2014 ask an admin to add you.");
|
|
2010
2298
|
}
|
|
2011
2299
|
const instance = render(
|
|
2012
|
-
/* @__PURE__ */
|
|
2300
|
+
/* @__PURE__ */ jsx8(App, { client, bootstrap, host, state: state ?? {} }),
|
|
2013
2301
|
// Ctrl-C is handled in App (press twice), so Ink must not exit on the first.
|
|
2014
2302
|
{ exitOnCtrlC: false }
|
|
2015
2303
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecollective/oc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|