@crouton-kit/crouter 0.1.1 → 0.1.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.
Files changed (50) hide show
  1. package/bin/crouter +2 -0
  2. package/bin/crtr +2 -0
  3. package/dist/cli.js +36 -4
  4. package/dist/commands/config.d.ts +2 -0
  5. package/dist/commands/config.js +134 -0
  6. package/dist/commands/doctor.d.ts +2 -0
  7. package/dist/commands/doctor.js +216 -0
  8. package/dist/commands/marketplace.d.ts +2 -0
  9. package/dist/commands/marketplace.js +365 -0
  10. package/dist/commands/plan.d.ts +2 -0
  11. package/dist/commands/plan.js +9 -0
  12. package/dist/commands/plugin.d.ts +2 -0
  13. package/dist/commands/plugin.js +364 -0
  14. package/dist/commands/skill.d.ts +2 -0
  15. package/dist/commands/skill.js +405 -0
  16. package/dist/commands/spec.d.ts +2 -0
  17. package/dist/commands/spec.js +9 -0
  18. package/dist/commands/update.d.ts +4 -0
  19. package/dist/commands/update.js +140 -0
  20. package/dist/core/artifact.d.ts +14 -0
  21. package/dist/core/artifact.js +187 -0
  22. package/dist/core/auto-update.d.ts +1 -0
  23. package/dist/core/auto-update.js +86 -0
  24. package/dist/core/config.d.ts +10 -0
  25. package/dist/core/config.js +96 -0
  26. package/dist/core/errors.d.ts +12 -0
  27. package/dist/core/errors.js +28 -0
  28. package/dist/core/frontmatter.d.ts +8 -0
  29. package/dist/core/frontmatter.js +156 -0
  30. package/dist/core/fs-utils.d.ts +18 -0
  31. package/dist/core/fs-utils.js +115 -0
  32. package/dist/core/git.d.ts +18 -0
  33. package/dist/core/git.js +71 -0
  34. package/dist/core/manifest.d.ts +5 -0
  35. package/dist/core/manifest.js +15 -0
  36. package/dist/core/output.d.ts +35 -0
  37. package/dist/core/output.js +99 -0
  38. package/dist/core/resolver.d.ts +28 -0
  39. package/dist/core/resolver.js +228 -0
  40. package/dist/core/scope.d.ts +12 -0
  41. package/dist/core/scope.js +87 -0
  42. package/dist/prompts/plan.d.ts +1 -0
  43. package/dist/prompts/plan.js +106 -0
  44. package/dist/prompts/skill.d.ts +1 -0
  45. package/dist/prompts/skill.js +49 -0
  46. package/dist/prompts/spec.d.ts +1 -0
  47. package/dist/prompts/spec.js +113 -0
  48. package/dist/types.d.ts +115 -0
  49. package/dist/types.js +33 -0
  50. package/package.json +8 -5
