@groeponline/pi-wishcraft 0.17.3
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/AGENTS.md +68 -0
- package/CHANGELOG.md +724 -0
- package/CONTRIBUTING.md +37 -0
- package/README.md +648 -0
- package/RELEASE.md +117 -0
- package/ROADMAP.md +52 -0
- package/bash-mode/completion-providers.ts +269 -0
- package/bash-mode/completion.ts +416 -0
- package/bash-mode/editor-ghost.ts +40 -0
- package/bash-mode/editor-input.ts +80 -0
- package/bash-mode/editor.ts +437 -0
- package/bash-mode/history.ts +263 -0
- package/bash-mode/shell-session.ts +286 -0
- package/bash-mode/transcript.ts +108 -0
- package/bash-mode/types.ts +80 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/queue/store.ts +443 -0
- package/queue/types.ts +54 -0
- package/src/config/custom-items.ts +182 -0
- package/src/config/extension-statuses.ts +51 -0
- package/src/config/layout.ts +60 -0
- package/src/config/parse.ts +127 -0
- package/src/config/powerline-config.ts +18 -0
- package/src/config/presets.ts +245 -0
- package/src/config/primitives.ts +117 -0
- package/src/config/segment-ids.ts +114 -0
- package/src/config/segment-options.ts +128 -0
- package/src/config/settings-patch.ts +26 -0
- package/src/config/types.ts +277 -0
- package/src/core/frontmatter.ts +40 -0
- package/src/editor/autocomplete-chain.ts +41 -0
- package/src/extension/activate.ts +28 -0
- package/src/extension/bash-mode-actions.ts +104 -0
- package/src/extension/commands.ts +268 -0
- package/src/extension/constants.ts +46 -0
- package/src/extension/custom-editor.ts +406 -0
- package/src/extension/git-invalidation.ts +40 -0
- package/src/extension/layout.ts +160 -0
- package/src/extension/menu-views.ts +393 -0
- package/src/extension/powerline-widgets.ts +95 -0
- package/src/extension/prompt-history.ts +219 -0
- package/src/extension/queue-commands.ts +245 -0
- package/src/extension/queue-context.ts +12 -0
- package/src/extension/queue-integration.ts +434 -0
- package/src/extension/segment-context.ts +212 -0
- package/src/extension/session-lifecycle.ts +373 -0
- package/src/extension/settings-io.ts +202 -0
- package/src/extension/shortcuts-config.ts +357 -0
- package/src/extension/shortcuts-router.ts +383 -0
- package/src/extension/skills/inline-invocation.ts +174 -0
- package/src/extension/skills/ook.md +6 -0
- package/src/extension/skills/test.md +6 -0
- package/src/extension/stale-context.ts +10 -0
- package/src/extension/stash-history.ts +103 -0
- package/src/extension/state.ts +159 -0
- package/src/extension/status-line-renderers.ts +222 -0
- package/src/extension/types.ts +97 -0
- package/src/extension/vibe-command.ts +160 -0
- package/src/extension/welcome-control.ts +27 -0
- package/src/extension/welcome-integration.ts +153 -0
- package/src/git/status.ts +332 -0
- package/src/paths/agent-dirs.ts +67 -0
- package/src/render/timer.ts +46 -0
- package/src/segments/core.ts +256 -0
- package/src/segments/custom.ts +114 -0
- package/src/segments/index.ts +3 -0
- package/src/segments/registry.ts +87 -0
- package/src/segments/shared.ts +36 -0
- package/src/segments/system.ts +235 -0
- package/src/segments/usage.ts +178 -0
- package/src/shell/cd-command.ts +190 -0
- package/src/shortcuts/matching.ts +61 -0
- package/src/theme/colors.ts +60 -0
- package/src/theme/icons.ts +175 -0
- package/src/theme/separators.ts +41 -0
- package/src/theme/theme.ts +211 -0
- package/src/tools/graph.ts +75 -0
- package/src/tools/patch.ts +179 -0
- package/src/tools/ripgrep.ts +104 -0
- package/src/usage/context.ts +97 -0
- package/src/usage/ledger.ts +293 -0
- package/src/usage/rates.ts +155 -0
- package/src/welcome/auto-dismiss.ts +43 -0
- package/src/welcome/banner.ts +68 -0
- package/src/welcome/discover.ts +234 -0
- package/src/welcome/format.ts +18 -0
- package/src/welcome/index.ts +5 -0
- package/src/welcome/layout.ts +36 -0
- package/src/welcome/overlay.ts +80 -0
- package/src/welcome/renderer.ts +157 -0
- package/src/welcome/sessions.ts +107 -0
- package/src/welcome/types.ts +41 -0
- package/src/welcome/widgets/graph-widget.ts +25 -0
- package/src/welcome/widgets/index.ts +20 -0
- package/src/welcome/widgets/queue-widget.ts +26 -0
- package/src/welcome/widgets/sessions-widget.ts +23 -0
- package/src/welcome/widgets/shortcuts-widget.ts +17 -0
- package/src/welcome/widgets/system-widget.ts +29 -0
- package/src/working-vibes/generate.ts +144 -0
- package/src/working-vibes/index.ts +24 -0
- package/src/working-vibes/manager.ts +198 -0
- package/src/working-vibes/provider.ts +163 -0
- package/src/working-vibes/storage.ts +357 -0
- package/theme.example.json +24 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { ansi, fgOnly, getFgAnsiCode } from "../theme/colors.ts";
|
|
3
|
+
import { centerText, fitToWidth, getBoxLayout } from "./layout.ts";
|
|
4
|
+
import type { WelcomeData, WelcomeWidget, WidgetRenderContext } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
import { QueueWidget } from "./widgets/queue-widget.ts";
|
|
7
|
+
import { SessionsWidget } from "./widgets/sessions-widget.ts";
|
|
8
|
+
import { ShortcutsWidget } from "./widgets/shortcuts-widget.ts";
|
|
9
|
+
import { SystemWidget } from "./widgets/system-widget.ts";
|
|
10
|
+
|
|
11
|
+
const PI_LOGO = [
|
|
12
|
+
" . * ",
|
|
13
|
+
" * ╭───╮ . ",
|
|
14
|
+
" . │ │ * ",
|
|
15
|
+
" │ │ ",
|
|
16
|
+
" * ╰─┬─╯ . ",
|
|
17
|
+
" . ┴ ",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const GRADIENT_COLORS = [
|
|
21
|
+
"\x1b[38;5;199m",
|
|
22
|
+
"\x1b[38;5;171m",
|
|
23
|
+
"\x1b[38;5;135m",
|
|
24
|
+
"\x1b[38;5;99m",
|
|
25
|
+
"\x1b[38;5;75m",
|
|
26
|
+
"\x1b[38;5;51m",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function bold(text: string): string {
|
|
30
|
+
return `\x1b[1m${text}\x1b[22m`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function dim(text: string): string {
|
|
34
|
+
return getFgAnsiCode("sep") + text + ansi.reset;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gradientLine(line: string): string {
|
|
38
|
+
const reset = ansi.reset;
|
|
39
|
+
let result = "";
|
|
40
|
+
let colorIdx = 0;
|
|
41
|
+
const step = Math.max(1, Math.floor(line.length / GRADIENT_COLORS.length));
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < line.length; i++) {
|
|
44
|
+
if (i > 0 && i % step === 0 && colorIdx < GRADIENT_COLORS.length - 1)
|
|
45
|
+
colorIdx++;
|
|
46
|
+
const char = line[i];
|
|
47
|
+
if (char !== " ") {
|
|
48
|
+
result += GRADIENT_COLORS[colorIdx] + char + reset;
|
|
49
|
+
} else {
|
|
50
|
+
result += char;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildLeftColumn(ctx: WidgetRenderContext): string[] {
|
|
57
|
+
const logoColored = PI_LOGO.map((line) => gradientLine(line));
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
"",
|
|
61
|
+
...logoColored.map((l) => centerText(l, ctx.width)),
|
|
62
|
+
"",
|
|
63
|
+
centerText(fgOnly("model", ctx.data.modelName), ctx.width),
|
|
64
|
+
centerText(dim(ctx.data.providerName), ctx.width),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildRightColumn(
|
|
69
|
+
ctx: WidgetRenderContext,
|
|
70
|
+
widgets: WelcomeWidget[]
|
|
71
|
+
): string[] {
|
|
72
|
+
const hChar = "─";
|
|
73
|
+
const separator = ` ${dim(hChar.repeat(Math.max(1, ctx.width - 2)))}`;
|
|
74
|
+
const lines: string[] = [];
|
|
75
|
+
|
|
76
|
+
lines.push(` ${bold(fgOnly("accent", "Signals & Wishes"))}`);
|
|
77
|
+
lines.push(` ${dim("Write it down, let it rise, keep your focus clear.")}`);
|
|
78
|
+
lines.push(separator);
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < widgets.length; i++) {
|
|
81
|
+
const wLines = widgets[i].render(ctx);
|
|
82
|
+
lines.push(...wLines);
|
|
83
|
+
if (i < widgets.length - 1) {
|
|
84
|
+
lines.push(separator);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
lines.push("");
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function renderWelcomeBox(
|
|
93
|
+
data: WelcomeData,
|
|
94
|
+
termWidth: number,
|
|
95
|
+
bottomLine: string,
|
|
96
|
+
): string[] {
|
|
97
|
+
const layout = getBoxLayout(termWidth);
|
|
98
|
+
if (!layout) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { boxWidth, leftCol, rightCol } = layout;
|
|
103
|
+
|
|
104
|
+
const hChar = "─";
|
|
105
|
+
const v = dim("│");
|
|
106
|
+
const tl = dim("╭");
|
|
107
|
+
const tr = dim("╮");
|
|
108
|
+
const bl = dim("╰");
|
|
109
|
+
const br = dim("╯");
|
|
110
|
+
|
|
111
|
+
const rightWidgets = [
|
|
112
|
+
SystemWidget,
|
|
113
|
+
QueueWidget,
|
|
114
|
+
ShortcutsWidget,
|
|
115
|
+
SessionsWidget,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const leftCtx: WidgetRenderContext = {
|
|
119
|
+
data,
|
|
120
|
+
width: leftCol,
|
|
121
|
+
dim,
|
|
122
|
+
bold,
|
|
123
|
+
color: fgOnly,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const rightCtx: WidgetRenderContext = {
|
|
127
|
+
data,
|
|
128
|
+
width: rightCol,
|
|
129
|
+
dim,
|
|
130
|
+
bold,
|
|
131
|
+
color: fgOnly,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const leftLines = buildLeftColumn(leftCtx);
|
|
135
|
+
const rightLines = buildRightColumn(rightCtx, rightWidgets);
|
|
136
|
+
|
|
137
|
+
const lines: string[] = [];
|
|
138
|
+
|
|
139
|
+
const title = " pi-wishcraft ";
|
|
140
|
+
const titlePrefix = dim(hChar.repeat(3));
|
|
141
|
+
const titleStyled = titlePrefix + fgOnly("model", title);
|
|
142
|
+
const titleVisLen = 3 + visibleWidth(title);
|
|
143
|
+
const afterTitle = boxWidth - 2 - titleVisLen;
|
|
144
|
+
const afterTitleText = afterTitle > 0 ? dim(hChar.repeat(afterTitle)) : "";
|
|
145
|
+
lines.push(tl + titleStyled + afterTitleText + tr);
|
|
146
|
+
|
|
147
|
+
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
148
|
+
for (let i = 0; i < maxRows; i++) {
|
|
149
|
+
const left = fitToWidth(leftLines[i] ?? "", leftCol);
|
|
150
|
+
const right = fitToWidth(rightLines[i] ?? "", rightCol);
|
|
151
|
+
lines.push(v + left + v + right + v);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
lines.push(bl + bottomLine + br);
|
|
155
|
+
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, basename } from "node:path";
|
|
3
|
+
import { getAgentSessionDirs } from "../paths/agent-dirs.ts";
|
|
4
|
+
import { logDiscoveryError } from "./discover.ts";
|
|
5
|
+
import { formatTimeAgo } from "./format.ts";
|
|
6
|
+
import type { RecentSession } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
const MAX_HEADER_SIZE = 8192;
|
|
9
|
+
|
|
10
|
+
interface SessionRecord {
|
|
11
|
+
name: string;
|
|
12
|
+
mtime: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseProjectNameFromHeader(filePath: string): string | null {
|
|
16
|
+
try {
|
|
17
|
+
const buffer = readFileSync(filePath);
|
|
18
|
+
const bytesToRead = Math.min(buffer.length, MAX_HEADER_SIZE);
|
|
19
|
+
const firstLine = buffer
|
|
20
|
+
.toString("utf8", 0, bytesToRead)
|
|
21
|
+
.split(/\r?\n/, 1)[0]
|
|
22
|
+
?.trim();
|
|
23
|
+
|
|
24
|
+
if (!firstLine) return null;
|
|
25
|
+
|
|
26
|
+
const header: unknown = JSON.parse(firstLine);
|
|
27
|
+
|
|
28
|
+
if (typeof header !== "object" || header === null) return null;
|
|
29
|
+
if (!("cwd" in header)) return null;
|
|
30
|
+
|
|
31
|
+
const cwd = (header as { cwd: unknown }).cwd;
|
|
32
|
+
if (typeof cwd !== "string" || cwd.trim().length === 0) return null;
|
|
33
|
+
|
|
34
|
+
return basename(cwd) || cwd;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractProjectNameFromDir(dir: string): string {
|
|
41
|
+
const parentName = basename(dir);
|
|
42
|
+
if (!parentName.startsWith("--")) {
|
|
43
|
+
return parentName;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parts = parentName.split("-").filter(Boolean);
|
|
47
|
+
return parts[parts.length - 1] || parentName;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function scanSessionDirectory(dir: string, sessions: SessionRecord[]): void {
|
|
51
|
+
if (!existsSync(dir)) return;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const entries = readdirSync(dir);
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const entryPath = join(dir, entry);
|
|
57
|
+
try {
|
|
58
|
+
const stats = statSync(entryPath);
|
|
59
|
+
if (stats.isDirectory()) {
|
|
60
|
+
scanSessionDirectory(entryPath, sessions);
|
|
61
|
+
} else if (entry.endsWith(".jsonl")) {
|
|
62
|
+
const projectName =
|
|
63
|
+
parseProjectNameFromHeader(entryPath) ??
|
|
64
|
+
extractProjectNameFromDir(dir);
|
|
65
|
+
sessions.push({ name: projectName, mtime: stats.mtimeMs });
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logDiscoveryError(`Failed to inspect session entry ${entryPath}`, error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logDiscoveryError(`Failed to scan sessions dir ${dir}`, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function filterAndFormatSessions(sessions: SessionRecord[], maxCount: number): RecentSession[] {
|
|
77
|
+
if (sessions.length === 0) return [];
|
|
78
|
+
|
|
79
|
+
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
80
|
+
|
|
81
|
+
const seen = new Set<string>();
|
|
82
|
+
const uniqueSessions: SessionRecord[] = [];
|
|
83
|
+
|
|
84
|
+
for (const s of sessions) {
|
|
85
|
+
if (!seen.has(s.name)) {
|
|
86
|
+
seen.add(s.name);
|
|
87
|
+
uniqueSessions.push(s);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
return uniqueSessions.slice(0, maxCount).map((s) => ({
|
|
93
|
+
name: s.name.length > 20 ? s.name.slice(0, 17) + "…" : s.name,
|
|
94
|
+
timeAgo: formatTimeAgo(now - s.mtime),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getRecentSessions(maxCount: number = 3): RecentSession[] {
|
|
99
|
+
const sessionsDirs = getAgentSessionDirs();
|
|
100
|
+
const sessions: SessionRecord[] = [];
|
|
101
|
+
|
|
102
|
+
for (const dir of sessionsDirs) {
|
|
103
|
+
scanSessionDirectory(dir, sessions);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return filterAndFormatSessions(sessions, maxCount);
|
|
107
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface LoadedCounts {
|
|
2
|
+
contextFiles: number;
|
|
3
|
+
extensions: number;
|
|
4
|
+
skills: number;
|
|
5
|
+
promptTemplates: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RecentSession {
|
|
9
|
+
name: string;
|
|
10
|
+
timeAgo: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WelcomeData {
|
|
14
|
+
modelName: string;
|
|
15
|
+
providerName: string;
|
|
16
|
+
recentSessions: RecentSession[];
|
|
17
|
+
loadedCounts: LoadedCounts;
|
|
18
|
+
initialContextTokens: number | null;
|
|
19
|
+
queueCount?: number;
|
|
20
|
+
hasStash?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WelcomeWidget {
|
|
24
|
+
id: string;
|
|
25
|
+
render(ctx: WidgetRenderContext): string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WidgetRenderContext {
|
|
29
|
+
data: WelcomeData;
|
|
30
|
+
width: number;
|
|
31
|
+
dim: (text: string) => string;
|
|
32
|
+
bold: (text: string) => string;
|
|
33
|
+
color: (semantic: string, text: string) => string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WelcomeLayoutConfig {
|
|
37
|
+
width: number;
|
|
38
|
+
padding: number;
|
|
39
|
+
columnGap: number;
|
|
40
|
+
breakpoint: number;
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WelcomeWidget, WidgetRenderContext } from "../types.ts";
|
|
2
|
+
import { renderSparkline } from "../../tools/graph.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Graph widget that renders a mini token sparkline and telemetry activity.
|
|
6
|
+
*/
|
|
7
|
+
export const graphWidget: WelcomeWidget = {
|
|
8
|
+
id: "graph",
|
|
9
|
+
render(ctx: WidgetRenderContext): string[] {
|
|
10
|
+
const lines: string[] = [];
|
|
11
|
+
const hChar = "─";
|
|
12
|
+
const separator = ` ${ctx.dim(hChar.repeat(Math.max(1, ctx.width - 2)))}`;
|
|
13
|
+
|
|
14
|
+
lines.push(` ${ctx.bold(ctx.color("accent", "Telemetry Sparkline"))}`);
|
|
15
|
+
|
|
16
|
+
// Sample usage trend points
|
|
17
|
+
const sampleData = [1200, 2400, 3100, 2800, 4500, 6200, 8900, 11400, 9800, 12400];
|
|
18
|
+
const sparkline = renderSparkline(sampleData, 12);
|
|
19
|
+
|
|
20
|
+
lines.push(` ${ctx.dim("activity:")} ${ctx.color("gitClean", sparkline)} ${ctx.dim("12.4k tok")}`);
|
|
21
|
+
lines.push(separator);
|
|
22
|
+
|
|
23
|
+
return lines;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WelcomeWidget } from "../types.ts";
|
|
2
|
+
import { SystemWidget } from "./system-widget.ts";
|
|
3
|
+
import { QueueWidget } from "./queue-widget.ts";
|
|
4
|
+
import { ShortcutsWidget } from "./shortcuts-widget.ts";
|
|
5
|
+
import { SessionsWidget } from "./sessions-widget.ts";
|
|
6
|
+
import { graphWidget } from "./graph-widget.ts";
|
|
7
|
+
|
|
8
|
+
export { SystemWidget } from "./system-widget.ts";
|
|
9
|
+
export { QueueWidget } from "./queue-widget.ts";
|
|
10
|
+
export { ShortcutsWidget } from "./shortcuts-widget.ts";
|
|
11
|
+
export { SessionsWidget } from "./sessions-widget.ts";
|
|
12
|
+
export { graphWidget } from "./graph-widget.ts";
|
|
13
|
+
|
|
14
|
+
export const ALL_WELCOME_WIDGETS: WelcomeWidget[] = [
|
|
15
|
+
SystemWidget,
|
|
16
|
+
graphWidget,
|
|
17
|
+
QueueWidget,
|
|
18
|
+
ShortcutsWidget,
|
|
19
|
+
SessionsWidget,
|
|
20
|
+
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { WelcomeWidget, WidgetRenderContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export const QueueWidget: WelcomeWidget = {
|
|
4
|
+
id: "queue",
|
|
5
|
+
render(ctx: WidgetRenderContext): string[] {
|
|
6
|
+
const { data, dim, color } = ctx;
|
|
7
|
+
const lines: string[] = [];
|
|
8
|
+
|
|
9
|
+
const prefix = dim("- ");
|
|
10
|
+
if (data.queueCount && data.queueCount > 0) {
|
|
11
|
+
lines.push(` ${prefix}${color("gitClean", `${data.queueCount}`)} queued items ready`);
|
|
12
|
+
} else {
|
|
13
|
+
lines.push(` ${prefix}type ${color("model", "# <idea>")} to capture a thought`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (data.hasStash) {
|
|
17
|
+
lines.push(` ${prefix}${color("gitClean", "1")} draft stashed (Alt+S to pop)`);
|
|
18
|
+
} else {
|
|
19
|
+
lines.push(` ${prefix}press ${color("model", "alt+s")} to park a draft`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
lines.push(` ${prefix}${dim("dreaming & mission queue ready")}`);
|
|
23
|
+
|
|
24
|
+
return lines;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WelcomeWidget, WidgetRenderContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export const SessionsWidget: WelcomeWidget = {
|
|
4
|
+
id: "sessions",
|
|
5
|
+
render(ctx: WidgetRenderContext): string[] {
|
|
6
|
+
const { data, dim, bold, color } = ctx;
|
|
7
|
+
const lines: string[] = [];
|
|
8
|
+
|
|
9
|
+
lines.push(` ${bold(color("accent", "Recent Crafts"))}`);
|
|
10
|
+
|
|
11
|
+
if (data.recentSessions.length === 0) {
|
|
12
|
+
lines.push(` ${dim("No recent sessions")}`);
|
|
13
|
+
} else {
|
|
14
|
+
for (const session of data.recentSessions.slice(0, 3)) {
|
|
15
|
+
lines.push(
|
|
16
|
+
` ${dim("• ")}${color("path", session.name)}${dim(` (${session.timeAgo})`)}`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WelcomeWidget, WidgetRenderContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export const ShortcutsWidget: WelcomeWidget = {
|
|
4
|
+
id: "shortcuts",
|
|
5
|
+
render(ctx: WidgetRenderContext): string[] {
|
|
6
|
+
const { dim, bold, color } = ctx;
|
|
7
|
+
const lines: string[] = [];
|
|
8
|
+
|
|
9
|
+
lines.push(` ${bold(color("accent", "Quick Launch / Tactical"))}`);
|
|
10
|
+
lines.push(` ${dim("# <idea> ")} capture idea to queue`);
|
|
11
|
+
lines.push(` ${dim("alt+p ")} tactical powerline overlay`);
|
|
12
|
+
lines.push(` ${dim("!cmd ")} sticky bash session`);
|
|
13
|
+
lines.push(` ${dim("alt+s ")} stash/pop prompt draft`);
|
|
14
|
+
|
|
15
|
+
return lines;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { formatTokens } from "../format.ts";
|
|
2
|
+
import type { WelcomeWidget, WidgetRenderContext } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export const SystemWidget: WelcomeWidget = {
|
|
5
|
+
id: "system",
|
|
6
|
+
render(ctx: WidgetRenderContext): string[] {
|
|
7
|
+
const { data, dim, bold, color } = ctx;
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
|
|
10
|
+
const prefix = dim("- ");
|
|
11
|
+
lines.push(` ${bold(color("accent", "Active Horizon"))}`);
|
|
12
|
+
|
|
13
|
+
lines.push(` ${prefix}Model: ${color("model", data.modelName)} (${dim(data.providerName)})`);
|
|
14
|
+
|
|
15
|
+
if (data.initialContextTokens !== null && data.initialContextTokens > 0) {
|
|
16
|
+
lines.push(
|
|
17
|
+
` ${prefix}${color("gitClean", `≈ ${formatTokens(data.initialContextTokens)}`)} initial prompt tokens`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { extensions, skills } = data.loadedCounts;
|
|
22
|
+
const toolsCount = extensions + skills;
|
|
23
|
+
if (toolsCount > 0) {
|
|
24
|
+
lines.push(` ${prefix}${color("gitClean", `${toolsCount}`)} skills/extensions loaded`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// generate.ts
|
|
2
|
+
// CLI-arg parsing and batch generation for `/vibe generate`.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
BATCH_PROMPT,
|
|
6
|
+
getVibeFilePath,
|
|
7
|
+
saveVibesToFile,
|
|
8
|
+
vibeState,
|
|
9
|
+
} from "./storage.ts";
|
|
10
|
+
import { buildAiContext, completeVibe } from "./provider.ts";
|
|
11
|
+
|
|
12
|
+
export type GenerateVibesResult =
|
|
13
|
+
| { success: true; count: number; filePath: string }
|
|
14
|
+
| { success: false; count: 0; filePath: string; error: string };
|
|
15
|
+
|
|
16
|
+
export function parseVibeGenerateArgs(
|
|
17
|
+
args: readonly string[],
|
|
18
|
+
): { theme: string; count: number } | null {
|
|
19
|
+
if (args.length === 0) return null;
|
|
20
|
+
|
|
21
|
+
const last = args.at(-1);
|
|
22
|
+
const parsedCount =
|
|
23
|
+
last && /^\d+$/.test(last) ? Number.parseInt(last, 10) : Number.NaN;
|
|
24
|
+
const hasCount = Number.isFinite(parsedCount) && args.length > 1;
|
|
25
|
+
const theme = hasCount ? args.slice(0, -1).join(" ") : args.join(" ");
|
|
26
|
+
if (!theme) return null;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
theme,
|
|
30
|
+
count: hasCount ? Math.min(Math.max(Math.floor(parsedCount), 1), 500) : 100,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function generateVibesBatch(
|
|
35
|
+
theme: string,
|
|
36
|
+
count: number = 100,
|
|
37
|
+
): Promise<GenerateVibesResult> {
|
|
38
|
+
const filePath = getVibeFilePath(theme);
|
|
39
|
+
const safeCount = Number.isFinite(count)
|
|
40
|
+
? Math.min(Math.max(Math.floor(count), 1), 500)
|
|
41
|
+
: 100;
|
|
42
|
+
|
|
43
|
+
if (!vibeState.extensionCtx) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
count: 0,
|
|
47
|
+
filePath,
|
|
48
|
+
error: "Extension not initialized",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse model spec
|
|
53
|
+
const slashIndex = vibeState.config.modelSpec.indexOf("/");
|
|
54
|
+
if (slashIndex === -1) {
|
|
55
|
+
return { success: false, count: 0, filePath, error: "Invalid model spec" };
|
|
56
|
+
}
|
|
57
|
+
const provider = vibeState.config.modelSpec.slice(0, slashIndex);
|
|
58
|
+
const modelId = vibeState.config.modelSpec.slice(slashIndex + 1);
|
|
59
|
+
|
|
60
|
+
// Resolve model
|
|
61
|
+
const model = vibeState.extensionCtx.modelRegistry.find(provider, modelId);
|
|
62
|
+
if (!model) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
count: 0,
|
|
66
|
+
filePath,
|
|
67
|
+
error: `Model not found: ${vibeState.config.modelSpec}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get auth
|
|
72
|
+
const auth =
|
|
73
|
+
await vibeState.extensionCtx.modelRegistry.getApiKeyAndHeaders(model);
|
|
74
|
+
if (!auth.ok) {
|
|
75
|
+
return { success: false, count: 0, filePath, error: auth.error };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Build batch prompt
|
|
79
|
+
const prompt = BATCH_PROMPT.replace(/\{theme\}/g, theme).replace(
|
|
80
|
+
/\{count\}/g,
|
|
81
|
+
String(safeCount),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const aiContext = buildAiContext(prompt);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Use longer timeout for batch generation (30 seconds)
|
|
88
|
+
const signal = AbortSignal.timeout(30000);
|
|
89
|
+
const response = await completeVibe(provider, model, aiContext, {
|
|
90
|
+
apiKey: auth.apiKey,
|
|
91
|
+
headers: auth.headers,
|
|
92
|
+
env: auth.env,
|
|
93
|
+
signal,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
97
|
+
if (!textContent?.text) {
|
|
98
|
+
const error =
|
|
99
|
+
response.stopReason === "error" && response.errorMessage
|
|
100
|
+
? response.errorMessage
|
|
101
|
+
: "Empty response from model";
|
|
102
|
+
return { success: false, count: 0, filePath, error };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse response: one vibe per line
|
|
106
|
+
const vibes = textContent.text
|
|
107
|
+
.split("\n")
|
|
108
|
+
.map((line) => line.trim())
|
|
109
|
+
.filter((line) => line.length > 0)
|
|
110
|
+
.map((line) => {
|
|
111
|
+
// Clean up each line
|
|
112
|
+
let vibe = line.replace(/^["'\d.\-)\s]+/, "").trim(); // Remove leading quotes, numbers, bullets
|
|
113
|
+
vibe = vibe.replace(/["']$/g, ""); // Remove trailing quotes
|
|
114
|
+
if (!vibe.endsWith("...")) {
|
|
115
|
+
vibe = vibe.replace(/\.+$/, "") + "...";
|
|
116
|
+
}
|
|
117
|
+
return vibe;
|
|
118
|
+
})
|
|
119
|
+
.filter((vibe) => vibe.length > 3 && vibe !== "..."); // Filter invalid
|
|
120
|
+
|
|
121
|
+
if (vibes.length === 0) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
count: 0,
|
|
125
|
+
filePath,
|
|
126
|
+
error: "No valid vibes generated",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Save to file
|
|
131
|
+
saveVibesToFile(theme, vibes);
|
|
132
|
+
|
|
133
|
+
// Clear cache so next use loads fresh
|
|
134
|
+
if (vibeState.vibeCacheTheme === theme) {
|
|
135
|
+
vibeState.vibeCache = [];
|
|
136
|
+
vibeState.vibeCacheTheme = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { success: true, count: vibes.length, filePath };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
142
|
+
return { success: false, count: 0, filePath, error: message };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
// Barrel re-exporting the public API for working-vibes
|
|
3
|
+
// (AI-generated contextual working messages that match a user's preferred theme/vibe).
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
initVibeManager,
|
|
7
|
+
getVibeTheme,
|
|
8
|
+
setVibeTheme,
|
|
9
|
+
getVibeModel,
|
|
10
|
+
setVibeModel,
|
|
11
|
+
onVibeBeforeAgentStart,
|
|
12
|
+
onVibeAgentStart,
|
|
13
|
+
onVibeToolCall,
|
|
14
|
+
onVibeAgentEnd,
|
|
15
|
+
getVibeMode,
|
|
16
|
+
setVibeMode,
|
|
17
|
+
hasVibeFile,
|
|
18
|
+
getVibeFileCount,
|
|
19
|
+
} from "./manager.ts";
|
|
20
|
+
|
|
21
|
+
export type { VibeMode } from "./storage.ts";
|
|
22
|
+
|
|
23
|
+
export type { GenerateVibesResult } from "./generate.ts";
|
|
24
|
+
export { parseVibeGenerateArgs, generateVibesBatch } from "./generate.ts";
|