the-local 0.1.0
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 +55 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +23 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +46 -0
- package/dist/companion.d.ts +2 -0
- package/dist/companion.js +49 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +35 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/installer.d.ts +9 -0
- package/dist/installer.js +29 -0
- package/dist/provider.d.ts +16 -0
- package/dist/provider.js +83 -0
- package/dist/reference.d.ts +1 -0
- package/dist/reference.js +92 -0
- package/dist/scope.d.ts +6 -0
- package/dist/scope.js +4 -0
- package/dist/trigger.d.ts +8 -0
- package/dist/trigger.js +32 -0
- package/package.json +49 -0
- package/the-local/agents/the-local-develop.md +95 -0
- package/the-local/agents/the-local-info.md +95 -0
- package/the-local/agents/the-local-install.md +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# the-local
|
|
2
|
+
|
|
3
|
+
Ship Claude Code **locals** (resident expert subagents) from npm packages into a
|
|
4
|
+
host TS/JS app — the TypeScript port of the [`the_local`](https://github.com/tylercschneider/the_local)
|
|
5
|
+
Ruby gem. A provider package commits its agent `.md` files; a host runs
|
|
6
|
+
`the-local install` to copy the aggregated set from its direct dependencies into
|
|
7
|
+
`.claude/agents/`, plus a delegation rule in `CLAUDE.md`.
|
|
8
|
+
|
|
9
|
+
> Status: early. Core install pipeline + CLI are in place and tested. See the
|
|
10
|
+
> open issues for remaining work (contract doc, npm publish, workspace support).
|
|
11
|
+
|
|
12
|
+
## The model
|
|
13
|
+
|
|
14
|
+
- **Providers commit locals.** A package ships pre-rendered agent files at
|
|
15
|
+
`the-local/agents/<prefix>-<name>.md` and declares them in its `package.json`.
|
|
16
|
+
- **Hosts install verbatim.** `the-local install` reads the host's direct
|
|
17
|
+
`dependencies`, copies each provider's committed `.md` byte-for-byte into
|
|
18
|
+
`.claude/agents/`, and writes the delegation trigger into `CLAUDE.md`.
|
|
19
|
+
- **Direct-dependency scope.** Only the host's direct dependencies contribute
|
|
20
|
+
locals; transitive providers are filtered out.
|
|
21
|
+
- **Cross-language contract.** The agent `.md` format and the `CLAUDE.md`
|
|
22
|
+
`<!-- the_local:begin -->` / `<!-- the_local:end -->` markers are identical to
|
|
23
|
+
the Ruby gem's, so a Ruby gem and a TS package install into the same host
|
|
24
|
+
without clobbering each other. See [`docs/contract.md`](docs/contract.md).
|
|
25
|
+
|
|
26
|
+
## Use (host app)
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx the-local install # or: refresh
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Declare locals (provider package)
|
|
33
|
+
|
|
34
|
+
```jsonc
|
|
35
|
+
// package.json
|
|
36
|
+
{
|
|
37
|
+
"the-local": {
|
|
38
|
+
"prefix": "keystone",
|
|
39
|
+
"scope": "UI — pages, forms, tables",
|
|
40
|
+
"agentsDir": "the-local/agents"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then commit `the-local/agents/keystone-*.md` alongside your code.
|
|
46
|
+
|
|
47
|
+
## Develop
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pnpm install
|
|
51
|
+
pnpm test # vitest
|
|
52
|
+
pnpm typecheck
|
|
53
|
+
pnpm lint
|
|
54
|
+
pnpm build # emits dist/, including the the-local CLI
|
|
55
|
+
```
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Agent {
|
|
2
|
+
prefix: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
tools: string;
|
|
6
|
+
body: string;
|
|
7
|
+
knowledge?: string | string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function qualifiedName(agent: Pick<Agent, "prefix" | "name">): string;
|
|
10
|
+
export declare function agentFilename(agent: Pick<Agent, "prefix" | "name">): string;
|
|
11
|
+
export declare function toMarkdown(agent: Agent): string;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function qualifiedName(agent) {
|
|
2
|
+
return `${agent.prefix}-${agent.name}`;
|
|
3
|
+
}
|
|
4
|
+
export function agentFilename(agent) {
|
|
5
|
+
return `${qualifiedName(agent)}.md`;
|
|
6
|
+
}
|
|
7
|
+
export function toMarkdown(agent) {
|
|
8
|
+
const knowledge = Array.isArray(agent.knowledge)
|
|
9
|
+
? agent.knowledge.join("\n\n")
|
|
10
|
+
: (agent.knowledge ?? "");
|
|
11
|
+
return [
|
|
12
|
+
"---",
|
|
13
|
+
`name: ${qualifiedName(agent)}`,
|
|
14
|
+
`description: ${agent.description}`,
|
|
15
|
+
`tools: ${agent.tools}`,
|
|
16
|
+
"---",
|
|
17
|
+
"",
|
|
18
|
+
agent.body,
|
|
19
|
+
"",
|
|
20
|
+
knowledge,
|
|
21
|
+
"",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { installLocals } from "./installer.js";
|
|
5
|
+
import { buildProvider, scaffoldProvider } from "./provider.js";
|
|
6
|
+
const COMMANDS = new Set(["install", "refresh"]);
|
|
7
|
+
export function run(argv, hostDir) {
|
|
8
|
+
const command = argv[0] ?? "install";
|
|
9
|
+
if (!COMMANDS.has(command)) {
|
|
10
|
+
process.stderr.write(`the-local: unknown command "${command}" (expected install, refresh, provider, or build)\n`);
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
const { providers, agents } = installLocals(hostDir);
|
|
14
|
+
process.stdout.write(`the-local: installed ${agents.length} agent(s) from ${providers.length} provider(s).\n`);
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
// Dispatches every command. The provider-authoring commands run from a package
|
|
18
|
+
// directory; install/refresh fall through to `run`. Async because `build` loads
|
|
19
|
+
// the provider's config by dynamic import.
|
|
20
|
+
export async function main(argv, cwd) {
|
|
21
|
+
const [command, target] = argv;
|
|
22
|
+
if (command === "provider") {
|
|
23
|
+
const { config, created } = scaffoldProvider(target ?? cwd);
|
|
24
|
+
process.stdout.write(created
|
|
25
|
+
? `the-local: scaffolded provider "${config.prefix}" — edit the-local.config.js, then run the-local build.\n`
|
|
26
|
+
: `the-local: the-local.config.js already exists; run the-local build to re-render.\n`);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (command === "build") {
|
|
30
|
+
const written = await buildProvider(target ?? cwd);
|
|
31
|
+
process.stdout.write(`the-local: rendered ${written.length} agent(s).\n`);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
return run(argv, cwd);
|
|
35
|
+
}
|
|
36
|
+
// Node sets `import.meta.url` to the module's real path, but leaves
|
|
37
|
+
// `process.argv[1]` as the symlink npm creates in `node_modules/.bin`. Resolve
|
|
38
|
+
// the symlink before comparing so the CLI still runs when invoked via `npx`.
|
|
39
|
+
export function isEntrypoint(moduleUrl, invokedPath) {
|
|
40
|
+
if (!invokedPath)
|
|
41
|
+
return false;
|
|
42
|
+
return moduleUrl === pathToFileURL(realpathSync(invokedPath)).href;
|
|
43
|
+
}
|
|
44
|
+
if (isEntrypoint(import.meta.url, process.argv[1])) {
|
|
45
|
+
void main(process.argv.slice(2), process.cwd()).then((code) => process.exit(code));
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { reference } from "./reference.js";
|
|
2
|
+
// the-local's own locals — the companion that makes the-local its own provider,
|
|
3
|
+
// dogfooding the same model every other provider uses. Each agent carries the
|
|
4
|
+
// reference verbatim as its knowledge; the rendered files live committed under
|
|
5
|
+
// the-local/agents/ and are what a host installs.
|
|
6
|
+
const PREFIX = "the-local";
|
|
7
|
+
export const companionAgents = [
|
|
8
|
+
{
|
|
9
|
+
prefix: PREFIX,
|
|
10
|
+
name: "info",
|
|
11
|
+
description: "Use to learn how the-local works — providers, the committed-.md install " +
|
|
12
|
+
"model, the delegation trigger, and the direct-dependency scope rule.",
|
|
13
|
+
tools: "Read",
|
|
14
|
+
body: "You explain how the-local works, answering only from the reference: " +
|
|
15
|
+
"providers ship committed locals, `the-local install` copies them verbatim " +
|
|
16
|
+
"into a host's .claude/agents/, the CLAUDE.md delegation trigger makes the " +
|
|
17
|
+
"host delegate, and only direct dependencies contribute. You make no changes.",
|
|
18
|
+
knowledge: reference,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
prefix: PREFIX,
|
|
22
|
+
name: "install",
|
|
23
|
+
description: "Use to add the-local to a host app and set it up correctly.",
|
|
24
|
+
tools: "Bash, Read, Edit",
|
|
25
|
+
body: "You set the-local up in a host package or app, following the reference's " +
|
|
26
|
+
"install section exactly: add the-local to the host's dependencies, install, " +
|
|
27
|
+
"run `the-local install` to sync locals into .claude/agents/ and write the " +
|
|
28
|
+
"delegation trigger, and re-run it after dependency changes. You do not " +
|
|
29
|
+
"invent steps the reference does not list.",
|
|
30
|
+
knowledge: reference,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
prefix: PREFIX,
|
|
34
|
+
name: "develop",
|
|
35
|
+
description: "Use PROACTIVELY to turn a package into a the-local provider — declaring " +
|
|
36
|
+
"the provider block, authoring the locals, and committing the rendered " +
|
|
37
|
+
"agents. MUST BE USED instead of wiring a provider by hand.",
|
|
38
|
+
tools: "Read, Write, Edit, Grep",
|
|
39
|
+
body: "You turn a package into a the-local provider following the reference's " +
|
|
40
|
+
"provider-author workflow: add the `the-local` block to package.json, author " +
|
|
41
|
+
"the standard locals (info, install, and a domain worker) with a guide as " +
|
|
42
|
+
"their knowledge, and render each to the-local/agents/. The deliverable is " +
|
|
43
|
+
"the committed, shipped the-local/agents/*.md — that is the whole contract a " +
|
|
44
|
+
"host reads from disk; a host never loads the package, so unless those files " +
|
|
45
|
+
"are committed and in package.json's files allowlist, the package contributes " +
|
|
46
|
+
"nothing. You keep them in sync with toMarkdown.",
|
|
47
|
+
knowledge: reference,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface DiscoveredProvider {
|
|
2
|
+
packageName: string;
|
|
3
|
+
prefix: string;
|
|
4
|
+
scope: string | null;
|
|
5
|
+
agentFiles: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function directDependencies(hostDir: string): string[];
|
|
8
|
+
export declare function discoverProviders(hostDir: string): DiscoveredProvider[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DEFAULT_AGENTS_DIR = "the-local/agents";
|
|
4
|
+
function readManifest(packageJsonPath) {
|
|
5
|
+
if (!existsSync(packageJsonPath))
|
|
6
|
+
return null;
|
|
7
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
8
|
+
}
|
|
9
|
+
export function directDependencies(hostDir) {
|
|
10
|
+
const manifest = readManifest(join(hostDir, "package.json"));
|
|
11
|
+
return Object.keys(manifest?.dependencies ?? {});
|
|
12
|
+
}
|
|
13
|
+
export function discoverProviders(hostDir) {
|
|
14
|
+
const nodeModulesDir = join(hostDir, "node_modules");
|
|
15
|
+
const providers = [];
|
|
16
|
+
for (const dependency of directDependencies(hostDir)) {
|
|
17
|
+
const packageDir = join(nodeModulesDir, dependency);
|
|
18
|
+
const manifest = readManifest(join(packageDir, "package.json"));
|
|
19
|
+
const declaration = manifest?.["the-local"];
|
|
20
|
+
if (!manifest || !declaration)
|
|
21
|
+
continue;
|
|
22
|
+
const prefix = declaration.prefix ?? dependency;
|
|
23
|
+
const agentsDir = join(packageDir, declaration.agentsDir ?? DEFAULT_AGENTS_DIR);
|
|
24
|
+
if (!existsSync(agentsDir)) {
|
|
25
|
+
throw new Error(`the-local: ${dependency} declares the-local locals but ships no committed agents at ` +
|
|
26
|
+
`${declaration.agentsDir ?? DEFAULT_AGENTS_DIR}. Build and commit them in ${dependency}.`);
|
|
27
|
+
}
|
|
28
|
+
const agentFiles = readdirSync(agentsDir)
|
|
29
|
+
.filter((entry) => entry.endsWith(".md"))
|
|
30
|
+
.sort()
|
|
31
|
+
.map((entry) => join(agentsDir, entry));
|
|
32
|
+
providers.push({ packageName: dependency, prefix, scope: declaration.scope ?? null, agentFiles });
|
|
33
|
+
}
|
|
34
|
+
return providers;
|
|
35
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { type Agent, agentFilename, qualifiedName, toMarkdown } from "./agent.js";
|
|
2
|
+
export { BEGIN_MARKER, END_MARKER, type ProviderTrigger, delegationRule, mergeTrigger } from "./trigger.js";
|
|
3
|
+
export { type ScopeInput, allowedProviders } from "./scope.js";
|
|
4
|
+
export { type DiscoveredProvider, directDependencies, discoverProviders } from "./discovery.js";
|
|
5
|
+
export { type InstallResult, installAgents, installLocals, writeTrigger } from "./installer.js";
|
|
6
|
+
export { reference } from "./reference.js";
|
|
7
|
+
export { companionAgents } from "./companion.js";
|
|
8
|
+
export { type ProviderAgentSpec, type ProviderConfig, buildProvider, prefixFromName, renderProvider, scaffoldProvider, starterConfig, } from "./provider.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { agentFilename, qualifiedName, toMarkdown } from "./agent.js";
|
|
2
|
+
export { BEGIN_MARKER, END_MARKER, delegationRule, mergeTrigger } from "./trigger.js";
|
|
3
|
+
export { allowedProviders } from "./scope.js";
|
|
4
|
+
export { directDependencies, discoverProviders } from "./discovery.js";
|
|
5
|
+
export { installAgents, installLocals, writeTrigger } from "./installer.js";
|
|
6
|
+
export { reference } from "./reference.js";
|
|
7
|
+
export { companionAgents } from "./companion.js";
|
|
8
|
+
export { buildProvider, prefixFromName, renderProvider, scaffoldProvider, starterConfig, } from "./provider.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DiscoveredProvider } from "./discovery.js";
|
|
2
|
+
import { type ProviderTrigger } from "./trigger.js";
|
|
3
|
+
export declare function installAgents(hostDir: string, providers: DiscoveredProvider[]): string[];
|
|
4
|
+
export declare function writeTrigger(hostDir: string, providers: ProviderTrigger[], filename?: string): void;
|
|
5
|
+
export interface InstallResult {
|
|
6
|
+
providers: DiscoveredProvider[];
|
|
7
|
+
agents: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function installLocals(hostDir: string): InstallResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { discoverProviders } from "./discovery.js";
|
|
4
|
+
import { delegationRule, mergeTrigger } from "./trigger.js";
|
|
5
|
+
const AGENTS_DIR = ".claude/agents";
|
|
6
|
+
export function installAgents(hostDir, providers) {
|
|
7
|
+
const destination = join(hostDir, AGENTS_DIR);
|
|
8
|
+
mkdirSync(destination, { recursive: true });
|
|
9
|
+
const written = [];
|
|
10
|
+
for (const provider of providers) {
|
|
11
|
+
for (const source of provider.agentFiles) {
|
|
12
|
+
const target = join(destination, basename(source));
|
|
13
|
+
copyFileSync(source, target);
|
|
14
|
+
written.push(target);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return written;
|
|
18
|
+
}
|
|
19
|
+
export function writeTrigger(hostDir, providers, filename = "CLAUDE.md") {
|
|
20
|
+
const path = join(hostDir, filename);
|
|
21
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
22
|
+
writeFileSync(path, `${mergeTrigger(existing, delegationRule(providers))}\n`);
|
|
23
|
+
}
|
|
24
|
+
export function installLocals(hostDir) {
|
|
25
|
+
const providers = discoverProviders(hostDir);
|
|
26
|
+
const agents = installAgents(hostDir, providers);
|
|
27
|
+
writeTrigger(hostDir, providers);
|
|
28
|
+
return { providers, agents };
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Agent } from "./agent.js";
|
|
2
|
+
export declare function prefixFromName(packageName: string): string;
|
|
3
|
+
export interface ProviderConfig {
|
|
4
|
+
prefix: string;
|
|
5
|
+
scope?: string;
|
|
6
|
+
agentsDir?: string;
|
|
7
|
+
agents: ProviderAgentSpec[];
|
|
8
|
+
}
|
|
9
|
+
export type ProviderAgentSpec = Omit<Agent, "prefix">;
|
|
10
|
+
export declare function renderProvider(config: ProviderConfig, packageDir: string): string[];
|
|
11
|
+
export declare function starterConfig(packageName: string): ProviderConfig;
|
|
12
|
+
export declare function scaffoldProvider(packageDir: string): {
|
|
13
|
+
config: ProviderConfig;
|
|
14
|
+
created: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare function buildProvider(packageDir: string): Promise<string[]>;
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { agentFilename, toMarkdown } from "./agent.js";
|
|
5
|
+
const DEFAULT_AGENTS_DIR = "the-local/agents";
|
|
6
|
+
// The agent filename namespace defaults to the package name with any npm scope
|
|
7
|
+
// dropped: `@event-engine/core` -> `core`.
|
|
8
|
+
export function prefixFromName(packageName) {
|
|
9
|
+
return packageName.replace(/^@[^/]+\//, "");
|
|
10
|
+
}
|
|
11
|
+
// Render each config agent to `<packageDir>/<agentsDir>/<prefix>-<name>.md` and
|
|
12
|
+
// return the written paths. Pure apart from the writes — the same render used
|
|
13
|
+
// for the-local's own companion, generalised to any provider config.
|
|
14
|
+
export function renderProvider(config, packageDir) {
|
|
15
|
+
const agentsDir = join(packageDir, config.agentsDir ?? DEFAULT_AGENTS_DIR);
|
|
16
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
17
|
+
return config.agents.map((spec) => {
|
|
18
|
+
const agent = { prefix: config.prefix, ...spec };
|
|
19
|
+
const path = join(agentsDir, agentFilename(agent));
|
|
20
|
+
writeFileSync(path, toMarkdown(agent));
|
|
21
|
+
return path;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const CONFIG_FILE = "the-local.config.js";
|
|
25
|
+
// The starter config a freshly-scaffolded provider gets: the standard interface
|
|
26
|
+
// of a read-only `info` explainer and a `develop` domain worker, with TODO
|
|
27
|
+
// placeholders the author fills in. Mirrors the Ruby provider generator.
|
|
28
|
+
export function starterConfig(packageName) {
|
|
29
|
+
const prefix = prefixFromName(packageName);
|
|
30
|
+
const knowledge = `## ${prefix}\n\nTODO: document ${packageName} — what it does, how to use it, the conventions to enforce.`;
|
|
31
|
+
return {
|
|
32
|
+
prefix,
|
|
33
|
+
scope: `TODO: one-line phrase describing ${packageName}'s domain`,
|
|
34
|
+
agents: [
|
|
35
|
+
{
|
|
36
|
+
name: "info",
|
|
37
|
+
description: `Use to learn what ${packageName} offers and how to use it.`,
|
|
38
|
+
tools: "Read",
|
|
39
|
+
body: `You explain ${packageName}, answering only from the reference. You make no changes.`,
|
|
40
|
+
knowledge,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "develop",
|
|
44
|
+
description: `Use PROACTIVELY for work involving ${packageName}.`,
|
|
45
|
+
tools: "Read, Write, Edit, Grep",
|
|
46
|
+
body: `You do work involving ${packageName}, following the reference's conventions exactly.`,
|
|
47
|
+
knowledge,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Scaffold the provider side into a package: write a starter config (without
|
|
53
|
+
// clobbering an authored one). Later cycles wire package.json and render.
|
|
54
|
+
export function scaffoldProvider(packageDir) {
|
|
55
|
+
const manifestPath = join(packageDir, "package.json");
|
|
56
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
57
|
+
const config = starterConfig(manifest.name);
|
|
58
|
+
// Never clobber an authored config: if one already exists, the package is a
|
|
59
|
+
// provider already — leave its config, package.json, and rendered agents
|
|
60
|
+
// alone and let `the-local build` re-render from the author's config.
|
|
61
|
+
const configPath = join(packageDir, CONFIG_FILE);
|
|
62
|
+
const created = !existsSync(configPath);
|
|
63
|
+
if (!created)
|
|
64
|
+
return { config, created };
|
|
65
|
+
writeFileSync(configPath, `export default ${JSON.stringify(config, null, 2)};\n`);
|
|
66
|
+
const agentsDir = config.agentsDir ?? DEFAULT_AGENTS_DIR;
|
|
67
|
+
manifest["the-local"] = { prefix: config.prefix, scope: config.scope, agentsDir };
|
|
68
|
+
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
69
|
+
if (!files.includes(agentsDir))
|
|
70
|
+
files.push(agentsDir);
|
|
71
|
+
manifest.files = files;
|
|
72
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
73
|
+
renderProvider(config, packageDir);
|
|
74
|
+
return { config, created };
|
|
75
|
+
}
|
|
76
|
+
// Re-render a provider's committed agents from its authored `the-local.config.js`
|
|
77
|
+
// (the analog of Ruby's `rake the_local:build`). Loaded by dynamic import so the
|
|
78
|
+
// config stays plain ESM data with no toolchain — run after editing the config.
|
|
79
|
+
export async function buildProvider(packageDir) {
|
|
80
|
+
const configUrl = pathToFileURL(join(packageDir, CONFIG_FILE)).href;
|
|
81
|
+
const loaded = (await import(configUrl));
|
|
82
|
+
return renderProvider(loaded.default, packageDir);
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const reference = "## the-local\n\n> **DO NOT** explore the the-local package source code. This reference is the\n> complete user-facing API, embedded verbatim into every the-local local so\n> their guidance never drifts. Keep it the single source of truth.\n\nthe-local is the engine that lets any npm package or app ship resident Claude\nCode expert subagents (\"locals\") that know its conventions. A provider package\ndeclares its locals and commits them as rendered `.md` files; the-local installs\nthe aggregated set from every directly-depended provider into a consuming app's\n`.claude/agents/`, plus a delegation rule so the host's agent actually uses them.\n\n### The model\n\n- **Providers ship committed locals.** A provider declares itself in its\n `package.json` with a `\"the-local\"` block and commits one rendered file per\n local at `the-local/agents/<prefix>-<name>.md`. **These committed files are the\n contract** \u2014 they are what a host reads. A provider can build them from a\n companion + `toMarkdown`, but the committed `.md` is all a host ever sees.\n- **Install discovers committed `.md` on disk.** In a host, `the-local install`\n reads each direct dependency's committed `the-local/agents/*.md` straight from\n its package path under `node_modules` and copies them into `.claude/agents/`\n byte-for-byte \u2014 no provider code is loaded. Output depends only on the provider\n package version, so it is a true carbon copy across every app, and a fragile\n provider can't crash the install.\n- **The delegation trigger.** Install also writes a generated block into the\n host's `CLAUDE.md`, between the `<!-- the_local:begin -->` and\n `<!-- the_local:end -->` markers, telling the host agent to delegate to these\n locals. This is what makes delegation actually happen. The markers are shared\n with the Ruby `the_local` gem, so a gem and an npm package never clobber each\n other's block.\n- **Direct-dependency scope.** Only the host's direct dependencies contribute\n locals; transitive providers are filtered out, so a host gets exactly the\n experts for the packages it chose.\n\n### Install (in any package or app)\n\n1. Add `the-local` to the host's `package.json` dependencies and install it.\n2. Run `the-local install` (or `npx the-local install`). This syncs every direct\n provider's committed locals into `.claude/agents/` and writes the delegation\n trigger into `CLAUDE.md`. It runs from the host's working directory.\n3. Re-run `the-local install` after any dependency change (a provider added,\n removed, or upgraded) to bring the host's locals back in sync. The package\n only exposes the command; a script or hook can automate re-running it.\n\n### Author a provider (turn a package into a provider)\n\n1. Add a `\"the-local\"` block to the package's `package.json` and create the\n `the-local/agents/` directory.\n2. Write each local in the standard interface \u2014 `info` (read-only explainer),\n `install` (sets the package up in a host), and a domain worker (`develop` for\n libraries, `operate` for CLIs) \u2014 tailoring its `description`, `tools`, and\n `body` to your package, with a guide as its embedded `knowledge`.\n3. Render each local to `the-local/agents/<prefix>-<name>.md` with `toMarkdown`,\n then **commit and ship** those files (they must be in `package.json`'s `files`\n allowlist). This is the whole contract: a host discovers your locals by\n reading these committed files from your package on disk \u2014 it never loads your\n code \u2014 so if they aren't committed and shipped, you contribute nothing, and if\n they are, you contribute everything. A drift test asserting each committed\n file equals its `toMarkdown` keeps the artifact honest.\n\n### The package.json declaration\n\n```json\n{\n \"the-local\": {\n \"prefix\": \"my-pkg\",\n \"scope\": \"one-line domain phrase\",\n \"agentsDir\": \"the-local/agents\"\n }\n}\n```\n\n- `prefix` is the agent filename namespace; defaults to the package name.\n- `scope` is a one-line domain phrase used to generate the delegation trigger;\n omit it for a bare `- <prefix>-* agents` bullet.\n- `agentsDir` is the path to the committed `.md` files, relative to the package\n root; it defaults to `the-local/agents`.\n\n### Conventions\n\n- The committed `.md` files are the contract; commit them, and never render in\n the host at install time.\n- A local's guide documents the providing package only and stays the single\n source of truth; never let a rendered `.md` drift from `toMarkdown`.\n- Only direct dependencies contribute, so depend on a provider directly to gain\n its locals.";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// the-local's own knowledge guide, embedded verbatim as the `knowledge` of
|
|
2
|
+
// every companion agent (info / install / develop). This is the single source
|
|
3
|
+
// of truth for how the-local works; the rendered companion `.md` files are
|
|
4
|
+
// built from it, so it must describe the port's actual behaviour. Keep it free
|
|
5
|
+
// of a trailing newline so `toMarkdown` ends each agent on a single newline.
|
|
6
|
+
export const reference = `## the-local
|
|
7
|
+
|
|
8
|
+
> **DO NOT** explore the the-local package source code. This reference is the
|
|
9
|
+
> complete user-facing API, embedded verbatim into every the-local local so
|
|
10
|
+
> their guidance never drifts. Keep it the single source of truth.
|
|
11
|
+
|
|
12
|
+
the-local is the engine that lets any npm package or app ship resident Claude
|
|
13
|
+
Code expert subagents ("locals") that know its conventions. A provider package
|
|
14
|
+
declares its locals and commits them as rendered \`.md\` files; the-local installs
|
|
15
|
+
the aggregated set from every directly-depended provider into a consuming app's
|
|
16
|
+
\`.claude/agents/\`, plus a delegation rule so the host's agent actually uses them.
|
|
17
|
+
|
|
18
|
+
### The model
|
|
19
|
+
|
|
20
|
+
- **Providers ship committed locals.** A provider declares itself in its
|
|
21
|
+
\`package.json\` with a \`"the-local"\` block and commits one rendered file per
|
|
22
|
+
local at \`the-local/agents/<prefix>-<name>.md\`. **These committed files are the
|
|
23
|
+
contract** — they are what a host reads. A provider can build them from a
|
|
24
|
+
companion + \`toMarkdown\`, but the committed \`.md\` is all a host ever sees.
|
|
25
|
+
- **Install discovers committed \`.md\` on disk.** In a host, \`the-local install\`
|
|
26
|
+
reads each direct dependency's committed \`the-local/agents/*.md\` straight from
|
|
27
|
+
its package path under \`node_modules\` and copies them into \`.claude/agents/\`
|
|
28
|
+
byte-for-byte — no provider code is loaded. Output depends only on the provider
|
|
29
|
+
package version, so it is a true carbon copy across every app, and a fragile
|
|
30
|
+
provider can't crash the install.
|
|
31
|
+
- **The delegation trigger.** Install also writes a generated block into the
|
|
32
|
+
host's \`CLAUDE.md\`, between the \`<!-- the_local:begin -->\` and
|
|
33
|
+
\`<!-- the_local:end -->\` markers, telling the host agent to delegate to these
|
|
34
|
+
locals. This is what makes delegation actually happen. The markers are shared
|
|
35
|
+
with the Ruby \`the_local\` gem, so a gem and an npm package never clobber each
|
|
36
|
+
other's block.
|
|
37
|
+
- **Direct-dependency scope.** Only the host's direct dependencies contribute
|
|
38
|
+
locals; transitive providers are filtered out, so a host gets exactly the
|
|
39
|
+
experts for the packages it chose.
|
|
40
|
+
|
|
41
|
+
### Install (in any package or app)
|
|
42
|
+
|
|
43
|
+
1. Add \`the-local\` to the host's \`package.json\` dependencies and install it.
|
|
44
|
+
2. Run \`the-local install\` (or \`npx the-local install\`). This syncs every direct
|
|
45
|
+
provider's committed locals into \`.claude/agents/\` and writes the delegation
|
|
46
|
+
trigger into \`CLAUDE.md\`. It runs from the host's working directory.
|
|
47
|
+
3. Re-run \`the-local install\` after any dependency change (a provider added,
|
|
48
|
+
removed, or upgraded) to bring the host's locals back in sync. The package
|
|
49
|
+
only exposes the command; a script or hook can automate re-running it.
|
|
50
|
+
|
|
51
|
+
### Author a provider (turn a package into a provider)
|
|
52
|
+
|
|
53
|
+
1. Add a \`"the-local"\` block to the package's \`package.json\` and create the
|
|
54
|
+
\`the-local/agents/\` directory.
|
|
55
|
+
2. Write each local in the standard interface — \`info\` (read-only explainer),
|
|
56
|
+
\`install\` (sets the package up in a host), and a domain worker (\`develop\` for
|
|
57
|
+
libraries, \`operate\` for CLIs) — tailoring its \`description\`, \`tools\`, and
|
|
58
|
+
\`body\` to your package, with a guide as its embedded \`knowledge\`.
|
|
59
|
+
3. Render each local to \`the-local/agents/<prefix>-<name>.md\` with \`toMarkdown\`,
|
|
60
|
+
then **commit and ship** those files (they must be in \`package.json\`'s \`files\`
|
|
61
|
+
allowlist). This is the whole contract: a host discovers your locals by
|
|
62
|
+
reading these committed files from your package on disk — it never loads your
|
|
63
|
+
code — so if they aren't committed and shipped, you contribute nothing, and if
|
|
64
|
+
they are, you contribute everything. A drift test asserting each committed
|
|
65
|
+
file equals its \`toMarkdown\` keeps the artifact honest.
|
|
66
|
+
|
|
67
|
+
### The package.json declaration
|
|
68
|
+
|
|
69
|
+
\`\`\`json
|
|
70
|
+
{
|
|
71
|
+
"the-local": {
|
|
72
|
+
"prefix": "my-pkg",
|
|
73
|
+
"scope": "one-line domain phrase",
|
|
74
|
+
"agentsDir": "the-local/agents"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
- \`prefix\` is the agent filename namespace; defaults to the package name.
|
|
80
|
+
- \`scope\` is a one-line domain phrase used to generate the delegation trigger;
|
|
81
|
+
omit it for a bare \`- <prefix>-* agents\` bullet.
|
|
82
|
+
- \`agentsDir\` is the path to the committed \`.md\` files, relative to the package
|
|
83
|
+
root; it defaults to \`the-local/agents\`.
|
|
84
|
+
|
|
85
|
+
### Conventions
|
|
86
|
+
|
|
87
|
+
- The committed \`.md\` files are the contract; commit them, and never render in
|
|
88
|
+
the host at install time.
|
|
89
|
+
- A local's guide documents the providing package only and stays the single
|
|
90
|
+
source of truth; never let a rendered \`.md\` drift from \`toMarkdown\`.
|
|
91
|
+
- Only direct dependencies contribute, so depend on a provider directly to gain
|
|
92
|
+
its locals.`;
|
package/dist/scope.d.ts
ADDED
package/dist/scope.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const BEGIN_MARKER = "<!-- the_local:begin -->";
|
|
2
|
+
export declare const END_MARKER = "<!-- the_local:end -->";
|
|
3
|
+
export interface ProviderTrigger {
|
|
4
|
+
prefix: string;
|
|
5
|
+
scope?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function delegationRule(providers: ProviderTrigger[]): string;
|
|
8
|
+
export declare function mergeTrigger(existing: string, rule: string): string;
|
package/dist/trigger.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const BEGIN_MARKER = "<!-- the_local:begin -->";
|
|
2
|
+
export const END_MARKER = "<!-- the_local:end -->";
|
|
3
|
+
function escapeRegExp(value) {
|
|
4
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
}
|
|
6
|
+
function bullet(provider) {
|
|
7
|
+
const target = `${provider.prefix}-* agents`;
|
|
8
|
+
return `- ${provider.scope ? `${provider.scope} → ${target}` : target}`;
|
|
9
|
+
}
|
|
10
|
+
export function delegationRule(providers) {
|
|
11
|
+
return [
|
|
12
|
+
BEGIN_MARKER,
|
|
13
|
+
"## Delegate to your locals",
|
|
14
|
+
"",
|
|
15
|
+
"This project has installed expert subagents. Before doing work yourself,",
|
|
16
|
+
"check whether a local owns it and delegate — never work from memory on",
|
|
17
|
+
"something a local covers:",
|
|
18
|
+
"",
|
|
19
|
+
providers.map(bullet).join("\n"),
|
|
20
|
+
"",
|
|
21
|
+
"See each agent's description for specifics.",
|
|
22
|
+
END_MARKER,
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
export function mergeTrigger(existing, rule) {
|
|
26
|
+
const section = new RegExp(`${escapeRegExp(BEGIN_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`);
|
|
27
|
+
if (section.test(existing))
|
|
28
|
+
return existing.replace(section, rule);
|
|
29
|
+
if (existing.trim() === "")
|
|
30
|
+
return rule;
|
|
31
|
+
return `${existing.replace(/\r?\n$/, "")}\n\n${rule}`;
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "the-local",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Ship Claude Code locals from npm packages into a host TS/JS app — the TypeScript port of the the_local Ruby gem.",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/DYB-Development/the-local.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/DYB-Development/the-local#readme",
|
|
14
|
+
"bugs": "https://github.com/DYB-Development/the-local/issues",
|
|
15
|
+
"files": ["dist", "README.md", "the-local/agents"],
|
|
16
|
+
"the-local": {
|
|
17
|
+
"prefix": "the-local",
|
|
18
|
+
"scope": "Claude Code locals — packages ship subagents that the-local installs into a host app",
|
|
19
|
+
"agentsDir": "the-local/agents"
|
|
20
|
+
},
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"bin": {
|
|
23
|
+
"the-local": "dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
33
|
+
"build:agents": "pnpm build && node scripts/build-companion.mjs",
|
|
34
|
+
"prepare": "pnpm build",
|
|
35
|
+
"prepublishOnly": "pnpm build",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"lint": "eslint ."
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@eslint/js": "^9.17.0",
|
|
43
|
+
"@types/node": "^22.10.0",
|
|
44
|
+
"eslint": "^9.17.0",
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"typescript-eslint": "^8.18.0",
|
|
47
|
+
"vitest": "^2.1.8"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the-local-develop
|
|
3
|
+
description: Use PROACTIVELY to turn a package into a the-local provider — declaring the provider block, authoring the locals, and committing the rendered agents. MUST BE USED instead of wiring a provider by hand.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You turn a package into a the-local provider following the reference's provider-author workflow: add the `the-local` block to package.json, author the standard locals (info, install, and a domain worker) with a guide as their knowledge, and render each to the-local/agents/. The deliverable is the committed, shipped the-local/agents/*.md — that is the whole contract a host reads from disk; a host never loads the package, so unless those files are committed and in package.json's files allowlist, the package contributes nothing. You keep them in sync with toMarkdown.
|
|
8
|
+
|
|
9
|
+
## the-local
|
|
10
|
+
|
|
11
|
+
> **DO NOT** explore the the-local package source code. This reference is the
|
|
12
|
+
> complete user-facing API, embedded verbatim into every the-local local so
|
|
13
|
+
> their guidance never drifts. Keep it the single source of truth.
|
|
14
|
+
|
|
15
|
+
the-local is the engine that lets any npm package or app ship resident Claude
|
|
16
|
+
Code expert subagents ("locals") that know its conventions. A provider package
|
|
17
|
+
declares its locals and commits them as rendered `.md` files; the-local installs
|
|
18
|
+
the aggregated set from every directly-depended provider into a consuming app's
|
|
19
|
+
`.claude/agents/`, plus a delegation rule so the host's agent actually uses them.
|
|
20
|
+
|
|
21
|
+
### The model
|
|
22
|
+
|
|
23
|
+
- **Providers ship committed locals.** A provider declares itself in its
|
|
24
|
+
`package.json` with a `"the-local"` block and commits one rendered file per
|
|
25
|
+
local at `the-local/agents/<prefix>-<name>.md`. **These committed files are the
|
|
26
|
+
contract** — they are what a host reads. A provider can build them from a
|
|
27
|
+
companion + `toMarkdown`, but the committed `.md` is all a host ever sees.
|
|
28
|
+
- **Install discovers committed `.md` on disk.** In a host, `the-local install`
|
|
29
|
+
reads each direct dependency's committed `the-local/agents/*.md` straight from
|
|
30
|
+
its package path under `node_modules` and copies them into `.claude/agents/`
|
|
31
|
+
byte-for-byte — no provider code is loaded. Output depends only on the provider
|
|
32
|
+
package version, so it is a true carbon copy across every app, and a fragile
|
|
33
|
+
provider can't crash the install.
|
|
34
|
+
- **The delegation trigger.** Install also writes a generated block into the
|
|
35
|
+
host's `CLAUDE.md`, between the `<!-- the_local:begin -->` and
|
|
36
|
+
`<!-- the_local:end -->` markers, telling the host agent to delegate to these
|
|
37
|
+
locals. This is what makes delegation actually happen. The markers are shared
|
|
38
|
+
with the Ruby `the_local` gem, so a gem and an npm package never clobber each
|
|
39
|
+
other's block.
|
|
40
|
+
- **Direct-dependency scope.** Only the host's direct dependencies contribute
|
|
41
|
+
locals; transitive providers are filtered out, so a host gets exactly the
|
|
42
|
+
experts for the packages it chose.
|
|
43
|
+
|
|
44
|
+
### Install (in any package or app)
|
|
45
|
+
|
|
46
|
+
1. Add `the-local` to the host's `package.json` dependencies and install it.
|
|
47
|
+
2. Run `the-local install` (or `npx the-local install`). This syncs every direct
|
|
48
|
+
provider's committed locals into `.claude/agents/` and writes the delegation
|
|
49
|
+
trigger into `CLAUDE.md`. It runs from the host's working directory.
|
|
50
|
+
3. Re-run `the-local install` after any dependency change (a provider added,
|
|
51
|
+
removed, or upgraded) to bring the host's locals back in sync. The package
|
|
52
|
+
only exposes the command; a script or hook can automate re-running it.
|
|
53
|
+
|
|
54
|
+
### Author a provider (turn a package into a provider)
|
|
55
|
+
|
|
56
|
+
1. Add a `"the-local"` block to the package's `package.json` and create the
|
|
57
|
+
`the-local/agents/` directory.
|
|
58
|
+
2. Write each local in the standard interface — `info` (read-only explainer),
|
|
59
|
+
`install` (sets the package up in a host), and a domain worker (`develop` for
|
|
60
|
+
libraries, `operate` for CLIs) — tailoring its `description`, `tools`, and
|
|
61
|
+
`body` to your package, with a guide as its embedded `knowledge`.
|
|
62
|
+
3. Render each local to `the-local/agents/<prefix>-<name>.md` with `toMarkdown`,
|
|
63
|
+
then **commit and ship** those files (they must be in `package.json`'s `files`
|
|
64
|
+
allowlist). This is the whole contract: a host discovers your locals by
|
|
65
|
+
reading these committed files from your package on disk — it never loads your
|
|
66
|
+
code — so if they aren't committed and shipped, you contribute nothing, and if
|
|
67
|
+
they are, you contribute everything. A drift test asserting each committed
|
|
68
|
+
file equals its `toMarkdown` keeps the artifact honest.
|
|
69
|
+
|
|
70
|
+
### The package.json declaration
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"the-local": {
|
|
75
|
+
"prefix": "my-pkg",
|
|
76
|
+
"scope": "one-line domain phrase",
|
|
77
|
+
"agentsDir": "the-local/agents"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `prefix` is the agent filename namespace; defaults to the package name.
|
|
83
|
+
- `scope` is a one-line domain phrase used to generate the delegation trigger;
|
|
84
|
+
omit it for a bare `- <prefix>-* agents` bullet.
|
|
85
|
+
- `agentsDir` is the path to the committed `.md` files, relative to the package
|
|
86
|
+
root; it defaults to `the-local/agents`.
|
|
87
|
+
|
|
88
|
+
### Conventions
|
|
89
|
+
|
|
90
|
+
- The committed `.md` files are the contract; commit them, and never render in
|
|
91
|
+
the host at install time.
|
|
92
|
+
- A local's guide documents the providing package only and stays the single
|
|
93
|
+
source of truth; never let a rendered `.md` drift from `toMarkdown`.
|
|
94
|
+
- Only direct dependencies contribute, so depend on a provider directly to gain
|
|
95
|
+
its locals.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the-local-info
|
|
3
|
+
description: Use to learn how the-local works — providers, the committed-.md install model, the delegation trigger, and the direct-dependency scope rule.
|
|
4
|
+
tools: Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You explain how the-local works, answering only from the reference: providers ship committed locals, `the-local install` copies them verbatim into a host's .claude/agents/, the CLAUDE.md delegation trigger makes the host delegate, and only direct dependencies contribute. You make no changes.
|
|
8
|
+
|
|
9
|
+
## the-local
|
|
10
|
+
|
|
11
|
+
> **DO NOT** explore the the-local package source code. This reference is the
|
|
12
|
+
> complete user-facing API, embedded verbatim into every the-local local so
|
|
13
|
+
> their guidance never drifts. Keep it the single source of truth.
|
|
14
|
+
|
|
15
|
+
the-local is the engine that lets any npm package or app ship resident Claude
|
|
16
|
+
Code expert subagents ("locals") that know its conventions. A provider package
|
|
17
|
+
declares its locals and commits them as rendered `.md` files; the-local installs
|
|
18
|
+
the aggregated set from every directly-depended provider into a consuming app's
|
|
19
|
+
`.claude/agents/`, plus a delegation rule so the host's agent actually uses them.
|
|
20
|
+
|
|
21
|
+
### The model
|
|
22
|
+
|
|
23
|
+
- **Providers ship committed locals.** A provider declares itself in its
|
|
24
|
+
`package.json` with a `"the-local"` block and commits one rendered file per
|
|
25
|
+
local at `the-local/agents/<prefix>-<name>.md`. **These committed files are the
|
|
26
|
+
contract** — they are what a host reads. A provider can build them from a
|
|
27
|
+
companion + `toMarkdown`, but the committed `.md` is all a host ever sees.
|
|
28
|
+
- **Install discovers committed `.md` on disk.** In a host, `the-local install`
|
|
29
|
+
reads each direct dependency's committed `the-local/agents/*.md` straight from
|
|
30
|
+
its package path under `node_modules` and copies them into `.claude/agents/`
|
|
31
|
+
byte-for-byte — no provider code is loaded. Output depends only on the provider
|
|
32
|
+
package version, so it is a true carbon copy across every app, and a fragile
|
|
33
|
+
provider can't crash the install.
|
|
34
|
+
- **The delegation trigger.** Install also writes a generated block into the
|
|
35
|
+
host's `CLAUDE.md`, between the `<!-- the_local:begin -->` and
|
|
36
|
+
`<!-- the_local:end -->` markers, telling the host agent to delegate to these
|
|
37
|
+
locals. This is what makes delegation actually happen. The markers are shared
|
|
38
|
+
with the Ruby `the_local` gem, so a gem and an npm package never clobber each
|
|
39
|
+
other's block.
|
|
40
|
+
- **Direct-dependency scope.** Only the host's direct dependencies contribute
|
|
41
|
+
locals; transitive providers are filtered out, so a host gets exactly the
|
|
42
|
+
experts for the packages it chose.
|
|
43
|
+
|
|
44
|
+
### Install (in any package or app)
|
|
45
|
+
|
|
46
|
+
1. Add `the-local` to the host's `package.json` dependencies and install it.
|
|
47
|
+
2. Run `the-local install` (or `npx the-local install`). This syncs every direct
|
|
48
|
+
provider's committed locals into `.claude/agents/` and writes the delegation
|
|
49
|
+
trigger into `CLAUDE.md`. It runs from the host's working directory.
|
|
50
|
+
3. Re-run `the-local install` after any dependency change (a provider added,
|
|
51
|
+
removed, or upgraded) to bring the host's locals back in sync. The package
|
|
52
|
+
only exposes the command; a script or hook can automate re-running it.
|
|
53
|
+
|
|
54
|
+
### Author a provider (turn a package into a provider)
|
|
55
|
+
|
|
56
|
+
1. Add a `"the-local"` block to the package's `package.json` and create the
|
|
57
|
+
`the-local/agents/` directory.
|
|
58
|
+
2. Write each local in the standard interface — `info` (read-only explainer),
|
|
59
|
+
`install` (sets the package up in a host), and a domain worker (`develop` for
|
|
60
|
+
libraries, `operate` for CLIs) — tailoring its `description`, `tools`, and
|
|
61
|
+
`body` to your package, with a guide as its embedded `knowledge`.
|
|
62
|
+
3. Render each local to `the-local/agents/<prefix>-<name>.md` with `toMarkdown`,
|
|
63
|
+
then **commit and ship** those files (they must be in `package.json`'s `files`
|
|
64
|
+
allowlist). This is the whole contract: a host discovers your locals by
|
|
65
|
+
reading these committed files from your package on disk — it never loads your
|
|
66
|
+
code — so if they aren't committed and shipped, you contribute nothing, and if
|
|
67
|
+
they are, you contribute everything. A drift test asserting each committed
|
|
68
|
+
file equals its `toMarkdown` keeps the artifact honest.
|
|
69
|
+
|
|
70
|
+
### The package.json declaration
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"the-local": {
|
|
75
|
+
"prefix": "my-pkg",
|
|
76
|
+
"scope": "one-line domain phrase",
|
|
77
|
+
"agentsDir": "the-local/agents"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `prefix` is the agent filename namespace; defaults to the package name.
|
|
83
|
+
- `scope` is a one-line domain phrase used to generate the delegation trigger;
|
|
84
|
+
omit it for a bare `- <prefix>-* agents` bullet.
|
|
85
|
+
- `agentsDir` is the path to the committed `.md` files, relative to the package
|
|
86
|
+
root; it defaults to `the-local/agents`.
|
|
87
|
+
|
|
88
|
+
### Conventions
|
|
89
|
+
|
|
90
|
+
- The committed `.md` files are the contract; commit them, and never render in
|
|
91
|
+
the host at install time.
|
|
92
|
+
- A local's guide documents the providing package only and stays the single
|
|
93
|
+
source of truth; never let a rendered `.md` drift from `toMarkdown`.
|
|
94
|
+
- Only direct dependencies contribute, so depend on a provider directly to gain
|
|
95
|
+
its locals.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the-local-install
|
|
3
|
+
description: Use to add the-local to a host app and set it up correctly.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You set the-local up in a host package or app, following the reference's install section exactly: add the-local to the host's dependencies, install, run `the-local install` to sync locals into .claude/agents/ and write the delegation trigger, and re-run it after dependency changes. You do not invent steps the reference does not list.
|
|
8
|
+
|
|
9
|
+
## the-local
|
|
10
|
+
|
|
11
|
+
> **DO NOT** explore the the-local package source code. This reference is the
|
|
12
|
+
> complete user-facing API, embedded verbatim into every the-local local so
|
|
13
|
+
> their guidance never drifts. Keep it the single source of truth.
|
|
14
|
+
|
|
15
|
+
the-local is the engine that lets any npm package or app ship resident Claude
|
|
16
|
+
Code expert subagents ("locals") that know its conventions. A provider package
|
|
17
|
+
declares its locals and commits them as rendered `.md` files; the-local installs
|
|
18
|
+
the aggregated set from every directly-depended provider into a consuming app's
|
|
19
|
+
`.claude/agents/`, plus a delegation rule so the host's agent actually uses them.
|
|
20
|
+
|
|
21
|
+
### The model
|
|
22
|
+
|
|
23
|
+
- **Providers ship committed locals.** A provider declares itself in its
|
|
24
|
+
`package.json` with a `"the-local"` block and commits one rendered file per
|
|
25
|
+
local at `the-local/agents/<prefix>-<name>.md`. **These committed files are the
|
|
26
|
+
contract** — they are what a host reads. A provider can build them from a
|
|
27
|
+
companion + `toMarkdown`, but the committed `.md` is all a host ever sees.
|
|
28
|
+
- **Install discovers committed `.md` on disk.** In a host, `the-local install`
|
|
29
|
+
reads each direct dependency's committed `the-local/agents/*.md` straight from
|
|
30
|
+
its package path under `node_modules` and copies them into `.claude/agents/`
|
|
31
|
+
byte-for-byte — no provider code is loaded. Output depends only on the provider
|
|
32
|
+
package version, so it is a true carbon copy across every app, and a fragile
|
|
33
|
+
provider can't crash the install.
|
|
34
|
+
- **The delegation trigger.** Install also writes a generated block into the
|
|
35
|
+
host's `CLAUDE.md`, between the `<!-- the_local:begin -->` and
|
|
36
|
+
`<!-- the_local:end -->` markers, telling the host agent to delegate to these
|
|
37
|
+
locals. This is what makes delegation actually happen. The markers are shared
|
|
38
|
+
with the Ruby `the_local` gem, so a gem and an npm package never clobber each
|
|
39
|
+
other's block.
|
|
40
|
+
- **Direct-dependency scope.** Only the host's direct dependencies contribute
|
|
41
|
+
locals; transitive providers are filtered out, so a host gets exactly the
|
|
42
|
+
experts for the packages it chose.
|
|
43
|
+
|
|
44
|
+
### Install (in any package or app)
|
|
45
|
+
|
|
46
|
+
1. Add `the-local` to the host's `package.json` dependencies and install it.
|
|
47
|
+
2. Run `the-local install` (or `npx the-local install`). This syncs every direct
|
|
48
|
+
provider's committed locals into `.claude/agents/` and writes the delegation
|
|
49
|
+
trigger into `CLAUDE.md`. It runs from the host's working directory.
|
|
50
|
+
3. Re-run `the-local install` after any dependency change (a provider added,
|
|
51
|
+
removed, or upgraded) to bring the host's locals back in sync. The package
|
|
52
|
+
only exposes the command; a script or hook can automate re-running it.
|
|
53
|
+
|
|
54
|
+
### Author a provider (turn a package into a provider)
|
|
55
|
+
|
|
56
|
+
1. Add a `"the-local"` block to the package's `package.json` and create the
|
|
57
|
+
`the-local/agents/` directory.
|
|
58
|
+
2. Write each local in the standard interface — `info` (read-only explainer),
|
|
59
|
+
`install` (sets the package up in a host), and a domain worker (`develop` for
|
|
60
|
+
libraries, `operate` for CLIs) — tailoring its `description`, `tools`, and
|
|
61
|
+
`body` to your package, with a guide as its embedded `knowledge`.
|
|
62
|
+
3. Render each local to `the-local/agents/<prefix>-<name>.md` with `toMarkdown`,
|
|
63
|
+
then **commit and ship** those files (they must be in `package.json`'s `files`
|
|
64
|
+
allowlist). This is the whole contract: a host discovers your locals by
|
|
65
|
+
reading these committed files from your package on disk — it never loads your
|
|
66
|
+
code — so if they aren't committed and shipped, you contribute nothing, and if
|
|
67
|
+
they are, you contribute everything. A drift test asserting each committed
|
|
68
|
+
file equals its `toMarkdown` keeps the artifact honest.
|
|
69
|
+
|
|
70
|
+
### The package.json declaration
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"the-local": {
|
|
75
|
+
"prefix": "my-pkg",
|
|
76
|
+
"scope": "one-line domain phrase",
|
|
77
|
+
"agentsDir": "the-local/agents"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `prefix` is the agent filename namespace; defaults to the package name.
|
|
83
|
+
- `scope` is a one-line domain phrase used to generate the delegation trigger;
|
|
84
|
+
omit it for a bare `- <prefix>-* agents` bullet.
|
|
85
|
+
- `agentsDir` is the path to the committed `.md` files, relative to the package
|
|
86
|
+
root; it defaults to `the-local/agents`.
|
|
87
|
+
|
|
88
|
+
### Conventions
|
|
89
|
+
|
|
90
|
+
- The committed `.md` files are the contract; commit them, and never render in
|
|
91
|
+
the host at install time.
|
|
92
|
+
- A local's guide documents the providing package only and stays the single
|
|
93
|
+
source of truth; never let a rendered `.md` drift from `toMarkdown`.
|
|
94
|
+
- Only direct dependencies contribute, so depend on a provider directly to gain
|
|
95
|
+
its locals.
|