@@ -0,0 +1,87 @@
1
+ import { homedir } from 'node:os';
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import { join, resolve, dirname } from 'node:path';
4
+ import { CRTR_DIR_NAME } from '../types.js';
5
+ import { usage } from './errors.js';
6
+ let cachedProjectRoot;
7
+ export function userScopeRoot() {
8
+ return join(homedir(), CRTR_DIR_NAME);
9
+ }
10
+ export function findProjectScopeRoot(startDir = process.cwd()) {
11
+ if (cachedProjectRoot !== undefined)
12
+ return cachedProjectRoot;
13
+ const userRoot = userScopeRoot();
14
+ let dir = resolve(startDir);
15
+ while (true) {
16
+ const candidate = join(dir, CRTR_DIR_NAME);
17
+ if (candidate !== userRoot && existsSync(candidate)) {
18
+ try {
19
+ if (statSync(candidate).isDirectory()) {
20
+ cachedProjectRoot = candidate;
21
+ return candidate;
22
+ }
23
+ }
24
+ catch {
25
+ /* fall through */
26
+ }
27
+ }
28
+ const parent = dirname(dir);
29
+ if (parent === dir) {
30
+ cachedProjectRoot = null;
31
+ return null;
32
+ }
33
+ dir = parent;
34
+ }
35
+ }
36
+ export function projectScopeRoot(startDir) {
37
+ return findProjectScopeRoot(startDir);
38
+ }
39
+ export function scopeRoot(scope) {
40
+ return scope === 'user' ? userScopeRoot() : projectScopeRoot();
41
+ }
42
+ export function requireScopeRoot(scope) {
43
+ const root = scopeRoot(scope);
44
+ if (!root) {
45
+ throw usage(`no ${scope} scope available — run \`crtr init\` here or use --scope user`);
46
+ }
47
+ return root;
48
+ }
49
+ export function ensureProjectScopeRoot(startDir = process.cwd()) {
50
+ const found = findProjectScopeRoot(startDir);
51
+ if (found)
52
+ return found;
53
+ // Initialize new project scope at startDir
54
+ const root = join(resolve(startDir), CRTR_DIR_NAME);
55
+ cachedProjectRoot = root;
56
+ return root;
57
+ }
58
+ export function pluginsDir(scope) {
59
+ const root = scopeRoot(scope);
60
+ return root ? join(root, 'plugins') : null;
61
+ }
62
+ export function marketplacesDir(scope) {
63
+ const root = scopeRoot(scope);
64
+ return root ? join(root, 'marketplaces') : null;
65
+ }
66
+ export function resolveScopeArg(scopeArg) {
67
+ if (scopeArg === undefined)
68
+ return 'all';
69
+ const value = scopeArg.toLowerCase();
70
+ if (value === 'user' || value === 'project' || value === 'all')
71
+ return value;
72
+ throw usage(`invalid --scope: ${scopeArg} (expected user|project|all)`);
73
+ }
74
+ export function listScopes(scopeArg) {
75
+ const v = resolveScopeArg(scopeArg);
76
+ if (v === 'all') {
77
+ const out = [];
78
+ if (projectScopeRoot())
79
+ out.push('project');
80
+ out.push('user');
81
+ return out;
82
+ }
83
+ return [v];
84
+ }
85
+ export function resetScopeCache() {
86
+ cachedProjectRoot = undefined;
87
+ }
@@ -0,0 +1 @@
1
+ export declare function planPrompt(plansDir: string): string;
@@ -0,0 +1,106 @@
1
+ export function planPrompt(plansDir) {
2
+ return `# Planning workflow
3
+
4
+ You are entering a focused planning session. The goal is to produce an
5
+ implementation plan that another agent (or you, in a later turn) can execute
6
+ without re-discovering everything. A plan is a map, not a tutorial.
7
+
8
+ Plans for this directory live at:
9
+ ${plansDir}
10
+
11
+ If a relevant prior plan already exists there, read it first.
12
+
13
+ ## Phase 1: Initial Understanding
14
+
15
+ Build a comprehensive picture of the user's request and the code involved.
16
+ Actively search for existing functions, utilities, and patterns that can be
17
+ reused — do not propose new code when a suitable implementation already
18
+ exists.
19
+
20
+ - **Launch up to 3 Explore subagents IN PARALLEL** (single message, multiple
21
+ tool calls) to cover the codebase efficiently.
22
+ - Use 1 agent when the task is isolated to known files, the user provided
23
+ specific paths, or the change is small and targeted.
24
+ - Use multiple agents when scope is uncertain, multiple areas of the codebase
25
+ are involved, or you need to understand existing patterns before planning.
26
+ - Quality over quantity — 3 agents maximum; usually 1 is right.
27
+ - When using multiple agents, give each a distinct focus (existing impls,
28
+ related components, test patterns) so they do not duplicate work.
29
+
30
+ ## Phase 2: Design
31
+
32
+ Design the implementation approach based on Phase 1 findings.
33
+
34
+ - **Default**: launch at least 1 Plan agent — it validates your understanding
35
+ and surfaces alternatives.
36
+ - **Skip agents** only for truly trivial tasks (typo fixes, single-line
37
+ changes, simple renames).
38
+ - **Multiple agents (up to 3)** for tasks that benefit from different
39
+ perspectives — large refactors, architectural changes, many edge cases.
40
+
41
+ In the Plan agent prompt:
42
+ - Provide comprehensive background context from Phase 1, including filenames
43
+ and code-path traces.
44
+ - Describe requirements and constraints.
45
+ - Request a detailed implementation plan.
46
+
47
+ ## Phase 3: Review
48
+
49
+ - Read the critical files identified by agents to deepen your understanding.
50
+ - Ensure the plan aligns with the user's original request.
51
+ - Use **AskUserQuestion** to clarify any remaining questions with the user.
52
+ Bias toward asking when a decision is non-obvious — interrupting once is
53
+ cheaper than building the wrong thing.
54
+
55
+ **Important:** Use AskUserQuestion ONLY to clarify requirements or choose
56
+ between approaches. Never use it to ask the user "is this plan okay?" or
57
+ "should I proceed?" — the save step below is the approval moment.
58
+
59
+ ## Phase 4: Final Plan
60
+
61
+ Save the plan with \`crtr plan --name <kebab-case-name>\`. Pipe the markdown
62
+ body in via stdin (heredoc):
63
+
64
+ \`\`\`bash
65
+ crtr plan --name <kebab-case-name> <<'EOF'
66
+ # Plan: <one-line title>
67
+
68
+ ## Context
69
+ <why this change is being made — the problem it addresses, what prompted it,
70
+ and the intended outcome>
71
+
72
+ ## Recommended approach
73
+ <your chosen approach. Include only the recommendation, not all alternatives.
74
+ Be concise enough to scan, detailed enough to execute.>
75
+
76
+ ## Files to modify / create
77
+ - \`path/to/file.ts\` — <what changes>
78
+ - ...
79
+
80
+ ## Existing utilities to reuse
81
+ - \`function-name\` from \`path/to/file.ts:LL\` — <why it fits>
82
+
83
+ ## Verification
84
+ <how to test the changes end-to-end — run the code, run tests, etc.>
85
+ EOF
86
+ \`\`\`
87
+
88
+ - Pick a short, descriptive kebab-case name. Names may be nested
89
+ (\`crtr plan --name auth/jwt-refresh\`) — they become subdirectories.
90
+ - The file lands at \`${plansDir}/<name>.md\`.
91
+ - If you are running inside tmux, the saved plan auto-opens in a side pane
92
+ via termrender. No extra step needed.
93
+
94
+ ## Phase 5: Done
95
+
96
+ Your turn ends after the save command succeeds. No need to summarize the plan
97
+ in chat — the user can read the file.
98
+
99
+ ## See also
100
+
101
+ - \`crtr plan list\` — list saved plans for the current directory
102
+ - \`crtr plan show <name>\` — print the body of a saved plan
103
+ - \`crtr plan edit <name>\` — open a saved plan in \$EDITOR
104
+ - \`crtr plan path [name]\` — absolute path of a plan or the plans directory
105
+ `;
106
+ }
@@ -0,0 +1 @@
1
+ export declare function skillPrompt(): string;
@@ -0,0 +1,49 @@
1
+ export function skillPrompt() {
2
+ return `# Skill workflow
3
+
4
+ \`crtr\` ships skills — markdown reference with frontmatter that you pull on
5
+ demand. When the user's task matches a skill's description, run
6
+ \`crtr skill show <name>\` and apply the guidance. Ambiguous names exit \`4\` —
7
+ disambiguate with \`<plugin>:<name>\`.
8
+
9
+ ## Discover
10
+
11
+ \`\`\`
12
+ crtr skill list # one per line: <scope>:<plugin>/<name> — <description>
13
+ crtr skill search <query> # rank by name, description, keywords
14
+ crtr skill grep <pattern> # regex search across SKILL.md bodies
15
+ \`\`\`
16
+
17
+ ## Load
18
+
19
+ \`\`\`
20
+ crtr skill show <name> # print SKILL.md body to stdout
21
+ crtr skill show <plugin>:<name> # disambiguate when names collide
22
+ crtr skill path <name> # absolute path to SKILL.md
23
+ crtr skill where <name> # {scope, plugin, path} as JSON
24
+ \`\`\`
25
+
26
+ \`show\` is the default verb: \`crtr skill <name>\` (with no verb) also prints
27
+ the body.
28
+
29
+ ## Author
30
+
31
+ \`\`\`
32
+ crtr skill new <plugin>:<name> --description "..." # scaffold a new skill
33
+ crtr skill show authoring-skills # the SKILL.md authoring guide
34
+ \`\`\`
35
+
36
+ ## Toggle
37
+
38
+ \`\`\`
39
+ crtr skill enable <name> # clear any disable in the chosen scope
40
+ crtr skill disable <name> # hide from list and agent discovery
41
+ \`\`\`
42
+
43
+ ## Exit codes
44
+
45
+ - \`0\` — success
46
+ - \`3\` — skill not found
47
+ - \`4\` — ambiguous name; use \`<plugin>:<name>\`
48
+ `;
49
+ }
@@ -0,0 +1 @@
1
+ export declare function specPrompt(specsDir: string): string;
@@ -0,0 +1,113 @@
1
+ export function specPrompt(specsDir) {
2
+ return `# Spec workflow
3
+
4
+ You are entering a focused spec session. The goal is to produce a design +
5
+ requirements spec — a document describing **what** to build, the shape of the
6
+ solution, and the behaviors it must satisfy. A spec is upstream of a plan: it
7
+ captures decisions, not implementation steps.
8
+
9
+ Specs for this directory live at:
10
+ ${specsDir}
11
+
12
+ If a relevant prior spec already exists there, read it first. Treat an
13
+ existing spec as the starting point — extend or revise rather than restart.
14
+
15
+ ## Phase 1: Shape
16
+
17
+ Build a comprehensive picture of the problem and the relevant code. Surface
18
+ existing patterns, constraints, and prior decisions.
19
+
20
+ - **Launch up to 3 Explore subagents IN PARALLEL** (single message, multiple
21
+ tool calls) to cover the codebase efficiently.
22
+ - Use 1 agent for narrow, well-scoped problems.
23
+ - Use multiple agents when the spec touches several subsystems or you need
24
+ to compare existing implementations.
25
+ - Quality over quantity — 3 agents maximum.
26
+
27
+ After exploration, draft a high-level design in your head: the shape of the
28
+ solution, the new or changed pieces, the boundaries.
29
+
30
+ ## Phase 2: Requirements
31
+
32
+ Translate the shape into concrete behavioral requirements. Each requirement
33
+ should be:
34
+
35
+ - **Testable** — has a clear pass/fail condition.
36
+ - **Behavior-focused** — describes what the system does, not how.
37
+ - **Scoped** — covers one observable behavior.
38
+
39
+ Prefer EARS-style phrasing where it fits (\`When <trigger>, the system shall
40
+ <behavior>\`), but do not force it. Group requirements by capability.
41
+
42
+ You may launch a Plan agent to draft requirements for a specific capability
43
+ in parallel, but for small specs writing them yourself is usually faster.
44
+
45
+ ## Phase 3: Deepen
46
+
47
+ - Read the critical files identified during Phase 1 to deepen your
48
+ understanding before locking decisions.
49
+ - Reconcile the requirements against the shape — if a requirement reveals a
50
+ gap in the design, refine the design before saving.
51
+ - Use **AskUserQuestion** for any remaining ambiguities. Bias toward asking
52
+ when a decision is non-obvious or when the user's intent is genuinely
53
+ unclear.
54
+
55
+ **Important:** Use AskUserQuestion ONLY to clarify requirements or choose
56
+ between approaches. Never use it to ask "is this spec okay?" or "should I
57
+ save?" — the save step below is the approval moment.
58
+
59
+ ## Phase 4: Save
60
+
61
+ Save the spec with \`crtr spec --name <kebab-case-name>\`. Pipe the markdown
62
+ body in via stdin (heredoc):
63
+
64
+ \`\`\`bash
65
+ crtr spec --name <kebab-case-name> <<'EOF'
66
+ # Spec: <one-line title>
67
+
68
+ ## Context
69
+ <the problem this spec addresses, what motivates it, and the intended
70
+ outcome. Include relevant constraints — user goals, stakeholders, deadlines.>
71
+
72
+ ## Design
73
+ <the shape of the solution. Components, data flow, key decisions and why
74
+ they were chosen. Reference existing code with \`file_path:line_number\`.>
75
+
76
+ ## Requirements
77
+ <grouped behavioral requirements. Each one testable.>
78
+
79
+ ### <Capability A>
80
+ - When <trigger>, the system shall <behavior>.
81
+ - ...
82
+
83
+ ### <Capability B>
84
+ - ...
85
+
86
+ ## Out of scope
87
+ <things explicitly NOT covered, so the next reader knows where the edges
88
+ are.>
89
+
90
+ ## Open questions
91
+ <anything you could not resolve. Empty if all decisions are pinned.>
92
+ EOF
93
+ \`\`\`
94
+
95
+ - Pick a short, descriptive kebab-case name. Names may be nested
96
+ (\`crtr spec --name auth/refresh-tokens\`).
97
+ - The file lands at \`${specsDir}/<name>.md\`.
98
+ - If you are running inside tmux, the saved spec auto-opens in a side pane
99
+ via termrender. No extra step needed.
100
+
101
+ ## Phase 5: Done
102
+
103
+ Your turn ends after the save command succeeds. No need to summarize the spec
104
+ in chat — the user can read the file.
105
+
106
+ ## See also
107
+
108
+ - \`crtr spec list\` — list saved specs for the current directory
109
+ - \`crtr spec show <name>\` — print the body of a saved spec
110
+ - \`crtr spec edit <name>\` — open a saved spec in \$EDITOR
111
+ - \`crtr spec path [name]\` — absolute path of a spec or the specs directory
112
+ `;
113
+ }
@@ -0,0 +1,115 @@
1
+ export type Scope = 'user' | 'project';
2
+ export declare const ExitCode: {
3
+ readonly SUCCESS: 0;
4
+ readonly GENERAL: 1;
5
+ readonly USAGE: 2;
6
+ readonly NOT_FOUND: 3;
7
+ readonly AMBIGUOUS: 4;
8
+ readonly NETWORK: 5;
9
+ };
10
+ export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
11
+ export declare const SCHEMA_VERSION = 1;
12
+ export interface OwnerRef {
13
+ name?: string;
14
+ email?: string;
15
+ }
16
+ export interface PluginManifest {
17
+ name: string;
18
+ version?: string;
19
+ description?: string;
20
+ source?: string;
21
+ owner?: OwnerRef;
22
+ kinds?: string[];
23
+ }
24
+ export interface MarketplacePluginEntry {
25
+ name: string;
26
+ source: string;
27
+ version?: string;
28
+ description?: string;
29
+ keywords?: string[];
30
+ }
31
+ export interface MarketplaceManifest {
32
+ name: string;
33
+ version?: string;
34
+ owner?: OwnerRef;
35
+ plugins: MarketplacePluginEntry[];
36
+ }
37
+ export interface ConfigMarketplaceEntry {
38
+ url: string;
39
+ ref: string;
40
+ installed_at: string;
41
+ }
42
+ export interface ConfigPluginEntry {
43
+ enabled: boolean;
44
+ source_marketplace?: string;
45
+ version?: string;
46
+ }
47
+ export interface ConfigSkillEntry {
48
+ enabled: boolean;
49
+ }
50
+ export type AutoUpdateMode = 'notify' | 'apply' | false;
51
+ export interface AutoUpdateConfig {
52
+ crtr: AutoUpdateMode;
53
+ content: AutoUpdateMode;
54
+ interval_hours: number;
55
+ }
56
+ export interface ScopeConfig {
57
+ schema_version: number;
58
+ marketplaces: Record<string, ConfigMarketplaceEntry>;
59
+ plugins: Record<string, ConfigPluginEntry>;
60
+ skills: Record<string, ConfigSkillEntry>;
61
+ auto_update: AutoUpdateConfig;
62
+ }
63
+ export interface ScopeState {
64
+ marketplaces: Record<string, {
65
+ last_updated?: string;
66
+ }>;
67
+ plugins: Record<string, {
68
+ last_updated?: string;
69
+ }>;
70
+ last_self_check?: string;
71
+ }
72
+ export interface SkillFrontmatter {
73
+ name: string;
74
+ description?: string;
75
+ keywords?: string[];
76
+ }
77
+ export interface Skill {
78
+ name: string;
79
+ plugin: string;
80
+ scope: Scope;
81
+ path: string;
82
+ pluginRoot: string;
83
+ frontmatter: SkillFrontmatter;
84
+ enabled: boolean;
85
+ disabledIn?: Scope;
86
+ }
87
+ export interface InstalledPlugin {
88
+ name: string;
89
+ scope: Scope;
90
+ root: string;
91
+ manifest: PluginManifest;
92
+ enabled: boolean;
93
+ sourceMarketplace?: string;
94
+ version?: string;
95
+ }
96
+ export interface InstalledMarketplace {
97
+ name: string;
98
+ scope: Scope;
99
+ root: string;
100
+ manifest: MarketplaceManifest;
101
+ url: string;
102
+ ref: string;
103
+ }
104
+ export declare const PLUGIN_MANIFEST_DIR = ".crouter-plugin";
105
+ export declare const PLUGIN_MANIFEST_FILE = "plugin.json";
106
+ export declare const MARKETPLACE_MANIFEST_DIR = ".crouter-marketplace";
107
+ export declare const MARKETPLACE_MANIFEST_FILE = "marketplace.json";
108
+ export declare const CRTR_DIR_NAME = ".crouter";
109
+ export declare const CONFIG_FILE = "config.json";
110
+ export declare const STATE_FILE = "state.json";
111
+ export declare const SKILL_ENTRY_FILE = "SKILL.md";
112
+ export declare const SKILLS_DIR = "skills";
113
+ export declare function defaultScopeConfig(): ScopeConfig;
114
+ export declare function skillConfigKey(plugin: string, name: string): string;
115
+ export declare function defaultScopeState(): ScopeState;
package/dist/types.js ADDED
@@ -0,0 +1,33 @@
1
+ export const ExitCode = {
2
+ SUCCESS: 0,
3
+ GENERAL: 1,
4
+ USAGE: 2,
5
+ NOT_FOUND: 3,
6
+ AMBIGUOUS: 4,
7
+ NETWORK: 5,
8
+ };
9
+ export const SCHEMA_VERSION = 1;
10
+ export const PLUGIN_MANIFEST_DIR = '.crouter-plugin';
11
+ export const PLUGIN_MANIFEST_FILE = 'plugin.json';
12
+ export const MARKETPLACE_MANIFEST_DIR = '.crouter-marketplace';
13
+ export const MARKETPLACE_MANIFEST_FILE = 'marketplace.json';
14
+ export const CRTR_DIR_NAME = '.crouter';
15
+ export const CONFIG_FILE = 'config.json';
16
+ export const STATE_FILE = 'state.json';
17
+ export const SKILL_ENTRY_FILE = 'SKILL.md';
18
+ export const SKILLS_DIR = 'skills';
19
+ export function defaultScopeConfig() {
20
+ return {
21
+ schema_version: SCHEMA_VERSION,
22
+ marketplaces: {},
23
+ plugins: {},
24
+ skills: {},
25
+ auto_update: { crtr: 'notify', content: 'notify', interval_hours: 24 },
26
+ };
27
+ }
28
+ export function skillConfigKey(plugin, name) {
29
+ return `${plugin}:${name}`;
30
+ }
31
+ export function defaultScopeState() {
32
+ return { marketplaces: {}, plugins: {} };
33
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.1.1",
4
- "description": "crouter CLI",
3
+ "version": "0.1.3",
4
+ "description": "crtr — fast access to skills, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "crouter": "dist/cli.js"
9
+ "crtr": "bin/crtr",
10
+ "crouter": "bin/crouter"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
@@ -18,12 +19,14 @@
18
19
  }
19
20
  },
20
21
  "files": [
21
- "dist"
22
+ "dist",
23
+ "bin"
22
24
  ],
23
25
  "scripts": {
24
26
  "build": "tsc",
25
27
  "dev": "tsx src/cli.ts",
26
- "link": "npm link"
28
+ "link": "npm link",
29
+ "prepublishOnly": "npm run build"
27
30
  },
28
31
  "repository": {
29
32
  "type": "git",