@wenathlan/devthink 1.1.9 → 1.1.11
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/CHANGELOG.md +19 -100
- package/package.json +1 -1
- package/terminal-ui.tsx +70 -20
- package/workspace.ts +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,107 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DevThink release notes
|
|
2
|
+
## 1.1.11 — Web-CLI parity and visual renewal
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
This release reconstructs DevThink as one command-first terminal workspace across its static React workbench and interactive CLI. The browser is the route-addressable React surface; the CLI is a React Ink terminal surface with the same categories, empty states, local credential boundary and command rail.
|
|
6
|
-
|
|
7
|
-
| Area | Change |
|
|
8
|
-
| --- | --- |
|
|
9
|
-
| Shared information architecture | Aligns `features`, `bugs`, `refactor`, `snippets`, `tasks`, `notes`, `all` and `settings` across web and CLI. |
|
|
10
|
-
| Browser workbench | Replaces the dashboard shell with a sparse black terminal workspace, top category strip, central canvas and bottom command rail. |
|
|
11
|
-
| Interactive CLI | Adds an Ink React renderer for TTY sessions, preserving the canonical ANSI fallback for noninteractive and compatibility contexts. |
|
|
12
|
-
| Binary portability | Uses Ink rather than a native TUI dependency so Bun can bundle the CLI for Linux, macOS and Windows release targets. |
|
|
13
|
-
| Release metadata | Aligns the CLI, static workbench, mobile shell, extension and package adapters on version 1.1.9. |
|
|
14
|
-
|
|
15
|
-
## 1.1.8 — Workflow trust boundaries and package adapters
|
|
16
|
-
|
|
17
|
-
This release removes `workflow_run` as a trigger from privileged distribution workflows. Publishing jobs now check out only a release tag or a manually supplied authenticated release tag, preventing a completed workflow from selecting arbitrary source code before package or release credentials become available.
|
|
18
|
-
|
|
19
|
-
| Area | Change |
|
|
20
|
-
| --- | --- |
|
|
21
|
-
| CodeQL remediation | Removes untrusted `workflow_run.head_sha` checkouts from npm, GitHub Packages, extension, container, GHCR and mobile publication workflows. |
|
|
22
|
-
| Maven | Adds a Java 17 process adapter and a source-backed Maven publication workflow for GitHub Packages. |
|
|
23
|
-
| NuGet | Adds a .NET 8 process adapter, package README and a source-backed NuGet publication workflow for GitHub Packages. |
|
|
24
|
-
| RubyGems | Adds a Ruby process adapter and a source-backed RubyGems publication workflow for GitHub Packages. |
|
|
25
|
-
| Release metadata | Aligns the CLI, static workbench, mobile shell, extension and adapter packages on version 1.1.8. |
|
|
26
|
-
|
|
27
|
-
## 1.1.7 — Security remediation
|
|
28
|
-
|
|
29
|
-
This release resolves the Trivy Dockerfile finding `DS-0026` by declaring an executable container health check that verifies the Bun-compiled DevThink CLI is available in the nonroot distroless runtime. The security workflow keeps its active Trivy, OSV, pnpm-audit and verified-secret gates; no scanner, severity threshold or release protection is weakened.
|
|
30
|
-
|
|
31
|
-
| Area | Change |
|
|
32
|
-
| --- | --- |
|
|
33
|
-
| Container hardening | Adds a native `HEALTHCHECK` instruction that executes `devthink --help` without requiring a shell in the distroless runtime. |
|
|
34
|
-
| Gateway reliability | Requests an operating-system-assigned loopback port by default, avoiding HTTP-client restricted ports during concurrent test execution in CI. |
|
|
35
|
-
| Dependency review | Confirms the workbench audit has no known vulnerabilities at low, moderate, high or critical severity. |
|
|
36
|
-
| Mobile dependency graph | Retains the `uuid` 11.1.1 override that removes the previously vulnerable transitive UUID version. |
|
|
37
|
-
| Release metadata | Aligns the CLI, static workbench, mobile shell and browser extension on version 1.1.7. |
|
|
38
|
-
|
|
39
|
-
## 1.1.6 — Mobile artifact activation
|
|
40
|
-
|
|
41
|
-
This release corrects the mobile workflow's source detector for the JSON Capacitor configuration. Android development APK/AAB and iOS simulator application packaging now execute whenever the source is present.
|
|
42
|
-
|
|
43
|
-
## 1.1.5 — Minimal container runtime
|
|
44
|
-
|
|
45
|
-
This release replaces the container runtime with a nonroot distroless compatibility image. The image contains only the compiled DevThink binary and its required C/C++ runtime libraries, reducing the operating-system package surface scanned before GHCR publication.
|
|
46
|
-
|
|
47
|
-
## 1.1.4 — Container runtime compatibility
|
|
48
|
-
|
|
49
|
-
This release runs the Bun-compiled CLI in a Bun runtime image, avoiding dynamic-library mismatches during GHCR smoke tests. Security workflows retain Trivy SARIF evidence even when the configured gate detects a high-severity issue.
|
|
50
|
-
|
|
51
|
-
## 1.1.3 — Distribution repair
|
|
52
|
-
|
|
53
|
-
This release corrects the mobile Capacitor configuration and resolves the vulnerable UUID transitively selected by the initial mobile lockfile. It also moves the Bun container runtime to a glibc-compatible base, so the compiled CLI runs in the released image, and limits Maven, NuGet, and RubyGems publication to manual source-backed dispatches.
|
|
54
|
-
|
|
55
|
-
## 1.1.2 — Distribution surfaces and artifacts
|
|
56
|
-
|
|
57
|
-
This release keeps `@wenathlan/devthink` as the only npm package and adds source-backed distribution surfaces for a browser extension, a Bun-compiled container, Android development artifacts, and an iOS simulator artifact. Maven, NuGet and RubyGems remain source-gated because the repository does not contain Java, .NET, or Ruby package sources.
|
|
58
|
-
|
|
59
|
-
| Area | Change |
|
|
60
|
-
| --- | --- |
|
|
61
|
-
| npm | Publishes only the DevThink CLI package. |
|
|
62
|
-
| Browser extension | Packages the static workbench as a Manifest V3 release ZIP with explicit local-gateway host permissions. |
|
|
63
|
-
| Container | Compiles the CLI with Bun in a multi-stage image and publishes a container release archive and GHCR image when triggered. |
|
|
64
|
-
| Mobile | Uses Capacitor source with Bun-built web assets to produce Android development artifacts and an unsigned iOS simulator application. |
|
|
65
|
-
|
|
66
|
-
## 1.1.1 — DevThink publication and workflow reconciliation
|
|
67
|
-
|
|
68
|
-
This release aligns the CLI and static workbench at version 1.1.1, refreshes direct web dependencies, and updates the GitHub Actions toolchain. The workbench remains a static deployment surface and is not published as a separate npm package.
|
|
69
|
-
|
|
70
|
-
| Area | Change |
|
|
71
|
-
| --- | --- |
|
|
72
|
-
| Distribution | Publishes only `@wenathlan/devthink` through the credential-safe npm workflow. |
|
|
73
|
-
| Workflows | Updates Actions runtimes and adds source-aware guards for optional ecosystem packages. |
|
|
74
|
-
| Compatibility | Raises the declared Node.js baseline to 26.7.0 and refreshes direct web dependencies to their current releases. |
|
|
75
|
-
| Static web | Preserves the deployable `web/` artifact for GitHub Pages, Vercel and Netlify. |
|
|
76
|
-
|
|
77
|
-
## Embedded gateway and web workbench implementation
|
|
78
|
-
|
|
79
|
-
This implementation turns the audited repository context into the DevThink feature map and expands the DevThink CLI with provider-owned endpoints, normalized streaming and a local embedded gateway.
|
|
4
|
+
### Added
|
|
80
5
|
|
|
81
|
-
| Area |
|
|
6
|
+
| Area | Addition |
|
|
82
7
|
| --- | --- |
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
| Security | Keeps cookie capture, CAPTCHA solving, stealth automation and anti-bot evasion outside the product core |
|
|
8
|
+
| Shared workspace contract | A renderer-neutral TypeScript destination model aligns chat, history, projects, providers, usage, routes and settings across the React web workbench and React Ink CLI. |
|
|
9
|
+
| Real session navigation | The web chrome now renders actual local session tabs, creates and closes tabs, and exposes a local history view without removing direct routes. |
|
|
10
|
+
| CLI control views | The interactive CLI now provides keyboard and slash-command access to local history, projects, providers, usage, routes and settings. |
|
|
11
|
+
| Browser rail | A responsive dark local rail keeps preserved control pages discoverable from the primary workspace while retaining their bookmarkable routes. |
|
|
12
|
+
| Entry refinement | The canonical ANSI mark, product wordmark, primary composer and local suggestions form a quieter first-intention surface. |
|
|
13
|
+
| Design records | Version-scoped inventory, reference synthesis and parity contract documents make the product surface and local-only security boundary explicit. |
|
|
90
14
|
|
|
91
|
-
## 1.
|
|
15
|
+
## 1.1.10 — Entry and browser-workspace redesign
|
|
92
16
|
|
|
93
17
|
### Added
|
|
94
18
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
This release deliberately excludes browser-cookie capture, undocumented web-session automation, CAPTCHA solving, anti-bot evasion, device-fingerprint forgery, and credential extraction. Provider failures are surfaced explicitly, and the release workflow scans product files for common embedded-secret patterns before publishing.
|
|
104
|
-
|
|
105
|
-
### Distribution
|
|
106
|
-
|
|
107
|
-
GitHub Actions builds self-contained Bun binaries for Linux x64, macOS arm64, and Windows x64 from version tags. The workflow reads the release version from `package.json`, runs tests, generates SHA-256 checksums, and attaches artifacts to the GitHub release.
|
|
19
|
+
| Area | Addition |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Landing entry | A first-command introduction with the canonical DevThink ANSI mark behind an oversized wordmark and one focused command field. |
|
|
22
|
+
| First-command transition | A submitted natural-language intention creates a local identity, records the first session intent and opens the workspace. |
|
|
23
|
+
| Browser workspace | A two-level navigation model with browser-shaped session chrome, an active local tab, task-category pills and a persistent command rail. |
|
|
24
|
+
| Visual system | Original black editorial surfaces, layered charcoal browser material, intentional orange states and orb-blue local connection signals. |
|
|
25
|
+
| CLI parity | The Ink renderer now starts on the same command-first entry surface and opens the same local workspace semantics after the first command. |
|
|
26
|
+
| Release packaging | Locked CLI dependencies are installed before Bun binary compilation and inside the container build so the React Ink renderer is included consistently. |
|
package/package.json
CHANGED
package/terminal-ui.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Design: DevThink v1.1.11 — Ink shares local workspace destinations, tab semantics and command language with the React web workbench. */
|
|
2
2
|
import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { listProviders } from "./providers.ts";
|
|
5
|
+
import { listSessions, type Session } from "./session.ts";
|
|
6
|
+
import type { DevThinkConfig, DevThinkPaths } from "./config.ts";
|
|
5
7
|
import type { ChatEvent } from "./stream.ts";
|
|
6
|
-
import type
|
|
8
|
+
import { isWorkspaceDestination, workspaceDestination, workspaceDestinations, type WorkspaceDestination } from "./workspace.ts";
|
|
7
9
|
|
|
8
|
-
const categories = ["features", "bugs", "refactor", "snippets", "tasks", "notes", "all"
|
|
9
|
-
const categoryGlyphs: Record<(typeof categories)[number], string> = { features: "ϟ", bugs: "⊗", refactor: "◌", snippets: "◫", tasks: "☑", notes: "□", all: "◉"
|
|
10
|
+
const categories = ["features", "bugs", "refactor", "snippets", "tasks", "notes", "all"] as const;
|
|
11
|
+
const categoryGlyphs: Record<(typeof categories)[number], string> = { features: "ϟ", bugs: "⊗", refactor: "◌", snippets: "◫", tasks: "☑", notes: "□", all: "◉" };
|
|
12
|
+
const entryArt = [" +++++", " +++++++++++++++", " ++++++ ++++++", " +++++ +++++", " +++++ +++++", " +++++ +++++", " ++++++ ++++++", " +++++++++++++", " +++++"];
|
|
10
13
|
|
|
11
|
-
type Runtime = { config: DevThinkConfig };
|
|
14
|
+
type Runtime = { config: DevThinkConfig; paths: DevThinkPaths };
|
|
12
15
|
type Execute = (prompt: string, current: Session | undefined, onEvent: (event: ChatEvent) => void) => Promise<Session | undefined>;
|
|
13
16
|
type Entry = { role: "user" | "devthink" | "error"; title: string; body: string };
|
|
14
17
|
|
|
@@ -16,10 +19,40 @@ function clip(value: string, width: number): string {
|
|
|
16
19
|
return value.length > width ? `${value.slice(0, Math.max(0, width - 1))}…` : value;
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
function TerminalEntry({ draft, provider, width }: { draft: string; provider: string; width: number }) {
|
|
23
|
+
return <Box flexDirection="column" width="100%" height="100%" minHeight={20} paddingX={2} paddingY={1} justifyContent="space-between">
|
|
24
|
+
<Box justifyContent="space-between"><Text color="#807970">✦ DEVTHINK</Text><Text color="#8ab4f8">LOCAL-FIRST WORKBENCH</Text></Box>
|
|
25
|
+
<Box flexDirection="column" alignItems="center"><Box flexDirection="column" alignItems="center">{entryArt.map((line, index) => <Text key={`${line}-${index}`} color="#382c23">{line}</Text>)}</Box><Box marginTop={-1}><Text color="#ff5f00" bold>DEVTHINK</Text></Box><Box marginTop={1}><Text color="#746f66">begin with one intention</Text></Box><Box marginTop={2}><Text color="#282724">┌{"─".repeat(Math.max(16, width - 2))}┐</Text></Box><Text color="#e8e1d7">│ <Text color="#ff5f00">›_ </Text><Text color={draft ? "#e8e1d7" : "#746f66"}>{clip(draft || "Ask anything…", Math.max(12, width - 22))}</Text><Text color="#8ab4f8"> {provider}</Text><Text color="#ff5f00"> [open]</Text> │</Text><Text color="#282724">└{"─".repeat(Math.max(16, width - 2))}┘</Text></Box>
|
|
26
|
+
<Text color="#746f66">enter opens a local workspace · esc exits</Text>
|
|
27
|
+
</Box>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function projectRows(runtime: Runtime): string[] {
|
|
31
|
+
const projects = new Map<string, { title: string; sessions: number }>();
|
|
32
|
+
for (const session of listSessions(runtime.paths)) projects.set(session.workspaceId, { title: session.title, sessions: (projects.get(session.workspaceId)?.sessions || 0) + 1 });
|
|
33
|
+
return [...projects.entries()].map(([id, project]) => `${id} ${project.sessions} ${project.sessions === 1 ? "session" : "sessions"} ${project.title}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function viewRows(view: WorkspaceDestination, runtime: Runtime, session: Session | undefined): string[] {
|
|
37
|
+
if (view === "history") return listSessions(runtime.paths).map((item) => `${item.id} ${item.title}`) || ["No saved sessions."];
|
|
38
|
+
if (view === "projects") return projectRows(runtime) || ["No local projects."];
|
|
39
|
+
if (view === "providers") return listProviders().map((provider) => `${provider.id} ${provider.protocol} ${provider.env}`);
|
|
40
|
+
if (view === "usage") {
|
|
41
|
+
const sessions = listSessions(runtime.paths);
|
|
42
|
+
return [`workspaces ${new Set(sessions.map((item) => item.workspaceId)).size}`, `sessions ${sessions.length}`, `tabs ${sessions.reduce((total, item) => total + item.tabs.length, 0)}`, `messages ${sessions.reduce((total, item) => total + item.messages.length, 0)}`];
|
|
43
|
+
}
|
|
44
|
+
if (view === "routes") return ["GET /health", "GET /providers", "GET /workspaces", "GET /usage", "POST /sessions", "POST /chat"];
|
|
45
|
+
if (view === "settings") return [`provider ${runtime.config.activeProvider || runtime.config.provider || "not configured"}`, "credentials remain in ~/.config/devthink/auth.json", "pair create emits a one-time local browser invitation"];
|
|
46
|
+
if (session) return [`workspace ${session.workspaceId}`, `session ${session.id}`, `tab ${session.activeTabId}`];
|
|
47
|
+
return ["Describe the next feature, bug or piece of work below."];
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
function TerminalWorkspace({ runtime, execute, version }: { runtime: Runtime; execute: Execute; version: string }) {
|
|
20
51
|
const { exit } = useApp();
|
|
21
52
|
const { columns } = useWindowSize();
|
|
53
|
+
const [entered, setEntered] = useState(false);
|
|
22
54
|
const [active, setActive] = useState<(typeof categories)[number]>("all");
|
|
55
|
+
const [view, setView] = useState<WorkspaceDestination>("chat");
|
|
23
56
|
const [entries, setEntries] = useState<Entry[]>([]);
|
|
24
57
|
const [session, setSession] = useState<Session>();
|
|
25
58
|
const [draft, setDraft] = useState("");
|
|
@@ -27,20 +60,32 @@ function TerminalWorkspace({ runtime, execute, version }: { runtime: Runtime; ex
|
|
|
27
60
|
const provider = runtime.config.activeProvider || runtime.config.provider || "local";
|
|
28
61
|
const model = runtime.config.activeModel || runtime.config.model || "not configured";
|
|
29
62
|
const compact = columns < 100;
|
|
30
|
-
const tabLine = compact ? categories.map((category) => categoryGlyphs[category]).join(" ") : categories.map((category) => `${category === active ? "[" : ""}${categoryGlyphs[category]} ${category}${category === active ? "]" : ""}`).join(" ");
|
|
31
63
|
const terminalWidth = Math.max(38, columns - (compact ? 2 : 6));
|
|
64
|
+
const tabLine = compact ? workspaceDestinations.map((destination) => destination.glyph).join(" ") : workspaceDestinations.map((destination) => `${destination.id === view ? "[" : ""}${destination.glyph} ${destination.label}${destination.id === view ? "]" : ""}`).join(" ");
|
|
65
|
+
const rows = useMemo(() => viewRows(view, runtime, session), [runtime, session, view]);
|
|
66
|
+
|
|
67
|
+
function openView(next: WorkspaceDestination) {
|
|
68
|
+
setView(next);
|
|
69
|
+
setEntries((current) => [...current, { role: "devthink", title: workspaceDestination(next).label, body: rows.join(" · ") }]);
|
|
70
|
+
}
|
|
32
71
|
|
|
33
72
|
async function submit() {
|
|
34
73
|
const prompt = draft.trim();
|
|
35
74
|
if (!prompt || pending) return;
|
|
36
75
|
if (prompt === "/exit" || prompt === "/quit") return exit();
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
if (prompt === "/help") {
|
|
40
|
-
setEntries((current) => [...current, { role: "devthink", title: "command reference", body: "/new /clear /models /modes /sessions /exit" }]);
|
|
76
|
+
if (!entered) {
|
|
77
|
+
setEntries([{ role: "user", title: "first intention", body: prompt }, { role: "devthink", title: "local workspace ready", body: "The first command opened a local DevThink session. Use arrows or /view to inspect local history, projects, providers, usage, routes and settings." }]);
|
|
41
78
|
setDraft("");
|
|
79
|
+
setEntered(true);
|
|
42
80
|
return;
|
|
43
81
|
}
|
|
82
|
+
if (prompt === "/clear") { setEntries([]); setDraft(""); return; }
|
|
83
|
+
if (prompt === "/new") { setSession(undefined); setEntries([]); setView("chat"); setDraft(""); return; }
|
|
84
|
+
const viewMatch = prompt.match(/^\/(?:view\s+)?(chat|history|projects|providers|usage|routes|settings)$/);
|
|
85
|
+
if (viewMatch && isWorkspaceDestination(viewMatch[1])) { openView(viewMatch[1]); setDraft(""); return; }
|
|
86
|
+
if (prompt === "/sessions") { openView("history"); setDraft(""); return; }
|
|
87
|
+
if (prompt === "/help") { setEntries((current) => [...current, { role: "devthink", title: "command reference", body: "/view chat|history|projects|providers|usage|routes|settings /new /clear /models /modes /sessions /exit" }]); setDraft(""); return; }
|
|
88
|
+
setView("chat");
|
|
44
89
|
setEntries((current) => [...current, { role: "user", title: active, body: prompt }, { role: "devthink", title: "connecting", body: "waiting for local gateway events…" }]);
|
|
45
90
|
setDraft("");
|
|
46
91
|
setPending(true);
|
|
@@ -59,24 +104,29 @@ function TerminalWorkspace({ runtime, execute, version }: { runtime: Runtime; ex
|
|
|
59
104
|
|
|
60
105
|
useInput((input, key) => {
|
|
61
106
|
if (key.escape || (key.ctrl && input === "c")) return exit();
|
|
62
|
-
if (key.leftArrow || key.tab) return setActive((current) => categories[(categories.indexOf(current) + categories.length - 1) % categories.length]);
|
|
63
|
-
if (key.rightArrow) return setActive((current) => categories[(categories.indexOf(current) + 1) % categories.length]);
|
|
64
107
|
if (key.return) return void submit();
|
|
65
108
|
if (key.backspace || key.delete) return setDraft((current) => current.slice(0, -1));
|
|
109
|
+
if (entered && key.upArrow) return setView((current) => workspaceDestinations[(workspaceDestinations.findIndex((item) => item.id === current) + workspaceDestinations.length - 1) % workspaceDestinations.length].id);
|
|
110
|
+
if (entered && key.downArrow) return setView((current) => workspaceDestinations[(workspaceDestinations.findIndex((item) => item.id === current) + 1) % workspaceDestinations.length].id);
|
|
111
|
+
if (entered && view === "chat" && (key.leftArrow || key.tab)) return setActive((current) => categories[(categories.indexOf(current) + categories.length - 1) % categories.length]);
|
|
112
|
+
if (entered && view === "chat" && key.rightArrow) return setActive((current) => categories[(categories.indexOf(current) + 1) % categories.length]);
|
|
66
113
|
if (!key.ctrl && !key.meta && input) setDraft((current) => current + input);
|
|
67
114
|
});
|
|
68
115
|
|
|
116
|
+
if (!entered) return <TerminalEntry draft={draft} provider={provider} width={terminalWidth} />;
|
|
117
|
+
|
|
69
118
|
return <Box flexDirection="column" width="100%" height="100%" minHeight={20} paddingX={compact ? 1 : 3} paddingY={1}>
|
|
70
|
-
<Text color="#a19a91"
|
|
119
|
+
<Box justifyContent="space-between"><Text color="#a19a91">✦ DEVTHINK <Text color="#746f66">/ {workspaceDestination(view).label}</Text></Text><Text color="#8ab4f8">◉ {provider.toUpperCase()}</Text></Box>
|
|
71
120
|
<Text color="#282724">{"─".repeat(terminalWidth)}</Text>
|
|
72
|
-
<Text color="#
|
|
73
|
-
<Box
|
|
74
|
-
|
|
121
|
+
<Text color="#746f66">{clip(tabLine, terminalWidth)}</Text>
|
|
122
|
+
<Box marginTop={1}><Text color="#e8e1d7">{workspaceDestination(view).glyph} {workspaceDestination(view).label.toUpperCase()} <Text color="#746f66">· {clip(model, 22)}</Text></Text></Box>
|
|
123
|
+
<Box flexDirection="column" flexGrow={1} justifyContent={entries.length && view === "chat" ? "flex-start" : "center"} alignItems={entries.length && view === "chat" ? "flex-start" : "center"}>
|
|
124
|
+
{view !== "chat" ? rows.map((row, index) => <Text key={`${view}-${index}`} color={index === 0 ? "#e8e1d7" : "#746f66"}>▎ {clip(row, terminalWidth - 4)}</Text>) : entries.length ? entries.map((entry, index) => <Box key={`${entry.title}-${index}`} flexDirection="column" marginBottom={1} paddingLeft={1}><Box><Text color={entry.role === "user" ? "#ff5f00" : entry.role === "error" ? "#ff6b6b" : "#8ab4f8"}>▎ {entry.title.toUpperCase()}</Text></Box><Box><Text color="#e8e1d7">{clip(entry.body, terminalWidth - 6)}</Text></Box></Box>) : <Box flexDirection="column" alignItems="center"><Text color="#4b4842">Nothing is active yet.</Text><Text color="#625c54">Describe the next feature, bug or piece of work below.</Text><Box marginTop={2}><Text color="#ff5f00" bold>DEVTHINK</Text></Box></Box>}
|
|
75
125
|
</Box>
|
|
76
126
|
<Text color="#282724">┌{"─".repeat(Math.max(16, terminalWidth - 2))}┐</Text>
|
|
77
|
-
<Text color="#e8e1d7">│ <Text color="#ff5f00">›_ </Text><Text color={draft ? "#e8e1d7" : "#746f66"}>{clip(draft || `
|
|
127
|
+
<Text color="#e8e1d7">│ <Text color="#ff5f00">›_ </Text><Text color={draft ? "#e8e1d7" : "#746f66"}>{clip(draft || (view === "chat" ? `Ask DevThink about ${active === "all" ? "the work…" : `${active}…`}` : `Type /view chat to return…`), Math.max(12, terminalWidth - 20))}</Text><Text color="#8ab4f8"> {provider}</Text><Text color="#ff5f00"> [run]</Text> │</Text>
|
|
78
128
|
<Text color="#282724">└{"─".repeat(Math.max(16, terminalWidth - 2))}┘</Text>
|
|
79
|
-
<Text color="#746f66"
|
|
129
|
+
<Text color="#746f66">↑↓ destinations · ←→ categories · /help commands · v{version} · esc exit</Text>
|
|
80
130
|
</Box>;
|
|
81
131
|
}
|
|
82
132
|
|
package/workspace.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** DevThink workspace contract: renderer-neutral destination names, glyphs and labels shared by web and Ink. */
|
|
2
|
+
export const workspaceDestinations = [
|
|
3
|
+
{ id: "chat", label: "chat", glyph: "◉" },
|
|
4
|
+
{ id: "history", label: "history", glyph: "◷" },
|
|
5
|
+
{ id: "projects", label: "projects", glyph: "▦" },
|
|
6
|
+
{ id: "providers", label: "providers", glyph: "◌" },
|
|
7
|
+
{ id: "usage", label: "usage", glyph: "◫" },
|
|
8
|
+
{ id: "routes", label: "routes", glyph: "⌘" },
|
|
9
|
+
{ id: "settings", label: "settings", glyph: "⚙" },
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type WorkspaceDestination = (typeof workspaceDestinations)[number]["id"];
|
|
13
|
+
|
|
14
|
+
export function isWorkspaceDestination(value: string): value is WorkspaceDestination {
|
|
15
|
+
return workspaceDestinations.some((destination) => destination.id === value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function workspaceDestination(value: WorkspaceDestination) {
|
|
19
|
+
return workspaceDestinations.find((destination) => destination.id === value) || workspaceDestinations[0];
|
|
20
|
+
}
|