@fui-org/fui-cli 2.0.0 → 2.1.1
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 +2 -0
- package/dist/{fui-bmg5pnmq.js → fui-kchph3mb.js} +132 -132
- package/dist/fui.js +1 -1
- package/package.json +1 -1
- package/skills/fui-target/.claude-plugin/plugin.json +6 -0
- package/skills/fui-target/hooks/hooks.json +1 -0
- package/skills/fui-target/hooks/register.tsx +127 -0
- package/skills/fui-target/tsconfig.json +18 -0
- package/skills/fui-target/types/index.d.ts +13 -0
package/dist/fui.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-
|
|
2
|
+
import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-kchph3mb.js");await e();
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "modules": ["./register.tsx"] }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { atom, read, update } from "claude-code";
|
|
2
|
+
import type { EngineInterface as Engine, Register } from "claude-code";
|
|
3
|
+
|
|
4
|
+
import type { FuiTarget } from "../types";
|
|
5
|
+
|
|
6
|
+
type SessionTarget = { projectId?: string; moduleId?: string; moduleDir?: string; workspaceRoot?: string };
|
|
7
|
+
|
|
8
|
+
const target = atom({ plugin: "fui-target", key: "target" } as const, null);
|
|
9
|
+
|
|
10
|
+
// fui must be the program a shell segment runs, so `grep fui src` or `cd fui-cli` never count.
|
|
11
|
+
const FUI_CALL = /(?:^|[;&|(]|\bthen|\bdo)\s*(?:[A-Za-z_]\w*=\S*\s+)*(?:bunx\s+)?fui\s+([a-z-]+)(?:\s+([a-z-]+))?/m;
|
|
12
|
+
|
|
13
|
+
// Mirrors sessionFile() in src/core/session/state.ts.
|
|
14
|
+
async function sessionFile($: Engine, command: string) {
|
|
15
|
+
const key = command.match(/FUI_SESSION=([A-Za-z0-9_-]+)/)?.[1] ?? (await $.env.get("FUI_SESSION")) ?? "default";
|
|
16
|
+
const dir = (await $.env.get("FUI_SESSION_DIR")) ?? `${await $.env.get("HOME")}/.fui/session`;
|
|
17
|
+
return `${dir}/cli-${key}.json`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readJson($: Engine, path: string): Promise<unknown> {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(await $.fs.read(path));
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function httpsUrl(raw: unknown): string | undefined {
|
|
29
|
+
if (typeof raw !== "string" || !raw.trim()) return undefined;
|
|
30
|
+
const text = raw.trim();
|
|
31
|
+
try {
|
|
32
|
+
return new URL(text.startsWith("//") ? `https:${text}` : /^https?:\/\//i.test(text) ? text : `https://${text}`)
|
|
33
|
+
.href;
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// pDomain is a host, or on multi-domain projects a JSON array of hosts kept as a string (see parseDomainField).
|
|
40
|
+
function firstDomain(raw: unknown): string | undefined {
|
|
41
|
+
if (typeof raw !== "string") return undefined;
|
|
42
|
+
const text = raw.trim();
|
|
43
|
+
if (!text.startsWith("[")) return text || undefined;
|
|
44
|
+
try {
|
|
45
|
+
const hosts: unknown = JSON.parse(text);
|
|
46
|
+
return Array.isArray(hosts) && typeof hosts[0] === "string" ? hosts[0].trim() : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function moduleUrl($: Engine, session: SessionTarget) {
|
|
53
|
+
const info = (await readJson($, `${session.moduleDir}/_moduleInfo.json`)) as { url?: unknown } | undefined;
|
|
54
|
+
if (info?.url) return httpsUrl(info.url);
|
|
55
|
+
const list = await readJson($, `${session.workspaceRoot}/${session.projectId}/modules/_modules.json`);
|
|
56
|
+
const row = Array.isArray(list) ? list.find((item) => item?.ModuleID === session.moduleId) : undefined;
|
|
57
|
+
return httpsUrl(row?.url);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function projectUrl($: Engine, session: SessionTarget) {
|
|
61
|
+
const info = (await readJson($, `${session.workspaceRoot}/${session.projectId}/_projectInfo.json`)) as
|
|
62
|
+
| { pDomain?: unknown }
|
|
63
|
+
| undefined;
|
|
64
|
+
return httpsUrl(firstDomain(info?.pDomain));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function sync($: Engine, command: string, lastCommand: string) {
|
|
68
|
+
const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
|
|
69
|
+
const session = state?.target;
|
|
70
|
+
const found: FuiTarget = { lastCommand };
|
|
71
|
+
if (session?.projectId) {
|
|
72
|
+
found.project = session.projectId;
|
|
73
|
+
found.module = session.moduleId;
|
|
74
|
+
found.projectUrl = await projectUrl($, session);
|
|
75
|
+
found.moduleUrl = session.moduleId ? await moduleUrl($, session) : undefined;
|
|
76
|
+
}
|
|
77
|
+
await update($, target, () => found);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const register: Register = (on) => {
|
|
81
|
+
on("prompt.submit", async ($, e, next) => {
|
|
82
|
+
await update($, target, () => null);
|
|
83
|
+
return next(e);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
|
|
87
|
+
const result = await next(e);
|
|
88
|
+
const call = e.command.match(FUI_CALL);
|
|
89
|
+
if (!call) return result;
|
|
90
|
+
|
|
91
|
+
await sync($, e.command, [call[1], call[2]].filter(Boolean).join(" "));
|
|
92
|
+
return result;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
|
|
96
|
+
const current: FuiTarget | null = await read($, target);
|
|
97
|
+
if (e.props.hasSurvey || current === null) return next(e);
|
|
98
|
+
|
|
99
|
+
const { Box, Text, Link } = $.ui.resolve(e);
|
|
100
|
+
return (
|
|
101
|
+
<Box flexDirection="row" columnGap={1}>
|
|
102
|
+
<Text>🧩</Text>
|
|
103
|
+
{current.project ? (
|
|
104
|
+
<Text>
|
|
105
|
+
{current.projectUrl ? (
|
|
106
|
+
<Link href={current.projectUrl}>{current.project}</Link>
|
|
107
|
+
) : (
|
|
108
|
+
<Text bold color="cyan">
|
|
109
|
+
{current.project}
|
|
110
|
+
</Text>
|
|
111
|
+
)}
|
|
112
|
+
{current.module && <Text dimColor> › </Text>}
|
|
113
|
+
{current.module &&
|
|
114
|
+
(current.moduleUrl ? (
|
|
115
|
+
<Link href={current.moduleUrl}>{current.module}</Link>
|
|
116
|
+
) : (
|
|
117
|
+
<Text bold>{current.module}</Text>
|
|
118
|
+
))}
|
|
119
|
+
</Text>
|
|
120
|
+
) : (
|
|
121
|
+
<Text dimColor>no active module</Text>
|
|
122
|
+
)}
|
|
123
|
+
{current.lastCommand && <Text dimColor> · fui {current.lastCommand}</Text>}
|
|
124
|
+
</Box>
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Claude Code compiles hooks against its global `h`. Run /plugin-types from the repo root to write .claude/types.
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["es2023"],
|
|
6
|
+
"types": [],
|
|
7
|
+
"module": "esnext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"jsx": "react",
|
|
14
|
+
"jsxFactory": "h",
|
|
15
|
+
"jsxFragmentFactory": "Fragment"
|
|
16
|
+
},
|
|
17
|
+
"include": ["../../.claude/types", "hooks", "types"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type FuiTarget = {
|
|
2
|
+
project?: string;
|
|
3
|
+
module?: string;
|
|
4
|
+
projectUrl?: string;
|
|
5
|
+
moduleUrl?: string;
|
|
6
|
+
lastCommand?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
declare module "claude-code" {
|
|
10
|
+
interface PluginState {
|
|
11
|
+
"fui-target": { target: FuiTarget | null };
|
|
12
|
+
}
|
|
13
|
+
}
|