Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
2031ea6
feat(server): list the skills each enabled agent can use
n0mahd Oct 6, 2026
af8cc98
feat(web): add a Skills page to Settings
n0mahd Oct 6, 2026
ae6504f
docs: describe the Skills settings page
n0mahd Oct 6, 2026
7fab517
fix(server): read skills under the filesystem read scope
n0mahd Oct 9, 2026
56d86d2
fix(server): read a project's skills only for a registered project
n0mahd Oct 9, 2026
8079c18
fix(server): show a skill that Claude's settings switch off as not us…
n0mahd Oct 9, 2026
417975a
fix(web): don't show another environment's skills for an offline one
n0mahd Oct 9, 2026
bada129
fix(web): ignore a skills refresh that finishes after the page is gone
n0mahd Oct 9, 2026
392047c
feat(server): turn skills on or off for each agent by linking
n0mahd Oct 6, 2026
45c5167
feat(web): turn skills on or off for each agent in Settings
n0mahd Oct 6, 2026
388eb11
docs: describe turning skills on or off for an agent
n0mahd Oct 6, 2026
b5b2d9c
feat(server): move and delete skills, and refresh the $ picker after …
n0mahd Oct 6, 2026
340d3e4
feat(web): move and delete skills in Settings
n0mahd Oct 6, 2026
a934f9f
docs: describe moving and deleting skills
n0mahd Oct 6, 2026
473bc7a
feat(server): turn a skill on for all agents, or name an agent by its…
n0mahd Oct 6, 2026
0ce7224
feat(server): let agents list and turn skills on or off through MCP t…
n0mahd Oct 6, 2026
47539b1
feat(contracts): describe agent skill settings, groups and placing sk…
n0mahd Oct 8, 2026
9797070
refactor(server): share Claude's skillOverrides layers and keep file …
n0mahd Oct 8, 2026
598315d
feat(server): let Codex instances write a skill's setting through the…
n0mahd Oct 8, 2026
1c4c26d
feat(server): switch skills off through each agent's own settings
n0mahd Oct 8, 2026
966e4a2
feat(server): read and move the skills CLI's records of where skills …
n0mahd Oct 8, 2026
8e16e2e
feat(server): list skills kept in the library and the projects that u…
n0mahd Oct 8, 2026
30d7e9b
feat(server): use a Global skill in only some projects
n0mahd Oct 8, 2026
4710a40
feat(server): link a project's library skills into its new worktrees
n0mahd Oct 8, 2026
a692641
feat(web): one switch per skill, with each agent's switch a click away
n0mahd Oct 8, 2026
416d554
feat(web): group skills by where they came from, and select several t…
n0mahd Oct 8, 2026
34f2096
feat(web): choose where a skill is used with Use in…
n0mahd Oct 8, 2026
2e36032
docs: describe the skill switches, groups, selecting and Use in…
n0mahd Oct 8, 2026
92d616c
fix(server): switch the agents of a skill used in only some projects …
n0mahd Oct 8, 2026
a1671f6
fix(server): keep a moved skill's Codex setting, and say when its sou…
n0mahd Oct 8, 2026
7f45927
fix(server): keep Claude's local settings file out of git when T3 Cod…
n0mahd Oct 8, 2026
382329f
feat(web): say when a moved skill won't update from its source any more
n0mahd Oct 8, 2026
f230ce4
fix(web): change more than 200 skills at a time
n0mahd Oct 8, 2026
fa81b5d
docs: describe agents' own settings and skills used in only some proj…
n0mahd Oct 8, 2026
ac95182
fix(web): section and group switches follow their rows
n0mahd Oct 8, 2026
3506fa7
fix(web): tidy skill row panel and group row on phones
n0mahd Oct 8, 2026
a757d5d
fix(server): change skills under the filesystem write scope, and chec…
n0mahd Oct 9, 2026
943158e
fix(server): read a Codex skill's setting from the home Codex runs in
n0mahd Oct 9, 2026
a827543
refactor(server): link library skills into a new worktree from the gi…
n0mahd Oct 9, 2026
3f607f8
fix(server): link a library skill into a worktree at the project's ow…
n0mahd Oct 9, 2026
9bb589e
docs: describe the skill switches by what they do, not by what is on …
n0mahd Oct 9, 2026
a5b1a96
fix(server): refuse a skill change in a folder that is gone instead o…
n0mahd Oct 9, 2026
19e81de
fix(server): link a library skill into a worktree by its absolute path
n0mahd Oct 10, 2026
b5814df
fix(server): leave an unfinished exclude block as it is
n0mahd Oct 10, 2026
d5953ec
feat(contracts): describe agent instruction files and their RPCs
n0mahd Oct 8, 2026
a7a838d
feat(server): record which instruction files each agent reads
n0mahd Oct 8, 2026
d5b969b
refactor(server): share the agent config folder lookup and git tracki…
n0mahd Oct 8, 2026
ecac0a8
feat(server): edit instruction files and choose which agents read them
n0mahd Oct 8, 2026
79be4fa
feat(server): let agents list and turn instruction files on or off th…
n0mahd Oct 8, 2026
f8ae0ec
feat(client-runtime): add commands for the agent instruction RPCs
n0mahd Oct 8, 2026
3212287
refactor(web): share the skill page's Escape handling, path copy, Mar…
n0mahd Oct 8, 2026
99d8194
feat(web): see and edit agent instructions on the Skills page
n0mahd Oct 8, 2026
170426e
docs: describe the Instructions section of the Skills page
n0mahd Oct 8, 2026
a0d23b5
fix(web): tidy the Global instructions panel and its wording
n0mahd Oct 8, 2026
acf983f
feat(web): group instructions under Project and Global, and move CLAU…
n0mahd Oct 9, 2026
5a44046
fix(server): read and change instruction files under the filesystem s…
n0mahd Oct 9, 2026
7593704
fix(server): read instruction files only under a registered project's…
n0mahd Oct 9, 2026
2879ff1
fix(server): keep a byte order mark in an instruction file's text
n0mahd Oct 9, 2026
917d574
fix(server): keep the cause of a failed instruction change and say it…
n0mahd Oct 9, 2026
6ff78d9
docs: describe the instruction files by what they do, not by what is …
n0mahd Oct 9, 2026
cb9096e
docs: name every file that makes Claude skip a project's AGENTS.md
n0mahd Oct 10, 2026
27de753
feat(client-runtime): share the skills and instructions logic with ev…
n0mahd Oct 10, 2026
99f087c
feat(server): move instruction files between a project and Global
n0mahd Oct 10, 2026
78d2076
feat(web): move an instruction file to Global, or copy Global to a pr…
n0mahd Oct 10, 2026
c6076a2
docs: say how instruction files move between a project and Global
n0mahd Oct 10, 2026
5647afa
feat(server): make a new skill from a name and a description
n0mahd Oct 10, 2026
84a3988
feat(server): let agents create a skill through MCP
n0mahd Oct 10, 2026
a7f182a
feat(server): let agents move and delete skills after seeing the plan
n0mahd Oct 10, 2026
4988e4c
feat(server): let agents edit and create instruction files
n0mahd Oct 10, 2026
dfb2c8b
feat(client-runtime): label skill moves, deletions and instruction edits
n0mahd Oct 10, 2026
c341134
feat(server): move a skill out of an agent's own folder into the shar…
n0mahd Oct 10, 2026
fb9703e
docs: agents can move and delete skills and edit instruction files
n0mahd Oct 10, 2026
fd3d200
feat(web): tidy up a project's skills in one pass
n0mahd Oct 10, 2026
9d2463b
docs: describe Tidy up in the skills guide
n0mahd Oct 10, 2026
2ef471a
fix(client-runtime): sort skill groups in a way the phone can run
n0mahd Oct 10, 2026
ccfa6a7
feat(mobile): share one skills state across the phone's Skills screens
n0mahd Oct 10, 2026
cfe40ba
feat(mobile): manage skills and instruction files from Settings
n0mahd Oct 10, 2026
f716abd
docs: say what the phone app can do with skills
n0mahd Oct 10, 2026
4bfb827
fix(server): name the right skills when git can undo only some of a d…
n0mahd Oct 10, 2026
ed64f5d
feat(web): create a skill from the Skills page
n0mahd Oct 10, 2026
05f0497
docs: say how to create a skill
n0mahd Oct 10, 2026
5f5f41f
refactor(server): catch the file errors of a new skill by tag
n0mahd Oct 10, 2026
d42ddad
feat(server): list the skills that come with Codex and Claude plugins
n0mahd Oct 10, 2026
27fd8b6
feat(server): never move, delete or link a skill that comes with an a…
n0mahd Oct 10, 2026
3397d58
feat(web): show built-in and plugin skills apart on the Skills page
n0mahd Oct 10, 2026
5683220
feat(web): reveal an open skill or instruction file in the file manager
n0mahd Oct 10, 2026
69b5811
docs: built-in and plugin skills, and revealing a skill's files
n0mahd Oct 10, 2026
93e67da
feat(contracts): describe checking skills against their source and up…
n0mahd Oct 10, 2026
a12e336
feat(server): read a skill's GitHub source by tree and blob SHA
n0mahd Oct 10, 2026
bf2af22
feat(server): tell a source's changes from yours and update skills wi…
n0mahd Oct 10, 2026
609df37
feat(server): serve skill updates in their own RPC group
n0mahd Oct 10, 2026
f51059a
feat(client-runtime): add commands to check and update skills
n0mahd Oct 10, 2026
65b069a
feat(web): check skills for updates and preview each one before updating
n0mahd Oct 10, 2026
138f2a2
docs: explain updating skills from their source
n0mahd Oct 10, 2026
fb680f8
fix(server): count instruction moves in the RPC metrics
n0mahd Oct 11, 2026
18e0292
fix: test skill updates with the repo's test runner and keep gitTreeS…
n0mahd Oct 11, 2026
2d585ed
fix(client-runtime): keep entryFileName private to the instructions l…
n0mahd Oct 11, 2026
461d1ee
Merge branch 'feat/skills-builtin' into feat/skills-roadmap
n0mahd Oct 11, 2026
238e597
Merge branch 'feat/skills-create' into feat/skills-roadmap
n0mahd Oct 11, 2026
6840cf7
Merge branch 'feat/instructions-move' into feat/skills-roadmap
n0mahd Oct 11, 2026
96da5ec
Merge branch 'feat/skills-agent-tools' into feat/skills-roadmap
n0mahd Oct 11, 2026
04f7df3
Merge branch 'feat/skills-tidy' into feat/skills-roadmap
n0mahd Oct 11, 2026
cb340c3
Merge branch 'feat/skills-updates' into feat/skills-roadmap
n0mahd Oct 11, 2026
684066d
Merge branch 'feat/skills-mobile' into feat/skills-roadmap
n0mahd Oct 11, 2026
c05ed9c
fix(server): agents' skill move and delete plans leave built-in skill…
n0mahd Oct 11, 2026
4e8b9a9
fix(web): Tidy up leaves a project's plugin skills alone
n0mahd Oct 11, 2026
afeadca
fix(mobile): leave built-in skills off the phone and word a refusal f…
n0mahd Oct 11, 2026
43fbfba
docs: say which skills features are web and desktop only
n0mahd Oct 11, 2026
eeef4bc
fix(client-runtime): word the refusal to move an instruction file ont…
n0mahd Oct 11, 2026
72f7831
test(web): word a placement plan's change without the update kind
n0mahd Oct 11, 2026
56d3640
test(server): import the plans test's runner from vite-plus
n0mahd Oct 11, 2026
dc671ff
fix(web): say "it" when Tidy up moves one skill out of an agent's folder
n0mahd Oct 11, 2026
38fb9a7
fix(web): don't offer Tidy up again for what a Tidy up kept
n0mahd Oct 11, 2026
f790f5d
fix(server): import jsonc-parser's ESM build so the bundled server st…
n0mahd Oct 11, 2026
6cf86a7
Merge branch 'feat/skills-manage' into feat/skills-roadmap
n0mahd Oct 11, 2026
7d0a6a3
fix(server): bundle jsonc-parser's ESM build and import the package a…
n0mahd Oct 11, 2026
638b1cf
Merge branch 'feat/skills-manage' into feat/skills-roadmap
n0mahd Oct 11, 2026
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
28 changes: 27 additions & 1 deletion apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ import {
ScheduledTaskBranchPickerRouteScreen,
} from "./features/settings/ScheduledTaskPickerScreens";
import { ScheduledTaskEditorProvider } from "./features/settings/scheduled-task-editor";
import { SettingsInstructionRouteScreen } from "./features/settings/skills/SettingsInstructionRouteScreen";
import { SettingsSkillRouteScreen } from "./features/settings/skills/SettingsSkillRouteScreen";
import { SettingsSkillsRouteScreen } from "./features/settings/skills/SettingsSkillsRouteScreen";
import { SettingsSkillUseInRouteScreen } from "./features/settings/skills/SettingsSkillUseInRouteScreen";
import { SkillsSettingsProvider } from "./features/settings/skills/skills-settings";
import { SettingsKeyboardRouteScreen } from "./features/settings/SettingsKeyboardRouteScreen";
import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen";
import {
Expand Down Expand Up @@ -238,6 +243,25 @@ const SettingsContentStack = createV5SheetStackNavigator({
linking: "agent-behavior",
options: { title: "Agent behavior" },
}),
SettingsSkills: createNativeStackScreen({
screen: SettingsSkillsRouteScreen,
linking: "skills",
options: { title: "Skills" },
}),
// The open skill, file and Use in… screens read the list the Skills screen loaded, so they
// have no `linking:` path of their own.
SettingsSkill: createNativeStackScreen({
screen: SettingsSkillRouteScreen,
options: { title: "Skill" },
}),
SettingsSkillUseIn: createNativeStackScreen({
screen: SettingsSkillUseInRouteScreen,
options: { title: "Use in…" },
}),
SettingsInstruction: createNativeStackScreen({
screen: SettingsInstructionRouteScreen,
options: { title: "Instructions" },
}),
SettingsProviderAccounts: createNativeStackScreen({
screen: SettingsProviderAccountsRouteScreen,
linking: "provider-accounts",
Expand Down Expand Up @@ -418,7 +442,9 @@ const SettingsSheetStack = createV5SheetStackNavigator({
linking: "",
layout: ({ children }) => (
<SettingsEnvironmentFilterProvider>
<ScheduledTaskEditorProvider>{children}</ScheduledTaskEditorProvider>
<ScheduledTaskEditorProvider>
<SkillsSettingsProvider>{children}</SkillsSettingsProvider>
</ScheduledTaskEditorProvider>
</SettingsEnvironmentFilterProvider>
),
}),
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/components/AppSymbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import IconArrowsDiagonal2 from "@tabler/icons-react-native/IconArrowsDiagonal2"
import IconArrowsMinimize from "@tabler/icons-react-native/IconArrowsMinimize";
import IconBellRinging from "@tabler/icons-react-native/IconBellRinging";
import IconBolt from "@tabler/icons-react-native/IconBolt";
import IconBook from "@tabler/icons-react-native/IconBook";
import IconBox from "@tabler/icons-react-native/IconBox";
import IconBrain from "@tabler/icons-react-native/IconBrain";
import IconCamera from "@tabler/icons-react-native/IconCamera";
Expand Down Expand Up @@ -145,6 +146,7 @@ const ANDROID_ICON_BY_SF_SYMBOL = {
"bell.badge": IconBellRinging,
"bolt.circle": IconBolt,
"bolt.horizontal.circle": IconBolt,
book: IconBook,
brain: IconBrain,
camera: IconCamera,
"chart.bar.xaxis": IconChartBar,
Expand Down Expand Up @@ -213,6 +215,7 @@ const ANDROID_ICON_BY_SF_SYMBOL = {
"sidebar.left": IconLayoutSidebar,
"sidebar.right": IconLayoutSidebarRight,
"slider.horizontal.3": IconAdjustmentsHorizontal,
sparkles: IconSparkles,
"square.and.pencil": IconEdit,
"square.on.square": IconCopy,
"square.grid.2x2": IconApps,
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile/src/features/files/FileMarkdownPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ interface MarkdownPreviewStyles {
readonly nativeTextStyle: NativeMarkdownTextStyle;
}

function useMarkdownPreviewStyles(renderImage?: MarkdownImageRenderer): MarkdownPreviewStyles {
export function useMarkdownPreviewStyles(
renderImage?: MarkdownImageRenderer,
): MarkdownPreviewStyles {
const { appearance } = useAppearancePreferences();
const markdownFontSizes = useMemo(
() => resolveMarkdownFontSizes(appearance.baseFontSize),
Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/src/features/settings/SettingsRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ function SettingsIndexSections() {
target="SettingsEnvironmentSourceControl"
disabled={noServerTargets}
/>
<SettingsRow
icon="book"
label="Skills"
target="SettingsSkills"
disabled={noServerTargets}
/>
<SettingsRow
icon="text.alignleft"
label="Agent behavior"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type SettingsSheetTarget =
| "SettingsEnvironmentAgentBehavior"
| "SettingsEnvironmentMaintenance"
| "SettingsProviderAccounts"
| "SettingsSkills"
| "SettingsKeyboard"
| "SettingsFollowUp"
| "SettingsScheduledTasks"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import type { StaticScreenProps } from "@react-navigation/native";
import {
findInstructionRow,
instructionChips,
type InstructionData,
type InstructionRow,
} from "@t3tools/client-runtime/skills/instructions";
import type { EnvironmentId, InstructionReadResult } from "@t3tools/contracts";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AppText as Text } from "../../../components/AppText";
import { ScreenScrollView as ScrollView } from "../../../components/ScreenScrollView";
import { copyTextWithHaptic } from "../../../lib/copyTextWithHaptic";
import { serverEnvironment } from "../../../state/server";
import { useAtomCommand } from "../../../state/use-atom-command";
import { SettingsActionRow } from "../components/SettingsActionRow";
import { SettingsScreen } from "../components/SettingsScreen";
import { SettingsSection } from "../components/SettingsSection";
import {
SkillAgentSwitchRow,
SkillsMarkdown,
SkillsNotice,
SkillsPillButton,
} from "./skills-components";
import { useSkillsSettings } from "./skills-settings";

type Props = StaticScreenProps<{ readonly id: string }>;

/** An instruction file, read-only, with the agents that read it. */
export function SettingsInstructionRouteScreen({ route }: Props) {
const insets = useSafeAreaInsets();
const settings = useSkillsSettings();
const { instructions, instructionsCtx } = settings;
const row = useMemo(
() =>
instructions ? findInstructionRow(instructions, instructionsCtx, route.params.id) : null,
[instructions, instructionsCtx, route.params.id],
);
const environmentId = settings.environment?.environmentId ?? null;
return (
<SettingsScreen title={row?.heading ?? "Instructions"}>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
showsVerticalScrollIndicator={false}
className="flex-1"
contentContainerClassName="gap-5 px-5 pt-4"
contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }}
>
{settings.notice ? (
<SkillsNotice text={settings.notice} onDismiss={settings.dismissNotice} />
) : null}
{row && instructions && environmentId !== null ? (
<InstructionDetail
key={row.id}
row={row}
data={instructions}
environmentId={environmentId}
cwd={settings.cwd}
/>
) : (
<Text className="px-2 text-base text-foreground-muted">
This file isn't there any more.
</Text>
)}
</ScrollView>
</SettingsScreen>
);
}

type LoadState =
| { readonly status: "loading" }
| { readonly status: "ready"; readonly result: InstructionReadResult }
| { readonly status: "error" };

function InstructionDetail(props: {
readonly row: InstructionRow;
readonly data: InstructionData;
readonly environmentId: EnvironmentId;
readonly cwd: string | null;
}) {
const { row, data, environmentId, cwd } = props;
const { entry } = row;
const settings = useSkillsSettings();
const ctx = settings.instructionsCtx;
const locked = settings.busy || !settings.canChangeInstructions;
const readInstruction = useAtomCommand(serverEnvironment.readInstruction, {
reportFailure: false,
});
const [load, setLoad] = useState<LoadState>({ status: "loading" });
const id = entry.id;
const chips = useMemo(() => instructionChips(entry, ctx, data), [entry, ctx, data]);

const read = useCallback(
(): Promise<LoadState> =>
readInstruction({ environmentId, input: { id, ...(cwd ? { cwd } : {}) } }).then(
(result) =>
result._tag === "Success"
? { status: "ready", result: result.value }
: { status: "error" },
() => ({ status: "error" }),
),
[readInstruction, environmentId, id, cwd],
);
useEffect(() => {
let cancelled = false;
void read().then((next) => {
if (!cancelled) setLoad(next);
});
return () => {
cancelled = true;
};
}, [read]);

return (
<>
{row.headingNote !== "" ? (
<Text className="px-2 text-sm text-foreground-muted">{row.headingNote}</Text>
) : null}

{entry.exists && chips.length > 0 ? (
<SettingsSection title="Used by">
{chips.map((chip, index) => (
<SkillAgentSwitchRow
key={chip.agent.instanceId}
agent={chip.agent}
agents={ctx.installed}
on={chip.on}
blocker={chip.locked ? chip.lines.join(" ") : null}
disabled={locked || chip.plan === null}
separated={index > 0}
onToggle={() => {
if (chip.plan) settings.runInstructionPlan(chip.plan);
}}
/>
))}
</SettingsSection>
) : null}

<SettingsSection>
<View className="p-4">
{load.status === "loading" ? (
<ActivityIndicator accessibilityLabel="Loading the file" />
) : load.status === "error" ? (
<View className="items-start gap-3">
<Text className="text-base text-warning-foreground">The file couldn't be read.</Text>
<SkillsPillButton
label="Try again"
onPress={() => {
setLoad({ status: "loading" });
void read().then(setLoad);
}}
/>
</View>
) : load.result.tooLarge ? (
<Text className="text-base text-warning-foreground">
This file is too large to show here.
</Text>
) : (load.result.contents ?? "") === "" ? (
<Text className="text-base text-foreground-muted">This file is empty.</Text>
) : (
<SkillsMarkdown text={load.result.contents ?? ""} />
)}
</View>
</SettingsSection>

<SettingsSection>
<SettingsActionRow
icon="doc.on.doc"
label="Copy path"
onPress={() => copyTextWithHaptic(entry.path, { target: "instruction path" })}
/>
</SettingsSection>
</>
);
}
Loading
Loading