Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"t3": {
"repo": "pingdotgg/t3code",
"commit": "f855f016b3fe103f0b6cb05f26de08cb735b1f7b",
"commit": "5ef56853ef1854da50bc1909c6c0a32700f2cb13",
"paths": [
"apps/server/src/mcp/toolkits/attachment/tools.ts",
"apps/server/src/mcp/toolkits/device/tools.ts",
Expand Down
1 change: 1 addition & 0 deletions t3/contract/apps/server/src/mcp/toolkits/thread/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const ThreadOrganizeTool = Tool.make("t3_thread_organize", {
],
})
.annotate(Tool.Title, "Organize a thread")
.annotate(Tool.Strict, true)
.annotate(Tool.Destructive, true);

const commandTool = {
Expand Down
41 changes: 27 additions & 14 deletions t3/contract/apps/server/src/provider/Drivers/ClaudeSkills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as Path from "effect/Path";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as HostProcess from "@t3tools/shared/HostProcess";
import { parseCliArgs } from "@t3tools/shared/cliArgs";
import { tokenizeCliArgs } from "@t3tools/shared/cliArgs";
import { fromLenientJson } from "@t3tools/shared/schemaJson";
import { SKILL_MENTION_PATTERN } from "@t3tools/shared/composerInlineTokens";
import { parse as parseYamlDocument } from "yaml";
Expand Down Expand Up @@ -427,6 +427,26 @@ const resolveClaudeConfigDirPath = Effect.fn("resolveClaudeConfigDirPath")(funct
return path.join(home, ".claude");
});

// Claude's --add-dir is repeatable and variadic; parseCliArgs keeps only one value.
export function claudeLaunchArgDirectories(launchArgs?: string): ReadonlyArray<string> {
const directories: string[] = [];
let collecting = false;
for (const token of tokenizeCliArgs(launchArgs)) {
if (token === "--") break;
if (token === "--add-dir") {
collecting = true;
} else if (token.startsWith("--add-dir=")) {
directories.push(token.slice("--add-dir=".length));
collecting = false;
} else if (token.startsWith("-")) {
collecting = false;
} else if (collecting) {
directories.push(token);
}
}
return directories;
}

/** First name wins, matching Claude Code's precedence of user skills over project skills. */
export const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (
config: Pick<ClaudeSettings, "homePath"> & Partial<Pick<ClaudeSettings, "launchArgs">>,
Expand All @@ -442,22 +462,15 @@ export const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function*
environment ?? process.env,
);

const additionalDirectory = parseCliArgs(config.launchArgs ?? "").flags["add-dir"];
const roots: ReadonlyArray<ClaudeSkillRoot> = [
{ directory: path.join(configDirPath, "skills"), scope: "user" },
...(cwd ? [{ directory: path.join(cwd, ".claude", "skills"), scope: "project" as const }] : []),
...(additionalDirectory && (cwd || path.isAbsolute(additionalDirectory))
? [
{
directory: path.join(
path.resolve(cwd ?? ".", additionalDirectory),
".claude",
"skills",
),
scope: "project" as const,
},
]
: []),
...claudeLaunchArgDirectories(config.launchArgs)
.filter((directory) => directory && (cwd || path.isAbsolute(directory)))
.map((directory) => ({
directory: path.join(path.resolve(cwd ?? ".", directory), ".claude", "skills"),
scope: "project" as const,
})),
...(yield* discoverPluginSkillRoots(
configDirPath,
cwd,
Expand Down
3 changes: 2 additions & 1 deletion t3/contract/packages/shared/src/htmlRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export const HTML_RENDER_THEME_GUIDE = [
export const HTML_RENDER_LAYOUT_GUIDE = [
`Frame width is reply width (${HTML_RENDER_COLUMN_WIDTH}px desktop, about 360px phones), on the thread background.`,
"Leave html, body, and the outer element with no background color, no outer card, border, banner title, or side padding. This wins over any rule that sets a page background.",
"T3 adds an overridable 8px html gutter for selection rings and shadows. Use fluid widths within it.",
"A box that needs its own background gets 16px padding and var(--radius) corners. Give charts fixed pixel heights.",
"Let content set the height. Do not use vh units or height:100% on html or body. Both are rejected or loop.",
].join(" ");
Expand Down Expand Up @@ -353,7 +354,7 @@ export function htmlRenderThemeMessage(theme: HtmlRenderTheme) {
}

const BASE_CSS =
"html{background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;scrollbar-width:none}" +
"html{box-sizing:border-box;padding:8px;background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;scrollbar-width:none}" +
"html::-webkit-scrollbar{display:none}body{margin:0}code,kbd,pre,samp{font-family:var(--font-mono)}" +
// Focus outlines paint inside the element, so the frame edge never clips them.
":where(:focus-visible){outline:2px solid var(--ring)}:focus-visible{outline-width:2px!important;outline-offset:-2px!important}" +
Expand Down
Loading